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:
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:
So far, it looks clear, MicrosoftEdge.exe calls RegSetValu
e 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
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.