Thursday, 15 October 2009

crackme12 - bruteforce 3

Since my last post I tried starting from scratch, and as far as I can tell the first byte used to XOR the binary is in fact 0x95.
So what I've found out until now is that:
a) a file named iss.rba should exist, and should have the attribute HIDDEN
b) a checksum exists (I'm ignoring this checksum for the time being) - I just change the zero flag and carry on with the debug
c) the first byte of the file is used to XOR all the bytes from 0x401145 to 0x4012AB
d) after this cycle there is a test, if we have our file like this:

  x0    | x1 | x2 | x3 | x4
0x95  | x1 | x2 | x3 | x4

x2 ^ x3 ^ x4 ^ 0xAB must equal 0

if the test succeeds, the binary proceeds to decypher bytes 0x4011B6 up to 0x4011A8.
Unlike the first cycle, instead of XOR'ing all the bytes in the binary with the same byte, it will XOR 1 dword at a time (x1x2x3x4), so ... from what I can tell, bruteforcing "blindly" is something I'm trying to stay clear of.

I thought that there should exist a sequence of bytes in this new block that had been used previously, particularly, I thought that the error message routine, which will push the caption and message, and then call MessageBoxA should exist, therefore, I copied the opcodes pertaining to the pushs's before the call o MessageBoxA (the call itself doesn't interest me since it is relative to the current position, the pushs's however are not), and tried a script that would go from 0x0 to 0xFFFFFFFF and try to find a sequence of bytes equal to the sequence of bytes relative to the  MessageBoxA prologue. This didn't work.

Tuesday, 13 October 2009

crackme12 - bruteforce 2

Today I thought I had managed to find the correct byte to XOR the crackme with. I made some progress, until the execution stopped with an illegal access, thus everything I "learnt" about the crackme today is probably useless to its solution.
Despite all this, after the cycle that XOR's the binary, everything looks good, but while stepping through the changed instructions the disassembly apparently changes (I'm yet to find a decent explanation for this) and I get an instruction that raises the afore mentioned exception.

Monday, 12 October 2009

crackme12 - bruteforce

Since my last post, I tried finding a correct value to use as the xor key.
That didn't go as well as I expected, so I started thinking about a way to bruteforce it. I tried creating 255 files, with the corresponding byte as content, patch the binary such that the jz after the addition-test  became jmp, and check each file, but this was way to slow. As an alternative I tried coding a small program that could test all the combinations of the variables, so the addition test would succeed. If this worked, then at least I would have to test only a subset of all the 255 files, should the system have more than one solution, but again, this would take an enormous ammount of time.
So ... I'm left with an alternative, probably the one I should have started with.
I have an IDC script coded that will XOR the bytes in the binary, now all I have to do is patch the binary (so that the binary won't run the xor-cycle again) and try each value.

Saturday, 10 October 2009

crackme12 - part1

This crackme starts by getting the number of cycles, and then storing the high order 32 bits into a varible (I called highOrder32BitsTSC):
CODE:004012B0 start proc far
CODE:004012B0 rdtsc
CODE:004012B2 mov ds:highOrder32BitsTSC, edx

The crackme procedes to the calculation of a filename, based on a string:
CODE:00401000 sub_401000
CODE:00401000 push ebx
CODE:00401001 push esi
CODE:00401002 push edi
CODE:00401003 xor ecx, ecx
CODE:00401005 jmp short loc_401036
CODE:00401007 ; ---------------------------------------------------------------------------
CODE:00401007
CODE:00401007 calc_fileName:
CODE:00401007 mov eax, ds:lpFileName
CODE:0040100C cmp bl, 2Eh
CODE:0040100F jz short loc_401035
CODE:00401011 mov edx, ds:lpFileName
CODE:00401017 movsx eax, bl
CODE:0040101A mov edx, ds:lpFileName
CODE:00401020 mov ebx, 1Ah
CODE:00401025 movsx edx, byte ptr [edx+ecx+1]
CODE:0040102A imul edx
CODE:0040102C cdq
CODE:0040102D idiv ebx
CODE:0040102F add dl, 61h
CODE:00401032 mov [esi+ecx], dl
CODE:00401035
CODE:00401035 loc_401035:
CODE:00401035 inc ecx
CODE:00401036
CODE:00401036 loc_401036:
CODE:00401036 mov esi, ds:lpFileName
CODE:0040103C mov bl, [esi+ecx]
CODE:0040103F test bl, bl
CODE:00401041 jnz short calc_fileName

The result from these calculations is always the same, since the input is always the same (the string xor.key:
DATA:00402000 lpFileName      dd offset aXor_key
DATA:00402010 aXor_key    db 'xor.key',0 )

so, until the end of the string 'xor.key' calc_fileName is going to be called as we can see from:

CODE:00401036 mov     esi, ds:lpFileName
CODE:0040103C mov     bl, [esi+ecx]
CODE:0040103F test    bl, bl
CODE:00401041 jnz     short calc_fileName

and what does calc_fileName do?

CODE:00401007  calc_fileName:
CODE:00401007  mov     eax, ds:lpFileName
CODE:0040100C  cmp     bl, 2Eh
CODE:0040100F  jz      short loc_401035
CODE:00401011  mov     edx, ds:lpFileName
CODE:00401017  movsx   eax, bl
CODE:0040101A  mov     edx, ds:lpFileName
CODE:00401020  mov     ebx, 1Ah
CODE:00401025  movsx   edx, byte ptr [edx+ecx+1]
CODE:0040102A  imul    edx
CODE:0040102C  cdq
CODE:0040102D  idiv    ebx
CODE:0040102F  add     dl, 61h
CODE:00401032  mov     [esi+ecx], dl
CODE:00401035
CODE:00401035 loc_401035:
CODE:00401035  inc     ecx

move the address of aXor_key to eax (401007)
compare bl (the current byte) with 2Eh (the hexa for '.')
if bl equals '.' proceed to the following byte (40100F)
move the address of aXor_key to edx (401011)
move with sign extend the current byte to eax (401017)
move the address of aXor_key to edx (40101A)
move 1Ah to ebx 401020
move with sign extend the next byte to edx (401025)
eax = eax * edx (40102A)
extend the high order bit of EAX throughout EDX (40102C)
intiger division of eax by ebx (40102D)
eax = eax / ebx (eax = eax / 1Ah)
edx = eax % ebx (edx = eax % 1Ah)
add 61h to edx (the ascii code of 'a')
and store it in the first byte of the string aXor_key (401032)
note:
Since we are dividing eax by 1A, the remainder will be between 0 and 25, by adding 61h
will guarantee that the final file name will be between 'a' and 'z'.

After this is done, the string aXor_key will hold the value 'iss.rba'.

Aftewards a few checks are going to take place:
The crackme tries to get the file attributes from a file named 'iss.rba', if it fails to do so, a control variable (named control_variable) will be set to 1:

CODE:00401043  mov     eax, ds:lpFileName
CODE:00401043                                         
CODE:00401048  push    eax                           
CODE:00401049  call    GetFileAttributesA            
CODE:0040104E  cmp     eax, 0FFFFFFh                 
CODE:00401053  jnz     short isHidden                
CODE:00401055  mov     ds:control_variable, 1
CODE:0040105F  jmp     short loc_40106F

if it succeeds, then we will test to see if the file is hidden:

CODE:00401061 isHidden:
CODE:00401061 test    al, 2 ; FILE_ATTRIBUTE_HIDDEN
CODE:00401063  jnz     short loc_40106F
CODE:00401065   ds:control_variable, 1

Afterwards it tries to open the file, and read 27h bytes to a variable named file_buffer (location 40205C)

CODE:0040106F loc_40106F:
CODE:0040106F   mov     edx, ds:control_variable
CODE:00401075   test    edx, edx                    
CODE:00401077  jnz     short loc_401098            
CODE:00401079   push    0                           
CODE:0040107B   mov     ecx, ds:lpFileName
CODE:00401081   push    ecx                         
CODE:00401082   call    _lopen                      
CODE:00401087   mov     edi, eax
CODE:00401089   cmp     edi, 0FFFFFFFFh             
CODE:0040108C   jnz     short loc_401098            
CODE:0040108E  mov     ds:control_variable, 1
CODE:00401098
CODE:00401098 loc_401098:
CODE:00401098  mov     eax, ds:control_variable
CODE:0040109D  test    eax, eax                    
CODE:0040109F  jnz     short test_control_var      
CODE:004010A1  xor     ecx, ecx                     
CODE:004010A3  mov     eax, offset file_buffer
CODE:004010A8
CODE:004010A8 clearBufferAndReadFile:               
CODE:004010A8   mov     byte ptr [eax], 0
CODE:004010AB   inc     ecx                           
CODE:004010AC   inc     eax                           
CODE:004010AD   cmp     ecx, 28h                      
CODE:004010B0   jl      short clearBufferAndReadFile    )
CODE:004010B2   push    27h                           
CODE:004010B4   push    offset file_buffer            
CODE:004010B9   push    edi                           
CODE:004010BA   call    _lread                        
CODE:004010BF   push    edi                           
CODE:004010C0   call    _lclose                       
CODE:004010C5   xor     eax, eax                      
CODE:004010C7   xor     ecx, ecx                      
CODE:004010C9   mov     edx, offset file_buffer

And now, another test comes up:
CODE:004010CE loc_4010CE:
CODE:004010CE  mov     ebx, [edx]                    
CODE:004010D0  add     ebx, eax                      
CODE:004010D2  mov     eax, ebx
CODE:004010D4  add     ecx, 4                        
CODE:004010D7  add     edx, 4                        
CODE:004010DA  cmp     ecx, 25h                      
CODE:004010DD  jl      short loc_4010CE              
CODE:004010DF  test    eax, eax                      
CODE:004010E1  jz      short test_control_var        
CODE:004010E3  mov     ds:control_variable, 1
CODE:004010ED  mov     eax, ds:control_variable
CODE:004010F2  test    eax, eax                        ; Logical Compare
CODE:004010F4  jz      short not_bad

Assuming we have the following in our file iss.rba:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
equivalently:
414243444546474849...
what is happening here is that
44434241 + 48474645 ... & 0xFFFFFFFF = 0 so this loop will run 10 times.
Aditionally, the first byte, is going to be used, to XOR
4012AB - 401145 bytes of the program, starting at 401145
which will be the first executed instruction after the loop that XOR's all the bytes.
We can see that happening here:

CODE:00401112 not_bad:
CODE:00401112 C7 05 C4 20 40 00+mov     ds:dword_4020C4, offset loc_401145
CODE:0040111C C7 05 C8 20 40 00+mov     ds:dword_4020C8, offset loc_4012AB
CODE:00401126 EB 0E             jmp     short loc_401136                ; Jump
CODE:00401128                   ; ---------------------------------------------------------------------------
CODE:00401128
CODE:00401128 loc_401128:
CODE:00401128   mov     dl, ds:file_buffer
CODE:0040112E  xor     [eax], dl                     
CODE:00401130  inc     ds:dword_4020C4               
CODE:00401136 loc_401136:
CODE:00401136  mov     eax, ds:dword_4020C4
CODE:0040113B  mov     ecx, ds:dword_4020C8
CODE:00401141  cmp     eax, ecx                      
CODE:00401143  jb      short loc_401128              
CODE:00401145 loc_401145:                           
CODE:00401145  35 C8 B5 D5 95 xor     eax, 95D5B5C8h   (with opcodes)       

The information I provided above, is all I know about crackme12.
Currently, I'm trying to figure out if there are any other constraints enforced on the first byte of the file iss.rba.
The first idea that came to my mind was simply "we don't have to find a byte that XOR'ed with 358 bytes will generate legal instructions. If we XOR 35h with E9 and the following 4 bytes are a legal address, outside of the 358-xor'ed-bytes-region, we should be ok", again, it was over optimistic :P.

I think I will try with other flow changing instructions, and see where I can go from there, since I'm trying to find out a byte that generates legal instructions, and then build a string such that the addition-test mentioned previously succeeds.

As far as the number of cycles go, I can't say for sure, but the only possible reason I can see for the number of cycles to be stored in a variable, is for later comparision with the number of cycles executed at a later point in the program. One can do this for two reasons (at least, these are the only two reasons I can think of right now): benchmarking, and anti-debug. It will be interesting to see if/how the number of cycles will be used in this crackme.

crackme11-final

The solution, and keygen code for crackme11:

This crackme starts by XOR'ing 1200h bytes starting at 402000:

CODE:00401000 mov ecx, 1200h
CODE:00401005 mov edi, offset unk_402000
CODE:0040100A mov esi, edi
CODE:0040100C
CODE:0040100C loc_40100C:
CODE:0040100C lodsb
CODE:0040100D xor al, 21h
CODE:0040100F stosb
CODE:00401010 loop loc_40100C

and then it installs the exception handler:

CODE:00401012 call sub_40107C
CODE:00401017
CODE:00401017 exception_handler:
....

disassembly with opcodes:

CODE:0040107C 64 67 FF 36 00 00     push dword ptr fs:0
CODE:00401082 64 67 89 26 00 00     mov fs:0, esp
CODE:00401088 60                     pusha
CODE:00401089 9C                     pushf
CODE:0040108A 33 C0                 xor eax, eax
CODE:0040108C E8 09 00 00 00         call near ptr loc_401099+1
CODE:00401091 E8 E8 23 00 00         call near ptr unk_40347E
CODE:00401096 00 7A 23                 add [edx+23h], bh
CODE:00401096 sub_40107C             endp
CODE:00401096
CODE:00401099
CODE:00401099 loc_401099:
CODE:00401099 A0 8B 04 24 EB         mov al, ds:0EB24048Bh
CODE:0040109E 03 7A 29                 add edi, [edx+29h]
CODE:004010A1 E9 C6 00 90 C3         jmp near ptr 0C3D0116Ch

so, what we have here is an unaligned disassembly. Since this will be a
recurring problem throughout this crackme, we can run a script


correct_calls1.idc:

auto a;
auto i;
auto b;
i = 0x401044;
a = 0x00;
b = 0x00;
while(a != 0xffffffff){

a = FindBinary(i, 0x01, "A0 8B 04 24");
if(a != b){
MakeUnkn(a, 0);
PatchByte(a, 0x90);
MakeCode(a+1);
Message("Byte Patched at:%x\n",a);
b = a;
}
i = i+1;
}

We look for the bytes A0 8B 04 24. Why is this? At CODE:0040108C call near ptr loc_401099+1
we're calling a procedure at 401099+1, and at 401099 we have the following bytes:
CODE:00401099 A0 8B 04 24 EB, since 8B is the opcode for call, and A0 is going to be ignored, and since A0 is the opcode for mov, which takes arguments, if we can NOP A0 and tell IDA to "define as code" at 8B, then the disassembly must resemble more closely our program, let's try it by hand:

If we undefine the code at 401099, and then define it at 401099+1 we go from this:
CODE:00401099 A0 8B 04 24 EB  mov     al, ds:0EB24048Bh

to:
CODE:0040109A    loc_40109A:
CODE:0040109A 8B 04 24    mov     eax, [esp]
CODE:0040109D EB 03        jmp     short loc_4010A2

and at loc_4010A2 we can see that the code has become:
CODE:004010A2    loc_4010A2:
CODE:004010A2 C6 00 90    mov     byte ptr [eax], 90h
CODE:004010A5 C3        retn

so, in eax we have the return address (which is 00401091 which is the address right
after our call to 401099+1), and we are NOP'ing the instruction (mov byte ptr [eax], 90h),

so instead of having
CODE:00401091 E8 E8 23 00 00 call    near ptr unk_40347E
as the instruction to which we will return, we will actually have:

CODE:00401092 E8 23 00 00 00                    call    near ptr dword_4010AC+0Eh
CODE:00401097 7A 23                             jp      short near ptr dword_4010AC+10h

since at 401091 we will have a NOP which is a 0 argument instruction and this is why we want to run our second script:

We can run the nop_ret.idc script, so that all the return
addresses from dummy calls appear as NOP'ed


nop_ret.idc:

auto a;
auto i;
auto b;
auto c;
auto failed;

i = 0x401044;
a = 0x00;
b = 0x00;
failed = 0;
while(a != 0xffffffff){

a = FindBinary(i, 0x01, "E8 E8 23");
if(a != b){
MakeUnkn(a, 0);
PatchByte(a, 0x90);
c = MakeCode(a+1);
if(c == 0)
failed = failed + 1;
Message("Byte Patched at:%x with ret = %d\n",a, c);
b = a;
}
i = i+1;
}
Message("%d conversions Failed\n", failed);

this script however, fails in the following addresses,
where I corrected by hand:

addr addr+1 instruction @ addr+1 | word @ call_arg
401091 | 401092 | call near ptr dword_4010AC+0Eh | EBE908EBh
40134f | 401350 | call near ptr dword_401364+14h | DB8B4000h
4017b0 | 4017b1 | call near ptr dword_4017CC+0Dh | F7EBE908h
4018d8 | 4018d9 | call near ptr dword_4018F4+0Dh | F7EBE908h

Ok let's take a look at what happens in 401091, for instance:
After running the script, we have this:
call near ptr dword_4010AC+0Eh
If we go to 4010AC and undefine the code and define the code at
4010AC+E this is what we see:

CODE:004010BA    locret_4010BA:
CODE:004010BA C3    retn

so ... we go back to the return address:
CODE:00401097 7A 23    jp      short near ptr unk_4010BC
we take the jump, and define as code:

CODE:004010BC    loc_4010BC:
CODE:004010BC 7A E9        jp      short near ptr word_4010A6+1
CODE:004010BE    loc_4010BE:                             
CODE:004010BE 70 DA        jo      short sub_40109A
CODE:004010C0 7B D1        jnp     short near ptr loc_401092+1
CODE:004010C2 71 F3        jno     short loc_4010B7

so once again, let's look at 4010A6+1:
(undefine code @ 4010A6, define code at 4010A7)
CODE:004010A7 70 F0                             jo      short near ptr unk_401099
CODE:004010A9 87 D2                             xchg    edx, edx
CODE:004010AB 71 07                             jno     short loc_4010B4

We can automate this process through the following script:

correct_calls2.idc

auto a;
auto i;
auto b;
i = 0x401044;
a = 0x00;
b = 0x00;
while(a != 0xffffffff){

a = FindBinary(i, 0x01, "70 F0 87 D2");
if(a != b){
MakeUnkn(a, 0);
MakeCode(a);
Message("Byte Patched at:%x\n",a);
b = a;
}
i = i+1;
}

So, thus far we have XOR'ed 1200h bytes of data, and set an exception
handler. After stepping through a couple of instructions, we arrive at

CODE:004010CC mov     esi, offset unk_402000
....
CODE:00401114 popa
CODE:00401115 push    4
CODE:00401117 push    1000h
CODE:0040111C push    6000h
CODE:00401121 push    0
CODE:00401123 call    VirtualAlloc
LPVOID WINAPI VirtualAlloc(
__in_opt  LPVOID lpAddress,
__in      SIZE_T dwSize,
__in      DWORD flAllocationType,
__in      DWORD flProtect
);

Reserves or commits a region of pages in the virtual address space of
the calling process. Memory allocated by this function is automatically
initialized to zero, unless MEM_RESET is specified.
6000h bytes of memory are being allocated with AllocationType set to 1000h and flProtect set to 4. From msdn:
flAllocationType = 1000h - Allocates physical storage in memory or in the paging file on disk for the specified reserved memory pages. The function initializes the memory to zero.
flpProtect = 4 - Enables execute, read-only, or read/write access to the committed region of pages

afterwards there is a call to mojo1 CODE:00401133 call mojo1 mojo1 is
responsible to read the previously XOR'ed data, make changes to it, and
write it in the virtualalloc allocated chunck of memory. When mojo1
ends, we have what seems like a PE in the allocated chunck of memory.
and then the memory chunck's contents are written to a file named
DBG.VXD (004011FF)



At
ODE:004012AA   popf                    
CODE:004012AB  popa                    
CODE:004012AC  push    0
CODE:004012AE  push    4000000h
CODE:004012B3  push    0
CODE:004012B5  push    0
CODE:004012B7  push    0
CODE:004012B9  push    0
CODE:004012BB  push    esi
CODE:004012BC  call    CreateFileA     
CODE:004012C1  or      eax, eax        

We check if we can get an handle for the file. If we can't get it, we
can create our window with 004012F7 as our call back function for event
handling.



In our call back function, we're interested in wm_command
CODE:00401318                   case_wm_command:                        
CODE:00401318                 cmp     ax, 111h        
CODE:0040131C                 jnz     short case_WM_INITDIALOG
CODE:0040131E                 jmp     loc_40143C  

CODE:0040143C loc_40143C:                             
CODE:0040143C                 cmp     word ptr [ebp+arg_8], 9 
CODE:00401441                 jz      loc_4014E2      
CODE:00401447                 cmp     word ptr [ebp+arg_8], 3E8h 
CODE:0040144D                 jnz     short loc_401457 
CODE:0040144F                 push    [ebp+hDlg]      
CODE:00401452                 call    call_getDlgItem 
CODE:00401457
CODE:00401457 loc_401457:                             
CODE:00401457                 cmp     word ptr [ebp+arg_8], 3E9h 
CODE:0040145D                 jnz     short loc_401467 
CODE:0040145F                 push    [ebp+hDlg]      
CODE:00401462                 call    call_getDlgItem 
CODE:00401467
CODE:00401467 loc_401467:                             
CODE:00401467                 cmp     [ebp+arg_8], 1  
CODE:0040146B                 jz      short near ptr CHECK_PRESSED 
CODE:0040146D

We can also see that call_getDlgItem is coded as
CODE:00401474 call_getDlgItem proc near               
CODE:00401474                                         
CODE:00401474
CODE:00401474 hDlg            = dword ptr  8
CODE:00401474
CODE:00401474                 enter   0, 0            
CODE:00401478                 push    ebx
CODE:00401479                 push    ecx
CODE:0040147A                 push    edx
CODE:0040147B                 push    28h             
CODE:0040147D                 push    offset String   
CODE:00401482                 push    3E8h            
CODE:00401487                 push    [ebp+hDlg]      
CODE:0040148A                 call    GetDlgItemTextA 
CODE:0040148F                 mov     edi, offset String 
CODE:00401494                 push    28h             
CODE:00401496                 push    (offset String+28h) 
CODE:0040149B                 push    3E9h            
CODE:004014A0                 push    [ebp+hDlg]      
CODE:004014A3                 call    GetDlgItemTextA 
CODE:004014A8                 mov     edi, offset String 
CODE:004014AD                 pop     edx
CODE:004014AE                 pop     ecx
CODE:004014AF                 pop     ebx
CODE:004014B0                 leave                   
CODE:004014B1                 retn    4               
CODE:004014B1 call_getDlgItem endp

which means that basically everytime we type something, it is copied to
String and String+28h, so the first 0x28 bytes of String hold the
username, from String+0x28 until String+0x50 is where the entered serial
resides.

After we've typed username and serial, we will press the Check button,
and we will jump to "CHECK_PRESSED"

CODE:00401467                 cmp     [ebp+arg_8], 1  
CODE:0040146B                 jz      short near ptr CHECK_PRESSED 

CODE:004014DB CHECK_PRESSED db 8Dh 
CODE:004014DC dd 0CBE9C0h
CODE:004014E0 db 2 dup(0)

at CHECK_PRESSED an illegal instruction is going to take place, so we
should set a breakpoint at our exception handler, and analyze what is
going on in there.



The Exception Handler:

CODE:00401017 except_handler:                         
CODE:00401017                 pusha
CODE:00401018                 mov     eax, [esp+20h+arg_0] ; struct _EXCEPTION_RECORD
CODE:0040101C                 mov     ebx, [esp+20h+arg_8] ; struct _CONTEXT
CODE:00401020                 cmp     dword ptr [eax], 0C000001Dh 
CODE:00401026                 jz      short illegal_inst_excp 
CODE:00401028                 cmp     dword ptr [eax], 0C0000005h 
CODE:0040102E                 jz      short access_viol_excp 
CODE:00401030
CODE:00401030 loc_401030:                            
CODE:00401030                                        
CODE:00401030                 popa                   
CODE:00401031                 xor     eax, eax       
CODE:00401033                 retn                   
CODE:00401034 ; ---------------------------------------------------------------------------
CODE:00401034
CODE:00401034 access_viol_excp:                       
CODE:00401034                 lea     esi, [ebx+4]    
CODE:00401037                 mov     ecx, 4
CODE:0040103C
CODE:0040103C loc_40103C:                             
CODE:0040103C                 lodsd                   
CODE:0040103D                 add     dword ptr [esi-4], 12345678h 
CODE:00401044                 add     [ebx+0B0h], eax 
CODE:0040104A                 loop    loc_40103C      
CODE:0040104C                 add     dword ptr [ebx+0B8h], 2 
CODE:00401053                 jmp     short loc_401030 
CODE:00401055 ; ---------------------------------------------------------------------------
CODE:00401055
CODE:00401055 illegal_inst_excp:                      
CODE:00401055                 lea     edi, [ebx+4]    ; debug registers
CODE:00401058                 mov     eax, 0DEADC0DEh
CODE:0040105D                 rol     eax, 1          
CODE:0040105F                 stosd              ; change dr0  
CODE:00401060                 rol     eax, 1          
CODE:00401062                 stosd              ; change dr1     
CODE:00401063                 rol     eax, 1          
CODE:00401065                 stosd              ; change dr2    
CODE:00401066                 rol     eax, 1          
CODE:00401068                 stosd              ; change dr3     
CODE:00401069                 and     eax, 0          
CODE:0040106C                 stosd              ; change dr6 
CODE:0040106D                 add     eax, 155h       
CODE:00401072                 stosd              ; change dr7     
CODE:00401073                 add     dword ptr [ebx+0B8h], 2 ; change _EIP in _context
CODE:0040107A                 jmp     short loc_401030 

So our exception handler starts to check the exception code, and it is
programmed to handle ACCESS_VIOLATION_EXCEPTION's and
ILLEGAL_INSTRUCTION_EXCEPTION's Let's look at what happens, when an
illegal instruction exception is being handled (since an illegal
instruction is what brought us here - this time -): So the debug
registers are being set with constants, and finally we disable hardware
breakpoints by setting debug control register to 0x155. The eip value in
the CONTEXT structure is corrected and the exception handler returns.

After we continue with the execution of the crackme, we arrive at


CODE:00401A7D generate_ascii:                         
CODE:00401A7D pusha                                   
CODE:00401A7E mov     edi, (offset String+50h)
CODE:00401A83 xor     al, al                          
CODE:00401A85
CODE:00401A85 generate_ascii_loop:                   
CODE:00401A85 stosb                                   
CODE:00401A86 inc     al                              
CODE:00401A88 test    al, al                          
CODE:00401A8A jnz     short generate_ascii_loop       
CODE:00401A8C popa                                    
CODE:00401A8D retn    

this procedure stores all numbers from 0 to 0xff in the String, after
the last char of the serial (which has a maximunm of 0x28 chars). Next
the length of the username string is calculated:

CODE:00401682 strlen_username:                        
CODE:00401682 popf                                    
CODE:00401683 popa                                    
CODE:00401684 mov     edi, offset String              
CODE:00401689 repne scasb                             
...

CODE:004016CD mini_mojo4:                             
CODE:004016CD popf                                    
CODE:004016CE popa                                    
CODE:004016CF not     ecx                             
CODE:004016D1 dec     ecx                            ; two's complement -> ecx now holds the correct username's length
CODE:004016D2 cmp     ecx, 3                          
CODE:004016D5 jb      wrong_length    
....

and finally we get to the core of this crackme, the serial generation
procedure:


CODE:00401A8E generate_serial:                        
CODE:00401A8E pusha                                   
CODE:00401A8F xor     eax, eax                        
CODE:00401A91 mov     esi, offset String              
CODE:00401A96 mov     ebx, (offset String+50h)
CODE:00401A9B mov     edi, (offset String+150h)
CODE:00401AA0 pusha                                   
CODE:00401AA1 mov     ecx, 100h
CODE:00401AA6 rep stosb                               
CODE:00401AA8 popa                                    
CODE:00401AA9
CODE:00401AA9 loc_401AA9:                             
CODE:00401AA9 lodsb                                   
CODE:00401AAA mov     edx, ecx                        
CODE:00401AAC imul    edx, eax                        
CODE:00401AAF add     al, dl                          
CODE:00401AB1
CODE:00401AB1 loc_401AB1:                             
CODE:00401AB1                                         
CODE:00401AB1 xor     al, [ebx+ecx]                   
CODE:00401AB4 add     [ebx+ecx*4], cl                 
CODE:00401AB7 xor     [ebx+eax], cl                   
CODE:00401ABA add     al, [ebx+ecx*4]                 
CODE:00401ABD push    edx
CODE:00401ABE xor     edx, edx                        
CODE:00401AC0 mov     edx, [edx]
CODE:00401AC2 pop     edx
CODE:00401AC3 cmp     al, 41h                         
CODE:00401AC5 jb      short loc_401AB1                
CODE:00401AC7 cmp     al, 5Ah                         
CODE:00401AC9 ja      short loc_401AB1                
CODE:00401ACB stosb                                   
CODE:00401ACC loop    loc_401AA9                      
CODE:00401ACE popa                                    
CODE:00401ACF retn    

The general idea is that, the username string is going to be read, each
read byte will be changed according to a certain logic, if the resulting
char is not between 0x41 and 0x5A (meaning it's not between 'A' and 'Z')
the calculations are going to take place again. However ...
CODE:00401ABE xor edx, edx CODE:00401AC0 mov edx, [edx] this implies
that exceptions are going to take place during the algorithm,
specifically, illegal access exceptions, so let's look at what our
exception handler behaviour is as far as these exceptions go:
CODE:00401034 access_viol_excp:                       
CODE:00401034                 lea     esi, [ebx+4]    
CODE:00401037                 mov     ecx, 4
CODE:0040103C
CODE:0040103C loc_40103C:                             
CODE:0040103C                 lodsd                   
CODE:0040103D                 add     dword ptr [esi-4], 12345678h 
CODE:00401044                 add     [ebx+0B0h], eax                 ; adds to CONTEXT's eax the starting value in the debug register
CODE:0040104A                 loop    loc_40103C      
CODE:0040104C                 add     dword ptr [ebx+0B8h], 2 
CODE:00401053                 jmp     short loc_401030 

What is happening is that whatever is written in the current debug
register is read to eax, the value in the debug register is incremented
by 12345678h, and the value of eax in the CONTEXT structure is
incremented with the earlier value in the debug register. This value is
then used in the generate_serial loop. Finally the value of eip in
CONTEXT is incremented by 2 (40104C). Therefore, when we return from the
exception handler, the next instruction may or may not be a valid
instruction, so another exception may be raised, and the values in the
debug registers reset. In order to emulate this behaviour, since it is
non-linear, and I couldn't find a better way to do it, I re-implemented
the algorithm, with the exception handler included to create the keygen.

When this is done, the entered serial is compared against the calculated
serial, and the number of chars that are equal, must be the same as the
username's length.

I didn't see the created file being used for anything appart from the
handle-check mentioned in the solution, that's why I didn't go into much detail about Mojo1.



Related readings:
http://www.scribd.com/doc/6825346/590BypassHardwareBreakpointProtection
http://vx.netlux.org/lib/vsc04.html#p2
http://www.hexblog.com/2005/12/tracing_exception_handlers.html
http://www.microsoft.com/msj/0197/exception/exception.aspx

The keygen:
The keygen base code was take from one of iczelion's tutorials
CONTROLS.ASM:

.386
.model flat,stdcall
option casemap:none
;assume NOTHING


include c:\masm32\include\windows.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\masm32.inc
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\masm32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
Mojo proto :DWORD
GenerateASCII proto
GenerateSerial proto
SetExceptHandler proto
;except_handler proto :EXCEPTION_RECORD,:DWORD,:CONTEXT,:DWORD

.data
ClassName db "SimpleWinClass",0
AppName  db "Our First Window",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0
MenuName db "FirstMenu",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit1 HWND ?
hwndEdit2 HWND ?

username db 512 dup(?)
serial db 512 dup(?)
bigString db 1024 dup(?)

.const
ButtonID equ 0
EditID1 equ 1
EditID2 equ 2

IDM_HELLO equ 3
IDM_CLEAR equ 4
IDM_GETTEXT equ 5
IDM_EXIT equ 6

.code
except_handler:
pusha
;lea     eax, arg_0
mov eax, ebp
add eax, 8
mov     eax, [eax]
;lea     ebx, arg_8
mov ebx, ebp
add ebx, 16
mov     ebx, [ebx]
cmp     dword ptr [eax], 0C000001Dh     
jz      short illegal_inst_excp         
cmp     dword ptr [eax], 0C0000005h     
jz      short access_viol_excp             

loc_401030:                                             

popa                                    
xor     eax, eax                        
retn                                       
access_viol_excp:                                        
lea     esi, [ebx+4]                     
mov     ecx, 4

loc_40103C:                                             
lodsd                                   
add     dword ptr [esi-4], 12345678h     
add     [ebx+0B0h], eax                 
loop    loc_40103C                      
add     dword ptr [ebx+0B8h], 2         
jmp     short loc_401030                 

illegal_inst_excp:                      
lea     edi, [ebx+4]    
mov     eax, 0DEADC0DEh
rol     eax, 1          
stosd                   
rol     eax, 1          
stosd                   
rol     eax, 1          
stosd                   
rol     eax, 1          
stosd                   
and     eax, 0          
stosd                   
add     eax, 155h       
stosd                   
add     dword ptr [ebx+0B8h], 2 ; change _EIP in _context (4014DB + 2 = 4014DD)
jmp     short loc_401030         


start:
;#####################################
;invoke SetExceptHandler

invoke GetModuleHandle, NULL
mov    hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
;#####################################

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov   wc.cbSize,SIZEOF WNDCLASSEX
mov   wc.style, CS_HREDRAW or CS_VREDRAW
mov   wc.lpfnWndProc, OFFSET WndProc
mov   wc.cbClsExtra,NULL
mov   wc.cbWndExtra,NULL
push  hInst
pop   wc.hInstance
mov   wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName, OFFSET MenuName
mov   wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov   wc.hIcon,eax
mov   wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov   wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov   hwnd,eax
INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
INVOKE UpdateWindow, hwnd
.WHILE TRUE
INVOKE GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDW
mov     eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,EditID1,hInstance,NULL
mov  hwndEdit1,eax
invoke SetFocus, hwndEdit1
invoke CreateWindowEx, WS_EX_CLIENTEDGE, ADDR EditClassName, NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50, 70, 200, 25, hWnd, EditID2, hInstance, NULL
mov hwndEdit2, eax
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
70,100,140,25,hWnd,ButtonID,hInstance,NULL
mov  hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit1,ADDR TestString
invoke SendMessage,hwndEdit1,WM_KEYDOWN,VK_END,NULL
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit1,NULL
.ELSEIF  ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit1,ADDR username,512
invoke MessageBox,NULL,ADDR username,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke GetWindowText,hwndEdit1,ADDR bigString,28h
invoke Mojo, ADDR username
;invoke SetWindowText,hwndEdit2,ADDR serial
invoke SetWindowText,hwndEdit2,ADDR (bigString+150h)
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor    eax,eax
ret
WndProc endp 

Mojo proc text:DWORD
;invoke SetExceptHandler
ASSUME FS:NOTHING

push except_handler
push fs:[0]
mov fs:[0], esp
invoke GenerateASCII
;illegal instruction
db 8Dh
db 0C0h

invoke GenerateSerial

ret
Mojo endp



GenerateASCII proc
mov edi, offset bigString+50h
xor al, al
loop_begin:
stosb
inc al
test al, al
jnz loop_begin
ret
GenerateASCII endp

GenerateSerial proc

pusha                                       
xor     eax, eax                            
mov     esi, offset bigString ; ""
mov     ebx, (offset bigString+50h)
mov     edi, (offset bigString+150h)
pusha                                       
mov     ecx, 100h
rep stosb                                   
popa   
;#####strlen(username)
mov edi, offset bigString
xor eax, eax
or ecx, 0FFFFFFFFh
cld
repne scasb
not ecx
dec ecx
;#####strlen(username)    
mov     edi, (offset bigString+150h)                 

loc_401AA9:                                                 
lodsb                                       
mov     edx, ecx                            
imul    edx, eax                            
add     al, dl                              

loc_401AB1:                                                 

xor     al, [ebx+ecx]                       
add     [ebx+ecx*4], cl                     
xor     [ebx+eax], cl                       
add     al, [ebx+ecx*4]                     
push    edx
xor     edx, edx                            
mov     edx, [edx]
pop     edx
cmp     al, 41h                             
jb      short loc_401AB1                     
cmp     al, 5Ah                             
ja      short loc_401AB1                     
stosb                                       
loop    loc_401AA9                          
popa                                        
retn             
GenerateSerial endp



SetExceptHandler proc
ASSUME FS:NOTHING

push except_handler
push fs:[0]
mov fs:[0], esp
xor eax, eax


retn
SetExceptHandler endp

end start


CONTORLS.RC:
#define IDM_HELLO 3
#define IDM_CLEAR 4
#define IDM_GETTEXT 5
#define IDM_EXIT 6

FirstMenu MENU
{
POPUP "&Test Controls"
{
MENUITEM "Say Hello",IDM_HELLO
MENUITEM "Clear Edit Box",IDM_CLEAR
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
}
------------------------

crackme11-day4/5/6

In my last post I had an estimation which was over optimistic.
I managed to have a keygen coded and a solution written three days ago, but I wanted to have some feedback before posting them here. So two days ago I spent sometime getting some more info about hardware breakpoints vs software breakpoints and anti-debugging with hardware breakpoints, namely:

http://www.scribd.com/doc/6825346/590BypassHardwareBreakpointProtection
http://www.technochakra.com/software-breakpoints/
http://www.nynaeve.net/?p=80
http://www.nynaeve.net/?p=81

I spent some time trying to get a little more info on variable argument's functions, but I wasn't getting any results, so I started with crackme12.

Saturday, 3 October 2009

crackme11-day2/3

Yesterday was spent debugging a little more of crackme11 and I managed to find where the serial calculation was taking place.
Today I spent some of the afternoon re-reading Iczelion's Win32 ASM tutorials and I started coding the keygen. I think I will have to rely on the exception handler just as the crackme does to generate the serial.

Hopefully tomorrow I will have the keygen coded, and monday I will present the solution here.

Thursday, 1 October 2009

crackme11-day1

Actually this is not day 1 of this crackme solution, but it is day1 of this blog, so it's basically the same.

I've spent all day remembering what I had previously done in this crackme and remembering some concepts associated with SEH.

Afterwards I delved deeper in the crackme, and I found some more of it's inner workings.

There is still a lot to do.

References:
A Crash Course on the Depths of Win32 Structured Exception Handling
Win32 Exception Handling for assembler programmers