Microsoft Edge and ProtectedHomepages

By | May 11, 2016

Recently, I have had a small but curious research project with the requirement to decrypt ProtectedHomepages binary value stored under [HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\Protected - It is a violation of Windows Policy to modify. See aka.ms/browserpolicy]. While googling around the problem, I have seen a related question on Stack Overflow, so I decided that it may have sense to share the results of this research with the community.

If you open [HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\Protected - It is a violation of Windows Policy to modify. See aka.ms/browserpolicy] you will see several values, with two of them are of particular interest. These are ProtectedHomepages and ProtectedSearchScopes. Both are represented in binary form, and it is hard to understand what is behind it. An example, if you set a homepage in Microsoft Edge to https://www.ntkernel.com and open ProtectedHomepages in RegEdit then you will see something of this kind:

ProtectedHomepages in Windows Registry
ProtectedHomepages in Windows Registry

It does not look to have anything common with https://www.ntkernel.com, so let’s try to determine what happens.

First steps are obvious, let’s take ProcessMonitor from Sysinternals to find the process which writes the ProtectedHomepages value:

Using ProcessMonitor to find out the  process which sets the ProtectedHomepages
Using ProcessMonitor to find out the process which sets the ProtectedHomepages

So far, it looks clear, MicrosoftEdge.exe calls RegSetValue to save new settings. Now let’s attach WinDbg to MicrosoftEdge.exe process and let’s put breakpoint on KERNELBASE!RegSetValueExW. Try to change homepages list once again, resulting the call to KERNELBASE!RegSetValueExW with the following call stack:

<code>ChildEBP RetAddr<br>
067fec54 5d0b85fd KERNELBASE!RegSetValueExW<br>
067fee9c 5d10ac31 eModel!SettingStore::CRegistryKey::SetValue+0x3d<br>
067feed8 5d10abb3 eModel!SettingStore::_SetValueInPhysicalStore+0x66<br>
067ff32c 5d10aab4 eModel!SettingStore::SetExtValueWorker+0xe7<br>
067ff374 5d10aa40 eModel!SetExtValue_Internal+0x6d<br>
067ff390 5d117e4c eModel!SPSetExtValue+0x20<br>
067ff3fc 5d117caf eModel!SettingsProtection::SettingProtector::s_SaveSettingBlob+0x16f<br>
067ff450 5d2fcee5 eModel!SettingsProtection::SettingProtector::s_SaveSetting+0x107<br>
067ff48c 5d2fe156 eModel!SettingsProtection::SettingProtector::_SetEffectiveSetting+0x119<br>
067ff4c0 5d2422ce eModel!SettingsProtection::SettingProtector::SetHomepages+0x56<br>
067ff4f4 5d15f7e7 eModel!SpartanCore::SettingsFacadeHelper::SetHomePages+0x94<br>
067ff524 5d0ad939 eModel!SpartanCore::SettingsFacadeHelper::HandleCommand+0xb4283<br>
067ff548 5d1019e2 eModel!SpartanCore::FrameUIFacade::InvokeCommand+0x259<br>
067ff5dc 5d0fef83 eModel!CAsyncBoundaryLayer::_ProcessRequest+0x16d2<br>
067ff64c 75035b83 eModel!CAsyncBoundaryLayer::s_WndProc+0x163<br>
067ff678 75019d1a USER32!_InternalCallWinProc+0x2b<br>
067ff710 75019860 USER32!UserCallWinProcCheckWow+0x1aa<br>
067ff770 750196b0 USER32!DispatchMessageWorker+0x1a0<br>
067ff77c 5d0c911f USER32!DispatchMessageW+0x10<br>
067ff7c4 5d08ba30 eModel!CBrowserFrame::FrameMessagePump+0x16f<br>
067ff804 5d0890d3 eModel!_BrowserThreadProc+0x9e<br>
067ffa94 00ee8372 eModel!LCIEStartAsFrame+0x693<br>
067ffae0 76f395f4 MicrosoftEdge!s_FrameThreadProc+0x62<br>
067ffaf4 7782241a KERNEL32!BaseThreadInitThunk+0x24<br>
067ffb3c 778223e9 ntdll!__RtlUserThreadStart+0x2b<br>
067ffb4c 00000000 ntdll!_RtlUserThreadStart+0x1b<br>
</code>

As follows from the call stack, the module of our interest is eModel.dll, which contains classes responsible for saving/loading settings from the registry and probably for encrypting/decrypting them. If we look closer at the names, then the most promising call on the stack is eModel!SettingsProtection::SettingProtector::s_SaveSettingBlob so let’s look closer at this function in disassemble:

.text:10127DB3                 lea     ecx, [esp+44h+MaxCount]
.text:10127DB7                 mov     byte ptr [esp+44h+var_4], 2
.text:10127DBC                 push    ecx
.text:10127DBD                 mov     ecx, [esp+48h+var_24]
.text:10127DC1                 add     eax, 4
.text:10127DC4                 push    eax
.text:10127DC5                 mov     edx, edi
<strong>.text:10127DC7                 call    ?ObfuscateData@Encoding@@SGJPBEIPAPAEPAI@Z ; Encoding::ObfuscateData(uchar const *,uint,uchar * *,uint *)</strong>
.text:10127DCC                 mov     byte ptr [esp+44h+var_4], 1
.text:10127DD1                 mov     esi, eax
.text:10127DD3                 cmp     [esp+44h+var_14], 0
.text:10127DD8                 jz      short loc_10127DF4
.text:10127DDA                 mov     ebx, [esp+44h+var_1C]
.text:10127DDE                 mov     edi, [esp+44h+var_18]
.text:10127DE2                 cmp     edi, [ebx]
.text:10127DE4                 jz      short loc_10127DF0
.text:10127DE6                 push    dword ptr [ebx] ; lpMem
.text:10127DE8                 call    ??3@YAXPAX@Z    ; operator delete(void *)
.text:10127DED                 pop     ecx
.text:10127DEE                 mov     [ebx], edi
.text:10127DF0
.text:10127DF0 loc_10127DF0:                           ; CODE XREF: SettingsProtection::SettingProtector::s_SaveSettingBlob(ushort const *,tagBLOB,tagBLOB)+107j
.text:10127DF0                 mov     ebx, [esp+44h+Src]
.text:10127DF4
.text:10127DF4 loc_10127DF4:                           ; CODE XREF: SettingsProtection::SettingProtector::s_SaveSettingBlob(ushort const *,tagBLOB,tagBLOB)+FBj
.text:10127DF4                 test    esi, esi
.text:10127DF6                 js      short loc_10127E51
.text:10127DF8                 and     [esp+44h+var_28], 0
.text:10127DFD                 lea     eax, [esp+44h+Src]
.text:10127E01                 push    4               ; MaxCount
.text:10127E03                 push    eax             ; Src
.text:10127E04                 lea     ecx, [esp+4Ch+var_28]
.text:10127E08                 mov     [esp+4Ch+Src], 1
.text:10127E10                 call    ?Append@CBlob@@QAEJPBXI@Z ; CBlob::Append(void const *,uint)
.text:10127E15                 mov     esi, eax
.text:10127E17                 test    esi, esi
.text:10127E19                 js      short loc_10127E51
.text:10127E1B                 push    [esp+44h+MaxCount] ; MaxCount
.text:10127E1F                 lea     ecx, [esp+48h+var_28]
.text:10127E23                 push    [esp+48h+lpMem] ; Src
.text:10127E27                 call    ?Append@CBlob@@QAEJPBXI@Z ; CBlob::Append(void const *,uint)
.text:10127E2C                 mov     esi, eax
.text:10127E2E                 test    esi, esi
.text:10127E30                 js      short loc_10127E51
.text:10127E32                 push    ebx
.text:10127E33                 push    1
.text:10127E35                 push    [esp+4Ch+var_28]
.text:10127E39                 push    [esp+50h+var_24]
.text:10127E3D                 push    0Bh
.text:10127E3F                 push    2
.text:10127E41                 push    ds:?SPVALUE_Browser_ProtectedSetting@SettingStore@@3U?$SPEXTVALUE1ID@PAE@1@B ; SettingStore::SPEXTVALUE1ID const SettingStore::SPVALUE_Browser_ProtectedSetting
<strong>.text:10127E47                 call    _SPSetExtValue</strong>

You can notice that the call to _SPSetExtValue is preceded by a call to Encoding::ObfuscateData which is very likely to be the function of our interest. Actually, there are two functions: Encoding::ObfuscateData and Encoding::UnobfuscateData and if we disassemble these functions and step through them in WinDbg we will see that this is what we were looking for. Below is a quick and dirty implementation of UnobfuscateData and wrapping code which reads ProtectedHomepages from the registry and decrypts it by calling UnobfuscateData.

bool UnobfuscateData (unsigned char* input_buffer_ptr, unsigned input_buffer_size, unsigned char** output_buffer_ptr, size_t *output_buffer_size_ptr)
{
	SIZE_T output_buffer_size; 
	BYTE* decrypted_buffer; 
	int rand_seed; 
	BYTE* moving_input_ptr; 
	BYTE* moving_output_ptr;
	size_t i;
	char v14; 
	char v15; 
	unsigned int v16; 
	unsigned int v17; 
	char v19; 
	bool result; 
	int v21; 

	output_buffer_size = input_buffer_size - 4;
	*output_buffer_size_ptr = output_buffer_size;

	if (input_buffer_size == 4)
	{
		*output_buffer_ptr = nullptr;
		result = true;
	}
	else
	{
		decrypted_buffer = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, output_buffer_size);
		*output_buffer_ptr = decrypted_buffer;

		if (decrypted_buffer)
		{
			memset(decrypted_buffer, 0, *output_buffer_size_ptr);
			rand_seed = *(int*)input_buffer_ptr;
			moving_input_ptr = (BYTE*)((int*)input_buffer_ptr + 1);
			moving_output_ptr = decrypted_buffer;
			for (i = *output_buffer_size_ptr >> 2; i; --i)
			{
				rand_seed = 214013 * rand_seed + 2531011;
				v14 = rand_seed ^ *(BYTE *)moving_input_ptr;
				v21 = rand_seed;
				*(BYTE *)moving_output_ptr = v14;
				*(BYTE *)(moving_output_ptr + 1) = BYTE1(rand_seed) ^ *(_BYTE *)(moving_input_ptr + 1);
				*(_BYTE *)(moving_output_ptr + 2) = BYTE2(v21) ^ *(_BYTE *)(moving_input_ptr + 2);
				v15 = *(_BYTE *)(moving_input_ptr + 3);
				moving_input_ptr += 4;
				*(_BYTE *)(moving_output_ptr + 3) = BYTE3(v21) ^ v15;
				moving_output_ptr += 4;
			}
			v16 = 0;
			v21 = 214013 * rand_seed + 2531011;
			v17 = *output_buffer_size_ptr & 3;
			if (*output_buffer_size_ptr & 3)
			{
				do
				{
					++moving_input_ptr;
					v19 = *(_BYTE *)(moving_input_ptr - 1) ^ *((_BYTE *)&v21 + v16++);
					*(_BYTE *)(moving_output_ptr - 1) = v19;
				} while (v16 < v17);
			}
			result = true;
		}
		else
		{
			result = false;
		}
	}
	return result;
}

void DumpData(PUCHAR Data, size_t DataLength)
{
	ULONG k, m;

	for (k = 0; k < DataLength / 16; ++k)
	{
		for (m = 0; m < 16; ++m)
		{
			if (isprint(Data[k * 16 + m]))
				printf("%c ", Data[k * 16 + m]);
			else
				printf(". ");
		}

		printf("\n");
	}

	for (k = 0; k < DataLength - (DataLength / 16) * 16; ++k)
	{
		if (isprint(Data[(DataLength / 16) * 16 + k]))
			printf("%c ", Data[(DataLength / 16) * 16 + k]);
		else
			printf(". ");
	}

	printf("\n\n");
}

void main()
{
	HKEY hKey = nullptr;
	BYTE* pbDecrypted = nullptr;
	size_t size_of_decrypted = 0;

	LONG lResult = RegOpenKeyEx(
		HKEY_CURRENT_USER,
		TEXT("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge\\Protected - It is a violation of Windows Policy to modify. See aka.ms/browserpolicy"),
		0,
		KEY_QUERY_VALUE,
		&hKey);

	if (lResult != ERROR_SUCCESS)
	{
		printf("Failed to open Edge registry key. Error code 0x%X\n", lResult);
		return;
	}

	DWORD BufferSize = USN_PAGE_SIZE;
	DWORD cbData;
	DWORD dwRet;

	BYTE* pbEncrypted = (BYTE*)malloc(BufferSize);
	cbData = BufferSize;

	printf("Retrieving the data...\n");

	dwRet = RegQueryValueEx(
		hKey,
		TEXT("ProtectedHomepages"),
		NULL,
		NULL,
		(LPBYTE)pbEncrypted,
		&cbData);

	while (dwRet == ERROR_MORE_DATA)
	{
		// Get a buffer that is big enough.

		BufferSize += USN_PAGE_SIZE;
		pbEncrypted = (BYTE*)realloc(pbEncrypted, BufferSize);
		cbData = BufferSize;

		printf(".");
		dwRet = RegQueryValueEx(
			hKey,
			TEXT("ProtectedHomepages"),
			NULL,
			NULL,
			(LPBYTE)pbEncrypted,
			&cbData);
	}

	if (dwRet == ERROR_SUCCESS)
		printf("Have read %d bytes from the value\n", cbData);
	else
	{
		printf("RegQueryValueEx failed (%d)\n", dwRet);
		RegCloseKey(hKey);
		return;
	}

	if (UnobfuscateData(pbEncrypted + 4, cbData - 4, (unsigned char**)&pbDecrypted, &size_of_decrypted))
	{
		printf("Succesfully decrypted Edge ProtectedHomepages value (%d)\n", dwRet);

		if (pbDecrypted)
		{
			DumpData(pbDecrypted, size_of_decrypted);
			HeapFree(GetProcessHeap(), 0, pbDecrypted);
		}
	}
	else
		printf("UnobfuscateData failed (%d)\n", dwRet);

	RegCloseKey(hKey);
	_getch();
} 

On the test system with homepage set to https://www.ntkernel.com, the code above will provide the following output:

C:\test>decrypt
 Retrieving the data...
 Have read 120 bytes from the value
 Succesfully decrypted Edge ProtectedHomepages value (0)
 . . . . R . . . . . . . . . . .
 . . . . . . . . . . . . . . . .
 . . . . 2 . . . h . t . t . p .
 s . : . / . / . w . w . w . . .
 n . t . k . e . r . n . e . l .
 . . c . o . m . . . . . . . . .
 . . e U b p i N g o z U 4 % 3 d

I think you have also noticed some mysterious trailing bytes which follow https://www.ntkernel.com string. I think it needs some explanation. The function eModel!Encoding::ObfuscateData actually does not encrypt, but instead obfuscates buffer using a random-generated seed (it is placed in the header of the block). Encoding::UnobfuscateData reads the seed value from the buffer header and deobfuscates the buffer. If this were the whole story, then it would be too easy to modify ProtectedHomepages, so to make this more complex, developers added a cryptographic hash to the tail of the buffer.

Complete source code for the project is available on GitHub

12 thoughts on “Microsoft Edge and ProtectedHomepages

  1. HT

    This is very nice article! Thank you for sharing!

    Do you happen to know how the cryptographic hash is actually calculated?

    I need to programmatically enable/disable installed extensions and they are also protected.

    HKEY_CURRENT_USER\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\Protected – It is a violation of Windows Policy to modify. See aka.ms/browserpolicy\Extensions

    If I change an extension state it will cause an error in Edge due to protection.

    Using your code I got the following for Amazon Extension:
    xxx_AmazoncomAmazonAssistant_343d40qqvtj1t
    Retrieving the data…
    Have read 30 bytes from the value
    Successfully decrypted Edge Extension value (0)
    . . . . . . . . d E v x e 8 z B
    7 p E d . .

    Not sure if it is correct either as I was not sure about the _BYTE; I am on MS and I did this for _BYTEx parts:
    typedef BYTE _BYTE;
    #define BYTEn(x, n) (*((_BYTE*)&(x)+n))
    #define BYTE1(x) BYTEn(x, 1)
    #define BYTE2(x) BYTEn(x, 2)
    #define BYTE3(x) BYTEn(x, 3)

    Anyway, let me know if you can help with creating the hash and obfuscating the value to be saved back into the Registry.

    Thanks!

    Reply
  2. ntkernel.com

    I’m glad if it helps. As far as I remember the crypto-hash trail is generated using current user credentials, but for the purposes of this particular project I had no need to go that deep and reverse the algorithm. I did this research by request of a small anti-malware vendor and they only needed to detect the key modification.

    You need Hex-Rays definitions to compile the code, you can get it here: http://www.cnblogs.com/shangdawei/p/3537773.html

    Reply
    1. Julian

      It’s a pity you couldn’t make a command line tool to set the registry key because I really need something like that to change the Edge home page to our Intranet website. 😞

      Reply
  3. HT

    Thank you for your promptly response.
    If I ever find a solution I will let you know.
    Cheers!

    Reply
  4. D_Man

    Hi! Thank you for doing all this hard work.

    I’m a noob at this and just want to see what the value is. I have next to nil experience in Windows programming, but I’ve downloaded your code and the Hex-Ray definitions and am trying to use MinGW g++ compiler but not able to. I continue to get “SIZE_T not defined” and so on.

    Do I need to feed it any data manually before compiling?

    Thanks!
    -D_Man

    Reply
  5. garegin

    I keep erasing those registry keys and something still restores them after I open edge. I deleted edge and restored through SFC /scannow and the home page is still yandex.ru . Tried process monitor, no clues.
    Do you have any ideas why edge keeps setting the home page as yandex.ru

    Reply
    1. Vadim Smirnov

      Erasing ProtectedHomepages resets homepage settings. At least on the fresh system. So it must be something external what restores this registry key to yandex.ru. Some sort of yandex crapware probably…

      Reply
  6. M

    Hi Vadim,
    sorry for bother you, could you please release a version of your decrypt.exe which checks the contents of ProtectedSearchScopes as well?

    I’m not very ready on compiling.

    Regards

    M

    Reply
  7. R

    Hi Vadim,
    Thank you very much for this great article. I will be very grateful if you provide me the source code in c# language.

    Reply

Leave a Reply to Julian Cancel reply

Your email address will not be published. Required fields are marked *