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!
|