Crackmes.de – san01suke's SomeCrypto~02
- July 15, 2014
- reverse engineering
- crackmes
- no comments
The author san01suke submitted three crackmes to [www.crackmes.de][1] on July 1st. This is my attempt to solve the second one, called SomeCrypto~02. You can view and download the crackme [here][2]. The short description simply says:
“Just write a valid keygen for this crackme.”
− san01suke
Reverse Engineering the Crackme
Getting the Disassembly
The user interface looks exactly like the one from [SomeCrypto~01][3]:
Let’s open the code in OllyDbg. At the entry point we get the following picture:
One can spot two interesting things:
- The code above 4014D3 could not be disassembled and looks obfuscated
- The code that follows the entry point clearly modifies the code. Starting at location
401000
, the snippet XORs exactly4D3h
bytes with 20h.
Let’s run the code up to 4014DA
to get the changed code section. The bytes clearly changed
but OllyDbg doesn’t automatically reanalyze the section and still displays the section as data bytes. Hit CTRL+A
to analyze the code again and now you should see meaningful disassembly:
I then created a patched version of SomeCrypto~02 with the bytes from 401000 to 4014D2 XORed with 20h. This allowed me to get the correct disassembly in IDA Pro.
sub_4011E0 – Part 1
I then opened the patched executable in IDA Pro and went to the strings subview to follow the “Success” string (like in [SomeCrypto~01][3]). The good boy message is produced here:
.text:00401475 call sub_4011E0
.text:0040147A add esp, 8
.text:0040147D mov byte_403270, al
.text:00401482 test al, al
.text:00401484 jz short loc_4014A2
.text:00401486 mov edx, [esp+0Ch]
.text:0040148A push 0
.text:0040148C push offset aSuccess ; "Success"
.text:00401491 push edx
.text:00401492 push esi
.text:00401493 call ds:MessageBoxA
.text:00401499 push 0
We will see the “Success”-message when sub_4011E0
returns a non zero value in eax
. Let’s walk through subroutine sub_4011E0
.text:004011E0 ; =============== S U B R O U T I N E =======================================
.text:004011E0
.text:004011E0 ; Attributes: bp-based frame
.text:004011E0
.text:004011E0 sub_4011E0 proc near ; CODE XREF: .text:00401475p
.text:004011E0
.text:004011E0 var_1C = byte ptr -1Ch
.text:004011E0 arg_0 = dword ptr 8
.text:004011E0 arg_4 = dword ptr 0Ch
.text:004011E0
.text:004011E0 push ebp
.text:004011E1 mov ebp, esp
.text:004011E3 xor eax, eax
.text:004011E5 sub esp, 1Ch
.text:004011E8 cmp [edi], al
.text:004011EA jz loc_401296
.text:004011F0
In OllyDbg we see that edi
contains the serial:
The snippet translate to the following pseudo code:
serial = edi
IF strlen(serial) == 0 THEN
RETURN 0 // loc_401296
END
This is the code at loc_401296
that returns 0:
.text:00401296 loc_401296: ; CODE XREF: sub_4011E0+Aj
.text:00401296 ; sub_4011E0+1Aj ...
.text:00401296 xor al, al
.text:00401298 mov esp, ebp
.text:0040129A pop ebp
.text:0040129B retn
.text:0040129B sub_4011E0 endp
If the serial is not empty the next section is executed:
.text:004011F0 loc_4011F0: ; CODE XREF: sub_4011E0+15j
.text:004011F0 inc eax
.text:004011F1 cmp byte ptr [eax+edi], 0
.text:004011F5 jnz short loc_4011F0
.text:004011F7 cmp eax, 7
.text:004011FA jnz loc_401296
The lines calculate the length of the serial and return 0 if the length is not 7:
serial_length = strlen(serial)
IF serial_length != 7 THEN
RETURN 0 // loc_401296
END
Next follows a call to another subroutine:
.text:00401200 mov edx, [ebp+arg_0]
.text:00401203 lea eax, [ebp+var_1C]
.text:00401206 call sub_401000</pre>
eax
is a local variable. The memory location ebp+arg_0 = ebp+8
points to the content of the name input box, as can be seen in OllyDbg:
So the call boils down to:
unknown_type var_1C;
sub_401000(&var_1C, name)
sub_401000
The subroutine sub_401000
looks as follows:
.text:00401000 ; =============== S U B R O U T I N E =======================================
.text:00401000
.text:00401000
.text:00401000 sub_401000 proc near ; CODE XREF: sub_4011E0+26p
.text:00401000 ; DATA XREF: .text:004014DAo
.text:00401000 mov dword ptr [eax], 0
.text:00401006 mov dword ptr [eax+4], 1
.text:0040100D mov dword ptr [eax+8], 2
.text:00401014 mov dword ptr [eax+0Ch], 3
.text:0040101B mov dword ptr [eax+10h], 4
.text:00401022 mov dword ptr [eax+14h], 5
.text:00401029 mov dword ptr [eax+18h], 6
.text:00401030 mov cl, [edx]
.text:00401032 test cl, cl
.text:00401034 jz short locret_40108C
.text:00401036 push esi
.text:00401037 jmp short loc_401040
.text:00401037 ; ---------------------------------------------------------------------------
.text:00401039 align 10h
.text:00401040
.text:00401040 loc_401040: ; CODE XREF: sub_401000+37j
.text:00401040 ; sub_401000+89j
.text:00401040 movsx ecx, cl
.text:00401043 and ecx, 80000001h
.text:00401049 jns short loc_401050
.text:0040104B dec ecx
.text:0040104C or ecx, 0FFFFFFFEh
.text:0040104F inc ecx
.text:00401050
.text:00401050 loc_401050: ; CODE XREF: sub_401000+49j
.text:00401050 jz short loc_40105C
.text:00401052 mov esi, [eax+4]
.text:00401055 mov ecx, [eax]
.text:00401057 mov [eax], esi
.text:00401059 mov [eax+4], ecx
.text:0040105C
.text:0040105C loc_40105C: ; CODE XREF: sub_401000:loc_401050j
.text:0040105C mov ecx, [eax]
.text:0040105E mov esi, [eax+4]
.text:00401061 mov [eax], esi
.text:00401063 mov esi, [eax+8]
.text:00401066 mov [eax+4], esi
.text:00401069 mov esi, [eax+0Ch]
.text:0040106C mov [eax+8], esi
.text:0040106F mov esi, [eax+10h]
.text:00401072 mov [eax+0Ch], esi
.text:00401075 mov esi, [eax+14h]
.text:00401078 mov [eax+10h], esi
.text:0040107B mov esi, [eax+18h]
.text:0040107E mov [eax+14h], esi
.text:00401081 inc edx
.text:00401082 mov [eax+18h], ecx
.text:00401085 mov cl, [edx]
.text:00401087 test cl, cl
.text:00401089 jnz short loc_401040
.text:0040108B pop esi
.text:0040108C
.text:0040108C locret_40108C: ; CODE XREF: sub_401000+34j
.text:0040108C retn
.text:0040108C sub_401000 endp
</pre>
The code translates to the following pseudo code:
FUNCTION sub_401000(mapping<var_1C>, name)
mapping = {0,1,2,3,4,5,6} // in eax = &var_1C
FOR character IN name DO
IF character % 2 != 0 DO
swap(mapping[0], mapping[1])
ENDIF
circular_left_shift(mapping)
ENDFOR
END
swap(mapping[0], mapping[1])
means {0,1,2,3,4,5,6}
would become {1,0,2,3,4,5,6}
. circular_left_shift(mapping)
means, {0,1,2,3,4,5,6}
becomes {1,2,3,4,5,6,0}
.
sub_4011E0 – Part 2
Let’s go back to the caller where we continue with line 27:
.text:0040120B xor eax, eax
.text:0040120D lea ecx, [ecx+0]
.text:00401210
.text:00401210 loc_401210: ; CODE XREF: sub_4011E0+3Fj
.text:00401210 mov cl, byte_403010[eax]
.text:00401216 mov byte_403140[eax], cl
.text:0040121C inc eax
.text:0040121D test cl, cl
.text:0040121F jnz short loc_401210
.text:00401221 push esi
.text:00401222 xor esi, esi
.text:00401224 cmp byte_403140, 0
.text:0040122B jz short loc_40123A
These line simply copy the hard coded null-terminated string at byte_403010
to byte_403140
and check if the destination address is not null:
STRCPY(byte_403140, byte_403010) // copy string byte_403010 to byte_403140
IF byte_403140 == NULL THEN
GOTO loc_40123A \\ should never happen
ENDIF
Next up is another small loop:
.text:0040122D lea ecx, [ecx+0]
.text:00401230
.text:00401230 loc_401230: ; CODE XREF: sub_4011E0+58j
.text:00401230 inc esi
.text:00401231 cmp byte_403140[esi], 0
.text:00401238 jnz short loc_401230
.text:0040123A
The code searches the null-byte in string byte_403140
. When the null byte is found, then the index in esi
corresponds to the length of the string at byte_403140
:
esi = strlen(byte_403140)
Another subroutine call follows:
.text:0040123A loc_40123A: ; CODE XREF: sub_4011E0+4Bj
.text:0040123A push esi
.text:0040123B lea eax, [ebp+var_1C]
.text:0040123E call sub_401110
The only parameter is var_1C
, which we know contains the scrambled sequence of numbers 0 to 7 that sub_401000
generated: sub_401110(mapping)
sub_401110
The subroutine is a little hard to read, because it uses a few local variables to shuffle characters. Here’s the disassembly:
.text:00401110 sub_401110 proc near ; CODE XREF: sub_4011E0+5Ep
.text:00401110 ; sub_4011E0+71p
.text:00401110
.text:00401110 var_1C = dword ptr -1Ch
.text:00401110 var_18 = word ptr -18h
.text:00401110 var_16 = byte ptr -16h
.text:00401110 var_14 = dword ptr -14h
.text:00401110 var_10 = dword ptr -10h
.text:00401110 var_C = dword ptr -0Ch
.text:00401110 var_8 = dword ptr -8
.text:00401110 var_4 = dword ptr -4
.text:00401110 arg_0 = dword ptr 8
.text:00401110
.text:00401110 push ebp
.text:00401111 mov ebp, esp
.text:00401113 mov ecx, 7
.text:00401118 sub esp, 1Ch
.text:0040111B cmp [ebp+arg_0], ecx
.text:0040111E jle loc_4011CB
.text:00401124 mov edx, [eax+8]
.text:00401127 lea edx, [ebp+edx+var_1C]
.text:0040112B mov [ebp+var_4], edx
.text:0040112E mov edx, [eax+0Ch]
.text:00401131 lea edx, [ebp+edx+var_1C]
.text:00401135 mov [ebp+var_8], edx
.text:00401138 mov edx, [eax+10h]
.text:0040113B lea edx, [ebp+edx+var_1C]
.text:0040113F push ebx
.text:00401140 push esi
.text:00401141 mov esi, [eax]
.text:00401143 mov [ebp+var_C], edx
.text:00401146 mov edx, [eax+14h]
.text:00401149 push edi
.text:0040114A mov edi, [eax+4]
.text:0040114D mov eax, [eax+18h]
.text:00401150 lea edx, [ebp+edx+var_1C]
.text:00401154 mov [ebp+var_10], edx
.text:00401157 lea edx, [ebp+eax+var_1C]
.text:0040115B mov eax, offset unk_403142
.text:00401160 lea esi, [ebp+esi+var_1C]
.text:00401164 lea edi, [ebp+edi+var_1C]
.text:00401168 mov [ebp+var_14], edx
.text:0040116B sub ecx, eax
.text:0040116D lea ecx, [ecx+0]
.text:00401170
.text:00401170 loc_401170: ; CODE XREF: sub_401110+B6j
.text:00401170 movzx edx, byte ptr [eax-2]
.text:00401174 mov ebx, [ebp+var_4]
.text:00401177 mov [esi], dl
.text:00401179 movzx edx, byte ptr [eax-1]
.text:0040117D mov [edi], dl
.text:0040117F movzx edx, byte ptr [eax]
.text:00401182 mov [ebx], dl
.text:00401184 movzx edx, byte ptr [eax+1]
.text:00401188 mov ebx, [ebp+var_8]
.text:0040118B mov [ebx], dl
.text:0040118D movzx edx, byte ptr [eax+2]
.text:00401191 mov ebx, [ebp+var_C]
.text:00401194 mov [ebx], dl
.text:00401196 movzx edx, byte ptr [eax+3]
.text:0040119A mov ebx, [ebp+var_10]
.text:0040119D mov [ebx], dl
.text:0040119F movzx edx, byte ptr [eax+4]
.text:004011A3 mov ebx, [ebp+var_14]
.text:004011A6 mov [ebx], dl
.text:004011A8 mov edx, [ebp+var_1C]
.text:004011AB mov [eax-2], edx
.text:004011AE mov dx, [ebp+var_18]
.text:004011B2 mov [eax+2], dx
.text:004011B6 movzx edx, [ebp+var_16]
.text:004011BA mov [eax+4], dl
.text:004011BD add eax, 7
.text:004011C0 lea edx, [ecx+eax]
.text:004011C3 cmp edx, [ebp+arg_0]
.text:004011C6 jl short loc_401170
.text:004011C8 pop edi
.text:004011C9 pop esi
.text:004011CA pop ebx
.text:004011CB
.text:004011CB loc_4011CB: ; CODE XREF: sub_401110+Ej
.text:004011CB mov esp, ebp
.text:004011CD pop ebp
.text:004011CE retn 4
.text:004011CE sub_401110 endp
All the code does is shuffle the characters in the string byte_403140
based on the mapping that sub_401000
generated:
FUNCTION sub_401110(mapping)
i = 0
message = byte_403140
WHILE i+7 < strlen(mapping) DO
tmp[7]
FOR j=0 TO 6 DO:
tmp[j] = message[i + mapping[j]]
ENDFOR
FOR j=0 TO 6 DO:
message[j] = tmp[j]
ENDFOR
END
END
The subroutine applies a permutation to the message in byte_403140
. It permutates blocks of 7 characters based on the mapping that the subroutine sub_401000
generated. Let’s say the mapping is {3, 1, 0, 2, 5, 6, 4}, then the characters inside a 7 letter group would change like this:
sub_4011E0 - Part 3
Back at caller we see yet another subroutine:
.text:00401243 mov ecx, edi
.text:00401245 lea eax, [ebp+var_1C]
.text:00401248 call sub_401090
The subroutine operates on [ebp+var_1C]
which we know contains the mapping
, and on ecx = edi
which holds the text from the serial input box:
sub_401090(mapping, serial)
sub_401090
This is sub_401090
:
.text:00401090 ; =============== S U B R O U T I N E =======================================
.text:00401090
.text:00401090
.text:00401090 sub_401090 proc near ; CODE XREF: sub_4011E0+68p
.text:00401090 movsx edx, byte ptr [ecx]
.text:00401093 add edx, 0FFFFFFD0h
.text:00401096 push esi
.text:00401097 xor esi, esi
.text:00401099 mov [eax], edx
.text:0040109B cmp edx, 7
.text:0040109E jb short loc_4010A2
.text:004010A0 mov [eax], esi
.text:004010A2
.text:004010A2 loc_4010A2: ; CODE XREF: sub_401090+Ej
.text:004010A2 movsx edx, byte ptr [ecx+1]
.text:004010A6 add edx, 0FFFFFFD0h
.text:004010A9 mov [eax+4], edx
.text:004010AC cmp edx, 7
.text:004010AF jb short loc_4010B4
.text:004010B1 mov [eax+4], esi
.text:004010B4
.text:004010B4 loc_4010B4: ; CODE XREF: sub_401090+1Fj
.text:004010B4 movsx edx, byte ptr [ecx+2]
.text:004010B8 add edx, 0FFFFFFD0h
.text:004010BB mov [eax+8], edx
.text:004010BE cmp edx, 7
.text:004010C1 jb short loc_4010C6
.text:004010C3 mov [eax+8], esi
.text:004010C6
.text:004010C6 loc_4010C6: ; CODE XREF: sub_401090+31j
.text:004010C6 movsx edx, byte ptr [ecx+3]
.text:004010CA add edx, 0FFFFFFD0h
.text:004010CD mov [eax+0Ch], edx
.text:004010D0 cmp edx, 7
.text:004010D3 jb short loc_4010D8
.text:004010D5 mov [eax+0Ch], esi
.text:004010D8
.text:004010D8 loc_4010D8: ; CODE XREF: sub_401090+43j
.text:004010D8 movsx edx, byte ptr [ecx+4]
.text:004010DC add edx, 0FFFFFFD0h
.text:004010DF mov [eax+10h], edx
.text:004010E2 cmp edx, 7
.text:004010E5 jb short loc_4010EA
.text:004010E7 mov [eax+10h], esi
.text:004010EA
.text:004010EA loc_4010EA: ; CODE XREF: sub_401090+55j
.text:004010EA movsx edx, byte ptr [ecx+5]
.text:004010EE add edx, 0FFFFFFD0h
.text:004010F1 mov [eax+14h], edx
.text:004010F4 cmp edx, 7
.text:004010F7 jb short loc_4010FC
.text:004010F9 mov [eax+14h], esi
.text:004010FC
.text:004010FC loc_4010FC: ; CODE XREF: sub_401090+67j
.text:004010FC movsx ecx, byte ptr [ecx+6]
.text:00401100 add ecx, 0FFFFFFD0h
.text:00401103 mov [eax+18h], ecx
.text:00401106 cmp ecx, 7
.text:00401109 jb short loc_40110E
.text:0040110B mov [eax+18h], esi
.text:0040110E
.text:0040110E loc_40110E: ; CODE XREF: sub_401090+79j
.text:0040110E pop esi
.text:0040110F retn
.text:0040110F sub_401090 endp
The code is quite long because the assembler did loop unwinding. The underlying algorithm is very simple though:
FUNCTION sub_401090(mapping, serial)
FOR i = 0 TO 6 DO
nr = serial[i] - '0'
IF nr >= 7 THEN
mapping[i] = 0
ELSE
mapping[i] = nr
ENDIF
ENDFOR
END
So the serial number (7 letters) is converted to 7 integers that are stored in mapping
(as long as numbers are smaller than 7):
FUNCTION sub_401090(mapping, serial)
FOR i = 0 TO 6 DO
nr = serial[i] - '0'
IF nr >= 7 THEN
mapping[i] = 0
ELSE
mapping[i] = nr
ENDIF
ENDFOR
END
sub_4011E0 - Part 4
After sub_401090
loaded the serial into the mapping we find a second call to the permutation routine sub_401110
.text:0040124D push esi
.text:0040124E lea eax, [ebp+var_1C]
.text:00401251 call sub_401110
The routine finishes up with some code that calculates a hash of byte_403140
. If the hash matches 0B45D7873h
we get the success message:
.text:00401256 or eax, 0FFFFFFFFh
.text:00401259 mov ecx, esi
.text:0040125B mov edx, offset byte_403140
.text:00401260 test esi, esi
.text:00401262 jz short loc_40127D
.text:00401264
.text:00401264 loc_401264: ; CODE XREF: sub_4011E0+9Bj
.text:00401264 movzx esi, byte ptr [edx]
.text:00401267 xor esi, eax
.text:00401269 and esi, 0FFh
.text:0040126F shr eax, 8
.text:00401272 xor eax, ds:dword_402058[esi*4]
.text:00401279 inc edx
.text:0040127A dec ecx
.text:0040127B jnz short loc_401264
.text:0040127D
.text:0040127D loc_40127D: ; CODE XREF: sub_4011E0+82j
.text:0040127D not eax
.text:0040127F pop esi
.text:00401280 cmp eax, 0B45D7873h
.text:00401285 jnz short loc_401296
.text:00401287 mov eax, [ebp+arg_4]
.text:0040128A mov dword ptr [eax], offset byte_403140
.text:00401290 mov al, 1
.text:00401292 mov esp, ebp
.text:00401294 pop ebp
.text:00401295 retn
.text:00401296 ; ---------------------------------------------------------------------------
.text:00401296
.text:00401296 loc_401296: ; CODE XREF: sub_4011E0+Aj
.text:00401296 ; sub_4011E0+1Aj ...
.text:00401296 xor al, al
.text:00401298 mov esp, ebp
.text:0040129A pop ebp
.text:0040129B retn
.text:0040129B sub_4011E0 endp
In pseudo code:
some_hash = some_hash_routine(message)
IF some_hash = '0B45D7873h' THEN
RETURN 1 // success
ELSE
RETURN 0 // failure
ENDIF
Pseudo-Code
To summarize, here’s the cleaned up pseudo code:
FUNCTION name_mapping(name)
mapping = {0,1,2,3,4,5,6} // in eax = &var_1C
FOR character IN name DO
IF character % 2 != 0 DO
swap(mapping[0], mapping[1])
ENDIF
circular_left_shift(mapping)
ENDFOR
RETURN mapping
END
FUNCTION serial_mapping(serial)
message[7]
FOR i = 0 TO 6 DO
nr = serial[i] - '0'
IF nr >= 7 THEN
mapping[i] = 0
ELSE
mapping[i] = nr
ENDIF
ENDFOR
RETURN message
END
FUNCTION permutation(message, mapping)
i = 0
message = byte_403140
WHILE i+7 < strlen(mapping) DO
tmp[7]
FOR j=0 TO 6 DO:
tmp[j] = message[i + mapping[j]]
ENDFOR
FOR j=0 TO 6 DO:
message[j] = tmp[j]
ENDFOR
END
RETURN message
END
FUNCTION CHECK_SERIAL(serial, name)
IF strlen(serial) != 7 THEN
RETURN 0
END
mapping = name_mapping(name)
STRCPY(message, byte_403010) // hard coded message
IF message == NULL THEN
GOTO loc_40123A \\ should never happen
ENDIF
message = permutation(message, mapping)
mapping = serial_mapping(serial)
message = permutation(message, mapping)
some_hash = some_hash_routine(message)
IF some_hash = '0B45D7873h' THEN
RETURN 1 // success
ELSE
RETURN 0 // failure
ENDIF
END
CHECK_SERIAL(serial, name)
Cracking the Code
Now that we know how the algorithm works, we need to first figure out which permutation would produce the correct plaintext message. The encrypted message in byte_403010
is:
prncyI In cryp haorptg e ,apy iamttru onbxo b Po -(r ix so)o t ehami fbdofu- hftss i gulnpod t e tr ueemnrr tao bep s sorat cisbSs -osn xs ioer,us ptnntii eauf if gdh inw soatl r ienssoi npg.
To crack the code, it is enough to find the permutation for one 7 letter block. The first 7 letters of the ciphertext are:
prncyI
(there’s a space at the end). The capital letter I
comes first, the rest isn’t too hard to guess either:
In cryp
So the correct decryption mapping is: 6 4 1 3 5 0 2. (the first letter goes to the 6th position, the second letter to the 4th, etc.).
Keygen
The SomeCrypt~02
applies two permutations to the ciphertext. The first is based on the name, the second is given by the serial. To write a keygen for a given name we need to calculate the resulting mapping by running the name_mapping
routine. Then we can determine which second mapping, when combined with the name mapping, results in the correct mapping 6 4 1 3 5 0 2:
import argparse
from collections import deque
parser = argparse.ArgumentParser(description="SomeCrypto~02 keygen")
parser.add_argument('name')
args = parser.parse_args()
name = args.name
correct_key = [6, 4, 1, 3, 5, 0, 2]
cypher = deque(list(range(7)))
for c in name:
if ord(c) % 2:
cypher[0], cypher[1] = cypher[1], cypher[0]
cypher.rotate(-1)
serial = 7*[None]
for c, k in zip(cypher, correct_key):
serial[c] = k
print('serial: ' + ''.join(str(s) for s in serial))
Testing:
$ python keygen.py San01suke
serial: 2504613