Nuit du Hack 2013 Quals - Crackme3 - 300

We get credentials to a SSH server which has two files in the home directory once logged in. One is the challenge binary and the other is a Linux kernel image, both are 64-bit. Running the binary we get a password prompt and no matter what is entered is incorrect.
$ ./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: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:000000000040062E           rcpps   xmm4, xmm6
.text:0000000000400631           loopne  near ptr word_4005FA
.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.
  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: