ctfs.me – resqua
In this article I will explain how to unpack a packed/mangled binary and how to make a keygen. Using the ctfs.me resqua challenge as a example.
The binary I’ll be working with is not stripped, so the the symbol table is still intact. But there is an other methode used to make reverse engineering a little more difficult.
They have mangled some parts of the code, if we try to disassemble this we get a bunch of instructions that doesn’t make much sense.
When a program gets executed it’s first gets loaded into memory.
After this happens the executable sectors of the programs get a write protected flag set so the instructions can’t be altered.
In this case however some of the programs executable sectors are mangled.
and before it can get executed has to be de-mangeld first.
For this to happen the write flag has to be set on the memory, so instructions can be altered/ or un-mangled.
So first I will have to find out where this happens and what memory sectors are effected.
Below is a disassembly of the “unlock” function that un-mangles the code in memory.
unlock: 00000000004007dd 55 push rbp ; Begin of unwind block (FDE at 0x400cfc), CODE XREF=main+20 00000000004007de 4889E5 mov rbp, rsp 00000000004007e1 4883EC40 sub rsp, 0x40 00000000004007e5 B809004000 mov eax, 0x400009 00000000004007ea 8B00 mov eax, dword [rax] 00000000004007ec 8945C8 mov dword [rbp+var_38], eax 00000000004007ef B80D004000 mov eax, 0x40000d 00000000004007f4 0FB700 movzx eax, word [rax] 00000000004007f7 98 cwde 00000000004007f8 8945CC mov dword [rbp+var_34], eax 00000000004007fb C745D003000000 mov dword [rbp+var_30], 0x3 0000000000400802 48C745D801004000 mov qword [rbp+var_28], 0x400001 000000000040080a 8B45C8 mov eax, dword [rbp+var_38] 000000000040080d 4898 cdqe 000000000040080f 480500004000 add rax, 0x400000 0000000000400815 488945E0 mov qword [rbp+var_20], rax 0000000000400819 8B45C8 mov eax, dword [rbp+var_38] 000000000040081c 4863D0 movsxd rdx, eax 000000000040081f 8B45CC mov eax, dword [rbp+var_34] 0000000000400822 4898 cdqe 0000000000400824 4801D0 add rax, rdx 0000000000400827 480500004000 add rax, 0x400000 000000000040082d 488945E8 mov qword [rbp+var_18], rax 0000000000400831 BF1E000000 mov edi, 0x1e ; argument "__name" for method j_sysconf 0000000000400836 E885FEFFFF call j_sysconf ; sysconf 000000000040083b 488945F0 mov qword [rbp+var_10], rax 000000000040083f 488B45F0 mov rax, qword [rbp+var_10] 0000000000400843 48F7D8 neg rax 0000000000400846 4889C2 mov rdx, rax 0000000000400849 488B45E0 mov rax, qword [rbp+var_20] 000000000040084d 4821D0 and rax, rdx 0000000000400850 488945F8 mov qword [rbp+var_8], rax 0000000000400854 488B45E8 mov rax, qword [rbp+var_18] 0000000000400858 89C2 mov edx, eax 000000000040085a 488B45F8 mov rax, qword [rbp+var_8] 000000000040085e 29C2 sub edx, eax 0000000000400860 89D0 mov eax, edx 0000000000400862 8945D4 mov dword [rbp+var_2C], eax 0000000000400865 8B45D4 mov eax, dword [rbp+var_2C] 0000000000400868 4863C8 movsxd rcx, eax 000000000040086b 488B45F8 mov rax, qword [rbp+var_8] 000000000040086f BA07000000 mov edx, 0x7 ; argument "__prot" for method j_mprotect 0000000000400874 4889CE mov rsi, rcx ; argument "__len" for method j_mprotect 0000000000400877 4889C7 mov rdi, rax ; argument "__addr" for method j_mprotect 000000000040087a E831FEFFFF call j_mprotect ; mprotect 000000000040087f C745C400000000 mov dword [rbp+var_3C], 0x0 0000000000400886 EB3E jmp loc_4008c6 loc_400888: 0000000000400888 8B45C4 mov eax, dword [rbp+var_3C] ; CODE XREF=unlock+239 000000000040088b 4863D0 movsxd rdx, eax 000000000040088e 488B45E0 mov rax, qword [rbp+var_20] 0000000000400892 488D0C02 lea rcx, qword [rdx+rax] 0000000000400896 8B45C4 mov eax, dword [rbp+var_3C] 0000000000400899 4863D0 movsxd rdx, eax 000000000040089c 488B45E0 mov rax, qword [rbp+var_20] 00000000004008a0 4801D0 add rax, rdx 00000000004008a3 0FB600 movzx eax, byte [rax] 00000000004008a6 89C6 mov esi, eax 00000000004008a8 8B45C4 mov eax, dword [rbp+var_3C] 00000000004008ab 99 cdq 00000000004008ac F77DD0 idiv dword [rbp+var_30] 00000000004008af 89D0 mov eax, edx 00000000004008b1 4863D0 movsxd rdx, eax 00000000004008b4 488B45D8 mov rax, qword [rbp+var_28] 00000000004008b8 4801D0 add rax, rdx 00000000004008bb 0FB600 movzx eax, byte [rax] 00000000004008be 31F0 xor eax, esi 00000000004008c0 8801 mov byte [rcx], al 00000000004008c2 8345C401 add dword [rbp+var_3C], 0x1 loc_4008c6: 00000000004008c6 8B45C4 mov eax, dword [rbp+var_3C] ; CODE XREF=unlock+169 00000000004008c9 3B45CC cmp eax, dword [rbp+var_34] 00000000004008cc 7CBA jl loc_400888 00000000004008ce 8B45D4 mov eax, dword [rbp+var_2C] 00000000004008d1 4863C8 movsxd rcx, eax 00000000004008d4 488B45F8 mov rax, qword [rbp+var_8] 00000000004008d8 BA05000000 mov edx, 0x5 ; argument "__prot" for method j_mprotect 00000000004008dd 4889CE mov rsi, rcx ; argument "__len" for method j_mprotect 00000000004008e0 4889C7 mov rdi, rax ; argument "__addr" for method j_mprotect 00000000004008e3 E8C8FDFFFF call j_mprotect ; mprotect 00000000004008e8 C9 leave 00000000004008e9 C3 ret
I could dive into this code to see how it is changing what data, but that would be a waste of time since I only need the result of wat its doing and I could just run it and dump it from memory.
Dumping
In the disassembly of the “unlock” function you can see two calls to mprotect.
I set 2 breakpoints:
one right before the first call to mprotect.
and one right after the second call to mprotect.
after hitting the first breakpoint I check check the rax and rcx registers to see what arguments are passed to mprotect.
rax 0x0000000000400000
rcx 0x0000000000000ba2
rax holds the start address of the affected memory and the second and rcx holds the length.
So I dump the memory in that range.
>>> dump binary memory before.bin 0x400000 0x400ba2
Now i continue to the second breakpoint and i do the same.
>>> dump binary memory after.bin 0x400000 0x400ba2
I use bindiff to check if the dumpfiles indeed differ.
$ diff before.bin after.bin
Binary files before.bin and after.bin differ
Patching
To analyze the code it is help full to be able to run it in a debugger.
to do this with the dumped code, I will now make a patched executable.
The dump starts at offset 0x400000 and since this is the beginning of the file I can just append the data from the original file starting at the offset 0xba2 to the dumpfile.
$ cp after.bin patched
$ dd if=resqua bs=1 skip=2978 of=patched oflag=append conv=notruncd
The mangled part of code is now un-mangled in the binary. So the call to “unlock” has to be overwritten by NOP’s. I do this hopper since I’m going to use it for disassembly anyway.
main: 00000000004008ea 55 push rbp ; End of unwind block (FDE at 0x400cfc), Begin of unwind block (FDE at 0x400d5c), DATA XREF=_start+29 00000000004008eb 4889E5 mov rbp, rsp 00000000004008ee 4883EC10 sub rsp, 0x10 00000000004008f2 897DFC mov dword [rbp+var_4], edi 00000000004008f5 488975F0 mov qword [rbp+var_10], rsi 00000000004008f9 B800000000 mov eax, 0x0 00000000004008fe E8DAFEFFFF call unlock ; unlock 0000000000400903 B800000000 mov eax, 0x0 0000000000400908 E8C1000000 call run ; run 000000000040090d B800000000 mov eax, 0x0 0000000000400912 C9 leave 0000000000400913 C3 ret
main: 00000000004008ea 55 push rbp ; End of unwind block (FDE at 0x400cfc), Begin of unwind block (FDE at 0x400d5c), DATA XREF=_start+29 00000000004008eb 4889E5 mov rbp, rsp 00000000004008ee 4883EC10 sub rsp, 0x10 00000000004008f2 897DFC mov dword [rbp+var_4], edi 00000000004008f5 488975F0 mov qword [rbp+var_10], rsi 00000000004008f9 B800000000 mov eax, 0x0 00000000004008fe 0F1F440000 nop dword [rax+rax] ; unlock 0000000000400903 B800000000 mov eax, 0x0 0000000000400908 E8C1000000 call run ; run 000000000040090d B800000000 mov eax, 0x0 0000000000400912 C9 leave 0000000000400913 C3 ret
analyze the code
lets take a look at the “run” function at 0x4009ce that was mangled before.
00000000004009ce 55 push rbp ; End of unwind block (FDE at 0x400d1c), Begin of unwind block (FDE at 0x400d3c), CODE XREF=main+30 00000000004009cf 4889E5 mov rbp, rsp 00000000004009d2 4883C480 add rsp, 0xffffffffffffff80 00000000004009d6 64488B042528000000 mov rax, qword [fs:0x28] 00000000004009df 488945F8 mov qword [rbp+var_8], rax 00000000004009e3 31C0 xor eax, eax 00000000004009e5 BFB80B4000 mov edi, aEnterAValidSer ; argument "__format" for method j_printf, "Enter a valid serial number: " 00000000004009ea B800000000 mov eax, 0x0 00000000004009ef E87CFCFFFF call j_printf ; printf 00000000004009f4 488D45E0 lea rax, qword [rbp+var_20] 00000000004009f8 4889C7 mov rdi, rax ; argument "__str" for method j_gets 00000000004009fb E8A0FCFFFF call j_gets ; gets 0000000000400a00 488D45E0 lea rax, qword [rbp+var_20]
the first thing that gets checked after getting user input is the length of the input string. It hast to be 19 charcters long otherwise it wil jump to 0x400a2a and print out “Wrong format!”
0000000000400a00 488D45E0 lea rax, qword [rbp+var_20] 0000000000400a04 4889C7 mov rdi, rax ; argument "__s" for method j_strlen 0000000000400a07 E844FCFFFF call j_strlen ; strlen 0000000000400a0c 4883F813 cmp rax, 0x13 0000000000400a10 7518 jne Wrong input 0000000000400a12 0FB645E4 movzx eax, byte [rbp+var_1C] 0000000000400a16 3C2D cmp al, 0x2d 0000000000400a18 7510 jne Wrong input 0000000000400a1a 0FB645E9 movzx eax, byte [rbp+var_17] 0000000000400a1e 3C2D cmp al, 0x2d 0000000000400a20 7508 jne Wrong input 0000000000400a22 0FB645EE movzx eax, byte [rbp+var_12] 0000000000400a26 3C2D cmp al, 0x2d 0000000000400a28 740F je loc_400a39 Wrong input: 0000000000400a2a BFD60B4000 mov edi, 0x400bd6 ; argument "__s" for method j_puts, CODE XREF=run+66, run+74, run+82 0000000000400a2f E80CFCFFFF call j_puts ; puts 0000000000400a34 E953010000 jmp loc_400b8c
Then it will check if the 5th, 10th and 15th byte equals to 0x2d which has the ASCII value of “-” otherwise it will jump to 0x400a0c
Now we know that the serial code has to have the following format.
XXXX-XXXX-XXXX-XXXX
Lets check what happens next.
loc_400a39: 0000000000400a39 C7458C00000000 mov dword [rbp+var_74], 0x0 ; CODE XREF=run+90 0000000000400a40 EB26 jmp loc_400a68 loc_400a42: 0000000000400a42 8B458C mov eax, dword [rbp+var_74] ; CODE XREF=run+158 0000000000400a45 4898 cdqe 0000000000400a47 0FB64405E0 movzx eax, byte [rbp+rax+var_20] 0000000000400a4c 3C30 cmp al, 0x30 0000000000400a4e 7514 jne loc_400a64 0000000000400a50 BFF00B4000 mov edi, 0x400bf0 ; argument "__s" for method j_puts 0000000000400a55 E8E6FBFFFF call j_puts ; puts 0000000000400a5a BF01000000 mov edi, 0x1 ; argument "__status" for method j_exit 0000000000400a5f E87CFCFFFF call j_exit ; exit ; endp loc_400a64: 0000000000400a64 83458C01 add dword [rbp+var_74], 0x1 ; CODE XREF=run+128 loc_400a68: 0000000000400a68 837D8C13 cmp dword [rbp+var_74], 0x13 ; CODE XREF=run+114 0000000000400a6c 7ED4 jle loc_400a42
Here it will cycle trough the provided serial number and prints “Invalide serial number!” if any of the bytes equals 0x30 (or ASCII “0”)
now we have learned a little more about the requirements of a valid serial code.
0000000000400a6e 488D4DE0 lea rcx, qword [rbp+var_20] 0000000000400a72 488D45A0 lea rax, qword [rbp+var_60] 0000000000400a76 BA04000000 mov edx, 0x4 ; argument "__n" for method j_strncpy 0000000000400a7b 4889CE mov rsi, rcx ; argument "__src" for method j_strncpy 0000000000400a7e 4889C7 mov rdi, rax ; argument "__dest" for method j_strncpy 0000000000400a81 E8AAFBFFFF call j_strncpy ; strncpy 0000000000400a86 C645A400 mov byte [rbp+var_5C], 0x0 0000000000400a8a 488D45E0 lea rax, qword [rbp+var_20] 0000000000400a8e 488D4805 lea rcx, qword [rax+5] 0000000000400a92 488D45B0 lea rax, qword [rbp+var_50] 0000000000400a96 BA04000000 mov edx, 0x4 ; argument "__n" for method j_strncpy 0000000000400a9b 4889CE mov rsi, rcx ; argument "__src" for method j_strncpy 0000000000400a9e 4889C7 mov rdi, rax ; argument "__dest" for method j_strncpy 0000000000400aa1 E88AFBFFFF call j_strncpy ; strncpy 0000000000400aa6 C645B400 mov byte [rbp+var_4C], 0x0 0000000000400aaa 488D45E0 lea rax, qword [rbp+var_20] 0000000000400aae 488D480A lea rcx, qword [rax+0xa] 0000000000400ab2 488D45C0 lea rax, qword [rbp+var_40] 0000000000400ab6 BA04000000 mov edx, 0x4 ; argument "__n" for method j_strncpy 0000000000400abb 4889CE mov rsi, rcx ; argument "__src" for method j_strncpy 0000000000400abe 4889C7 mov rdi, rax ; argument "__dest" for method j_strncpy 0000000000400ac1 E86AFBFFFF call j_strncpy ; strncpy 0000000000400ac6 C645C400 mov byte [rbp+var_3C], 0x0 0000000000400aca 488D45E0 lea rax, qword [rbp+var_20] 0000000000400ace 488D480F lea rcx, qword [rax+0xf] 0000000000400ad2 488D45D0 lea rax, qword [rbp+var_30] 0000000000400ad6 BA04000000 mov edx, 0x4 ; argument "__n" for method j_strncpy 0000000000400adb 4889CE mov rsi, rcx ; argument "__src" for method j_strncpy 0000000000400ade 4889C7 mov rdi, rax ; argument "__dest" for method j_strncpy 0000000000400ae1 E84AFBFFFF call j_strncpy ; strncpy 0000000000400ae6 C645D400 mov byte [rbp+var_2C], 0x0 0000000000400aea 488D45A0 lea rax, qword [rbp+var_60] 0000000000400aee 4889C7 mov rdi, rax ; argument "__nptr" for method j_atoi 0000000000400af1 E8DAFBFFFF call j_atoi ; atoi 0000000000400af6 894590 mov dword [rbp+var_70], eax 0000000000400af9 488D45B0 lea rax, qword [rbp+var_50] 0000000000400afd 4889C7 mov rdi, rax ; argument "__nptr" for method j_atoi 0000000000400b00 E8CBFBFFFF call j_atoi ; atoi 0000000000400b05 894594 mov dword [rbp+var_6C], eax 0000000000400b08 488D45C0 lea rax, qword [rbp+var_40] 0000000000400b0c 4889C7 mov rdi, rax ; argument "__nptr" for method j_atoi 0000000000400b0f E8BCFBFFFF call j_atoi ; atoi 0000000000400b14 894598 mov dword [rbp+var_68], eax 0000000000400b17 488D45D0 lea rax, qword [rbp+var_30] 0000000000400b1b 4889C7 mov rdi, rax ; argument "__nptr" for method j_atoi 0000000000400b1e E8ADFBFFFF call j_atoi ; atoi 0000000000400b23 89459C mov dword [rbp+var_64], eax
Over here the chunks* of the serial number gets copied to their own parts in memory using strncopy(). Shown as var_60, var_50, var_40 and var_30 in hoppers disassembly
*(by chunks I mean the parts before, after and in between the ‘-‘ of the serial number)
changes these chucks of serial into integers using atoi(). and stores the results to other parts of memory. Shown as var_70, var_6c, var_68 and var_64.
0000000000400b26 8B4590 mov eax, dword [rbp+var_70] 0000000000400b29 89C7 mov edi, eax ; argument #1 for method c 0000000000400b2b E862FEFFFF call c ; c 0000000000400b30 85C0 test eax, eax 0000000000400b32 744E je loc_400b82 0000000000400b34 8B4594 mov eax, dword [rbp+var_6C] 0000000000400b37 89C7 mov edi, eax ; argument #1 for method c 0000000000400b39 E854FEFFFF call c ; c 0000000000400b3e 85C0 test eax, eax 0000000000400b40 7440 je loc_400b82 0000000000400b42 8B4598 mov eax, dword [rbp+var_68] 0000000000400b45 89C7 mov edi, eax ; argument #1 for method c 0000000000400b47 E846FEFFFF call c ; c 0000000000400b4c 85C0 test eax, eax 0000000000400b4e 7432 je loc_400b82 0000000000400b50 8B459C mov eax, dword [rbp+var_64] 0000000000400b53 89C7 mov edi, eax ; argument #1 for method c 0000000000400b55 E838FEFFFF call c ; c 0000000000400b5a 85C0 test eax, eax 0000000000400b5c 7424 je loc_400b82 0000000000400b5e 8B4590 mov eax, dword [rbp+var_70] 0000000000400b61 3B4594 cmp eax, dword [rbp+var_6C] 0000000000400b64 7D1C jge loc_400b82 0000000000400b66 8B4594 mov eax, dword [rbp+var_6C] 0000000000400b69 3B4598 cmp eax, dword [rbp+var_68] 0000000000400b6c 7D14 jge loc_400b82 0000000000400b6e 8B4598 mov eax, dword [rbp+var_68] 0000000000400b71 3B459C cmp eax, dword [rbp+var_64] 0000000000400b74 7D0C jge loc_400b82 0000000000400b76 BF100C4000 mov edi, 0x400c10 ; argument "__s" for method j_puts 0000000000400b7b E8C0FAFFFF call j_puts ; puts 0000000000400b80 EB0A jmp loc_400b8c loc_400b82: 0000000000400b82 BFF00B4000 mov edi, 0x400bf0 ; argument "__s" for method j_puts, CODE XREF=run+356, run+370, run+384, run+398, run+406, run+414, run+422 0000000000400b87 E8B4FAFFFF call j_puts ; puts loc_400b8c: 0000000000400b8c 488B45F8 mov rax, qword [rbp+var_8] ; CODE XREF=run+102, run+434 0000000000400b90 644833042528000000 xor rax, qword [fs:0x28] 0000000000400b99 7405 je loc_400ba0 0000000000400b9b E8C0FAFFFF call j___stack_chk_fail ; __stack_chk_fail ; endp loc_400ba0: 0000000000400ba0 C9 leave ; CODE XREF=run+459 0000000000400ba1 C3 ret
One by one the numbers get fed to the function c, if the fuction c returns 0x0 “Invalide serial number!” gets printed.
Also the first chunk cannot be greater than or equal to the second, the second not equal or greater than the third and the third not equal or greater than the fourth or “Invalide serial number!” gets printed
lets have a look at the function named c
c: 0000000000400992 55 push rbp ; Begin of unwind block (FDE at 0x400d1c), End of unwind block (FDE at 0x400dc4), CODE XREF=run+349, run+363, run+377, run+391 0000000000400993 4889E5 mov rbp, rsp 0000000000400996 897DEC mov dword [rbp+var_14], edi 0000000000400999 817DEC56040000 cmp dword [rbp+var_14], 1110 00000000004009a0 7F07 jg loc_4009a9 00000000004009a2 B800000000 mov eax, 0x0 00000000004009a7 EB23 jmp loc_4009cc loc_4009a9: 00000000004009a9 C745FC01000000 mov dword [rbp+var_4], 0x1 ; CODE XREF=c+14 00000000004009b0 EB0A jmp loc_4009bc loc_4009b2: 00000000004009b2 8B45FC mov eax, dword [rbp+var_4] ; CODE XREF=c+46 00000000004009b5 2945EC sub dword [rbp+var_14], eax 00000000004009b8 8345FC02 add dword [rbp+var_4], 0x2 loc_4009bc: 00000000004009bc 837DEC00 cmp dword [rbp+var_14], 0x0 ; CODE XREF=c+30 00000000004009c0 7FF0 jg loc_4009b2 00000000004009c2 837DEC00 cmp dword [rbp+var_14], 0x0 00000000004009c6 0F94C0 sete al 00000000004009c9 0FB6C0 movzx eax, al loc_4009cc: 00000000004009cc 5D pop rbp ; CODE XREF=c+21 00000000004009cd C3 ret
first the number gets checked if its grater than 0x456.
var_4 is set to 0x1
var_4 is substracted from the number
var_4 is incremented by 2
and the number gets checked if its grater than 0x0
and this loops until the number is 0x0 or less than 0x0
if the number is equal to 0x0 the function c will return 1 else it wil return 0
that last part translated to python it’s:
def c(num):
var_4 = 1
while num > 0:
num = num - var_4
var_4 +=2
if num == 0:
return 1
else:
return 0
Now I could just feed this function random 4 digit number bigger than 0x456 and see wich ones will pass this test.
Or we can flip it around and generate a list of numbers
def m():
num = 0
x = 1
while num < 9999:
num += x
x = x+2
if (num > 0x456):
print num
m()
The Keygen
Now its time to bring it al together,
Frist I generate an array containing numbers that will pass the check by the c function excluding all the numbers containing a zero
Then i will pick 4 random numbers from the array making sure that the second is higher than the first, third is higher than the second and the forth is higher then the third.
The array is generated in a ascending order so picking a higher number than the previous number can easily be done by restricting the rang were I randomly select from. Then I print the 4 numbers separated by dashes
!/usr/bin/python
from random import randint
chunks = []
num = 0
x = 1
while num < 9999:
num += x
x = x+2
if (num > 0x456) & (num < 10000) & ('0' not in str(num)):
chunks.append(num)
c1 = randint(0,len(chunks)-4)
c2 = randint(c1+1,len(chunks)-3)
c3 = randint(c2+1,len(chunks)-2)
c4 = randint(c3+1,len(chunks)-1)
print ("%d-%d-%d-%d" % (chunks[c1], chunks[c2], chunks[c3], chunks[c4]))
Now I can just pipe the output of my keygen to ./resqua and check if it works 100% of the time.
$ while true; do python gen.py| tee /dev/pts/2 |./resqua; done;
5184-7396-8649-8836
Enter a valid serial number: Congrats, valid serial number!
5776-6241-7225-8836
Enter a valid serial number: Congrats, valid serial number!
1225-6241-7744-8281
Enter a valid serial number: Congrats, valid serial number!
Enter a valid serial number: Congrats, valid serial number!
2116-5929-8836-9216
Enter a valid serial number: Congrats, valid serial number!
3721-5625-7921-8464
5476-8649-8836-9216
Enter a valid serial number: Congrats, valid serial number!
1936-3481-8836-9216
Enter a valid serial number: Congrats, valid serial number!
1764-5625-8649-8836
Enter a valid serial number: Congrats, valid serial number!
1521-3249-7569-8649
Enter a valid serial number: Congrats, valid serial number!
1225-3364-6889-8649
Enter a valid serial number: Congrats, valid serial number!
Enter a valid serial number: Congrats, valid serial number!
5184-5929-7569-9216
1156-3364-8649-9216
Enter a valid serial number: Congrats, valid serial number!
7744-8649-8836-9216
etc.... etc.... etc... etc.. etc.