Solution-LaFarges-crackme-2

From aldeid
Jump to navigation Jump to search

Challenge

Objective

The objective of this crackme (http://crackmes.de/users/lafarge/crackme_v0.2/) is to crack the serial generation algorithm to find the appropriate serial from a given password and develop a keygen.

My keygen in action

Here is my keygen in action (with debug information set to True)

$ python decode.py myusername
============================================
 FIRST TRANSFORMATION (0x40117A - 0x401199) 
============================================
0 => 0x79 ^ 0xaa = 0xd3
1 => 0x75 ^ 0x89 = 0xfc
2 => 0x73 ^ 0xc4 = 0xb7
3 => 0x65 ^ 0xfe = 0x9b
4 => 0x72 ^ 0x46 = 0x34
5 => 0x6e ^ 0x79 = 0x17
6 => 0x61 ^ 0x75 = 0x14
7 => 0x6d ^ 0x73 = 0x1e
8 => 0x65 ^ 0x65 = 0x0
----
result of 1st transformation
Memory location 0x406345 at breakpoint 0x40119B
['0x0', '0x0', '0x0', '0x0', '0x6d', '0xd3', '0xfc', '0xb7', '0x9b', '0x34', '0x17', '0x14', '0x1e', '0x0', '0x72']
=============================================
 SECOND TRANSFORMATION (0x4011A3 - 0x4011C7) 
=============================================
0 => 0x72 ^ 0x78 = 0xa
1 => 0x0 ^ 0xf0 = 0xf0
2 => 0x1e ^ 0xd0 = 0xce
3 => 0x14 ^ 0x3 = 0x17
4 => 0x17 ^ 0xe7 = 0xf0
5 => 0x34 ^ 0x72 = 0x46
6 => 0x9b ^ 0x0 = 0x9b
7 => 0xb7 ^ 0x1e = 0xa9
8 => 0xfc ^ 0x14 = 0xe8
9 => 0xd3 ^ 0x17 = 0xc4
----
result of 2nd transformation
Memory location 0x406345 at breakpoint 0x4011C9
['0x0', '0x0', '0x0', '0x0', '0x6d', '0xc4', '0xe8', '0xa9', '0x9b', '0x46', '0xf0', '0x17', '0xce', '0xf0', '0xa']
============================================
 THIRD TRANSFORMATION (0x4011D1 - 0x4011F0) 
============================================
0 => 0xc4 ^ 0xf7 = 0x33
1 => 0xe8 ^ 0xfd = 0x15
2 => 0xa9 ^ 0xf4 = 0x5d
3 => 0x9b ^ 0xe7 = 0x7c
4 => 0x46 ^ 0xb9 = 0xff
5 => 0xf0 ^ 0xc4 = 0x34
6 => 0x17 ^ 0xe8 = 0xff
7 => 0xce ^ 0xa9 = 0x67
8 => 0xf0 ^ 0x9b = 0x6b
9 => 0xa ^ 0x46 = 0x4c
----
result of 3rd transformation
Memory location 0x406345 at breakpoint 0x4011F2
['0x0', '0x0', '0x0', '0x0', '0x6d', '0x33', '0x15', '0x5d', '0x7c', '0xff', '0x34', '0xff', '0x67', '0x6b', '0x4c']
============================================
 FOURTH TRANSFORMATION (0x4011FA - 0x40121E) 
============================================
0 => 0x4c ^ 0xb5 = 0x86
1 => 0x6b ^ 0x1b = 0xe
2 => 0x67 ^ 0xc9 = 0x94
3 => 0xff ^ 0x50 = 0x2c
4 => 0x34 ^ 0x73 = 0x8c
5 => 0xff ^ 0x4c = 0x78
6 => 0x7c ^ 0x6b = 0x94
7 => 0x5d ^ 0x67 = 0x0
8 => 0x15 ^ 0xff = 0x94
9 => 0x33 ^ 0x34 = 0x78
----
result of 4th transformation
Memory location 0x406345 at breakpoint 0x401220
['0x0', '0x0', '0x0', '0x0', '0x6d', '0x7', '0xea', '0x3a', '0x17', '0xb3', '0x47', '0xaf', '0xae', '0x70', '0xf9']
============================================
 FIFTH TRANSFORMATION (0x401236 - 0x40124B) 
============================================
----
result of 5th transformation
Memory location 0x406345 at breakpoint 0x40124D
['0x2a', '0x2a', '0xe9', '0xc5', '0x6d', '0x7', '0xea', '0x3a', '0x17', '0xb3', '0x47', '0xaf', '0xae', '0x70', '0xf9']
============================================
 SIXTH TRANSFORMATION (0x40125A - 0x40126A) 
============================================
Initial value for EAX at breakpoint 0x401258: 0xc5e92a2a
0	0x13ca8437		0x34
1	0x1faa6d2		0x33
2	0x32aa48		0x32
3	0x51107		0x32
4	0x81b3		0x39
5	0xcf8		0x33
6	0x14c		0x30
7	0x21		0x32
8	0x3		0x33
9	0x0		0x33
----
result of 6th transformation
Memory location 0x406549 at breakpoint 0x40126C
['0x34', '0x33', '0x32', '0x32', '0x39', '0x33', '0x30', '0x32', '0x33', '0x33']

---------------------------
Serial: 3320392234
---------------------------

Code analysis

Overview

Let's open the executable in IDA-Pro and analyze the start function. At offset 0x40109D, there is a call to DialogBoxParamA where the ldDialogFunc parameter is calling the DialogFunc function:

.text:00401089 push    0               ; dwInitParam
.text:0040108B push    offset DialogFunc ; lpDialogFunc
.text:00401090 push    0               ; hWndParent
.text:00401092 push    offset Name     ; "#1000"
.text:00401097 push    hModule         ; hInstance
.text:0040109D call    DialogBoxParamA

It leads to offset 0x4010B4 where the algorithm lies. We can see 6 loops to get the serial. There are actually 7 loops but the last one is just reversing the characters of the serial so we won't spend much time on it.

Also we can notice that all loops are working at an offset of memory location 0x406345. Each loop is building part of a byte sequence that will produce a key which will eventually be used to produce the serial. This is why my keygen is using a global list (loc_406345).

First transformation

This first loop is XOR'ing each letter of the username with a rotating 5-bytes length array starting at location 0x406328.

ASM python
.text:00401163 loc_401163:
.text:00401163                 lea     edx, unk_406349
.text:00401169                 push    edx             ; lpString
.text:0040116A                 call    lstrlenA        ; username length
.text:0040116F                 mov     ebp, eax        ; ebp = username length
.text:00401171                 mov     ecx, 5          ; ecx = 5 (counter)
.text:00401176                 xor     esi, esi        ; esi = 0 (username letter index)
.text:00401178                 xor     eax, eax        ; eax = 0
.text:0040117A
.text:0040117A loc_40117A:
.text:0040117A                 mov     cl, [esi+edx]   ; 1st loop - each letter of username (except 1st one)
.text:0040117D                 mov     bl, cl          ; letter moved to BL
.text:0040117F                 xor     bl, byte_406328[eax] ; [0xAA, 0x89, 0xC4, 0xFE, 0x46]
.text:00401185                 inc     eax
.text:00401186                 cmp     eax, 5
.text:00401189                 mov     [edx+esi], bl
.text:0040118C                 mov     byte_406327[eax], cl
.text:00401192                 jnz     short loc_401196
.text:00401194                 xor     eax, eax
.text:00401196
.text:00401196 loc_401196:
.text:00401196                 inc     esi
.text:00401197                 cmp     esi, ebp
.text:00401199                 jb      short loc_40117A
key = [0xAA, 0x89, 0xC4, 0xFE, 0x46]
offset = 406349 - 406345

# first letter not transformed
loc_406345[offset] = ord(myusername[:1]) & 0xFF

# Loop thru all characters but the 1st one (string modified by lstrlenA)
for c, i in enumerate(myusername[1:]):
    loc_406345[c+offset+1] = (ord(i) & 0xFF) ^ (key[c%5] & 0xFF)
    key[c%5] = ord(i) & 0xFF

#Last char = empty char. XOR results in the key only
loc_406345[c+offset+2] = key[(c+1) % 5] & 0xFF
Note
To be noticed that the EDX register that contains the username is actually modified at 0x40116A by the call to lstrlenA. This behavior is decribed here.

Second transformation

This second tranformation is XOR'ing bytes obtained by the 1st tranformation (in reverse order) with a rotating 5-bytes long array starting at offset 0x40632D.

ASM python
.text:0040119B                 xor     edi, edi
.text:0040119D                 xor     ecx, ecx
.text:0040119F                 test    ebp, ebp
.text:004011A1                 jbe     short loc_4011C9
.text:004011A3
.text:004011A3 loc_4011A3:
.text:004011A3                 mov     bl, byte_40632D[edi] ; 2nd loop - [0x78, 0xF0, 0xD0, 0x3, 0xE7]
.text:004011A9                 mov     esi, ebp        ; username length
.text:004011AB                 sub     esi, ecx
.text:004011AD                 dec     esi
.text:004011AE                 mov     al, [edx+esi]
.text:004011B1                 xor     bl, al
.text:004011B3                 inc     edi
.text:004011B4                 mov     [edx+esi], bl
.text:004011B7                 mov     byte_40632C[edi], al
.text:004011BD                 cmp     edi, 5
.text:004011C0                 jnz     short loc_4011C4
.text:004011C2                 xor     edi, edi
.text:004011C4
.text:004011C4 loc_4011C4:
.text:004011C4                 inc     ecx
.text:004011C5                 cmp     ecx, ebp
.text:004011C7                 jb      short loc_4011A3
key = [0x78, 0xF0, 0xD0, 0x03, 0xE7]
c = 0
for i in range(len(myusername)):
    p = loc_406345[len(loc_406345)-i-1]
    loc_406345[len(loc_406345)-i-1] = p ^ key[i]
    key.append(p)
    c+=1

Third transformation

The 3rd transformation is XOR'ing bytes of the 2nd transfirmation with a 5-bytes rotating array starting at offset 0x406332.

ASM python
.text:004011C9
.text:004011C9 loc_4011C9:
.text:004011C9                 xor     esi, esi
.text:004011CB                 xor     edi, edi
.text:004011CD                 test    ebp, ebp
.text:004011CF                 jbe     short loc_4011F2
.text:004011D1
.text:004011D1 loc_4011D1:
.text:004011D1                 mov     al, [edx+edi]   ; 3rd loop
.text:004011D4                 mov     cl, byte_406332[esi]
.text:004011DA                 xor     cl, al
.text:004011DC                 inc     esi
.text:004011DD                 mov     [edx+edi], cl
.text:004011E0                 mov     byte_406331[esi], al
.text:004011E6                 cmp     esi, 5
.text:004011E9                 jnz     short loc_4011ED
.text:004011EB                 xor     esi, esi
.text:004011ED
.text:004011ED loc_4011ED:
.text:004011ED                 inc     edi
.text:004011EE                 cmp     edi, ebp
.text:004011F0                 jb      short loc_4011D1
key = [0xF7, 0xFD, 0xF4, 0xE7, 0xB9]
for c, i in enumerate(loc_406345[offset+1:]):
    loc_406345[c+offset+1] = ((i & 0xFF) ^ key[c]) & 0xFF
    key.append(i)

Fourth transformation

The 4th transformation is XOR'ing bytes of the 3rd transformation (in reverse order) with a 5-bytes rotating array starting at offset 0x406336.

ASM python
.text:004011F2
.text:004011F2 loc_4011F2:
.text:004011F2                 xor     edi, edi
.text:004011F4                 xor     ecx, ecx
.text:004011F6                 test    ebp, ebp
.text:004011F8                 jbe     short loc_401220
.text:004011FA
.text:004011FA loc_4011FA:
.text:004011FA                 mov     bl, byte ptr unk_406337[edi] ; 4th loop
.text:00401200                 mov     esi, ebp        ; myusername length
.text:00401202                 sub     esi, ecx
.text:00401204                 dec     esi
.text:00401205                 mov     al, [edx+esi]
.text:00401208                 xor     bl, al
.text:0040120A                 inc     edi
.text:0040120B                 mov     [edx+esi], bl
.text:0040120E                 mov     byte ptr unk_406336[edi], al
.text:00401214                 cmp     edi, 5
.text:00401217                 jnz     short loc_40121B
.text:00401219                 xor     edi, edi
.text:0040121B
.text:0040121B loc_40121B:
.text:0040121B                 inc     ecx
.text:0040121C                 cmp     ecx, ebp
.text:0040121E                 jb      short loc_4011FA
key = [0xB5, 0x1B, 0xC9, 0x50, 0x73]
for c, i in enumerate(loc_406345[offset+1:]):
    p = loc_406345[len(loc_406345)-c-1]
    loc_406345[len(loc_406345)-c-1] = p ^ key[c]
    key.append(p)

Fifth transformation

The 5th transformation is updating the first 4 bytes at offset 0x406345 by cumulating the values of these bytes with the ones of the key obtained with the 4 previous transformations.

ASM python
.text:00401220 loc_401220:
.text:00401220                 lea     edi, unk_406345 ; 5th tranform
.text:00401226                 xor     eax, eax
.text:00401228                 test    ebp, ebp
.text:0040122A                 mov     dword ptr unk_406345, 0
.text:00401234                 jbe     short loc_40124D
.text:00401236
.text:00401236 loc_401236:
.text:00401236                 mov     ecx, eax        ; EAX = counter
.text:00401238                 and     ecx, 3          ; ECX = counter+3
.text:0040123B                 mov     bl, [edi+ecx]   ; BL = key[ECX]
.text:0040123E                 lea     esi, [edi+ecx]
.text:00401241                 mov     cl, [edx+eax]   ; temp string chars
.text:00401244                 add     bl, cl
.text:00401246                 inc     eax
.text:00401247                 cmp     eax, ebp
.text:00401249                 mov     [esi], bl
.text:0040124B                 jb      short loc_401236
temp = [0x00]*4
offset = 5
for c, i in enumerate(loc_406345[offset:]):
    temp[c%4]+= i & 0xFF
for c, i in enumerate(temp):
    loc_406345[c] = i & 0xFF

Sixth transformation

The 6th transformation is using the DWORD at 0x406549 to get its initial value for EAX and is performing the following computation until EAX reaches zero:

  • Divides EAX by 0x0A (constant set at 0x40124E) and save the result in EAX
  • Adds 0x30 to the remainder of the division and use it as bytes for the serial, starting at 0x406549 (the serial will be reversed later)
ASM python
.text:0040124D loc_40124D:
.text:0040124D                 pop     ebp             ; 6th transformation
.text:0040124E                 mov     ecx, 0Ah
.text:00401253                 mov     eax, dword ptr unk_406345
.text:00401258                 xor     ebx, ebx
.text:0040125A
.text:0040125A loc_40125A:
.text:0040125A                 xor     edx, edx
.text:0040125C                 div     ecx
.text:0040125E                 add     dl, 30h
.text:00401261                 mov     byte ptr unk_406549[ebx], dl
.text:00401267                 inc     ebx
.text:00401268                 test    eax, eax
.text:0040126A                 jnz     short loc_40125A
loc_406549 = []
eax = int(''.join([hex(i).replace('0x', '').zfill(2) for i in reversed(loc_406345[:4])]), 16)

c = 0
while eax != 0:
    edx = (eax % 0x0A) + 0x30 # division remainder (0xA = constant at 0x40124E)
    eax = eax / 0x0A          # division result
    loc_406549.append(edx)
    c+=1

Seventh transformation

As explained before, this last transformation is actually just reversing the characters of the serial.

.text:0040126C                 push    offset unk_406549 ; lpString
.text:00401271                 call    lstrlenA
.text:00401276                 xor     ebx, ebx
.text:00401278
.text:00401278 loc_401278:
.text:00401278                 mov     cl, byte ptr unk_406548[eax]
.text:0040127E                 mov     byte_406749[ebx], cl
.text:00401284                 inc     ebx
.text:00401285                 dec     eax
.text:00401286                 jnz     short loc_401278

How to get a valid serial?

You can notice a call to lstrcmpA at location 0x4012B5. The serial provided by the user (String1) is compared with the expected serial (String2). If you set a breakpoint in OllyDbg at 0x4012B5, you will get a valid serial for the provided username:

004012AB   . 68 49654000    PUSH crackme.00406549                    ; /String2 = "3320392234"
004012B0   . 68 49694000    PUSH crackme.00406949                    ; |String1 = "1234"
004012B5   . E8 36010000    CALL <JMP.&kernel32.lstrcmpA>            ; \lstrcmpA

My keygen

#!/usr/bin/env python
import sys

def make_serial():

    loc_406345 = [0x00]*(len(myusername)+5)

    if debug:
        print "============================================"
        print " FIRST TRANSFORMATION (0x40117A - 0x401199) "
        print "============================================"
    key = [0xAA, 0x89, 0xC4, 0xFE, 0x46]
    offset = 406349 - 406345

    # first letter not transformed
    loc_406345[offset] = ord(myusername[:1]) & 0xFF
    
    # Loop thru all characters but the 1st one (string modified by lstrlenA)
    for c, i in enumerate(myusername[1:]):
        if debug:
            print "%s => %s ^ %s = %s" % (c, hex(ord(i) & 0xFF), hex(key[c%5] & 0xFF), hex((ord(i) & 0xFF) ^ (key[c%5] & 0xFF)))
        loc_406345[c+offset+1] = (ord(i) & 0xFF) ^ (key[c%5] & 0xFF)
        key[c%5] = ord(i) & 0xFF
    
    #Last char = empty char. XOR results in the key only
    loc_406345[c+offset+2] = key[(c+1) % 5] & 0xFF

    if debug:
        print "----"
        print "result of 1st transformation"
        print "Memory location 0x406345 at breakpoint 0x40119B"
        print [hex(i) for i in loc_406345]

    
    if debug:
        print "============================================="
        print " SECOND TRANSFORMATION (0x4011A3 - 0x4011C7) "
        print "============================================="
    key = [0x78, 0xF0, 0xD0, 0x03, 0xE7]
    c = 0
    for i in range(len(myusername)):
        p = loc_406345[len(loc_406345)-i-1]
        if debug:
            print "%s => %s ^ %s = %s" % (c, hex(p & 0xFF), hex(key[i] & 0xFF), hex((p & 0xFF) ^ (key[i] & 0xFF)))
        loc_406345[len(loc_406345)-i-1] = p ^ key[i]
        key.append(p)
        c+=1

    if debug:
        print "----"
        print "result of 2nd transformation"
        print "Memory location 0x406345 at breakpoint 0x4011C9"
        print [hex(i) for i in loc_406345]


    if debug:
        print "============================================"
        print " THIRD TRANSFORMATION (0x4011D1 - 0x4011F0) "
        print "============================================"
    key = [0xF7, 0xFD, 0xF4, 0xE7, 0xB9]
    for c, i in enumerate(loc_406345[offset+1:]):
        if debug:
            print "%s => %s ^ %s = %s" % (c, hex(i & 0xFF), hex(key[c]), hex((i & 0xFF) ^ key[c]))
        loc_406345[c+offset+1] = ((i & 0xFF) ^ key[c]) & 0xFF
        key.append(i)

    if debug:
        print "----"
        print "result of 3rd transformation"
        print "Memory location 0x406345 at breakpoint 0x4011F2"
        print [hex(i) for i in loc_406345]


    if debug:
        print "============================================"
        print " FOURTH TRANSFORMATION (0x4011FA - 0x40121E) "
        print "============================================"
    key = [0xB5, 0x1B, 0xC9, 0x50, 0x73]
    for c, i in enumerate(loc_406345[offset+1:]):
        p = loc_406345[len(loc_406345)-c-1]
        if debug:
            print "%s => %s ^ %s = %s" % (c, hex(p & 0xFF), hex(key[c]), hex((i & 0xFF) ^ key[c]))
        loc_406345[len(loc_406345)-c-1] = p ^ key[c]
        key.append(p)

    if debug:
        print "----"
        print "result of 4th transformation"
        print "Memory location 0x406345 at breakpoint 0x401220"
        print [hex(i) for i in loc_406345]


    if debug:
        print "============================================"
        print " FIFTH TRANSFORMATION (0x401236 - 0x40124B) "
        print "============================================"
    temp = [0x00]*4
    offset = 5
    for c, i in enumerate(loc_406345[offset:]):
        temp[c%4]+= i & 0xFF
    for c, i in enumerate(temp):
        loc_406345[c] = i & 0xFF
    
    if debug:
        print "----"
        print "result of 5th transformation"
        print "Memory location 0x406345 at breakpoint 0x40124D"
        print [hex(i) for i in loc_406345]


    if debug:
        print "============================================"
        print " SIXTH TRANSFORMATION (0x40125A - 0x40126A) "
        print "============================================"


    loc_406549 = []
    eax = int(''.join([hex(i).replace('0x', '').zfill(2) for i in reversed(loc_406345[:4])]), 16)
    
    if debug:
        print "Initial value for EAX at breakpoint 0x401258: %s" % hex(eax)
    c = 0
    while eax != 0:
        edx = (eax % 0x0A) + 0x30 # division remainder (0xA = constant at 0x40124E)
        eax = eax / 0x0A          # division result
        if debug:
            print "%s\t%s\t\t%s" % (c, hex(eax), hex(edx))
        loc_406549.append(edx)
        c+=1

    if debug:
        print "----"
        print "result of 6th transformation"
        print "Memory location 0x406549 at breakpoint 0x40126C"
        print [hex(i) for i in loc_406549]

    # Serial is read in reversed order
    print ""
    print "---------------------------"
    print "Serial: %s" % ''.join([chr(i) for i in reversed(loc_406549)])
    print "---------------------------"


if __name__ == '__main__':
    if len(sys.argv)<2:
        print "Usage: %s <username>" % sys.argv[0]
        sys.exit()
    
    myusername = sys.argv[1]
    if len(myusername) < 4:
        print "[WARNING] username should be at least 4 characters"
        sys.exit()
    
    debug = False
    make_serial()


Comments

Keywords: assembly x86 reverse-engineering crackme lafarges