Solution-borismilner-4N006135

From aldeid
Jump to navigation Jump to search

Level 0

Introduction

This level is the first level of a series of 4 crackmes available here.

Code analysis

This is very easy. It displays Password : at offset 0x4013F2 and waits for a user input at offset 0x401404.

Then the secret password (expected one) is saved to ESI at offset 0x40140C and the user input is saved to EDI at offset 0x401412. These 2 values are comared at offset 0x401418. If they don't match, the code jumps (at offset 0x40141A) to the bad boy.

.text:004013E0 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:004013E0                 public _main
.text:004013E0 _main           proc near
.text:004013E0                 push    offset __data_start__ ; "\nCrackme - Level 0 - by 60Ô15\n-------"...
.text:004013E5                 call    _printf
.text:004013EA                 add     esp, 4
.text:004013ED                 push    offset password_prompt ; "Password : "
.text:004013F2                 call    _printf
.text:004013F7                 add     esp, 4
.text:004013FA                 push    offset password_guess
.text:004013FF                 push    offset string_format ; "%20s"
.text:00401404                 call    _scanf
.text:00401409                 add     esp, 8
.text:0040140C                 mov     esi, secret_password ; defined as 'Easy' at offset 0x409049
.text:00401412                 mov     edi, ds:password_guess
.text:00401418                 cmp     esi, edi
.text:0040141A                 jnz     short incorrect_guess
.text:0040141C                 push    offset good_job ; "\nGood Job !\n"
.text:00401421                 call    _printf
.text:00401426                 add     esp, 4
.text:00401429                 retn
.text:0040142A ; ---------------------------------------------------------------------------
.text:0040142A
.text:0040142A incorrect_guess:
.text:0040142A                 push    offset incorrect_password ; "\nNope, try again !\n"
.text:0040142F                 call    _printf
.text:00401434                 add     esp, 4
.text:00401437                 retn
.text:00401437 _main           endp

Now, in IDA-Pro, by double clicking on secret_password, it jumps to offset 0x409049 where we can see:

.data:00409049 secret_password dd 79736145h

This corresponds to the expected password in ASCII and can be decoded as follows:

$ python
>>> '79736145'.decode('hex')[::-1]
'Easy'

The password is Easy:

C:\crackmes>level-0.exe
 
Crackme - Level 0 - by 60Ô15
----------------------------

Password : Easy

Good Job !

Level 1

Introduction

Description

This is level 2 of a series of 4 challenges available here.

Code overview

The layout is as follows:

Code analysis

Initialization

This block of code asks for a username at offset 0x401404 and a password at offset 0x401423. The username and password are resectively saved at memory locations 0x40D020 and 0x40D035 and the username is saved to eax at offset 0x40142D.

.text:004013E0 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:004013E0                 public _main
.text:004013E0 _main           proc near
.text:004013E0                 push    offset __data_start__ ; "\nCrackme - Level 1 - by 60Ô15\n-------"...
.text:004013E5                 call    _printf
.text:004013EA                 add     esp, 4
.text:004013ED                 push    offset username_prompt ; "Username : "
.text:004013F2                 call    _printf
.text:004013F7                 add     esp, 4
.text:004013FA                 push    offset username ; offset 0x40D020
.text:004013FF                 push    offset scanf_string_format ; "%20s"
.text:00401404                 call    _scanf
.text:00401409                 add     esp, 8
.text:0040140C                 push    offset password_prompt ; "Password : "
.text:00401411                 call    _printf
.text:00401416                 add     esp, 4
.text:00401419                 push    offset password ; offset 0x40D035
.text:0040141E                 push    offset scanf_integer_format ; "%d"
.text:00401423                 call    _scanf
.text:00401428                 add     esp, 8
.text:0040142B                 xor     ecx, ecx
.text:0040142D                 mov     eax, offset username
.text:00401432                 dec     eax
Note
Notice the expected type from the first parameter sent to the scanf function: 20 characters maximum (%20s) for the username and a numeric value (%d) for the password

Transformation

This block of code is performing a transforming based on the username:

.text:00401433 keep_going:
.text:00401433 inc     eax                  ; eax += 1
.text:00401434 movsx   ebx, byte ptr [eax]  ; ebx = username[i]
.text:00401437 add     ecx, ebx             ; ecx += username[i]
.text:00401439 cmp     byte ptr [eax], 0    ; if not last character...
.text:0040143C jnz     short keep_go        ; continue loop

Remember that the username was saved to EAX at the initialization (see previous section) and EAX was decresed by 1 (dec eax) at offset 0x401432. The block of code above is looping over all characters in the username and accumulates the values in ecx. At the end of the loop, ecx is the sum of all characters of the username, which could be explained in python as follows:

>>> s = "aldeid"
>>> ecx = 0
>>> for i in s:
...     ecx += ord(i)
...     print "%s\t%s\t%s" % (i, hex(ord(i)), hex(ecx))
... 
a	0x61	0x61
l	0x6c	0xcd
d	0x64	0x131
e	0x65	0x196
i	0x69	0x1ff
d	0x64	0x263

Comparison

As shown on the below extract, the password provided by the user is compared to ECX at offset 0x40143E. If it is not equal, the code jumps to the bad boy, else, to the good boy.

.text:0040143E                 cmp     ecx, ds:password
.text:00401444                 jnz     short wrong_password
.text:00401446                 push    offset good_job ; "\nGood job !\n"
.text:0040144B                 call    _printf
.text:00401450                 add     esp, 4
.text:00401453                 retn
.text:00401454 ; ---------------------------------------------------------------------------
.text:00401454
.text:00401454 wrong_password:
.text:00401454                 push    offset try_again ; "Nope, try again !\n"
.text:00401459                 call    _printf
.text:0040145E                 add     esp, 4
.text:00401461                 retn

Solution

As explained previously, the password should be equal to the sum of all characters of the username. Here is how you can write a very simple keygen in python:

#!/usr/bin/env python
import sys

def make_serial(username):
    ecx = 0
    for i in username:
        ecx += ord(i)
    return ecx

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print "Usage: %s <username>" % sys.argv[0]
        sys.exit(1)
    username = sys.argv[1]
    if len(username) > 20:
        print "[ERROR] Username should be 20 chars max"
        sys.exit(2)
    print "Serial: %d" % make_serial(username)

Here are some usage examples:

keygen check
$ ./keygen.py aldeid
Serial: 611
C:\crackmes>level-1.exe
 
Crackme - Level 1 - by 60Ô15
----------------------------

Username : aldeid
Password : 611

Good job !
$ ./keygen.py abcdefghij
Serial: 1015
C:\crackmes>level-1.exe

Crackme - Level 1 - by 60Ô15
----------------------------

Username : abcdefghij
Password : 1015

Good job !
$ ./keygen.py abcdefghijklmnopqrst
Serial: 2130
C:\crackmes>level-1.exe

Crackme - Level 1 - by 60Ô15
----------------------------

Username : abcdefghijklmnopqrst
Password : 2130

Good job !

Level 2

Introduction

Description

This crackme is the 3rd challenge of a series of 4 challenges available here.

When run, the program displays a User Id and prompts a password:

C:\crackmes>level-2.exe

Crackme - Level 2 - by 60Ô15
----------------------------

User Id  : 3719630010
Password : abcd

Nope, try again !

Code overview

Code analysis

User Id

The program starts by building a 0x20 long array (variable user_id) with 0x4F:

.text:004013E0 public _main
.text:004013E0 _main proc near
.text:004013E0 mov     edi, offset user_id ; mem loc 0x40D020
.text:004013E5 mov     ecx, 20h            ; size 0x20
.text:004013EA mov     al, 4Fh             ; items = 0x4F
.text:004013EC rep stosb               ; fill 32 bytes with 0x4F starting from 0x40D020

Then, the value of the Read Time Stamp Counter (RDTSC) is gathered via the rdtsc intruction at offset 0x401401 and displayed as the User Id.

.text:004013EE inc     edi
.text:004013EF mov     edi, 0
.text:004013F4 push    offset __data_start__ ; "\nCrackme - Level 2 - by 60Ô15\n-------"...
.text:004013F9 call    _printf
.text:004013FE add     esp, 4
.text:00401401 rdtsc
.text:00401403 mov     ebx, eax
.text:00401405 push    ebx                   ; ebx = rdtsc
.text:00401406 push    offset public_message ; "User Id  : %u\n"
.text:0040140B call    _printf
.text:00401410 add     esp, 8

The user_id array is moved to EDI at offset 0x401413:

.text:00401413 mov     edi, offset user_id

Bit test

At offset 0x401418, a bit test is processed by the bt instruction. It will update the first byte of user_id if this bit is not 0. Example: suppose the rdtsc is 1297941641 (01001101010111010000010010001001 in binary). Here is how the test is performed:

 ebx
┌─────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ bit │ 31 │ 30 │ 29 │ 28 │ 27 │ 26 │ 25 │ 24 │ 23 │ 22 │ 21 │ 20 │ 19 │ 18 │ 17 │ 16 │ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 09 │ 08 │ 07 │ 06 │ 05 │ 04 │ 03 │ 02 │ 01 │ 00 │
├─────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ val │  0 │  1 │  0 │  0 │  1 │  1 │  0 │  1 │  0 │  1 │  0 │  1 │  1 │  1 │  0 │  1 │  0 │  0 │  0 │  0 │  0 │  1 │  0 │  0 │  1 │  0 │  0 │  0 │  1 │  0 │  0 │  1 │
└─────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
                                                                                                                                                                    │
              ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── bit #0 = 1 ◄──┘
              │
 user_id      ▼
┌──────────┬────────────────┬────────────────┬────────────────┬────────────────┐
│ 0x40D020 │ 2A  4F  4F  4F │ 4F  4F  4F  4F │ 4F  4F  4F  4F │ 4F  4F  4F  4F │
│ 0x40D030 │ 4F  4F  4F  4F │ 4F  4F  4F  4F │ 4F  4F  4F  4F │ 4F  4F  4F  4F │
└──────────┴────────────────┴────────────────┴────────────────┴────────────────┘
.text:00401413                 mov     edi, offset user_id ; edi points to 0x40D020
.text:00401418                 bt      ebx, 0              ; convert rdtsc (eax) to binary
.text:00401418                                             ; CF = bit 0 of binary value
.text:00401418                                             ; bin(rdtsc)[-1]
.text:0040141C                 jnb     short zero_bit_set  ; if CF==0, jump to zero_bit_set
.text:0040141E                 mov     byte ptr [edi], 2Ah ; user_id[0] = 0x2A

rdtsc > 0xB16B00B5?

Then, a test is performed against the value of rdtsc. If it is lower or equal than 0xB16B00B5, the byte at 0x40D021 is overwritten with 0x2A.

.text:00401421                 inc     edi                    ; edi = 0x40D021
.text:00401422                 cmp     ebx, 0B16B00B5h        ; \ if rdtsc > B16B00B5
.text:00401428                 ja      short above_b16b00b5h  ; / take jump
.text:0040142A                 mov     byte ptr [edi], 2Ah    ; user_id[1] = 0x2A

Parity test

This block of code increments EDI. At offset 0x40142E, EDI equals 0x40D022, which, is equal to 10000001101000000100010 in binary. The parity test is performed at offset 0x40142E. The jump at offset 0x401430 will never be taken because the number of 1 in the low 8 bits of EDI is even, as depicted below:

                                                                                                                              ┌──────────── parity test ──────────────┐
┌─────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ bit │ 31 │ 30 │ 29 │ 28 │ 27 │ 26 │ 25 │ 24 │ 23 │ 22 │ 21 │ 20 │ 19 │ 18 │ 17 │ 16 │ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 09 │ 08 │ 07 │ 06 │ 05 │ 04 │ 03 │ 02 │ 01 │ 00 │
├─────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ val │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  1 │  0 │  0 │  0 │  0 │  0 │  0 │  1 │  1 │  0 │  1 │  0 │  0 │  0 │  0 │  0 │  0 │  1 │  0 │  0 │  0 │  1 │  0 │
└─────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
                                                                                                                                        └──┬─┘              └──┬─┘
                                                                                                                 even number of 1  ◄───────┴───────────────────┘
                                                                                                                 => PF = 1
.text:0040142D                 inc     edi                  ; edi = 0x40D022. PF = 1 (parity flag)
.text:0040142E                 jnp     short no_parity      ; jump will never be taken because PF = 1
.text:00401430                 mov     byte ptr [edi], 2Ah  ; user_id[2] = 0x2A

0x401433 - 0x401464

Starting at offset 0x401434, byte #3 of the user_id array is overwriten with 0x2A. Then, the program will loop 28 times to update the remaining bytes of user_id by applying the following logic:

ebx = ebx >> 1
if i % 2 == 0:
    uid[i] = ebx % 0x1A + 0x41
else:
    uid[i] = ebx % 0x1A + 0x61
.text:00401433                 inc     edi
.text:00401434                 mov     byte ptr [edi], 2Ah ; user_id[3] = 0x2A
.text:00401437                 mov     ecx, 1Ch        ; ecx = 28 (counter)
.text:0040143C
.text:0040143C get_byte:
.text:0040143C                 shr     ebx, 1          ; ebx = ebx >> 1
.text:0040143E                 mov     edx, 0          ; edx = 0 (clear dividend)
.text:00401443                 mov     eax, ebx        ; eax = ebx
.text:00401445                 mov     esi, 1Ah        ; esi = 0x1A
.text:0040144A                 div     esi             ; edx = eax % 0x1A
.text:0040144C                 test    ecx, 1          ; \ will take the jump
.text:00401452                 jz      short add_97    ; / once every 2 loops (when ecx is even)
.text:00401454                 add     edx, 41h        ; when ecx is odd
.text:00401454                                         ; edx += 0x41
.text:00401457                 mov     [edi], dl       ; user_id[i] = dl
.text:00401457                                         ; with i starting from 3
.text:00401459                 inc     edi
.text:0040145A                 loop    get_byte        ; ecx -= 1
.text:0040145C                 jmp     short go_on
.text:0040145E ; ---------------------------------------------------------------------------
.text:0040145E
.text:0040145E add_97:
.text:0040145E                 add     edx, 61h        ; when ecx is even
.text:0040145E                                         ; edx += 0x61
.text:00401461                 mov     [edi], dl       ; user_id[i] = dl
.text:00401461                                         ; with i starting from 3
.text:00401463                 inc     edi
.text:00401464                 loop    get_byte        ; ecx -= 1

Password

The password is then prompted at offset 0x40147D:

.text:00401466 push    offset private_prompt
.text:0040146B call    _printf
.text:00401470 add     esp, 4
.text:00401473 push    offset password
.text:00401478 push    offset scanf_format ; "%50s"
.text:0040147D call    _scanf
.text:00401482 add     esp, 8
.text:00401485 mov     esi, offset user_id
.text:0040148A mov     edi, offset password

Each byte of the provided password is then compared with the expected password and if the test fails for a given byte, the loop exits and goes to the bad boy.

.text:00401485                 mov     esi, offset user_id
.text:0040148A                 mov     edi, offset password
.text:0040148F
.text:0040148F keep_comparing:
.text:0040148F                 cmp     byte ptr [esi], 0
.text:00401492                 jz      short yepp
.text:00401494                 mov     al, [esi]       ; al = user_id (expected password)
.text:00401496                 cmp     al, [edi]       ; edi = provided password
.text:00401498                 jnz     short nope      ; if user_id[i] != provided_password[i], goto badboy
.text:0040149A                 inc     esi
.text:0040149B                 inc     edi
.text:0040149C                 jmp     short keep_comparing
.text:0040149E ; ---------------------------------------------------------------------------
.text:0040149E
.text:0040149E yepp:
.text:0040149E                 push    offset good_job ; "\nGood job ! - Now please prepare a key"...
.text:004014A3                 call    _printf
.text:004014A8                 add     esp, 4
.text:004014AB                 retn
.text:004014AC ; ---------------------------------------------------------------------------
.text:004014AC
.text:004014AC nope:
.text:004014AC                 push    offset try_again ; "\nNope, try again !\n"
.text:004014B1                 call    _printf
.text:004014B6                 add     esp, 4
.text:004014B9                 retn

Solution

Keygen

Below is the keygen I wrote for this challenge. The assembly code has been left as comments to help understanding it.

#!/usr/bin/env python
import sys

"""
.text:004013E0 mov     edi, offset user_id ; mem loc 0x40D020
.text:004013E5 mov     ecx, 20h
.text:004013EA mov     al, 4Fh
.text:004013EC rep stosb               ; fill 32 bytes with 0x4F starting from 0x40D0
"""
uid = [0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F,
       0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F,
       0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F,
       0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F]

def bit_test_0(n):
    return int(bin(n)[-1])

def is_odd(n):
    return bin(n)[-8:].count('1') % 2

def keygen(user_id):

    """
    .text:004013EE  inc     edi
    ...
    .text:00401418  bt      ebx, 0              ; CF = bit 0 of binary value bin(rdtsc)[-1]
    .text:0040141C  jnb     short zero_bit_set  ; if CF==0, jump to zero_bit_set
    .text:0040141E  mov     byte ptr [edi], 2Ah ; user_id[0] = 0x2A
    """
    edi = 0x40D021
    if bit_test_0(user_id) != 0:
        uid[0] = 0x2A

    """
    .text:00401421                 inc     edi
    .text:00401422                 cmp     ebx, 0B16B00B5h ;       \ if rdtsc > 0xB16B00B5
    .text:00401428                 ja      short above_b16b00b5h ; / take jump
    .text:0040142A                 mov     byte ptr [edi], 2Ah ; user_id[1] = 0x2A
    """
    edi += 1
    if user_id <= 0xB16B00B5:
        uid[1] = 0x2A
    
    """
    .text:0040142D                 inc     edi
    .text:0040142E                 jnp     short no_parity ; jump will never be taken
    .text:00401430                 mov     byte ptr [edi], 2Ah ; user_id[2] = 0x2A
    """
    edi += 1
    if is_odd(edi) == 1:
        uid[2] = 0x2A
    
    """
    .text:00401433                 inc     edi
    .text:00401434                 mov     byte ptr [edi], 2Ah ; user_id[3] = 0x2A
    .text:00401437                 mov     ecx, 1Ch        ; ecx = 0x1C
    """
    uid[3] = 0x2A
    ecx = 0x1C
    
    """
    .text:0040143C                 shr     ebx, 1          ; ebx = ebx >> 1
    .text:0040143E                 mov     edx, 0          ; edx = 0 (clear dividend)
    .text:00401443                 mov     eax, ebx        ; eax = ebx
    .text:00401445                 mov     esi, 1Ah        ; esi = 0x1A
    .text:0040144A                 div     esi             ; edx = eax % 0x1A
    .text:0040144C                 test    ecx, 1          ; \ will take the jump
    .text:00401452                 jz      short add_97    ; / once every 2 loops (when ecx is even)
    .text:00401454                 add     edx, 41h        ; when ecx is odd
    .text:00401454                                         ; edx += 0x41
    .text:00401457                 mov     [edi], dl       ; user_id[i] = dl
    .text:00401457                                         ; with i starting from 3
    .text:00401459                 inc     edi
    .text:0040145A                 loop    get_byte        ; ecx -= 1
    .text:0040145C                 jmp     short go_on
    .text:0040145E ; ---------------------------------------------------------------------------
    .text:0040145E
    .text:0040145E add_97:
    .text:0040145E                 add     edx, 61h        ; when ecx is even
    .text:0040145E                                         ; edx+=0x61
    .text:00401461                 mov     [edi], dl       ; user_id[i] = dl
    .text:00401461                                         ; with i starting from 3
    .text:00401463                 inc     edi
    .text:00401464                 loop    get_byte        ; ecx -= 1
    """
    ebx = user_id
    for i in range(3, 31):
        ebx = ebx >> 1
        if i % 2 == 0:
            uid[i] = ebx % 0x1A + 0x41
        else:
            uid[i] = ebx % 0x1A + 0x61
    
    return ''.join([chr(i) for i in uid])

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print "Usage: %s <user_id>" % sys.argv[0]
        sys.exit(1)
    
    print keygen(int(sys.argv[1]))

Tests

program keygen
C:\crackmes>level-2.exe

Crackme - Level 2 - by 60Ô15
----------------------------

User Id  : 310539076
Password : O**iRiEcBaNtWyZzZmGdOuKsWlSjEcBO

Good job ! - Now please prepare a keygen...
C:\crackmes>python keygen.py 310539076
O**iRiEcBaNtWyZzZmGdOuKsWlSjEcBO

Level 3

Introduction

Description

This crackme is the level 3 of a series of 4 challenges, available here. This level is very interesting because it manipulates flags to validate bits of the user input.

Overview

When run, the program asks for an input. If we enter letters, here is what we get:

C:\crackme>level-3.exe

Crackme - Level 3 - by 60Ô15
----------------------------

Guess : test

Sorry, not it.

On the other hand, a different message is displayed when providing a numeric value:

C:\crackme>level-3.exe

Crackme - Level 3 - by 60Ô15
----------------------------

Guess : 1234

Nope, try again !

Below is an overview of the code:

Code Analysis

Initialization

This block of code is the initialization phase of the sub_4013E0 function. It validates that the user input is numeric. If not, it jumps to the first failing message at 0x40149B.

.text:004013E0 sub_4013E0 proc near
.text:004013E0 inc     edi
.text:004013E1 mov     edi, 0
.text:004013E6 push    offset Format     ; "\nCrackme - Level 3 - by 60Ô15\n-------"...
.text:004013EB call    printf
.text:004013F0 add     esp, 4
.text:004013F3 mov     byte_409079, 64h
.text:004013FA push    offset aGuess     ; "Guess : "
.text:004013FF call    printf
.text:00401404 add     esp, 4
.text:00401407 push    offset my_guess   ; offset 0x40D020
.text:0040140C push    offset asc_409078 ; "%"
.text:00401411 call    scanf
.text:00401416 add     esp, 8
.text:00401419 cmp     eax, 0
.text:0040141C jz      short loc_40149B  ; my_guess should be numbers

Test 1

This test is checking that the number of 1 in the low 8 bits of the binary representation of the user input (my_guess) is even.

.text:0040141E mov     eax, ds:my_guess
.text:00401423 xor     eax, 0          ; affects Parity Flag (PF)
.text:00401426 pushf
.text:00401427 pop     ebx
.text:00401428 bt      ebx, 2          ; CF = Parity Flag (PF)
.text:0040142C jnb     short FAIL      ; jump to FAIL if CF = 0
.text:0040142C                         ; => bin(my_guess)[-8:].count('1') should be even

At offset 0x401423, the xor instruction will do nothing but update the Parity Flag (PF). It applies to the low 8 bits of eax, which has been set to the user input at offset 0x40141E.

At offset 0x401426, the FLAGS are pushed to the stack by the pushf instruction, as follows:

┌──────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ bit  │ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 09 │ 08 │ 07 │ 06 │ 05 │ 04 │ 03 │ 02 │ 01 │ 00 │
├──────┼────┼────┼────┴────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ flag │  0 │ NT │   IOPL  │ OF │ DF │ IF │ TF │ SF │ ZF │  0 │ AF │  0 │ PF │  1 │ CF │ 
└──────┴────┴────┴─────────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
         │    │    │         │    │    │    │    │    │    │    │    │    │    │    └─ Carry flag
         │    │    │         │    │    │    │    │    │    │    │    │    │    └─ Reserved 
         │    │    │         │    │    │    │    │    │    │    │    │    └─ Parity flag
         │    │    │         │    │    │    │    │    │    │    │    └─ Reserved
         │    │    │         │    │    │    │    │    │    │    └─ Adjust flag
         │    │    │         │    │    │    │    │    │    └─ Reserved
         │    │    │         │    │    │    │    │    └─ Zero flag
         │    │    │         │    │    │    │    └─ Sign flag
         │    │    │         │    │    │    └─ Trap flag
         │    │    │         │    │    └─ Interrupt enable flag
         │    │    │         │    └─ Direction flag
         │    │    │         └─ Overflow flag
         │    │    └─ I/O privilege level (286+ only), always 1 on 8086 and 186
         │    └─ Nested task flag (286+ only), always 1 on 8086 and 186
         └─ Reserved, always 1 on 8086 and 186, always 0 on later models

The instruction just after (offset 0x401427) is moving this value to ebx. At offset 0x401428, the bt instruction sets the Carry Flag (CF) to bit #2 (Parity Flag) of FLAGS. It jumps to FAIL if CF = 0.

To sum up, this block of code is checking that the following condition is satisfied:

The number of 1 in the low 8 bits of the binary representation of the user input should be even.

Test 2

.text:0040142E bt      eax, 1Eh        ; CF = bit #30 of my_guess
.text:00401432 pushf
.text:00401433 pop     ebx             ; ebx = CF
.text:00401434 bt      ebx, 0
.text:00401438 jb      short FAIL      ; jump to FAIL if CF=1
.text:00401438                         ; => bit #30 of my guess should be 0

At offset 0x40142E, bit #30 of the user input is checked. The value of FLAGS is pushed to the stack as explained previously (see test 1) and this time, bit 0 (Carry Flag) of FLAGS is used. It will jump to FAIL if it is set. We deduce that:

Bit #30 should be 0.

Test 3

.text:0040143A test    eax, 1          ; my_guess & 0x1
.text:0040143F pushf
.text:00401440 pop     ebx
.text:00401441 bt      ebx, 6          ; CF = ZF
.text:00401445 jb      short FAIL      ; jump to FAIL if ZF = 1
.text:00401445                         ; => bit #0 of my_guess should be 1

At offset 0x40143A, the test instruction is performing a logical AND with 0x1. It will return zero if bit #0 is not set to 1. The value of the Zero Flag (ZF) is then checked at offset 0x401441. In other terms:

Bit #0 of the user input should be set to 1

Test 4

.text:00401447 shl     eax, 1          ; CF = Bit #31 of my_guess
.text:00401449 pushf
.text:0040144A pop     ebx
.text:0040144B bt      ebx, 0          ; CF is read
.text:0040144F jnb     short FAIL      ; Jump to FAIL if CF = 0
.text:0040144F                         ; => Bit #31 of my_guess should be 1

At offset 0x401447, the user input is shifted to the left (shl). The 1st bit that is shifted (bit #31 of the user input) is saved to the Carry Flag (CF) and is then read at offset 0x40144B (bit #0 of FLAGS). The code jumps to FAIL if CF = 0. In other terms:

Bit #31 of the user input should be equal to 1

Test 5

.text:00401451 add     eax, 60000000h
.text:00401456 pushf
.text:00401457 pop     ebx
.text:00401458 bt      ebx, 0Bh        ; CF = Overflow Flag (OF)
.text:0040145C jnb     short FAIL      ; Jump to FAIL if CF = 0
.text:0040145C                         ; my_guess should be > (0xFFFFFFFF-0x60000000)/2 = 1,342,177,279

Remember that the user input was shifted left by 1 in the previous test. This is like multiplying it by 2. At offset 0x401451, the value 0x60000000 is added and the Overflow Flag (OF) is tested at offset 0x401458. The maximum value the user input can be will be computed as follows:

2 * x + 0x60000000 = 0xffffffff
x = (0xFFFFFFFF - 0x60000000) / 2
x = 1,342,177,279

This condition is useless since bits 31 and 30 should respectively be set to 1 and 0, as per previous tests. Hence:

minimum = 10000000000000000000000000000000 =  2,147,483,648

The minimum required by the previous tests is already above the minimum required by this test.

Test 6

.text:0040145E or      eax, 20000000h
.text:00401463 and     eax, 70000h     ; eax should be 0 after these 2 transformations
.text:00401468 pushf
.text:00401469 pop     ebx
.text:0040146A bt      ebx, 6          ; CF = ZF
.text:0040146E jnb     short FAIL      ; Jump to FAIL if CF=0
.text:0040146E                         ; => Bit #28 should be 1
.text:0040146E                         ; => Bits #15-17 should be 0

The Zero Flag (ZF) is checked at offset 0x40146A and should be zero.

Considering the previous transformations and the ones applied at offsets 0x40145E and 0x401463, here is what we have:

┌──────────────────────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│                      │ 31 │ 30 │ 29 │ 28 │ 27 │ 26 │ 25 │ 24 │ 23 │ 22 │ 21 │ 20 │ 19 │ 18 │ 17 │ 16 │ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 09 │ 08 │ 07 │ 06 │ 05 │ 04 │ 03 │ 02 │ 01 │ 00 │
├──────────────────────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ eax                  │  1   0 │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  1 │
│ shl eax,1            │  0 │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  x │  1   0 │
│ add eax, 0x600000000 │  0 │  1   1 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │
│ or eax, 0x20000000   │  0 │  0 │  1 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │
│ and eax, 0x70000     │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  1   1   1 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │  0 │
└──────────────────────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
                                    │                                                   └───────┬──────┘
                                    └──  The OR test is without effect because it               │
                                         applies to bit #29 (after shl) and the bit             └── To make the result 0, bits #16 to #18 (after shl) should
                                         #29 of 0x70000 is 0. Whatever value we                     be 0 because:
                                         have, it will never return 1 because:                         * 0 AND 1 = 0 (good)
                                            * 1 AND 0 = 0 (good)                                       * 1 AND 1 = 1 (bad)
                                            * 0 AND 0 = 0 (good)

To sump up, bits #16 to #18 should be set to 0, once the shift left (shl) operation has been applied to the user input, which means that:

Bits #15-#17 of the user input should be 0.

Test 7

.text:00401470                 mov     eax, ds:my_guess ; eax = my_guess
.text:00401475                 mov     ebx, 0          ; ebx = 0
.text:0040147A                 mov     ecx, 20h        ; ecx = 32
.text:0040147F                 mov     edx, 1Fh        ; edx = 31
.text:00401484
.text:00401484 loc_401484:
.text:00401484                 bt      eax, edx        ; CF = my_guess[i]
.text:00401487                 jnb     short loc_40148A ; jump if CF = 0
.text:00401489                 inc     ebx             ; count number of '1' in binary
.text:00401489                                         ; representation of eax
.text:0040148A
.text:0040148A loc_40148A:
.text:0040148A                 dec     edx             ; edx-=1
.text:0040148B                 loop    loc_401484      ; loop until ecx=0
.text:0040148D                 lea     eax, my_guess+1 ; 0x40D021 contains byte #1 (bits #8-15) of user input
.text:00401493                 mov     eax, [eax]
.text:00401495                 xor     al, bl
.text:00401497                 jnz     short FAIL      ; Nb of '1' in binary representation of my_guess
.text:00401497                                         ; should be equal to byte #1 of my_guess

At offset 0x401470, the user input is moved back to eax. Then, starting from offset 0x401484, there is a loop that is counting the number of 1 (saved in ebx) in the binary representation of the user input.

Then starting from offset 0x40148D, byte #1 of the user input (bits #8-15) is moved to eax. The XOR operation at offset 0x401495 is ensuring that this value equals the number of 1 obtained previously because x ^ x = 0.

Byte #1 (bits #8-15) of the user input should be equal to the total number of 1 in the binary representation of the user input.

Conditions and solution

Conditions

Below is a sum up of the conditions as seen in the previous tests:

Test Description
Test 1 The number of 1 in the low 8 bits of the binary representation of the user input should be even.
Test 2 bit #30 of the user input should be 0
Test 3 bit #0 of the user input should be 1
Test 4 Bit #31 of the user input should be 1
Test 5 The user input should be > 1,342,177,279
Test 6 Bits #15-17 of the user input should be 0
Test 7 The number of '1' in the binary representation of the user input should be equal to byte #1 of the user input

Valid codes

dec 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
2,415,920,643 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 1
3,109,031,439 1 0 1 1 1 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1
2,682,261,281 1 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 0 0 0 0 1
2,684,882,943 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1
 
count 1 total 1
2 6
4 14
2 15
8 15

Level 4

Level 4 is available here

Comments

Keywords: assembly x86 reverse-engineering crackme borismilner 4N006135 crackmes.de