Cracking geeko's nagoya - by Sunshine
Download whole package here! (includes original & reversed file and this tut)
So after a long time, I tried my luck with a crackme and came across this one. I think it's ideal for beginners: clearly written in assembler and easy to understand, not encrypted or packed and you have to remove a nag as well as to find a serial. And it's so small that you can just follow the crackme from entrypoint to the locations you are interested in, without getting lost in garbage code. I'll try to explain as detailed as possible, so that everybody should unterstand it... First start crackme to know what we will be faced: a nag and a register window wants to be attacked...
Let's start... killing the nag
So fire up Ollydbg and load the file. At the entrypoint you see a call to FindWindow which just ensures that you don't start the crackme twice so nothing special. After that the window is created with RegisterClass and CreateWindow; note following two lines:
|. C705 50204000> MOV DWORD PTR DS:,nagoya.WndProc
004010BF |. 68 84204000 PUSH nagoya.00402084 ; |WindowName = "Exercise 2"
So the window procedure starts at 401128. The other line shows us that the main window is created first and not the nag screen! So the nag will be created later, most probably when the WM_CREATE message of the main window is handled. So go to 401128 and scroll a bit down, we'll see:
|. 837D 0C 01 CMP [ARG.2],1
0040113D 74 20 JE SHORT nagoya.0040115F
means the second argument passed to WndProc and that's the message that was
sent. The 1 is the constant for WM_CREATE (use the tool WindowsMessages
by Assarbad I mentioned above if you don't know that). So let's look at 40115F
and we see that the nag window is created with a call to DialogBoxParam.
Ok, easy... we could just nop out the call at 40113D, so the nag will never be created. And that really works: go and try it, the nag will be gone... but then the serial calculation won't work anymore! ;-)
If we nop it out, then we find later something suspiciuos when reversing the serial:
|. 6A 0B PUSH
0B ; /Count
= B (11.)
004012C7 |. 68 05214000 PUSH nagoya.00402105 ; |Buffer = nagoya.00402105
004012CC |. A1 80204000 MOV EAX,DWORD PTR DS: ; |
004012D1 |. 50 PUSH EAX ; |ControlID => 3E8 (1000.)
004012D2 |. FF75 08 PUSH [ARG.1] ; |hWnd
004012D5 |. E8 0F020000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
004012DA |. 6A 0B PUSH 0B ; /Count = B (11.)
004012DC |. 68 15214000 PUSH nagoya.00402115 ; |Buffer = nagoya.00402115
004012E1 |. 68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
004012E6 |. FF75 08 PUSH [ARG.1] ; |hWnd
004012E9 |. E8 FB010000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
The text from the user name is taken twice and the text from the serial box is never read! There must be something wrong, so let's get back to the creation of the nag screen and examine its window procedure (@ 40135E). The WM_INITDIALOG message (constant 110) is processed, so follow the jump and we land here:
> \BE 80204000 MOV ESI,nagoya.00402080
004013CF . FF06 INC DWORD PTR DS:[ESI]
The value at adress
402080 is increased by one! And as we see above, at 402080 the control id from
the one of the textboxes is stored. So if the nag is created, the value at this
adress becomes 3E9 and the serial will be correctly read from the box. We we
must ensure that this will be always done.
So let's go back where the nag screen is created with DialogBoxParam and overwrite it with:
FF05 80204000 INC DWORD
00401165 E9 8C000000 JMP nagoya.004011F6
So instead of creating the nag, we set the flag and jump normally to the end of wndproc. Save these changes to file and goal 1 is completed.
The user name and
the serial are read with GetDlgItemText (see above). You can find this
location by following the WM_COMMAND message handler in the register
window wndproc or just be looking around (fortunately this file is so tiny).
But after that, the serial calculation is not immediately done; in fact at first it looks like nothing is done. Well, the calculation is done not until the register window is closed, look here:
|> \6A 00 PUSH
0 ; /lParam
0040121B |. 68 68124000 PUSH nagoya.00401268 ; |DlgProc = nagoya.00401268
00401220 |. FF75 08 PUSH [ARG.1] ; |hOwner
00401223 |. 68 9F204000 PUSH nagoya.0040209F ; |pTemplate = "DLG_REGIS"
00401228 |. FF35 74204000 PUSH DWORD PTR DS: ; |hInst = NULL
0040122E |. E8 92020000 CALL <JMP.&USER32.DialogBoxParamA> ; \DialogBoxParamA
00401233 |. 83F8 00 CMP EAX,0
00401236 |.^ 74 BE JE SHORT nagoya.004011F6
00401238 |. 68 05214000 PUSH nagoya.00402105 ; /Arg2 = 00402105 ASCII "????????????????????????????????"
0040123D |. 68 25214000 PUSH nagoya.00402125 ; |Arg1 = 00402125
00401242 |. E8 C9010000 CALL nagoya.00401410 ; \nagoya.00401410
00401247 |. 83C4 08 ADD ESP,8
0040124A |. A1 25214000 MOV EAX,DWORD PTR DS: -- move serial to eax
0040124F |. 3D 39300000 CMP EAX,3039 -- eax == 0x3039 ?
00401254 |. 74 0B JE SHORT nagoya.00401261 -- if so, go to good boy msg
At 402105 the entered serial is stored; the 402125 is just an empty buffer. As you remember, the entered user name was stored at 402115 and it's not pushed to the serial calculation routine. I can tell you know that's it's not needed. You can even let it empty. So let's trace the call at 401242 and after a few lines (@ 401418) we see another call to which to serial buffer is always pushed. Examine what's done here:
|. 8B75 08 MOV
esi = serial
004013EF |. 8BFE MOV EDI,ESI ; edi = serial
004013F1 |. 33C0 XOR EAX,EAX ; eax = 0 (loop var, call it i = 0)
004013F3 |> 8A06 /MOV AL,BYTE PTR DS:[ESI] ; al = serial[i]
004013F5 |. 46 |INC ESI ; i++;
004013F6 |. 84C0 |TEST AL,AL ; al == 0?
004013F8 |. 75 09 |JNZ SHORT nagoya.00401403
004013FA |. 90 |NOP ; al is 0, bye...
004013FB |. 90 |NOP
004013FC |. 90 |NOP
004013FD |. 90 |NOP
004013FE |. 8807 |MOV BYTE PTR DS:[EDI],AL ; save al back
00401400 |. 5E |POP ESI
00401401 |. 5D |POP EBP
00401402 |. C3 |RETN
00401403 |> 3C 39 |CMP AL,39 ; al > 0x39 ('9') ?
00401405 |.^ 7F EC |JG SHORT nagoya.004013F3 ; go on with next char
00401407 |. 2C 30 |SUB AL,30 ; al -= 0x30
00401409 |.^ 72 E8 |JB SHORT nagoya.004013F3 ; al < 0 go on with next char
0040140B |. 8807 |MOV BYTE PTR DS:[EDI],AL ; serial[i] = al
0040140D |. 47 |INC EDI ; edi++;
0040140E \.^ EB E3 \JMP SHORT nagoya.004013F3
So with my nodes
it should be easy to understand: every character from the entered serial is
checked if it's a number between '1' and '9'. If so, the numerical (not ascii!)
value is stored (subtract 0x30). All other values are thrown away. A 0 means
the end of the serial and we quit.
Example: You type in as serial "25A8N6", then the buffer where this serial is stored looks like: 32 35 41 38 4E 36. After this algorithm it look like: 02 05 08 06. Hope you get it.
After we return from this call, this new serial is checked if it's longer than 0. Then the real serial is calculated from the this serial. Here it is:
|. 8BC8 MOV
ecx = length(new_serial)
00401430 |. 33C0 XOR EAX,EAX ; eax = 0
00401432 |. 33DB XOR EBX,EBX ; ebx = 0
00401434 |> D1E0 /SHL EAX,1 ; eax = eax * 2
00401436 |. 8BD8 |MOV EBX,EAX ; ebx = eax
00401438 |. D1E0 |SHL EAX,1 ; eax = eax * 2
0040143A |. D1E0 |SHL EAX,1 ; eax = eax * 2
0040143C |. 021E |ADD BL,BYTE PTR DS:[ESI] ; bl = bl + serial[i]
0040143E |. 03C3 |ADD EAX,EBX ; eax = eax + ebx
00401440 |. 46 |INC ESI ; esi++
00401441 |.^ E2 F1 \LOOPD SHORT nagoya.00401434 ; loop while (ecx!= 0)
00401443 |> 8B75 08 MOV ESI,[ARG.1] ; esi = address(buffer)
00401446 |. 8906 MOV DWORD PTR DS:[ESI],EAX ; buffer = eax = serial
When we return from the serial calculation, we see this:
|. A1 25214000 MOV EAX,DWORD PTR
0040124F |. 3D 39300000 CMP EAX,3039
00401254 |. 74 0B JE SHORT nagoya.00401261 ; Good Boy!!
So the calculated serial must be 0x3039. So what serial we have
to enter that the calculated result serial is equal to 0x3039?
Well, we could travel the algorithm backwards and try to really reverse it. But as I am a lazy guy, I coded a little bruteforcer which tests just all possible serials and checks if after the calculation the result is 0x3039.
So to give you the solution:
User name: doesn't matter, can even be empty
Translating the routine to C should not be difficult. Perhaps the line where a value is written to bl is a bit tricky and needs some bit operations, but a bit thinking reveals the solution. So here the important code snippet:
int len = lstrlen(szNumber);
int eax = 0;
int ebx = 0;
int bl = 0;
(int i = 0; i < len; i++)
(int i = 0; i < len; i++)
(int i = 0; i < len; i++)
(int i = 0; i < 999999; i++)
Hopefully you found this solution interesting :-) Happy coding & reversing!
Sunshine, November 2k6
This site is part of Sunshine's Homepage