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 StackOverflow, 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 not that easy to understand what is behind it. An example, if you set 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 figure out 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:

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

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 disassembler:

.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 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 disassembly these functions and step through them in WinDbg we will see that this is what we were looking for. Below is quick and dirty implementation of UnobfuscateData and wrapping code which reads ProtectedHomepages from the registry and decrypts it by calling UnobfuscateData.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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 &gt;&gt; 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 &amp; 3;
			if (*output_buffer_size_ptr &amp; 3)
			{
				do
				{
					++moving_input_ptr;
					v19 = *(_BYTE *)(moving_input_ptr - 1) ^ *((_BYTE *)&amp;v21 + v16++);
					*(_BYTE *)(moving_output_ptr - 1) = v19;
				} while (v16 &lt; v17);
			}
			result = true;
		}
		else
		{
			result = false;
		}
	}
	return result;
}
 
void DumpData(PUCHAR Data, size_t DataLength)
{
	ULONG k, m;
 
	for (k = 0; k &lt; DataLength / 16; ++k)
	{
		for (m = 0; m &lt; 16; ++m)
		{
			if (isprint(Data[k * 16 + m]))
				printf(&quot;%c &quot;, Data[k * 16 + m]);
			else
				printf(&quot;. &quot;);
		}
 
		printf(&quot;\n&quot;);
	}
 
	for (k = 0; k &lt; DataLength - (DataLength / 16) * 16; ++k)
	{
		if (isprint(Data[(DataLength / 16) * 16 + k]))
			printf(&quot;%c &quot;, Data[(DataLength / 16) * 16 + k]);
		else
			printf(&quot;. &quot;);
	}
 
	printf(&quot;\n\n&quot;);
}
 
void main()
{
	HKEY hKey = nullptr;
	BYTE* pbDecrypted = nullptr;
	size_t size_of_decrypted = 0;
 
	LONG lResult = RegOpenKeyEx(
		HKEY_CURRENT_USER,
		TEXT(&quot;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&quot;),
		0,
		KEY_QUERY_VALUE,
		&amp;hKey);
 
	if (lResult != ERROR_SUCCESS)
	{
		printf(&quot;Failed to open Edge registry key. Error code 0x%X\n&quot;, lResult);
		return;
	}
 
	DWORD BufferSize = USN_PAGE_SIZE;
	DWORD cbData;
	DWORD dwRet;
 
	BYTE* pbEncrypted = (BYTE*)malloc(BufferSize);
	cbData = BufferSize;
 
	printf(&quot;Retrieving the data...\n&quot;);
 
	dwRet = RegQueryValueEx(
		hKey,
		TEXT(&quot;ProtectedHomepages&quot;),
		NULL,
		NULL,
		(LPBYTE)pbEncrypted,
		&amp;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(&quot;.&quot;);
		dwRet = RegQueryValueEx(
			hKey,
			TEXT(&quot;ProtectedHomepages&quot;),
			NULL,
			NULL,
			(LPBYTE)pbEncrypted,
			&amp;cbData);
	}
 
	if (dwRet == ERROR_SUCCESS)
		printf(&quot;Have read %d bytes from the value\n&quot;, cbData);
	else
	{
		printf(&quot;RegQueryValueEx failed (%d)\n&quot;, dwRet);
		RegCloseKey(hKey);
		return;
	}
 
	if (UnobfuscateData(pbEncrypted + 4, cbData - 4, (unsigned char**)&amp;pbDecrypted, &amp;size_of_decrypted))
	{
		printf(&quot;Succesfully decrypted Edge ProtectedHomepages value (%d)\n&quot;, dwRet);
 
		if (pbDecrypted)
		{
			DumpData(pbDecrypted, size_of_decrypted);
			HeapFree(GetProcessHeap(), 0, pbDecrypted);
		}
	}
	else
		printf(&quot;UnobfuscateData failed (%d)\n&quot;, 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. 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 seed value from the buffer header and un-obfuscates 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

9 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 Post author

    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
  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 Post author

      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

Leave a Reply

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