$ ./crackme
Password: qwerty
You loose :(
After downloading the binary and looking at the assembly it quickly becomes obvious that static analysis won't work. The "main()" function contains what appears to be garbage code and the ELF header has some unknown flags set. Also, running the binary on anything other than their server causes a segfault, since instructions make no sense.
$ readelf -h crackme
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400610
Start of program headers: 64 (bytes into file)
Start of section headers: 4752 (bytes into file)
Flags: 0x20
.text:0000000000400610 start proc near
.text:0000000000400610 and ebp, [rsi-6E561383h]
.text:0000000000400616 xchg eax, esp
.text:0000000000400617 jp short near ptr _malloc+2
.text:0000000000400617
.text:000000000040061A out 0D5h, al
.text:000000000040061C rol dword ptr [rsi], 1
.text:000000000040061E xlat
.text:000000000040061F mov bl, 0Ah
.text:0000000000400621 lock sbb eax, 0FF98C41Bh
.text:0000000000400627 xchg eax, ebx
.text:0000000000400628 xor ch, fs:[rax-47h]
.text:000000000040062C jrcxz near ptr byte_40066F
.text:000000000040062C
.text:000000000040062E rcpps xmm4, xmm6
.text:0000000000400631 loopne near ptr word_4005FA
.text:0000000000400631
.text:0000000000400633 adc ch, [rbx+30879A93h]
.text:0000000000400639 sub [rdx+8], bl
...
.text:0000000000400610 start proc near
.text:0000000000400610 and ebp, [rsi-6E561383h]
.text:0000000000400616 xchg eax, esp
.text:0000000000400617 jp short near ptr _malloc+2
.text:0000000000400617
.text:000000000040061A out 0D5h, al
.text:000000000040061C rol dword ptr [rsi], 1
.text:000000000040061E xlat
.text:000000000040061F mov bl, 0Ah
.text:0000000000400621 lock sbb eax, 0FF98C41Bh
.text:0000000000400627 xchg eax, ebx
.text:0000000000400628 xor ch, fs:[rax-47h]
.text:000000000040062C jrcxz near ptr byte_40066F
.text:000000000040062C
.text:000000000040062E rcpps xmm4, xmm6
.text:0000000000400631 loopne near ptr word_4005FA
.text:0000000000400631
.text:0000000000400633 adc ch, [rbx+30879A93h]
.text:0000000000400639 sub [rdx+8], bl
$ ./crackme
Segmentation fault (core dumped)
$ gdb crackme
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
Reading symbols from /mnt/hgfs/data/dxp/ctf/2013/ndh/crackme3/crackme...(no debugging symbols found)...done.
gdb$ r
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0x0000001C EBX: 0x00000000 ECX: 0x00000FFF EDX: 0xF7DE9740 o d I t s z a p c
ESI: 0x00300000 EDI: 0xF7FFE2C8 EBP: 0x00000000 ESP: 0xFFFFE530 EIP:Error while running hook_stop:
Value can't be converted to integer.
0x0000000000400610 in ?? ()
gdb$ x/5i $pc
=> 0x400610: and ebp,DWORD PTR [rsi-0x6e561383]
0x400616: xchg esp,eax
0x400617: rex.XB jp 0x4005f2
0x40061a: out 0xd5,al
0x40061c: rol DWORD PTR [rsi],1
gdb$ i r
rax 0x1c 0x1c
rbx 0x0 0x0
rcx 0xfff 0xfff
rdx 0x7ffff7de9740 0x7ffff7de9740
rsi 0x300000 0x300000
rdi 0x7ffff7ffe2c8 0x7ffff7ffe2c8
rbp 0x0 0x0
rsp 0x7fffffffe530 0x7fffffffe530
r8 0x1 0x1
r9 0x4 0x4
r10 0xd 0xd
r11 0x10800 0x10800
r12 0x400610 0x400610
r13 0x7fffffffe530 0x7fffffffe530
r14 0x0 0x0
r15 0x0 0x0
rip 0x400610 0x400610
eflags 0x10202 [ IF RF ]
cs 0x33 0x33
ss 0x2b 0x2b
ds 0x0 0x0
es 0x0 0x0
fs 0x0 0x0
gs 0x0 0x0
Initially, I tried dumping the memory from the binary on their server but without much success as the server was constantly dropping connections and it was nearly impossible to get anything done remotely. Later they removed GDB stating it was causing resource exhaustion problems. Considering that they also supplied a kernel image it would make sense to assume that the execution of certain files has to go through a decryption routine, similar to how iPhone executes encrypted apps. In the iPhone apps case the entire ".text" section is encrypted and the decryption key is within a kernel module. Thus, the idea is to analyze the supplied kernel image and identify where decryption occurs.
Poking around the kernel initially did not yield anything useful. Looking at the normal process execution sequence via the "execve()" call and following the entire procedure from start to finish did not yield anything that may indicate handling encryption. The typical sequence is for the correct system call stub to be called, which then goes into "sys_execve()" and onto "do_execve()" where the bulk of the logic is located. Nothing related to encryption stood out.
There's also functionality for handling various binary formats during process loading as well as the default ELF handling functions. The major one to parse the binary is the "load_elf_binary()" function. The function itself is a bit large, but not too complex and having a copy of the source code allows to go through it quicker and make sense of the structure parsing code.
Eventually, at very end of the function there's a rather peculiar code which first initializes a stack buffer with 35 constant values and then in a loop XORs a previously allocated buffer.
.text:FFFFFFFF8117A39C mov byte ptr [rbp+var_53], 12h
.text:FFFFFFFF8117A3A0 mov byte ptr [rbp+var_53+1], 43h ; 'C'
.text:FFFFFFFF8117A3A4 mov byte ptr [rbp+var_53+2], 34h ; '4'
.text:FFFFFFFF8117A3A8 mov byte ptr [rbp+var_53+3], 65h ; 'e'
.text:FFFFFFFF8117A3AC mov rax, rsi
.text:FFFFFFFF8117A3AF mov byte ptr [rbp+var_53+4], 78h ; 'x'
.text:FFFFFFFF8117A3B3 mov byte ptr [rbp+var_53+5], 0CFh ; '-'
.text:FFFFFFFF8117A3B7 sub rax, rdx
.text:FFFFFFFF8117A3BA mov byte ptr [rbp+var_53+6], 0DCh ; '_'
.text:FFFFFFFF8117A3BE mov byte ptr [rbp+var_53+7], 0CAh ; '-'
.text:FFFFFFFF8117A3C2 mov byte ptr [rbp+var_4B], 98h ; 'ÿ'
.text:FFFFFFFF8117A3C6 mov byte ptr [rbp+var_4B+1], 90h ; 'É'
.text:FFFFFFFF8117A3CA sub rcx, rax
.text:FFFFFFFF8117A3CD mov byte ptr [rbp+var_4B+2], 65h ; 'e'
.text:FFFFFFFF8117A3D1 mov byte ptr [rbp+var_4B+3], 31h ; '1'
.text:FFFFFFFF8117A3D5 xor edx, edx ; cnt = 0
.text:FFFFFFFF8117A3D7 mov byte ptr [rbp+var_4B+4], 21h ; '!'
.text:FFFFFFFF8117A3DB mov byte ptr [rbp+var_4B+5], 56h ; 'V'
.text:FFFFFFFF8117A3DF xor eax, eax
.text:FFFFFFFF8117A3E1 mov byte ptr [rbp+var_4B+6], 83h ; 'â'
.text:FFFFFFFF8117A3E5 mov byte ptr [rbp+var_4B+7], 0FAh ; '·'
.text:FFFFFFFF8117A3E9 mov [rbp+var_43], 0CDh ; '-'
.text:FFFFFFFF8117A3ED mov [rbp+var_42], 30h ; '0'
.text:FFFFFFFF8117A3F1 mov [rbp+var_41], 0FDh ; '²'
.text:FFFFFFFF8117A3F5 mov [rbp+var_40], 12h
.text:FFFFFFFF8117A3F9 mov [rbp+var_3F], 84h ; 'ä'
.text:FFFFFFFF8117A3FD mov [rbp+var_3E], 98h ; 'ÿ'
.text:FFFFFFFF8117A401 mov [rbp+var_3D], 0B7h ; '+'
.text:FFFFFFFF8117A405 mov [rbp+var_3C], 54h ; 'T'
.text:FFFFFFFF8117A409 mov [rbp+var_3B], 0A5h ; 'Ñ'
.text:FFFFFFFF8117A40D mov [rbp+var_3A], 62h ; 'b'
.text:FFFFFFFF8117A411 mov [rbp+var_39], 61h ; 'a'
.text:FFFFFFFF8117A415 mov [rbp+var_38], 0F9h ; '·'
.text:FFFFFFFF8117A419 mov [rbp+var_37], 0E3h ; 'p'
.text:FFFFFFFF8117A41D mov [rbp+var_36], 9
.text:FFFFFFFF8117A421 mov [rbp+var_35], 0C8h ; '+'
.text:FFFFFFFF8117A425 mov [rbp+var_34], 94h ; 'ö'
.text:FFFFFFFF8117A429 mov [rbp+var_33], 12h
.text:FFFFFFFF8117A42D mov [rbp+var_32], 0E6h ; 'µ'
.text:FFFFFFFF8117A431 mov [rbp+var_31], 87h ; 'ç'
Converting this code to Python and applying it to the extracted ".text" section from the binary we get the "executable" code section. Simply replacing the original section with the new one allows us to run the binary offline. However, we still get the same "Password" prompt and have to figure out the correct input to get the key.
The logic of the binary is to read a stream of bytes from STDIN, check for specific characters and either increment or decrement two variables depending on the characters used. Next, the variables are used as offsets into an array of ones and zeros and when the value of "1" is indexed into the process will exit with failure. Thus, the solution is to construct the correct character sequence that results in proper indexing without hitting a "1" and the length of input must be 80 characters.
The array represents a 15x15 matrix which is a maze that has a single solution. In this case it was easier to just print out the maze and apply the character sequences (w, s, a, d) which represent direction (up, down, left, and right) without coding a maze solver. Start from top-left and finish at the bottom-right.
$ pyton maze.py
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
$ ./crackme
Password: ssddsssassdddssssdssaawaasssddddddwwwwwwddwwwwwdwwdddsssssaassssaasssddddwwddsss
You win !
No comments:
Post a Comment