You are reading an old article on an old site. It only still exists so the information is not lost. Comments and mosts links are hidden and disabled.

Windows Mobile 6.x Hackery Explained

This week, the Windows Mobile 6.x Marketplace was closed. The end of an era :)

While most current readers will probably know me from my work with Android, I used to do a lot of things on Windows Mobile. Several of those projects were pretty unique, due to hacking techniques employed that few others knew. I am referring here to runtime/dynamically patching kernel code, coredll imports, etc.

Some of these projects include FPU Enabler (which replaced the standard floating point functions on the last few WM phones with FPU based variants - together with NuShrike), AutoClosePatch (improve multitasking behavior on low memory devices like the Samsung Omnia II), Marketplace Region Switch (switched your region in Marketplace, before Microsoft built in that option, by faking MCC data), and many more.

Of course, let us not forget Marketplace Advanced Protection Patch. Some people thought I didn't actually have this due to me not releasing it. At the time I thought it more important to protect other developers. As Marketplace is now shutdown however, I've decided to release the code.

Its a nice trip down memory lane, and I will take some time to somewhat explain some of the techniques I have used. I will do my best, but it has been three years since I have written this code, so not everything I write may be 100% correct. I will try to keep it short-ish, but I know the article will still be long - I could write entire books on this subject :)

You will need the accompanying code, which is hosted at GitHub.

The test project

Let us start with the Test_VS2K8 folder. This folder contains the Visual Studio test project. It is simply the minimal code to test Marketplace's "Advanced" Protection. Aside from _tmain function, the entirety of the MPAdvTest.cpp file is copy/pasted from Microsoft's example code.

From a code breaker's perspective, what we are looking for is a way to get the VerifyLicense function return TRUE. On Windows Mobile specifically, the easiest way to make this happen for all programs with a single patch is modifying return values of functions exposed by coredll.dll.

If you look closely at the code, you will notice that the VerifyLicense function will return TRUE if both:
- The HKCU\Security\Software\Microsoft\Marketplace\Licenses\<licensecode> registry key can be read and is of type REG_BINARY
- The CryptVerifySignature returns TRUE

We can achieve these conditions by:
- Making sure HKCU\Security\Software\Microsoft\Marketplace\Licenses exists
- Patching the RegQueryValueExW function
- Patching the CryptVerifySignatureW function

Both of these functions are exposed through coredll.dll, so let's get busy!

The patch project

The patch code is in the Patch_FPC folder. This folder contains the FreePascal test project. Yes, it's written in Pascal, get over it. It's also fairly visible I wrote it in a rush, the main unit is still called unit1 !

It's difficult to compile due to depending on KOL-CE for the UI, which at least at the time I wrote this patch had several versions that could conflict. It should be fairly easy to turn into a console project without that dependency, though.

It is unlikely you will be trying to compile this anyway, so let's just get down to the explaining.

The important background code employed for the hacking is located in the upatch.pas file. Various versions of this unit are used in several of my WM hacking projects. The code looks simple enough, but make no mistake, lots and lots of hours were lost by several hackers to get all this information together. Remember, it's not like we had Windows Mobile source code :)

You can mostly the ignore uregistry.pas, it contains utility functions only.

The main code can be found in unit1.pas.

Let's walk through the actual patch code, located in unit1.pas, starting at line 134:

Code
#
1
SetKMode(TRUE);
2
SetProcPermissions($FFFFFFFF);

These two lines do something very important: we switch to kernel mode and enable read/write access to all processes, threads, and memory range. Essentially we're disabling all those nasty protections designed to prevent exactly what we are trying to do.

Skipping over the registry key creation bit, we go on at lines 143-144:

Code
#
1
fNewCryptVerifySignature := GetFunctionCode(@newCryptVerifySignatureW, 1024, opPatch);
2
fNewRegQueryValueEx := GetFunctionCode(@newRegQueryValueExW, 1024, opPatch);

What we are doing here is filling an array with the raw bytes that are the compiled code of our replacement functions, defined on lines 61-96:

(Note: opPatch is simply a variable containing the raw binary for the "return" instruction, which should be used as end marker for copying the function - this is not always exactly the same for each function! We also know that the function will not be longer than 1024 bytes)

Code
#
1
function newCryptVerifySignatureW(hHash: DWORD; pbSignature: PBYTE; dwSigLen: DWORD; hPubKey: DWORD; sDescription: PWideChar; dwFlags: DWORD): BOOL;
2
begin
3
  Result := True;
4
end;
5
 
6
function newRegQueryValueExW(hKey:HKEY; lpValueName:LPCWSTR; lpReserved:LPDWORD; lpType:LPDWORD; lpData:pointer;lpcbData:LPDWORD):LONG;
7
var
8
  c: Byte;
9
  p: PBYTE;
10
  oldRQVE: TRegQueryValueExW;
11
begin
12
  oldRQVE := TRegQueryValueExW($12345678);
13
 
14
  Result := 6; // ERROR_INVALID_HANDLE
15
  
16
  if lpValueName <> nil then begin
17
    DWORD(p) := DWORD(lpValueName);
18
 
19
    c := 0;
20
    repeat
21
      if p^ = $2D then // '-'
22
        inc(c);
23
      inc(p, 2);
24
    until p^ = 0;
25
    
26
    if c = 4 then begin
27
      lpType^ := 3; // REG_BINARY
28
      lpcbData^ := 256; // MAX_LICENSE_LENGTH
29
 
30
      Result := 0; // ERROR_SUCCESS
31
    end;
32
  end;
33
  
34
  if Result <> 0 then
35
    Result := oldRQVE(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
36
end;

The newCryptVerifySignatureW function is simply enough - we just return true and that's that! If this wasn't proof-of-concept code, we would probably do some checking of parameters, to make sure that what we are verifying is actually a signature we want to falsely verify as correct.

The newRegQueryValueExW is a bit more complicated. We don't want to always return ERROR_SUCCESS, we need to check the parameters. RegQueryValueExW is such an often used and important function, if we patch it incorrectly, the system would constantly crash.

You might be surprised this code is not written in assembler for the patching. You can do that. For FPU Enabler we did that for performance reasons. But as long as you don't make any jumps outside of these patched functions, you can write them in C or Pascal just fine. If you call RTL functions though, an invalid jump address will be referenced and the target application will crash. For the same reason, you cannot use variables defined outside of the function either, and depending on the compiler, you cannot even use pre-defined non-literal constants.

Note that this is explicitly not the case for all functions exposed by coredll.dll, because all processes have that DLL loaded - and thanks to the nature of Windows Mobile, they all have it loaded at the exact same address, so those function call addresses are always valid. That is why we can call the original RegQueryValueExW (oldRQVE) function without issue.

What you need to keep in mind is that these functions we're patching into place, are not going to be called in the same context as our application.

So, as stated, we cannot use variables either. So how do we know what the address of the original RegQueryValueExW function? On the highlighted line you might notice we set the address of the original function to hex value 12345678. It is important that this number is unique (for a different function/reference, use a different number) otherwise the compiler will mess with the assembler output we want to create. What happens is that this literal value will actually be stored in the generated function code, next to the actual code - not somewhere else in the binary file.

The entire function, including that value, will be one continuous block, that we can directly relocate anywhere in memory - later on in the code, we will be replacing that 0x12345678 value with the actual jump address.

So, let's go back to the main code block. Due to the GetFunctionCode calls, we now have our compiled functions stored in an array. Let's move on to the heap creation calls, starting at line 146:

Code
#
1
  totalMem := 1024;
2
  
3
  hCoreDll := LoadLibrary('coredll.dll');
4
  try
5
    hHeap := HeapCreate(HEAP_SHARED_READONLY, totalMem, totalMem);
6
    if hHeap <> 0 then
7
    try
8
      pHeap := HeapAlloc(hHeap, HEAP_ZERO_MEMORY, totalMem);
9
      if pHeap <> nil then
10
      try
11
        cur := pHeap;

What we are doing here is creating a special heap writable by our patch process, and readable by all other processes. Importantly, the address for this heap will be exactly the same across process and thread boundaries, so we can directly refer to addresses inside this heap, and it will always work. We can even execute code located at these addresses - and you guessed right: that's exactly what we are going to do.

There is one caveat for larger hacks, though: heaps with sizes over 1024 will often fail to be allocated. There are of course workarounds, but let's not get into that right now :)

Note that we also store a second pointer to the start of the heap. We're going to increase that one as we go to keep track of where we are in our newly created heap.

Continuing on with lines 158-162:
Code
#
1
aOldCryptVerifySignature := GetProcAddress(hCoreDLL, 'CryptVerifySignatureW');
2
aNewCryptVerifySignature := cur;
3
Move(fNewCryptVerifySignature[0], aNewCryptVerifySignature^, Length(fNewCryptVerifySignature));
4
jNewCryptVerifySignature := CreateJumpCode(aNewCryptVerifySignature);
5
DWORD(cur) := DWORD(cur) + Length(fNewCryptVerifySignature);

What we are doing here is getting the normal "call address" for CryptVerifySignatureW using GetProcAddress. Then we store the replacement function at the address pointed to by the cur pointer. Lastly, we create an array containing the jump code to the address of the new function in our world-readable heap by using the CreateJumpCode function, and increase the cur pointer. The CreateJumpCode function simply generates a binary jump instruction to an address. We will need this jump code later.

The replacement function for CryptVerifySignatureW is now stored in the special heap we created, and we have all the pointers we want for later use.

On lines 164-180:
Code
#
1
aOldRegQueryValueEx := GetProcAddress(hCoreDLL, 'RegQueryValueExW');
2
 
3
tf := CreateJumpCode(Pointer(DWORD(aOldRegQueryValueEx) + 8));
4
SetLength(tf, 16);
5
Move(tf[0], tf[8], 8);
6
Move(GetCoreDLLAddress(hCoreDLL, aOldRegQueryValueEx)^, tf[0], 8);
7
Move(tf[0], cur^, 16);
8
aJumpRegQueryValueEx := cur;
9
DWORD(cur) := DWORD(cur) + 16;
10
        
11
aNewRegQueryValueEx := cur;
12
Move(fNewRegQueryValueEx[0], aNewRegQueryValueEx^, Length(fNewRegQueryValueEx));
13
jNewRegQueryValueEx := CreateJumpCode(aNewRegQueryValueEx);
14
DWORD(cur) := DWORD(cur) + Length(fNewRegQueryValueEx);
15
        
16
Move(aJumpRegQueryValueEx, cur^, 4);
17
DWORD(cur) := DWORD(cur) + 4;

We're doing the same thing for RegQueryValueExW as for CryptVerifySignatureW, but there a few complications we must overcome, because RegQueryValueExW is a bit more complicated function because we need to call the original function.

This is a problem, because later on we will patch the original function. We will overwrite the first 8 bytes of that function with a jump to our modified function! So if we'd call the original function again, we'd get some nice recursion and ultimately a stack overflow.

We circumvent this problem, by first saving those first 8 bytes of the original function, followed by a jump instruction to (original location + 8 bytes). Obvious enough once you've thought of it!

You might notice the first time use of GetCoreDLLAddress. If you're wondering why we do not just use a pointer returned from GetProcAddress, it's because the latter returns a pointer to a read-only "shadow" copy of coredll.dll. The GetCoreDLLAddress function returns the "real" location to the code.

Also note that at this point I have cheated a bit. You can't always just relocate the first 8 bytes of a function. It depends on the function code! Of course you would first confirm with IDA if this is possible, and if not, try a different way to get it all done.

After saving the original bytes and the extra jump code on our special heap, we copy the replacement function to our heap as well.

On the last two lines, we also write the address of the original function to our heap. This value replaces the 0x12345678 value we referenced earlier. That value is stored immediately after the return instruction in the compiled version of our replacement code. The reference to that literal is relative, that's why we can get away with this.

So, at this point, our special heap is filled as follows:

Code
#
1
X bytes: replacement CryptVerifySignatureW function
2
8 bytes: first 8 bytes of original RegQueryValueExW
3
8 bytes: jump code to original (RegQueryValueExW + 8)
4
Y bytes: replacement RegQueryValueExW function
5
4 bytes: original RegQueryValueExW address (replaces 0x12345678)

On lines 182-184 are the final calls that enable our hack:
Code
#
1
fOldCryptVerifySignature := PatchCode(GetCoreDLLAddress(hCoreDLL, aOldCryptVerifySignature), jNewCryptVerifySignature);
2
fOldRegQueryValueEx := PatchCode(GetCoreDLLAddress(hCoreDLL, aOldRegQueryValueEx), jNewRegQueryValueEx);
3
ClearCache;

These two calls overwrite the first 8 bytes of the two original functions with jump instructions to our replacement functions located on our special heap. We also call the ClearCache function to make sure the CPU's instruction caches are cleared.

After executing this code, any program calling either of these two functions will be calling our code instead. Neat, isn't it ?

After this, the code waits 30 seconds and unpatches.

That's all folks - I hope this little article and the code has been interesting to somebody!