Name: PacMe Author: Kwazy Wabbit URL: http://www.crackmes.cjb.net/ Difficulty: Just right ;) Protection: Keyfile

Reading the Keyfile - What, Where and How Much?

I assume you have installed SoftICE and set it up right, see my site for details on how to do this. You also need W32DASM and a hex editor. Right, let's open the crackme up with W32DASM, and look for a good import to BPX on or examine. This crackme uses the normal method - CreateFileA.
 " The CreateFile function creates or opens the following objects
   and returns a handle that can be used to access the object.    "
So, off we go into SoftICE and BPX CreateFileA. When we press "Check", we break in SoftICE:

-------------------------------------------------------------------------PROT32-
0167:004016D8  52                  PUSH      EDX                                
0167:004016D9  E81C010000          CALL      KERNEL32!CreateFileA               
0167:004016DE  83F8FF              CMP       EAX,-01                            
0167:004016E1  7464                JZ        00401747                           

The "CMP EAX, -01" is checking the return value to see if the file was opened or not. If -01 is returned, then the file doesn't exist. .You should see from the API ref that the last parameter is the filename to load, so do a "d edx". You see:

016F:00403192 4B 77 61 7A 79 57 65 62-2E 62 69 74 00 4B 77 61  KwazyWeb.bit.Kwa

At the end of the ".bit", there is a 00, or NULL, making this a null-terminated string. This matches up with our API: "Points to a null-terminated string that specifies the name of the object". This is the name of our keyfile! So, now let's make a "fake" keyfile. Time for a small tip from +Aesculapius:

"The target program will read strategic offset locations of the key file. A readable text inside it will warn about the precise location being read at any time."
(Taken From the Cracker's Notes, "Tips & Tricks for cracking Key File Protections")

Damn, I love the Cracker's Notes :). Anyway, I decided to use 20h bytes worth of the "Vindaloo" song (I dunno...?), like so:

00000000:  56 69 6E 64-61 6C 6F 6F-21 20 56 69-6E 64 79 6C  Vindaloo! Vindyl
00000010:  6F 6F 2C 20-6C 61 20 6C-61 21 20 4E-61 20 6E 61  oo, la la! Na na

OK, so let's fire up SoftICE and we see this next:

-------------------------------------------------------------------------PROT32-
0167:004016E8  6A00                PUSH      00                                
0167:004016EA  6848344000          PUSH      00403448                          
0167:004016EF  6A01                PUSH      01                                
0167:004016F1  68FA344000          PUSH      004034FA                          
0167:004016F6  FF3544344000        PUSH      DWORD PTR [00403444]              
0167:004016FC  E811010000          CALL      KERNEL32!ReadFile                 
0167:00401701  0FB605FA344000      MOVZX     EAX, BYTE PTR [004034FA]          
0167:00401708  85C0                TEST      EAX,EAX                           
0167:0040170A  743B                JZ        00401747                          


Have look at your API reference yet again, and you should see that this is reading 01 byte from the file handle stored at DWORD PTR [00403444] into 004034FA. Doesn't mean much yet? Let's see what it's just read into 4034FA. Do a "D 4034FA", and you'll see:

-----PACME!.data---------------------------------byte--------------PROT---(0)--
016F:004034FA 56 D7 00 00 00 00 00 00-00 00 00 00 00 00 00 00  V...............


So, this reads the first byte of our file into 4034FA, and then MOVZX's it into EAX. If TEST EAX, EAX comes out as zero, then EAX is zero. So it also tests if the first byte of the file is zero. Let's trace some more:

0167:0040170C  6A00                PUSH      00                                
0167:0040170E  6848344000          PUSH      00403448                          
0167:00401713  50                  PUSH      EAX                               
0167:00401714  6888324000          PUSH      00403288                          
0167:00401719  FF3544344000        PUSH      DWORD PTR [00403444]              
0167:0040171F  E8EE000000          CALL      KERNEL32!ReadFile                 
0167:00401724  E8D7F8FFFF          CALL      00401000                          


OK, so it's now using the first byte of the file to "know" how many more bytes to read. Let's replace the vindaloo song with something a little closer to the keyfile:

00000000:  07 42 6F 6F-6D 42 6F 78-           -             BoomBox


So it should now only read "BoomBox" in. Let's see what happens next:

0167:00401729  6A00                PUSH      00                                 
0167:0040172B  6848344000          PUSH      00403448                           
0167:00401730  6A12                PUSH      12                                 
0167:00401732  68E8344000          PUSH      004034E8                           
0167:00401737  FF3544344000        PUSH      DWORD PTR [00403444]               
0167:0040173D  E8D0000000          CALL      KERNEL32!ReadFile                  


OK, it's now reading in 12h bytes. 12h = 18 decimal, so lets update our keyfile with some padding:

00000000:  07 42 6F 6F-6D 42 6F 78-31 32 33 34-35 36 37 38  BoomBox12345678
00000010:  39 30 31 32-33 34 35 36-37 38      -             9012345678


OK, we now see:

0167:00401747  FF3544344000        PUSH      DWORD PTR [00403444]              
0167:0040174D  E8A2000000          CALL      KERNEL32!CloseHandle              
0167:00401752  EB15                JMP       00401769                          


So it's now finished reading the file, closing it, and jumping somewhere. Where?

0167:00401769  33C0                XOR       EAX,EAX                           
0167:0040176B  C9                  LEAVE                                       
0167:0040176C  C21000              RET       0010                              


This ends the call into the check.


The Check Part 1 - A Wierd Grid

:0040173D E8D0000000              Call 00401812              ; KERNEL32.ReadFile, Ord:01FDh
:00401742 E882F9FFFF              call 004010C9              ; ???
:00401747 FF3544344000            push dword ptr [00403444]  ; The file handle
:0040174D E8A2000000              Call 004017F4              ; KERNEL32.CloseHandle, Ord:0019h
Let's have a look at the call:

:004010C9 55                      push ebp
:004010CA 8BEC                    mov ebp, esp
:004010CC 83C4FC                  add esp, FFFFFFFC

:004010CF 6865334000              push 00403365  ; Wierd string
:004010D4 68BC314000              push 004031BC  ; The same string, I think
:004010D9 E83A070000              Call 00401818  ; KERNEL32.lstrcpyA, Ord:02DCh
Hum. Let's have a look at this wierd string from SoftICE:
-----PACME!.data 01BC-----------------------------byte--------------PROT---(0)--
016F:004031BC 2A 2A 2A 2A 2A 2A 2A 2A-2A 2A 2A 2A 2A 2A 2A 2A  ****************^
016F:004031CC 43 2A 2E 2E 2E 2E 2E 2E-2A 2E 2E 2E 2A 2A 2A 2A  C*......*...****^
016F:004031DC 20 2A 2E 2A 2A 2A 2A 2E-2E 2E 2A 2E 2E 2E 2E 2A   *.****...*....* 
016F:004031EC 2E 2A 2E 2E 2A 2A 2A 2A-2A 2A 2A 2A 2A 2A 2E 2A  .*..**********.* 
016F:004031FC 2E 2E 2A 2E 2E 2E 2E 2A-2E 2E 2E 2A 2E 2E 2E 2A  ..*....*...*...* 
016F:0040320C 2A 2E 2A 2A 2A 2A 2E 2A-2E 2A 2E 2E 2E 2A 2A 2A  *.****.*.*...*** 
016F:0040321C 2A 2E 2A 2E 2E 2E 2E 2A-2E 2A 2A 2A 2A 2A 2A 2A  *.*....*.******* 
016F:0040322C 2E 2E 2A 2E 2A 2A 2A 2E-2E 2A 2E 2E 2E 2E 2E 2A  ..*.***..*.....* 
016F:0040323C 2E 2A 2E 2E 2A 2A 2A 2E-2A 2A 2E 2A 2A 2A 2E 2A  .*..***.**.***.* 
016F:0040324C 2E 2E 2E 2A 2A 2A 2A 2E-2E 2E 2E 2A 58 2E 2E 2A  ...****....*X..* 
016F:0040325C 2A 2A 2A 2A 2A 2A 2A 2A-2A 2A 2A 2A 2A 2A 2A 2A  **************** 
016F:0040326C 00 55 4E 52 45 47 49 53-54 45 52 45 44 21 00 43  .UNREGISTERED!.C 
016F:0040327C 72 61 63 6B 65 64 20 62-79 20 3A 20 42 6F 6F 6D  racked by : Boom 
016F:0040328C 42 6F 78 00 00 00 00 00-00 00 00 00 00 00 00 00  Box............. 
016F:0040329C 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................ 
It looks kinda like a grid, huh? But what's the C and the X? Hmm, the crackme's called "PacMe". So, it wouldn't be dumb to suppose that you have to play Pac-Man, huh? Let's have a look at the Pac-Man Grid:

C*......*...****
 *.****...*....*
.*..**********.*
..*....*...*...*
*.****.*.*...***
*.*....*.*******
..*.***..*.....*
.*..***.**.***.*
...****....*X..*
****************
 

OK, let's remember this and come back to it.


The Check Part 2 - XORing

Tracing into the call:

:0040101D 8A15FB344000            mov dl, byte ptr [004034FB]
:00401023 B912000000              mov ecx, 00000012            ; Repeat 12h times
:00401028 B8E8344000              mov eax, 004034E8            ; mov eax, your 12h digits
:0040102D 3010                    xor byte ptr [eax], dl       ; XOR your 12h digits with something?
:0040102F 40                      inc eax                      ; Next digit
:00401030 E2FB                    loop 0040102D                ; A loop! :)
:00401032 C3                      ret
Ok, at the end of this, eax is our 12 bytes XOR B6. So, I'm gonna update me keyfile:
00000000:  07 42 6F 6F-6D 42 6F 78-87 84 85 82-83 80 81 8E  BoomBoxçäàéâÇüÄ
00000010:  8F 86 87 84-85 82 83 80-81 8E 8F   -             ÅåçäàéâÇüÄÅ
Back to the code: This is moving 4034FB, which is just past where the file was loaded into memory. This is a "hash" of my name. When I change the 42("B") to a 43("C"), it changes to B7. It might be a total of some sort. We now have some pseudo-code:
* Read the first byte of the file
* Check if it's zero
* Get a string of the same length as
  the value of the first byte of the file
* Make a hash of this & store
* Read the next 12h bytes
* XOR these 12h bytes with the hash
* Check the 12h bytes somehow...
I'm gonna try & find where the hash is calculated. Let's have a look at the next call:

:00401724 E8D7F8FFFF              call 00401000

:00401000 33C0                    xor eax, eax
:00401002 33D2                    xor edx, edx
:00401004 33C9                    xor ecx, ecx
:00401006 8A0DFA344000            mov cl, byte ptr [004034FA] ; The first byte of the file (See above)
:0040100C BE88324000              mov esi, 00403288           ; "BoomBox"
:00401011 AC                      lodsb
:00401012 03D0                    add edx, eax
:00401014 E2FB                    loop 00401011
:00401016 8815FB344000            mov byte ptr [004034FB], dl ; The address we saw earlier
:0040101C C3                      ret
What this does is take DL (The low byte of EDX), and add it to a running total. It then takes the low byte of the total, like so:
B  o  o  m  B  o  x
42+6F+6F+6D+42+6F+78 = 2B6

The Check Part 3 - A Loop Inside A Loop...?

Now, all this was called from here:
:004010E8 E830FFFFFF              call 0040101D
So let's go looking for something interesting after this...

:004010ED C645FE00                mov [ebp-02], 00              ; [EBP-02] -=> 00
:004010F1 33C0                    xor eax, eax
:004010F3 33C9                    xor ecx, ecx
:004010F5 C645FF08                mov [ebp-01], 08              ; [EBP-01] -=> 08
:004010F9 806DFF02                sub byte ptr [ebp-01], 02     ; [EBP-01] -=> 06
:004010FD 0FB64DFE                movzx ecx, byte ptr [ebp-02]  ; ECX -=> 00
:00401101 81C1E8344000            add ecx, 004034E8             ; ECX -=> 4034E8 -=> The grid
:00401107 8A01                    mov al, byte ptr [ecx]        ; AL -=> The grid
:00401109 8A4DFF                  mov cl, byte ptr [ebp-01]     ; Cl -=> 06
:0040110C D2E8                    shr al, cl
:0040110E 2403                    and al, 03
:00401110 E81EFFFFFF              call 00401033                 ; ???
:00401115 85C0                    test eax, eax                 ; eax=0 -=> Bad
:00401117 7411                    je 0040112A
:00401119 0FB655FF                movzx edx, byte ptr [ebp-01]
:0040111D 85D2                    test edx, edx
:0040111F 75D8                    jne 004010F9                  ; Small loop
:00401121 FE45FE                  inc [ebp-02]                  ; Big loop counter
:00401124 807DFE12                cmp byte ptr [ebp-02], 12     ; Bigger loop
:00401128 75CB                    jne 004010F5
:0040112A C9                      leave
:0040112B C3                      ret
We have 2 sets of CMP/JNE here, so there's probably 2 loops, a loop inside a loop. The first one uses [ebp-01] as a counter, the second [ebp-02]. I've commented the code to give you a better idea of what's going on. I had to come back and look at this to understand it, read on to find out why...


The Check Part 4 - Moving PacMan

Let's trace into the call...

:00401033 55                      push ebp
:00401034 8BEC                    mov ebp, esp
:00401036 83C4F8                  add esp, FFFFFFF8
:00401039 8B1584314000            mov edx, dword ptr [00403184]
:0040103F 8955FC                  mov dword ptr [ebp-04], edx
:00401042 0AC0                    or al, al                           ; 0
:00401044 7509                    jne 0040104F
:00401046 832D8431400010          sub dword ptr [00403184], 00000010  ; 0 -=> -10
:0040104D EB1F                    jmp 0040106E
:0040104F 3C01                    cmp al, 01                          ; 01
:00401051 7508                    jne 0040105B
:00401053 FF0584314000            inc dword ptr [00403184]            ; 01 -=> +1
:00401059 EB13                    jmp 0040106E
:0040105B 3C02                    cmp al, 02                          ; 02
:0040105D 7509                    jne 00401068
:0040105F 83058431400010          add dword ptr [00403184], 00000010  ; 02 -=> +10
:00401066 EB06                    jmp 0040106E
:00401068 FF0D84314000            dec dword ptr [00403184]            ; Else -=> 03 -=> -1
:0040106E 8B1584314000            mov edx, dword ptr [00403184]
:00401074 8A02                    mov al, byte ptr [edx]              ; AL -=> Our position on the grid
:00401076 3C2A                    cmp al, 2A                          ; CMP AL, "*"
:00401078 7506                    jne 00401080
:0040107A 33C0                    xor eax, eax
:0040107C C9                      leave
:0040107D C3                      ret

:00401080 3C58                    cmp al, 58                     ; CMP AL, "X"
:00401082 752F                    jne 004010B3
:00401084 6A00                    push 00000000
:00401086 8D1559334000            lea edx, dword ptr [00403359]
:0040108C 52                      push edx
:0040108D 8D15EC324000            lea edx, dword ptr [004032EC]
:00401093 52                      push edx
:00401094 6A00                    push 00000000
:00401096 8D15AC174000            lea edx, dword ptr [004017AC] ; MessageBoxA
:0040109C FFD2                    call edx
:0040109E 8D157B324000            lea edx, dword ptr [0040327B]
:004010A4 52                      push edx
:004010A5 FF3520344000            push dword ptr [00403420]
:004010AB 8D15DC174000            lea edx, dword ptr [004017DC] ; SetWindowTextA
:004010B1 FFD2                    call edx
Phew! What a lot of code. But easy to understand when commented properly ;) The last part of this is just a MessageBox to say "Success!", and a SetWindowText to change the "UNREGISTERED!" to "Cracked by : BoomBox".

Looking at the commented sections, we have 4 possible values of edx - 00, 01, 02, and 03. It then updates where we are on the grid, and checks if we are on a "*". Now, the grid has 10h lines to a row. So, if we take away 10h from our position, then we are moving up, get it? Likewise for +1, -10, and +10. Let's draw up a little chart:
00 -=> -10 -=> Up
01 -=>  +1 -=> Right
02 -=> +10 -=> Down
03 -=>  -1 -=> Left
Right, so let's now work out the correct sequence:
D,D,D,R,D,D,D,L,
D,D,R,R,U,R,U,U,
R,R,R,U,U,L,L,L,
U,L,U,U,R,R,R,R,
R,D,R,R,U,R,R,D,
R,R,R,D,D,L,L,D,
L,L,U,L,L,D,D,D,
L,D,D,R,R,R,U,U,
R,R,R,R,D,D,L,L
You have no idea how annoying it is trying to work that out, so I save you the trouble :}. Count them up - 72d moves! Huh? The crackme only reads in 18d bytes....? Hang on... 72/18 = 4. Hmm. So, we have to find a way of combining 4 numbers into 1. Also, these 4 numbers can each only be 0,1,2, or 3.

I then popped into HIEW again, and noticed a piece of text saying "Byte" in the scroll bar. I'd experimented with HIEW as soon as I got my paws on it, and knew that it allowed you to enter numbers in binary to form a single byte ... eight of them! In binary, a 2-digit number can only have 4 values - 00, 01, 10, and 11. So, let's redraw the table, split up the movements into sections of 4, and solve the crackme:
00 -=> Up
01 -=> Right
10 -=> Down
11 -=> Left

Correct sequence:
DDDR = 10 10 10 01 = A9
DDDL = 10 10 10 11 = AB
DDRR = 10 10 01 01 = A5
URUU = 00 01 00 00 = 10
RRRU = 01 01 01 00 = 54
ULLL = 00 11 11 11 = 3F
ULUU = 00 11 00 00 = 30
RRRR = 01 01 01 01 = 55
RDRR = 01 10 01 01 = 65
URRD = 00 01 01 10 = 16
RRRD = 01 01 01 10 = 56
DLLD = 10 11 11 10 = BE
LLUL = 11 11 00 11 = F3
LDDD = 11 10 10 10 = EA
LDDR = 11 10 10 01 = E9
RRUU = 01 01 00 00 = 50
RRRR = 01 01 01 01 = 55
DDLL = 10 10 11 11 = AF
Which after all that gives you:
A9 AB A5 10 54 3F 
30 55 65 16 56 BE 
F3 EA E9 50 55 AF
Which is then XORed with the low byte of your name, giving me my keyfile as:
00000000:  07 42 6F 6F-6D 42 6F 78-1F 1D 13 A6-E2 89 86 E3  BoomBoxªÔëåÒ
00000010:  D3 A0 E0 08-45 5C 5F E6-E3 19      -             ËáÓE\_µÒ

A Keygen... For A Keyfile?

Now, let's try and write a KeyFile generator. Let's draw up a plan of what to do:
* Get the user's name
* Find how long the user's name is
* Find the low byte of the total
* XOR our key with it
* Write the length byte to the file
* Write the user's name to the file
* Write the XORed key to the file
I'll write a keyfile generator when I can be bothered ;)

The End!

Thanks for listening to me waffle on. It should be noted that this is nowhere near the ideal way to solve the crackme, I should have been able to figure out everything from the code alone - but I'm just an un-skilled newbie ;). If you have anything to add, complaints, comments, additions, mail them to: BoomBox@FuckYou.co.uk. That's it!