Cracking geeko's nagoya - by Sunshine

Target : geeko's nagoya
Downloaded from : Crackmes.de
or download directly HERE (8kb)
Author of target: geeko
Requirements:

* Ollydbg (I used Shadow Ollydbg)
* a bit knowledge about Asm and Windows API
* useful but not necessary: WindowsMessages by Assarbad

Additional: * something to drink; I prefer always coffee or beer :-)
* good music; I listened to Flyleaf :-)


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++)
        szNumber[i] = szNumber[i] - 0x30;

    for (int i = 0; i < len; i++)
    {
        eax = eax << 1;
        ebx = eax;
        eax = eax << 1;
        eax = eax << 1;
        bl = ebx & 0x000000FF;
        bl += szNumber[i];
        ebx = ebx & 0xFFFFFF00;
        ebx = ebx | bl;
        eax += ebx;
    }

    for (int i = 0; i < len; i++)
    
    szNumber[i] = szNumber[i] + 0x30;
    return eax;
}

void Generate(HWND hwnd)
{
    
char temp[10];
    ZeroMemory(temp, 10);

    for (int i = 0; i < 999999; i++)
    {
        wsprintf(temp, "%d",i);
        
if (GenSerial(temp) == 0x3039)
        {
            
SendMessage(GetDlgItem(hwnd, ID_KEYLIST), LB_INSERTSTRING, -1, (LPARAM)"The serial is:");
            SendMessage(GetDlgItem(hwnd, ID_KEYLIST), LB_INSERTSTRING, -1, (LPARAM)temp);
        }
    }
}

Hopefully you found this solution interesting :-) Happy coding & reversing!

Sunshine, November 2k6


This site is part of Sunshine's Homepage