sábado, 2 de julho de 2011

Cracking a Pimp

Just came back from vacations and found the Pimp My Crackme contest. Although I'm not in time to participate in the competition, being a follower of the first prize authors work, I decided to take a look at their challenge.
What I found was a really interesting and defying piece of protection software, and I think worth mentioning in a post about cracking and RE.
What I'll be referring here though, will be just a specific piece of code that blocks the reverser from tracing from withing some debuggers, Olly being one of the affected, and not the entire puzzle cracking. If you're using Windbg you won't have to deal with this.

This specific issue relates to the authors having used a known issue where some debuggers get lost or fail to synchronize when an LDT is used to detour the execution path.

The reversed source code to setup the LDT entry is this:
codeSegment = getCodeSegment(); // CS=0x1B
GetThreadSelectorEntry(GetCurrentThread(),
                    codeSegment,
                    &SelectorEntry);
SelectorEntry.LimitLow = 0xFFEF;
SelectorEntry.HighWord.Bytes.Flags2.LimitHi = 0x7;
pHandle = GetCurrentProcess();
error = setLDT(pHandle,
            (int *)0x7FF,
             &SelectorEntry); // LDT = 0x7F8
...
memcpy(farCallClone, farCall, 0x1Fu);
In process, we've got this selector before setup:
And this afterwords:
Flags2 in LDT_ENTRY comprises LimitHi, Sys, Reserved_0, Default_Big, and Granularity in the selector entry. Being its definition:
DWORD LimitHi :4;
DWORD Sys :1;
DWORD Reserved_0 :1;
DWORD Default_Big :1;
DWORD Granularity  :1;
So they're setting up limitHi = 0x7 for selector entry 0x7FF. What this means to Olly, is that it fails to trace when far jumping, and consequently we fail to use it to find the correct key. To bypass this "protection" we need to fix the jumped segments to be the same than our start up segment. There are a couple of ways to do this, the simplest one I found for this case is just to patch position:
009200DD call far 07FF:00000000
to our original execution segment:
009200DD call far 001B:00000000
Now, the funny part is that they have escalated the problem using multiple segments. I'm not going to delve much in the cracking process or explain how the protection works, but in order to explain how the various segments are utilized, I need to refer that the core of the protection lies in a virtual machine architecture. This virtual machine has a set of functions that virtualize it's domain operations. Aside from other interesting approaches used, this functions where laid out, each one on it's own LDT segment. And shuffled after key validation. So you can imagine how Olly must feel, always jumping around segments.
How can we bypass this? As I pointed out before, we need to maintain execution to our home segment 0x1b. How can we do this? patching the key function that distributes all the work of the VM: I called it CallVMInstruction.
Replace this function with this code:
0040CD95 mov word ptr ss:[ebp-2],1B
0040CD9B jmp short pimp_cra.0040CDC1
0040CD9D nop
0040CD9E nop
0040CD9F nop
0040CDA0 push ebp
0040CDA1 mov ebp,esp
0040CDA3 sub esp,14
0040CDA6 mov eax,[arg.1]
0040CDA9 and al,0F8 
0040CDAB shr eax,1
0040CDAD mov edi,eax
0040CDAF shl eax,2
0040CDB2 add eax,edi 
0040CDB4 lea eax,dword ptr ds:[eax+91FE14] 
0040CDBA mov eax,dword ptr ds:[eax] 
0040CDBC mov dword ptr ss:[ebp-6],eax 
0040CDBF jmp short pimp_cra.0040CD95
0040CDC1 mov edi,dword ptr ds:[AF8EF8]
0040CDC7 push [arg.2]
0040CDCA call far fword ptr ss:[ebp-6]
What this code does is it resolves dynamically the function address to our home segment, based on the segment index passed as an argument to the dispatcher and retained in a VM call dispatcher table.

With this you can set a breakpoint at address 0x0040CDCA. Now just relax and wait for the key validation, and you can now trace all the virtual machine operations.

Finally two simple last patches in order to prepare for the flush and hashing operations:
Replace the code at address:
00403046 mov eax,9200C0
0040304B nop
0040304C nop
0040304D nop
0040304E nop
0040304F nop
00403050 nop

00403243 mov eax,9200C0
00403248 nop
If you find any others function that need to be patched, now you know what to do. Consider it an exercise.

Just a side note about a second problem that I found while debugging the crackme. The authors used a far call stub function for the previously discussed purpose. They rebased this function to address zero. Ollydbg gets totally lost when trying to trace a zero base address. The way I found to successfully trace the stub calls was to instead of setting a breakpoint in address zero, I set the breakpoint in the second instruction, address 4, at lea eax,dword ptr ds:[eax*4+8] instruction.
The reversed code for it is again presented here:
...

BaseAddress = 4;
RegionSize = 4092;
pHandle = GetCurrentProcess();
error = NtAllocateVirtualMemory(pHandle,
              &BaseAddress,
              0,
              &RegionSize,
              MEM_TOP_DOWNMEM_RESERVEMEM_COMMIT,
              PAGE_EXECUTE_READWRITE);
if ( error >= 0 )
{
 farCallClone = 0;
 memcpy(farCallClone,
        farCall,
        0x1F);
....

Good look finding the correct key.

2 comentários:

Ika Def disse...

Your Article is Amazing :)

I reposted your article in :

http://idelit.com/index.php?page=213

Thanks!

j00ru disse...

Hello,

I'm glad to see another blog post about the Pimp CrackMe. Neat work!

Just for the record, some interesting materials related to the competition have been recently published:

http://gdtr.wordpress.com/2011/06/24/solving-pimp-crackme-by-j00ru-and-gynvael-coldwind/ - a complete tutorial on solving the challenge

http://j00ru.vexillium.org/?p=866 - a more elaborate post, covering the use of the segmentation mechanism and debuggers' weaknesses.

http://www.secnews.pl/2011/07/01/rozwiazania-konkursu-pimp-my-crackme/ - the source code and a short docs of all contest submissions (including ours).

Take care!