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
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
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 >> 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. 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
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!
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
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. š
Thank you for your promptly response.
If I ever find a solution I will let you know.
Cheers!
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
No, it reads data directly from the registry. I have put the binaries for download here:
https://github.com/ntkernelcom/edge_decrypt/blob/master/Release/decrypt.exe
https://github.com/ntkernelcom/edge_decrypt/blob/master/x64/Release/decrypt.exe
Great work.
Just to let you know. The download link is not working.
Thanks for reporting this, added binaries to GitHub.
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
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…
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
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.