usd hacking challenge 2016 writeup: token 5

This is part 4 in a series of writeups on the 2016 usd AG hacking challenge.


The last token was hidden in a downloadable binary. It was announced on the challenge website as "reversing" and as "harder" than the other challenges. By many people's standards it probably was not really that hard, however, I never had any 1337 r3v3R1ng sk1llz to begin with, and my knowledge of assembly and using gdb is decidedly rusty, so finding this token certainly took me a while.


root@kali:~/usdhc# wget
--2016-03-19 15:55:38--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 34188 (33K)
Saving to: ‘reverseChallenge’

reverseChallenge                            100%[========================================================================================>]  33.39K  --.-KB/s    in 0.02s   

2016-03-19 15:55:38 (1.33 MB/s) - ‘reverseChallenge’ saved [34188/34188]

root@kali:~/usdhc# file reverseChallenge
reverseChallenge: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

A stripped 64-bit ELF executable for Linux. The website said "for Kali-Linux 64 bit", and in fact, the binary did not work correctly on my main Linux Mint installation, which means it thought I was running it inside a debugger when I was not (yet) - more on that later. It wanted a password:

root@kali:~/usdhc# ./reverseChallenge
Please supply a password!
root@kali:~/usdhc# ./reverseChallenge blah
Wrong Password

Strace just showed a bunch of mmap() and mprotect() calls, and running it in gdb was not so encouraging either:

root@kali:~/usdhc# gdb -q
(gdb) file reverseChallenge
Reading symbols from reverseChallenge...(no debugging symbols found)...done.
(gdb) info file
Symbols from "/root/usdhc/reverseChallenge".

Poking around with strings showed, among loads of garbage: $Info: This file is packed with the UPX executable packer $ and several occurrences of USD!:

root@kali:~/usdhc# strings reverseChallenge | grep USD

Unpacking with upx did not work:

root@kali:~/usdhc# upx -d reverseChallenge
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: reverseChallenge: NotPackedException: not packed by UPX

Unpacked 0 files.

After some quick research on that UPX packer it was clear that UPX had been replaced with USD in the packed binary, so I changed those strings back to UPX with hexeditor:

root@kali:~/usdhc# hexeditor reverseChallenge
root@kali:~/usdhc# strings reverseChallenge | grep USD
root@kali:~/usdhc# strings reverseChallenge | grep UPX
$Info: This file is packed with the UPX executable packer $
$Id: UPX 3.91 Copyright (C) 1996-2013 the UPX Team. All Rights Reserved. $
root@kali:~/usdhc# upx -d reverseChallenge
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     38524 <-     34188   88.74%  linux/ElfAMD   reverseChallenge

Unpacked 1 file.


Looking at the unpacked file with strings again, I saw ThisIsTheSuperSecretChallengePass, but even before trying this password for the sake of completeness it was obvious that I was being trolled just from the password choice alone. I would see that string again, though...

The unpacked binary could now be debugged on the asm level:

root@kali:~/usdhc# gdb -q
(gdb) file reverseChallenge
Reading symbols from reverseChallenge...(no debugging symbols found)...done.
(gdb) info file
Symbols from "/root/usdhc/reverseChallenge".
Local exec file:
    `/root/usdhc/reverseChallenge', file type elf64-x86-64.
    Entry point: 0x4006c0
(gdb) b *0x4006c0
Breakpoint 1 at 0x4006c0
(gdb) run blah
Starting program: /root/usdhc/reverseChallenge blah

Breakpoint 1, 0x00000000004006c0 in _start ()
(gdb) x/20i $pc
=> 0x4006c0 <_start>:   xor    ebp,ebp
   0x4006dd <_start+29>:    mov    rdi,0x400dbb
   0x4006e4 <_start+36>:    call   0x400630 <__libc_start_main@plt>
   0x4006e9 <_start+41>:    hlt    
(gdb) del
gdb) b *0x400dbb
Breakpoint 2 at 0x400dbb
(gdb) c

Breakpoint 2, 0x0000000000400dbb in main ()
(gdb) x/50i $pc
=> 0x400dbb <main>: push   rbp
   0x400dbc <main+1>:   mov    rbp,rsp
   0x400dbf <main+4>:   sub    rsp,0x10
   0x400dc3 <main+8>:   mov    DWORD PTR [rbp-0x4],edi
   0x400dc6 <main+11>:  mov    QWORD PTR [rbp-0x10],rsi
   0x400dca <main+15>:  mov    eax,0x0
   0x400dcf <main+20>:  call   0x4009c3 <bHw>

After setting a breakpoint at the beginning of <main> at 0x4006e4, I was finally seeing some code. The first function called from there is <bHw>:

(gdb) x/10i $pc
=> 0x4009c3 <bHw>:      push   rbp
   0x4009c4 <bHw+1>:    mov    rbp,rsp
   0x4009c7 <bHw+4>:    sub    rsp,0x20
   0x4009cb <bHw+8>:    call   0x4006a0 <fork@plt>
   0x4009d0 <bHw+13>:   mov    DWORD PTR [rbp-0x4],eax
   0x4009d3 <bHw+16>:   cmp    DWORD PTR [rbp-0x4],0xffffffff
   0x4009d7 <bHw+20>:   jne    0x4009e3 <bHw+32>

At first, I was puzzled by these fork() calls. Why fork a new process?? Happens in three places:

root@kali:~/usdhc# objdump -d reverseChallenge | grep fork
  4005d0:   e8 db 00 00 00          callq  4006b0 <fork@plt+0x10>
00000000004006a0 <fork@plt>:
  4009cb:   e8 d0 fc ff ff          callq  4006a0 <fork@plt>
  400aaa:   e8 f1 fb ff ff          callq  4006a0 <fork@plt>

The program forks copies of itself, but only some of them actually continue the main control flow, while others die while the parent process waits for that to happen. The debugger must either follow the child process (if it represents the main code path) or stay with the parent process (if the child process is just killed off). Instead of analyzing exactly how the interaction between fork() and waitpid() works, I figured out by trial and error that in the <bHw> function, I needed to follow the child, whereas in <gRb>, I had to stay with the parent. This is achieved in gdb with set follow-fork-mode child and set follow-fork-mode parent.

In <gRB>, something similar was happening:

(gdb) set follow-fork-mode child
(gdb) b *0x400a1a
Breakpoint 6 at 0x400a1a
(gdb) b *0x400bba
Breakpoint 7 at 0x400bba
(gdb) run blah
Starting program: /root/usdhc/reverseChallenge blah
[New process 1837]
[Switching to process 1837]

Breakpoint 6, 0x0000000000400a1a in gRb ()
(gdb) set follow-fork-mode parent
(gdb) c
HiUere. I don't like debuggers.
[Inferior 6 (process 1837) exited with code 01]

I set follow-fork-mode child, created breakpoints at the beginning and end of <gRb>, set follow-fork-mode parent after entering <gRb>, but the program quit with I don't like debuggers.

(gdb) x/30i $pc
=> 0x400acc <gRb+178>:  call   0x400680 <getppid@plt>
   0x400ad1 <gRb+183>:  mov    DWORD PTR [rbp-0xc],eax
   0x400ad4 <gRb+186>:  mov    eax,DWORD PTR [rbp-0xc]
   0x400ad7 <gRb+189>:  mov    ecx,0x0
   0x400adc <gRb+194>:  mov    edx,0x0
   0x400ae1 <gRb+199>:  mov    esi,eax
   0x400ae3 <gRb+201>:  mov    edi,0x10
   0x400ae8 <gRb+206>:  mov    eax,0x0
   0x400aed <gRb+211>:  call   0x400650 <ptrace@plt>
   0x400af2 <gRb+216>:  test   rax,rax
   0x400af5 <gRb+219>:  jne    0x400b4b <gRb+305>
   0x400af7 <gRb+221>:  mov    eax,DWORD PTR [rbp-0xc]
   0x400afa <gRb+224>:  mov    edx,0x0
   0x400aff <gRb+229>:  mov    esi,0x0
   0x400b04 <gRb+234>:  mov    edi,eax
   0x400b06 <gRb+236>:  call   0x400660 <waitpid@plt>
   0x400b0b <gRb+241>:  mov    edx,0x0
   0x400b10 <gRb+246>:  mov    esi,0x0
   0x400b15 <gRb+251>:  mov    edi,0x7
   0x400b1a <gRb+256>:  mov    eax,0x0
   0x400b1f <gRb+261>:  call   0x400650 <ptrace@plt>
   0x400b24 <gRb+266>:  mov    eax,DWORD PTR [rbp-0xc]
   0x400b27 <gRb+269>:  mov    ecx,0x0
   0x400b2c <gRb+274>:  mov    edx,0x0
   0x400b31 <gRb+279>:  mov    esi,eax
   0x400b33 <gRb+281>:  mov    edi,0x11
   0x400b38 <gRb+286>:  mov    eax,0x0
   0x400b3d <gRb+291>:  call   0x400650 <ptrace@plt>
   0x400b42 <gRb+296>:  mov    DWORD PTR [rbp-0x4],0x0
   0x400b49 <gRb+303>:  jmp    0x400b52 <gRb+312>

These calls to ptrace() in <gRb> are apparently an old anti-debugging trick, as explained e.g. here. The forked process (which is not being followed by the debugger) realizes that ptrace() has already been called and relays that information back to the parent process. The most straightforward way around this is to patch the program, i.e. to replace these calls with NOPs:

(gdb) set follow-fork-mode child
(gdb) run blah
Starting program: /root/usdhc/reverseChallenge blah
[New process 1872]
[Switching to process 1872]

Breakpoint 6, 0x0000000000400a1a in gRb ()
(gdb) set *(0x400aed)=0x90909090
(gdb) set *(0x400aed+2)=0x90909090
(gdb) set *(0x400b1f)=0x90909090
(gdb) set *(0x400b1f+2)=0x90909090
(gdb) set *(0x400b3d)=0x90909090
(gdb) set *(0x400b3d+2)=0x90909090
(gdb) set follow-fork-mode parent
(gdb) c

Breakpoint 7, 0x0000000000400bba in gRb ()

Et voilà, I got through <gRb> undisturbed and was still debugging.

The main function

I left breakpoints at the beginning of <bHw> and <gRb>, in case fork follow mode needed to be switched again.

Back in <main>, there is a sequence of checks. The first one makes sure a password was supplied:

(gdb) x/6i $pc
=> 0x400dde <main+35>:  cmp    DWORD PTR [rbp-0x4],0x2
   0x400de2 <main+39>:  je     0x400df8 <main+61>
   0x400de4 <main+41>:  mov    edi,0x407c88
   0x400de9 <main+46>:  call   0x400600 <puts@plt>
   0x400dee <main+51>:  mov    edi,0x1
   0x400df3 <main+56>:  call   0x400690 <exit@plt>
(gdb) j *0x400de4
Continuing at 0x400de4.
Please supply a password!
[Inferior 10 (process 2389) exited with code 01]

Then, the password is fetched from memory and checked for length:

(gdb) x/7i $pc
=> 0x400df8 <main+61>:  mov    rax,QWORD PTR [rbp-0x10]
   0x400dfc <main+65>:  add    rax,0x8
   0x400e00 <main+69>:  mov    rax,QWORD PTR [rax]
   0x400e03 <main+72>:  mov    rdi,rax
   0x400e06 <main+75>:  call   0x400620 <strlen@plt>
   0x400e0b <main+80>:  cmp    rax,0x22
   0x400e0f <main+84>:  je     0x400e1b <main+96>

A closer look:

0x0000000000400e06 in main ()
=> 0x0000000000400e06 <main+75>:    e8 15 f8 ff ff  call   0x400620 <strlen@plt>
(gdb) x/s $rdi
0x7fffffffe662: "blah"

Yep, that's my password. It is expected to have length 0x22, so from now on I would call the program with a password 34 characters long.

(gdb) x/2i $pc
=> 0x400e0b <main+80>:  cmp    rax,0x22
   0x400e0f <main+84>:  je     0x400e1b <main+96>
(gdb) x/s $rdi
0x7fffffffe644: 'A' <repeats 34 times>

So, this check succeeds. The next few instructions fetch something from memory and then compare it to our password. Could this be the solution??

gdb) x/5i $pc-8
   0x400e65 <main+170>: mov    rsi,rax
   0x400e68 <main+173>: mov    edi,0x407ca8
=> 0x400e6d <main+178>: call   0x400640 <strcmp@plt>
   0x400e72 <main+183>: mov    edx,eax
   0x400e74 <main+185>: mov    eax,DWORD PTR [rip+0x207402]        # 0x60827c <a>
   0x400e7a <main+191>: cmp    edx,eax
   0x400e7c <main+193>: jne    0x400e88 <main+205>
(gdb) x/s $rsi
0x7fffffffe644: 'A' <repeats 34 times>
(gdb) x/s $edi
0x407ca8:   "ThisIsTheSuperSecretChallengePass"

Haha, right. Keep on trolling. The program continues if the password does not match this string.

Password munging part one

The next new function, <doM>, consists mainly of a list of values - I noticed that there are 34 consecutive ones - and a loop. The important line is

0x400924 <doM+202>: xor    al,BYTE PTR [rbp-0x5]

The supplied password is XOR'd character by character with the values at rbp-0xf to rbp-0x30. The result is my password XOR'd with a bunch of arbitrary values:

Temporary breakpoint 25, 0x0000000000400934 in doM ()
=> 0x0000000000400934 <doM+218>:    5d  pop    rbp
(gdb) x/1s $rdx-33
0x7fffffffe644: "|\212\005E@AD_>*rb\241\311n3\351$,e\255\350n\255\256\207\354\064+*6\034%e"

This makes it a bit harder to extract the actual password in the next step.

Password munging part two

<tHw>, finally, is where it all happens. I found another list of values and a more complex series of transformations. Clearly, this was where the password was hidden. The exact mode of obfuscation did not matter any more now, though, because I had everything I needed. The password is revealed, character by character, on this line:

Breakpoint 27, 0x0000000000400d32 in tHw ()
=> 0x0000000000400d32 <tHw+375>:    3a 45 f7    cmp    al,BYTE PTR [rbp-0x9]
(gdb) x/10i $pc
=> 0x400d32 <tHw+375>:  cmp    al,BYTE PTR [rbp-0x9]
   0x400d35 <tHw+378>:  jne    0x400d53 <tHw+408>
   0x400d37 <tHw+380>:  cvtss2sd xmm0,DWORD PTR [rbp-0x8]
   0x400d3c <tHw+385>:  movsd  xmm1,QWORD PTR [rip+0x6f8c]        # 0x407cd0
   0x400d44 <tHw+393>:  addsd  xmm0,xmm1
   0x400d48 <tHw+397>:  cvtsd2ss xmm2,xmm0
   0x400d4c <tHw+401>:  movss  DWORD PTR [rbp-0x8],xmm2
   0x400d51 <tHw+406>:  jmp    0x400d5d <tHw+418>
   0x400d53 <tHw+408>:  mov    eax,0x0
   0x400d58 <tHw+413>:  call   0x40094e <wQx>

Looking at the values involved in this comparison:

(gdb) p /x $al
$8 = 0x7c
(gdb) x /x $rbp-0x9
0x7fffffffe257: 0x48

So, the XOR'd first character of the supplied password is expected to be 0x48, which means that XOR'ing 0x48 with the first value from the <doM> list would give me the first character of the actual password. Actually, the <doM> list is processed in reverse, so I needed to use 0x3d:

(gdb) x/5i 0x400869
   0x400869 <doM+15>:   mov    BYTE PTR [rbp-0x30],0x3d
   0x40086d <doM+19>:   mov    BYTE PTR [rbp-0x2f],0xcb
   0x400871 <doM+23>:   mov    BYTE PTR [rbp-0x2e],0x44
   0x400875 <doM+27>:   mov    BYTE PTR [rbp-0x2d],0x4
   0x400879 <doM+31>:   mov    BYTE PTR [rbp-0x2c],0x1

0x48 ^ 0x3d is 0x75... which is the letter u. Hmm, I had an idea where this was going.

The kind of work robots do

I could have done this manually now for every loop iteration, but that seemed tedious, so I wrote a script instead:

import gdb

passwd = ""
xorlist = []

# Replace ptrace() calls with NOPs
def callback_patch_ptrace():
    gdb.execute("set *(0x400aed)=0x90909090")
    gdb.execute("set *(0x400aed+2)=0x90909090")
    gdb.execute("set *(0x400b1f)=0x90909090")
    gdb.execute("set *(0x400b1f+2)=0x90909090")
    gdb.execute("set *(0x400b3d)=0x90909090")
    gdb.execute("set *(0x400b3d+2)=0x90909090")

# Follow child process after fork() when in <bHw>
def callback_follow_child():
    gdb.execute("set follow-fork-mode child")

# Follow parent process after fork() when in <gRb>
def callback_follow_parent():
    gdb.execute("set follow-fork-mode parent")

# Snarf the XOR list in <doM>
def callback_xorlist_append():
    global xorlist

# Apply XOR again to compared chars in <tHw>
def callback_passwd_append():
    global xorlist, passwd
    passwd += chr(int(gdb.parse_and_eval("*(byte *)($rbp-0x9)")) ^ xorlist.pop(0))
    print ("[+]", passwd)
    gdb.execute("set $al=*(byte *)($rbp-0x9)")

# Breakpoint handler from w3pwnz <>
class HitBreakpoint(gdb.Breakpoint):
    def __init__(self, loc, callback):
        super(HitBreakpoint, self).__init__(
            loc, gdb.BP_BREAKPOINT, internal=False
        self.callback = callback
    def stop(self):
        return False

HitBreakpoint("*0x400dbb", callback_patch_ptrace)
HitBreakpoint("*0x4009c3", callback_follow_child)
HitBreakpoint("*0x400a1a", callback_follow_parent)
HitBreakpoint("*0x400924", callback_xorlist_append)
HitBreakpoint("*0x400d32", callback_passwd_append)

It does all the stuff discussed so far. (I found the breakpoint handler here.)

Running it from scratch:

root@kali:~/usdhc# gdb -q
(gdb) file reverseChallenge
Reading symbols from reverseChallenge...(no debugging symbols found)...done.
(gdb) source
Breakpoint 1 at 0x400dbb
Breakpoint 2 at 0x4009c3
Breakpoint 3 at 0x400a1a
Breakpoint 4 at 0x400924
Breakpoint 5 at 0x400d32
Starting program: /root/usdhc/reverseChallenge AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[New process 2659]
[New process 2661]
[New process 2662]
[+] u
[+] us
[+] usd
[+] usd2
[+] usd20
[+] usd201
[+] usd2016
[+] usd2016-
[+] usd2016-H
[+] usd2016-Ha
[+] usd2016-Hac
[+] usd2016-Hack
[+] usd2016-Hacki
[+] usd2016-Hackin
[+] usd2016-Hacking
[+] usd2016-HackingC
[+] usd2016-HackingCh
[+] usd2016-HackingCha
[+] usd2016-HackingChal
[+] usd2016-HackingChall
[+] usd2016-HackingChalle
[+] usd2016-HackingChallen
[+] usd2016-HackingChalleng
[+] usd2016-HackingChallenge
[+] usd2016-HackingChallenge-
[+] usd2016-HackingChallenge-F
[+] usd2016-HackingChallenge-Fo
[+] usd2016-HackingChallenge-For
[+] usd2016-HackingChallenge-Fore
[+] usd2016-HackingChallenge-Foren
[+] usd2016-HackingChallenge-Forens
[+] usd2016-HackingChallenge-Forensi
[+] usd2016-HackingChallenge-Forensik
[+] usd2016-HackingChallenge-Forensik!

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 2662]
0x0000000000400d92 in red_pill ()

I did not even bother with this <red_pill> stuff any more. I think it is another trick to use a SIGSEGV handler to output the token in the end, which will not work in a debugger... or something.


root@kali:~/usdhc# ./reverseChallenge usd2016-HackingChallenge-Forensik!

Comments !