This crackme can be downloaded from here.

First overview

The first thing I do is start the program to see what it does. It asks for a name and a serial number and I simply try two random strings and press "Check". Nothing happens, so I load the program into IDA Pro and takes a look at the function list. It seems like MFC has been used. I search for GetDlgItemTextA which is likely used for retrieving the text strings and find all references to it. Only one exists and it is in the sub_4014B0 function.

sub_4014B0

.text:004014B0 sub_4014B0      proc near               ; DATA XREF: .rdata:004023C4o
.text:004014B0
.text:004014B0 var_80          = dword ptr -80h
.text:004014B0 var_6C          = dword ptr -6Ch
.text:004014B0 var_58          = dword ptr -58h
.text:004014B0 var_34          = byte ptr -34h
.text:004014B0
.text:004014B0                 sub     esp, 80h
.text:004014B6                 lea     eax, [esp+80h+var_58]
.text:004014BA                 push    esi
.text:004014BB                 push    edi
.text:004014BC                 push    20h
.text:004014BE                 push    eax
.text:004014BF                 mov     esi, ecx
.text:004014C1                 push    3E8h
.text:004014C6                 call    ?GetDlgItemTextA@CWnd@@QBEHHPADH@Z ; CWnd::GetDlgItemTextA(int,char *,int)
.text:004014CB                 lea     ecx, [esp+88h+var_34]
.text:004014CF                 push    28h
.text:004014D1                 push    ecx
.text:004014D2                 push    3E9h
.text:004014D7                 mov     ecx, esi
.text:004014D9                 mov     edi, eax
.text:004014DB                 call    ?GetDlgItemTextA@CWnd@@QBEHHPADH@Z ; CWnd::GetDlgItemTextA(int,char *,int)
.text:004014E0                 cmp     edi, 6
.text:004014E3                 jle     short loc_401540
.text:004014E5                 cmp     eax, 20h
.text:004014E8                 jle     short loc_401540
.text:004014EA                 lea     edx, [esp+88h+var_6C]
.text:004014EE                 lea     eax, [esp+88h+var_80]
.text:004014F2                 push    edx
.text:004014F3                 push    eax
.text:004014F4                 lea     ecx, [esp+90h+var_34]
.text:004014F8                 push    offset aS       ; "%[^+]+%s"
.text:004014FD                 push    ecx             ; char *
.text:004014FE                 call    ds:sscanf
.text:00401504                 lea     edx, [esp+98h+var_80]
.text:00401508                 lea     eax, [esp+98h+var_58]
.text:0040150C                 push    edx
.text:0040150D                 push    eax
.text:0040150E                 call    sub_401320
.text:00401513                 add     esp, 18h
.text:00401516                 test    eax, eax
.text:00401518                 jz      short loc_401540
.text:0040151A                 lea     ecx, [esp+88h+var_6C]
.text:0040151E                 lea     edx, [esp+88h+var_58]
.text:00401522                 push    ecx
.text:00401523                 push    edx
.text:00401524                 call    sub_4013E0
.text:00401529                 add     esp, 8
.text:0040152C                 test    eax, eax
.text:0040152E                 jz      short loc_401540
.text:00401530                 push    0
.text:00401532                 push    0
.text:00401534                 push    offset aSerialIsCorrec ; "Serial is Correct!!!"
.text:00401539                 mov     ecx, esi
.text:0040153B                 call    ?MessageBoxA@CWnd@@QAEHPBD0I@Z ; CWnd::MessageBoxA(char const *,char const *,uint)
.text:00401540
.text:00401540 loc_401540:                             ; CODE XREF: sub_4014B0+33j
.text:00401540                                         ; sub_4014B0+38j ...
.text:00401540                 pop     edi
.text:00401541                 pop     esi
.text:00401542                 add     esp, 80h
.text:00401548                 retn
.text:00401548 sub_4014B0      endp

The function starts by allocating room for local variables and then retrieves the content of one of the text fields at address 00414C6 into the local variable called var_58. Running the program in a debugger tells me that var_58 is the 'name' field. On address 004014D9 the return value of GetDlgItemTextA (the number of copied characters) is put into EDI and at address 004014E0 it is compared to 6. If the value is less than or equal to six the program jumps to the end.

So, the name must be longer than six characters.

Then the serial number is retrieved at address 004014DB into var_34. I rename var_58 to entered_name and var_34 to entered_serial. On address 004014E5 the result of GetDlgItemTextA (after having retrieved the serial) is compared to 32, and if the result is less than or equal to 32 the program jumps to the end.

The serial must be longer than 32 characters.

After having verified the length of the name and serial number the sscanf function is used on address 004014FE to split up the serial number into two parts seaparated by a '+' sign. That is done using the format string "%[^+]+%s". First part is put into var_80 which I thus rename to serial_part_1, and the second part is put into var_6C which I rename to serial_part_2.

Next the first part of the serial is verified with a call to sub_401320. It is passed two parameters, which are the address of the name, and the first part of the serial respectively. If the function returns 0 the program jumps to the end.

So sub_401320 has the prototype int sub_401320(char * name, char * serial1) and must return something different from 0 for the serial to have been accepted. I rename sub_401320 to verify_serial_part_1.

Last the serial part of the serial is verified with a call to sub_4013E0 which has the prototype int sub_4013E0(char * name, char * serial2) and also returns different from 0 if the serial is accepted.

I rename sub_4013E0 to verify_serial_part_2.

verify_serial_part_1

The first thing I do is rename arg_0 to name and arg_4 to serial_part_1.
.text:00401320 verify_serial_part_1 proc near          ; CODE XREF: sub_4014B0+5Ep
.text:00401320
.text:00401320 var_14          = byte ptr -14h
.text:00401320 name            = dword ptr  4
.text:00401320 serial_part_1   = dword ptr  8
.text:00401320
.text:00401320                 sub     esp, 14h
.text:00401323                 xor     ecx, ecx
.text:00401325                 mov     edx, 1
.text:0040132A                 push    ebx
.text:0040132B                 push    esi
.text:0040132C                 push    edi
.text:0040132D                 mov     edi, [esp+20h+name]
.text:00401331                 mov     al, [edi]
.text:00401333                 test    al, al
.text:00401335                 jz      short loc_401373
.text:00401337
.text:00401337 loc_401337:                             ; CODE XREF: verify_serial_part_1+51j
.text:00401337                 movsx   eax, al
.text:0040133A                 mov     esi, eax
.text:0040133C                 imul    eax, edx
.text:0040133F                 xor     esi, 32142001h
.text:00401345                 xor     edx, edx
.text:00401347                 add     ecx, esi
.text:00401349                 mov     ebx, 7
.text:0040134E                 or      eax, ecx
.text:00401350                 mov     esi, eax
.text:00401352                 div     ebx
.text:00401354                 mov     ebx, 5
.text:00401359                 add     edx, 2
.text:0040135C                 imul    ecx, edx
.text:0040135F                 mov     eax, ecx
.text:00401361                 xor     edx, edx
.text:00401363                 div     ebx
.text:00401365                 mov     al, [edi+1]
.text:00401368                 add     edx, 3
.text:0040136B                 imul    edx, esi
.text:0040136E                 inc     edi
.text:0040136F                 test    al, al
.text:00401371                 jnz     short loc_401337
.text:00401373
.text:00401373 loc_401373:                             ; CODE XREF: verify_serial_part_1+15j
.text:00401373                 push    edx
.text:00401374                 push    ecx
.text:00401375                 lea     eax, [esp+28h+var_14]
.text:00401379                 push    offset a08x08x  ; "%08X-%08X"
.text:0040137E                 push    eax             ; char *
.text:0040137F                 call    ds:sprintf
.text:00401385                 mov     esi, [esp+30h+serial_part_1]
.text:00401389                 add     esp, 10h
.text:0040138C                 lea     eax, [esp+20h+var_14]
.text:00401390
.text:00401390 loc_401390:                             ; CODE XREF: verify_serial_part_1+92j
.text:00401390                 mov     dl, [eax]
.text:00401392                 mov     bl, [esi]
.text:00401394                 mov     cl, dl
.text:00401396                 cmp     dl, bl
.text:00401398                 jnz     short loc_4013C6
.text:0040139A                 test    cl, cl
.text:0040139C                 jz      short loc_4013B4
.text:0040139E                 mov     dl, [eax+1]
.text:004013A1                 mov     bl, [esi+1]
.text:004013A4                 mov     cl, dl
.text:004013A6                 cmp     dl, bl
.text:004013A8                 jnz     short loc_4013C6
.text:004013AA                 add     eax, 2
.text:004013AD                 add     esi, 2
.text:004013B0                 test    cl, cl
.text:004013B2                 jnz     short loc_401390
.text:004013B4
.text:004013B4 loc_4013B4:                             ; CODE XREF: verify_serial_part_1+7Cj
.text:004013B4                 xor     eax, eax
.text:004013B6                 xor     ecx, ecx
.text:004013B8                 test    eax, eax
.text:004013BA                 setz    cl
.text:004013BD                 pop     edi
.text:004013BE                 pop     esi
.text:004013BF                 mov     eax, ecx
.text:004013C1                 pop     ebx
.text:004013C2                 add     esp, 14h
.text:004013C5                 retn
.text:004013C6 ; ---------------------------------------------------------------------------
.text:004013C6
.text:004013C6 loc_4013C6:                             ; CODE XREF: verify_serial_part_1+78j
.text:004013C6                                         ; verify_serial_part_1+88j
.text:004013C6                 sbb     eax, eax
.text:004013C8                 pop     edi
.text:004013C9                 sbb     eax, 0FFFFFFFFh
.text:004013CC                 xor     ecx, ecx
.text:004013CE                 test    eax, eax
.text:004013D0                 setz    cl
.text:004013D3                 pop     esi
.text:004013D4                 mov     eax, ecx
.text:004013D6                 pop     ebx
.text:004013D7                 add     esp, 14h
.text:004013DA                 retn
.text:004013DA verify_serial_part_1 endp

The function consists of three parts. First part from address 00401320-00401371 calculates the first part of the serial from the name. Second part from 00401373-00401390 generates a string which contains the first part of the serial, and the last part from address 00401390-004013DA check the calculated serial number against the provided one.

A C version of the serial generator could be written like this:

/**
 * Calculate first part of the serial for NTS-Crackme5.
 * @param name Name to calculate the serial from.
 * @param buffer Buffer where serial number will be placed. This must be 18 bytes large.
 */
void calculate_serial_1(char * name, char * buffer) {
    int eax, esi, ecx = 0, edx = 1;
    unsigned int ebx;
    while (*name) {
        esi = eax = *name;
        eax *= edx;
        esi ^= 0x32142001;
        edx = 0;
        ecx += esi;
        ebx = 7;
        eax |= ecx;
        esi = eax;
        edx = eax % ebx;
        eax = eax / ebx;
        ebx = 5;
        edx += 2;
        ecx *= edx;
        eax = ecx;
        edx = 0;
        edx = eax % ebx;
        eax = eax / ebx;
        edx += 3;
        edx *= esi;
        name++;
    }
    sprintf(buffer, "%08X-%08X", ecx, edx);
}

I have no idea what actually happens, but hey...who cares? It is a one way hashing function and a debugger tells me that it generates serial nubmers that the original can accept.

verify_serial_part_2

The first thing I do is rename arg_0 to name and arg_4 to serial_part_2.
.text:004013E0 verify_serial_part_2 proc near          ; CODE XREF: sub_4014B0+74p
.text:004013E0
.text:004013E0 var_14          = byte ptr -14h
.text:004013E0 name            = dword ptr  4
.text:004013E0 serial_part_2   = dword ptr  8
.text:004013E0
.text:004013E0                 sub     esp, 14h
.text:004013E3                 mov     ecx, 1
.text:004013E8                 push    ebx
.text:004013E9                 mov     ebx, [esp+18h+name]
.text:004013ED                 push    esi
.text:004013EE                 push    edi
.text:004013EF                 mov     al, [ebx]
.text:004013F1                 xor     esi, esi
.text:004013F3                 xor     edi, edi
.text:004013F5                 test    al, al
.text:004013F7                 jz      short loc_401444
.text:004013F9                 push    ebp
.text:004013FA
.text:004013FA loc_4013FA:                             ; CODE XREF: verify_serial_part_2+61j
.text:004013FA                 movsx   eax, al
.text:004013FD                 mov     edx, eax
.text:004013FF                 imul    eax, ecx
.text:00401402                 or      edx, 0F001F001h
.text:00401408                 mov     ecx, eax
.text:0040140A                 sub     esi, edx
.text:0040140C                 xor     edx, edx
.text:0040140E                 mov     ebp, 7
.text:00401413                 lea     eax, [ecx+esi]
.text:00401416                 or      ecx, esi
.text:00401418                 add     edi, eax
.text:0040141A                 mov     eax, edi
.text:0040141C                 div     ebp
.text:0040141E                 mov     eax, edi
.text:00401420                 mov     ebp, 0Bh
.text:00401425                 add     edx, 3
.text:00401428                 imul    ecx, edx
.text:0040142B                 xor     edx, edx
.text:0040142D                 add     ecx, edi
.text:0040142F                 div     ebp
.text:00401431                 mov     al, [ebx+1]
.text:00401434                 add     edx, 2
.text:00401437                 imul    edx, esi
.text:0040143A                 add     edx, edi
.text:0040143C                 inc     ebx
.text:0040143D                 test    al, al
.text:0040143F                 mov     esi, edx
.text:00401441                 jnz     short loc_4013FA
.text:00401443                 pop     ebp
.text:00401444
.text:00401444 loc_401444:                             ; CODE XREF: verify_serial_part_2+17j
.text:00401444                 push    ecx
.text:00401445                 push    esi
.text:00401446                 lea     ecx, [esp+28h+var_14]
.text:0040144A                 push    offset a08x08x  ; "%08X-%08X"
.text:0040144F                 push    ecx             ; char *
.text:00401450                 call    ds:sprintf
.text:00401456                 mov     esi, [esp+30h+serial_part_2]
.text:0040145A                 add     esp, 10h
.text:0040145D                 lea     eax, [esp+20h+var_14]
.text:00401461
.text:00401461 loc_401461:                             ; CODE XREF: verify_serial_part_2+A3j
.text:00401461                 mov     dl, [eax]
.text:00401463                 mov     bl, [esi]
.text:00401465                 mov     cl, dl
.text:00401467                 cmp     dl, bl
.text:00401469                 jnz     short loc_401497
.text:0040146B                 test    cl, cl
.text:0040146D                 jz      short loc_401485
.text:0040146F                 mov     dl, [eax+1]
.text:00401472                 mov     bl, [esi+1]
.text:00401475                 mov     cl, dl
.text:00401477                 cmp     dl, bl
.text:00401479                 jnz     short loc_401497
.text:0040147B                 add     eax, 2
.text:0040147E                 add     esi, 2
.text:00401481                 test    cl, cl
.text:00401483                 jnz     short loc_401461
.text:00401485
.text:00401485 loc_401485:                             ; CODE XREF: verify_serial_part_2+8Dj
.text:00401485                 xor     eax, eax
.text:00401487                 xor     ecx, ecx
.text:00401489                 test    eax, eax
.text:0040148B                 setz    cl
.text:0040148E                 pop     edi
.text:0040148F                 pop     esi
.text:00401490                 mov     eax, ecx
.text:00401492                 pop     ebx
.text:00401493                 add     esp, 14h
.text:00401496                 retn
.text:00401497 ; ---------------------------------------------------------------------------
.text:00401497
.text:00401497 loc_401497:                             ; CODE XREF: verify_serial_part_2+89j
.text:00401497                                         ; verify_serial_part_2+99j
.text:00401497                 sbb     eax, eax
.text:00401499                 pop     edi
.text:0040149A                 sbb     eax, 0FFFFFFFFh
.text:0040149D                 xor     ecx, ecx
.text:0040149F                 test    eax, eax
.text:004014A1                 setz    cl
.text:004014A4                 pop     esi
.text:004014A5                 mov     eax, ecx
.text:004014A7                 pop     ebx
.text:004014A8                 add     esp, 14h
.text:004014AB                 retn
.text:004014AB verify_serial_part_2 endp

This function looks very similar to verify_serial_part_1. The same three parts, but the first one is a little different. The code for checking the generated serial against the provided is an exact copy of that from verify_serial_part_1.

There is no reason trying to understand the hashing algorithm...I just rip it and recode it in C:

/**
 * Calculate second part of the serial for NTS-Crackme5.
 * @param name Name to calculate the serial from.
 * @param buffer Buffer where serial number will be placed.
 * This must be 18 bytes large.
 */
void calculate_serial_2(char * name, char * buffer) {
    int eax, esi = 0, ecx = 1, edx = 0, edi = 0;
    unsigned int ebp;
    while (*name) {
        eax = *name;
        edx = eax;
        eax *= ecx;
        edx |= 0x0f001f001;
        ecx = eax;
        esi -= edx;
        edx = 0;
        ebp = 7;
        eax = ecx + esi;
        ecx |= esi;
        edi += eax;
        eax = edi;
        edx = eax % ebp;
        eax = eax / ebp;
        eax = edi;
        ebp = 0x0b;
        edx += 3;
        ecx *= edx;
        edx = 0;
        ecx += edi;
        edx = eax % ebp;
        eax = eax / ebp;
        edx += 2;
        edx *= esi;
        edx += edi;
        name++;
        esi = edx;
    }
    sprintf(buffer, "%08X-%08X", esi, ecx);
}

Again a debugger verifies that it works.

A full key generator

We now have the functions to generate both parts of the serial number so I put them together to a full program:

#include <stdio.h>

/**
 * Calculate first part of the serial for NTS-Crackme5.
 * @param name Name to calculate the serial from.
 * @param buffer Buffer where serial number will be placed.
 * This must be 18 bytes large.
 */
void calculate_serial_1(char * name, char * buffer) {
    int eax, esi, ecx = 0, edx = 1;
    unsigned int ebx;
    while (*name) {
        esi = eax = *name;
        eax *= edx;
        esi ^= 0x32142001;
        edx = 0;
        ecx += esi;
        ebx = 7;
        eax |= ecx;
        esi = eax;
        edx = eax % ebx;
        eax = eax / ebx;
        ebx = 5;
        edx += 2;
        ecx *= edx;
        eax = ecx;
        edx = 0;
        edx = eax % ebx;
        eax = eax / ebx;
        edx += 3;
        edx *= esi;
        name++;
    }
    sprintf(buffer, "%08X-%08X", ecx, edx);
}

/**
 * Calculate second part of the serial for NTS-Crackme5.
 * @param name Name to calculate the serial from.
 * @param buffer Buffer where serial number will be placed.
 * This must be 18 bytes large.
 */
void calculate_serial_2(char * name, char * buffer) {
    int eax, esi = 0, ecx = 1, edx = 0, edi = 0;
    unsigned int ebp;
    while (*name) {
        eax = *name;
        edx = eax;
        eax *= ecx;
        edx |= 0x0f001f001;
        ecx = eax;
        esi -= edx;
        edx = 0;
        ebp = 7;
        eax = ecx + esi;
        ecx |= esi;
        edi += eax;
        eax = edi;
        edx = eax % ebp;
        eax = eax / ebp;
        eax = edi;
        ebp = 0x0b;
        edx += 3;
        ecx *= edx;
        edx = 0;
        ecx += edi;
        edx = eax % ebp;
        eax = eax / ebp;
        edx += 2;
        edx *= esi;
        edx += edi;
        name++;
        esi = edx;
    }
    sprintf(buffer, "%08X-%08X", esi, ecx);
}

int main (int argc, char ** argv) {
    char serial_part_1[18];
    char serial_part_2[18];

    if (argc < 2) {
        printf("Usage: %s \n", argv[0]);
        return 0;
    }

    if (strlen(argv[1]) < 7) {
        printf("Your name should be at least 7 characters long.\n");
        return -1;
    }

    calculate_serial_1(argv[1], serial_part_1);
    calculate_serial_2(argv[1], serial_part_2);
    printf("Serial: %s+%s\n", serial_part_1, serial_part_2);

    return 0;
}

And it works:

robert@robert-laptop:~$ gcc -o nts-crackme5-keygen nts-crackme5-keygen.c 
robert@robert-laptop:~$ ./nts-crackme5-keygen "Robert Larsen"
Serial: 94F368A3-303F87EE+C8CBAFEB-2B4BF68F
robert@robert-laptop:~$