Solution-LaFarges-crackme-2
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
|
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