keygen for crackme 9 record how I wrote a keygen for crackme 9th question.
crackme 9 UI looks as such
input user name and password, then click on check
.
decompile exe file and check source here is the decompiled code at memory address 004013121
, there is a je
jump, so the brute force way is to change it to jnz
or nop
, then it would be cracked successfully. that’s easy. but the question is we still don’t know how the key got calcualted, ie, how string1 Bon-xx-xx-xx
generated. that’s our target.
username processing see logic as such:
1 2 3 4 5 6 7 8 00401208 /$ 68 F8DC4000 push keygenme.0040DCF8 ; /String = "" 0040120D |. E8 80010000 call <jmp.&kernel32.lstrlenA> ; \lstrlenA 00401212 |. A3 86DC4000 mov dword ptr ds:[0x40DC86],eax 00401217 |. 833D 86DC4000>cmp dword ptr ds:[0x40DC86],0x4 0040121E |. 0F8C 29010000 jl keygenme.0040134D 00401224 |. 833D 86DC4000>cmp dword ptr ds:[0x40DC86],0x32 0040122B |. 0F8F 1C010000 jg keygenme.0040134D
keygenme.0040DCF8
has the username , push it to stack, then call strnlen
to check lenght. pseudo code as such:
1 2 3 4 s = username l = len (s) if (l < 4 ) {}if (l > 32 ) {}
then
1 2 3 4 5 00401231 |. 33C0 xor eax,eax 00401233 |. 33DB xor ebx,ebx 00401235 |. 33C9 xor ecx,ecx ; keygenme.<ModuleEntryPoint> 00401237 |. BF F8DC4000 mov edi,keygenme.0040DCF8 0040123C |. 8B15 86DC4000 mov edx,dword ptr ds:[0x40DC86] ; put username length to edx
1,2,3 clear all registers, then put username to edi , put lenght to edx , ebx is 0 right now continue
1 2 3 4 5 6 00401242 |> /0FB60439 /movzx eax,byte ptr ds:[ecx+edi] ; loop every char of the username 00401246 |. |83E8 19 |sub eax,0x19 ; minus 19 from the char 00401249 |. |2BD8 |sub ebx,eax 0040124B |. |41 |inc ecx ; i++ 0040124C |. |3BCA |cmp ecx,edx ; if i == username.length 0040124E |.^\75 F2 \jnz short keygenme.00401242
pesudo code
1 2 3 4 5 ebx = 0 for c in username: eax = c eax -= 0x19 ebx -= c
then
1 2 3 4 00401250 |. 53 push ebx ; /<%lX> = 0x3E0000 00401251 |. 68 F8DB4000 push keygenme.0040DBF8 ; |%lX 00401256 |. 68 F8E04000 push keygenme.0040E0F8 ; |s = keygenme.0040E0F8 0040125B |. E8 38010000 call <jmp.&user32.wsprintfA> ; \wsprintfA
print out the hex representation of ebx
formated string would be stored in keygenme.0040E0F8 then the stack looks like:
1 2 3 4 5 6 7 +-------------------------+ | 0x0040E0F8 (buffer) | <- esp + 8 +-------------------------+ | 0x0040DBF8 (format) | <- esp + 4 +-------------------------+ | value in ebx | <- esp +-------------------------+
then
1 2 3 4 00401260 |. 83C4 0C add esp,0xC 00401263 |. 33C0 xor eax,eax 00401265 |. 33D2 xor edx,edx ; keygenme.<ModuleEntryPoint> 00401267 |. 33C9 xor ecx,ecx ; keygenme.<ModuleEntryPoint>
since push 3 variables to stack, 3*4 = 12, line 1 expand the stack by 12, then clear all registers.
1 2 3 4 5 6 00401269 |. 03C3 add eax,ebx 0040126B |. 0FAFC3 imul eax,ebx 0040126E |. 03C8 add ecx,eax 00401270 |. 2BD3 sub edx,ebx 00401272 |. 33D0 xor edx,eax 00401274 |. 0FAFD8 imul ebx,eax
code:
1 2 3 4 5 6 7 eax = edx = ecx = 0 eax = ebx eax *= ebx ecx += eax edx -= ebx edx |= eax ebx *= eax
continue
1 2 3 4 00401277 |. 53 push ebx ; /<%lX> = 0x3E0000 00401278 |. 68 F8DB4000 push keygenme.0040DBF8 ; |%lX 0040127D |. 68 F8E14000 push keygenme.0040E1F8 ; |s = keygenme.0040E1F8 00401282 |. E8 11010000 call <jmp.&user32.wsprintfA> ; \wsprintfA
same, print out ebx continue
1 2 3 4 5 6 7 8 9 10 00401287 |. 83C4 0C add esp,0xC 0040128A |. 33C0 xor eax,eax 0040128C |. 33DB xor ebx,ebx 0040128E |. 33D2 xor edx,edx ; keygenme.<ModuleEntryPoint> 00401290 |. 33C9 xor ecx,ecx ; keygenme.<ModuleEntryPoint> 00401292 |. B8 F8E04000 mov eax,keygenme.0040E0F8 00401297 |. 03D8 add ebx,eax 00401299 |. 33CB xor ecx,ebx 0040129B |. 0FAFCB imul ecx,ebx 0040129E |. 2BC8 sub ecx,eax
expand stack by 12, the keep calculating code:
1 2 3 4 5 6 eax = ebx = ecx = edx = 0 eax = s ebx += eax ecx |= ebx ecx *= ebx ecx -= eax
then
1 2 3 4 004012A0 |. 51 push ecx ; /<%lX> = 0x401000 004012A1 |. 68 F8DB4000 push keygenme.0040DBF8 ; |%lX 004012A6 |. 68 F8E24000 push keygenme.0040E2F8 ; |s = keygenme.0040E2F8 004012AB |. E8 E8000000 call <jmp.&user32.wsprintfA> ; \wsprintfA
same, print out hex of ecx and store it in 0040E2F8 continue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 004012B0 |. 83C4 0C add esp,0xC 004012B3 |. 68 FCDB4000 push keygenme.0040DBFC ; /Bon- 004012B8 |. 68 F8DD4000 push keygenme.0040DDF8 ; |s = keygenme.0040DDF8 004012BD |. E8 D6000000 call <jmp.&user32.wsprintfA> ; \wsprintfA 004012C2 |. 83C4 08 add esp,0x8 004012C5 |. 68 F8E04000 push keygenme.0040E0F8 ; /StringToAdd = "" 004012CA |. 68 F8DD4000 push keygenme.0040DDF8 ; |ConcatString = "" 004012CF |. E8 B2000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 004012D4 |. 68 01DC4000 push keygenme.0040DC01 ; /- 004012D9 |. 68 F8DD4000 push keygenme.0040DDF8 ; |ConcatString = "" 004012DE |. E8 A3000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 004012E3 |. 68 F8E14000 push keygenme.0040E1F8 ; /StringToAdd = "" 004012E8 |. 68 F8DD4000 push keygenme.0040DDF8 ; |ConcatString = "" 004012ED |. E8 94000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 004012F2 |. 68 01DC4000 push keygenme.0040DC01 ; /- 004012F7 |. 68 F8DD4000 push keygenme.0040DDF8 ; |ConcatString = "" 004012FC |. E8 85000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 00401301 |. 68 F8E24000 push keygenme.0040E2F8 ; /StringToAdd = "" 00401306 |. 68 F8DD4000 push keygenme.0040DDF8 ; |ConcatString = "" 0040130B |. E8 76000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 00401310 |. B8 F8DD4000 mov eax,keygenme.0040DDF8 00401315 |. BB F8DE4000 mov ebx,keygenme.0040DEF8 0040131A |. 53 push ebx ; /String2 = "" 0040131B |. 50 push eax ; |String1 = "?" 0040131C |. E8 6B000000 call <jmp.&kernel32.lstrcmpA> ; \lstrcmpA 00401321 |. 74 15 je short keygenme.00401338 ; 爆破点!!!
then keep concating , keygenme.0040DDF8 is ConcatString = “”,
1 2 3 4 5 6 7 8 9 10 11 ConcatString = "" ConcatString += `Bon-` ConcatString += s1 ConcatString += '-' ConcatString += s2 ConcatString += '-' ConcatString += s3 if ConcatSting == key_you_typed: you hacked else : you failed
write keygen based on the static analysis of asm code, we can write keygen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <stdio.h> #include <stdlib.h> #include <string.h> int main ( ) { char username[100 ]; printf ("Enter username: " ); scanf ("%99s" , username); printf ("you entered: %s\n" , username); size_t len = strlen (username); if (len < 4 ) { printf ("username is too short" ); return 1 ; } else if (len > 32 ) { printf ("username is too long" ); return 1 ; } int32_t ebx = 0 ; for (size_t i = 0 ; i < len; i++) { int c = username[i]; c -= 0x19 ; ebx -= c; } char buffer1[100 ]; sprintf (buffer1, "%x" , ebx); int32_t eax =0 , ecx = 0 ; eax += ebx; eax *= ebx; ebx *= eax; char buffer2[100 ]; sprintf (buffer2, "%x" , ebx); ebx = ecx = 0 ; eax = 0x40E0F8 ; ebx += eax; ecx ^= ebx; ecx *= ebx; ecx -= eax; char buffer3[100 ]; sprintf (buffer3, "%x" , ecx); char code[100 ] = "Bon-" ; strcat (code, buffer1); strcat (code, "-" ); strcat (code, buffer2); strcat (code, "-" ); strcat (code, buffer3); printf ("keygen code: %s\n" ,code); return 0 ; }
run it
1 2 3 4 ~/github/algorithm-practice/crackme (master ✗) ./crack9 Enter username: testabc you entered: testabc keygen code: Bon-fffffdc9-f5229019-41720f48
works perfect.