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:
0040102B
|. C705 50204000> MOV DWORD PTR DS:[402050],nagoya.WndProc
-- (401128) ... 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:
00401139
|. 837D 0C 01 CMP [ARG.2],1
; --
WM_CREATE? 0040113D 74 20 JE SHORT nagoya.0040115F |
The [ARG.2]
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! ;-)
The Trap...
If we nop it out, then we find later something suspiciuos when reversing the serial:
004012C5
|. 6A 0B PUSH
0B ; /Count
= B (11.) 004012C7 |. 68 05214000 PUSH nagoya.00402105 ; |Buffer = nagoya.00402105 004012CC |. A1 80204000 MOV EAX,DWORD PTR DS:[402080] ; | 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:
004013CA
> \BE 80204000 MOV ESI,nagoya.00402080
;
-- FLAG! 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:
0040115F
FF05 80204000 INC DWORD
PTR DS:[402080] 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 serial...
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:
00401219
|> \6A 00 PUSH
0 ; /lParam
= NULL 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:[402074] ; |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:[402125] -- 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:
004013EC
|. 8B75 08 MOV
ESI,[ARG.1] ;
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:
0040142E
|. 8BC8 MOV
ECX,EAX ;
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:
0040124A
|. A1 25214000 MOV EAX,DWORD PTR
DS:[402125] 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
Serial: 12345
My Generator
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
GenSerial(LPSTR szNumber) { int len = lstrlen(szNumber); int eax = 0; int ebx = 0; int bl = 0; for
(int i = 0; i < len; i++) for
(int i = 0; i < len; i++) for
(int i = 0; i < len; i++) void
Generate(HWND hwnd) for
(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