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.