Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
r2con 2019 CTF writeups


The r2con CTF is the CTF for the r2con 2019 held during the weekend before the conference which consisted mainly of reversing challenges. I managed to solve all but one challenge (technicaly, at least...) and it was so much fun! I'd like to thank the organizers a lot for making the event happen :-)

There may be errors and inclompete sections. I tried to make a write-up for every challenge, just contact me if anything is unclear or missing!

[100] r2boy1

Time to remember the best games ever!

Would you able to find pancake? He is keeping a secret for you!

We download a gameboy rom. I played around with it in an emulator, then looked at it in r2. There flag is in strings:

> r2 -qqc 'izz~punk4ke'
909 0x000180be 0x000540be  18  19 (rombank06) ascii yay_punk4ke_f0wnd\n

[100] r2baby

Have you attended r2con before? Show me then


We download an ARM aarch64 ELF binary. Havin qemu-aarch64-static installed I can run the binary on my notebook:

> qemu-aarch64-static babyr2 
wr0ng length?

Ok cool. Lets do r2 -A babyr2. No more flags in strings, so lets seek to the main method. Right at the beginning is the following code:

│           0x00400408      a02f00b9       str w0, [arg_2ch]           ; argc                                                                                                                                                         |
│           0x0040040c      a11300f9       str x1, [arg_20h]           ; argv                                                                                                                                                         |
│           0x00400410      a02f40b9       ldr w0, [arg_2ch]           ; [0x2c:4]=-1 ; 44 ; tmp=0xffffffffffffffec ; w0=0xffffffff                                                                                                    |
│           0x00400414      1f100071       cmp w0, 4                   ; zf=0x0 ; nf=0x1 ; cf=0x1 ; vf=0x0                                                                                                                            |
│       ╭─< 0x00400418      c0000054       b.eq 0x400430               ; unlikely                                                                                                                                                     |
│       │   0x0040041c      600200f0       adrp x0, 0x44f000           ; x0=0x44f000 -> 0x97ff1324                                                                                                                                    |
│       │   0x00400420      00600891       add x0, x0, 0x218           ; 0x44f218 ; "wr0ng length?" 

This tells us that there are 3 arguments and my assumption was you will get the flag once you got them all right.

First arg

Right after the check against argc, the first argument is parsed to an integer and then checked against the static value of 0xcafe:

│           0x00400434      00200091       add x0, x0, 8               ; x0=0x7                                                                                                                                                   │           0x00400434      00200091       add x0, x0, 8               ; x0=0x7                                                                                                                                                       |
│           0x00400438      000040f9       ldr x0, [x0]                ; tmp=0x7 ; x0=0xffffffffffffffff                                                                                                                              |
│           0x0040043c      05140094       bl loc._x_36                ;[3] ; lr=0x400440 -> 0xb90037a0 ; pc=0x405450 -> 0xa9bf7bfd sym.atoi                                                                                          |
│           0x00400440      a03700b9       str w0, [arg_34h]                                                                                                                                                                          |
│           0x00400444      a13740b9       ldr w1, [arg_34h]           ; [0x34:4]=-1 ; 52 ; tmp=0x34 ; w1=0xffffffff                                                                                                                  |
│           0x00400448      c05f9952       movz w0, 0xcafe             ; w0=0xcafe                                                                                                                                                    |
│           0x0040044c      3f00006b       cmp w1, w0                  ; zf=0x0 ; nf=0x1 ; cf=0x1 ; vf=0x0                                                                                                                            |
│       ╭─< 0x00400450      c0000054       b.eq 0x400468               ; unlikely    |
│           0x00400438      000040f9       ldr x0, [x0]                ; tmp=0x7 ; x0=0xffffffffffffffff                                                                                                                              |
│           0x0040043c      05140094       bl loc._x_36                ;[3] ; lr=0x400440 -> 0xb90037a0 ; pc=0x405450 -> 0xa9bf7bfd sym.atoi                                                                                          |
│           0x00400440      a03700b9       str w0, [arg_34h]                                                                                                                                                                          |
│           0x00400444      a13740b9       ldr w1, [arg_34h]           ; [0x34:4]=-1 ; 52 ; tmp=0x34 ; w1=0xffffffff                                                                                                                  |
│           0x00400448      c05f9952       movz w0, 0xcafe             ; w0=0xcafe                                                                                                                                                    |
│           0x0040044c      3f00006b       cmp w1, w0                  ; zf=0x0 ; nf=0x1 ; cf=0x1 ; vf=0x0                                                                                                                            |
│       ╭─< 0x00400450      c0000054       b.eq 0x400468               ; unlikely

0xcafe is 51966 in decimal.

Third arg

Second arg looks more complicated, but the third arg is just a static strcmp against radare2c0n.

Second arg

Knowing the first and third arg and being a lazy person, I just decided to brute force the second arg using a simple shell script.

Turns out that the first hit for the second arg is 25:

> for i in {0..30}; do ./babyr2 51966 $i radare2c0n; done
ok,ok! you are not a baby r2 reverser!
The key to be a g00d reverser is: r2con{c0ffee}

[100] Land of Ecodelia

We don’t have much time. Let’s get started.

We download ctf.file which is a statically linked ELF64 binary.


First, by checking the strings we find the following:

[0x0047cb70]> iz~r2con
2191 0x000c3185 0x004c3185 2035 2037 (.rodata)  utf8 [...] r2con2019{all_your_answers_in_order_without_spaces} [...]

Let's just run run:

> ./ctf.file 
> ./ctf.file 
Welcome To The Greatest Adventure Of Your Life
In this game, I will ask you questions and you will have to answer. Some valid answers are y or n, but these are not the only ones. You will need to figure out the rest by yourself :)
Do you understand the rules?
Great. First question: Have you ever cried during sex?
Your poor decisions led you to your death. Sorry.

So the goal is to make it to last question :-) Let's analyze the binary. My workflow is usally to check the strings first and the functions:

:> afl~sym.go.main
0x0048a260   15 503          sym.go.main.encode
0x0048a460    8 68           sym.go.main.check
0x0048a4b0    8 2766         sym.go.main.main
0x0048af80    7 107          sym.go.main.init

After a quick analysis of the main function (sym.go.main.main) you can conclude that inside the main function there is a loop that scans some input, calls the encode function with your input and then check your input.

By having a glance at the disassembly inside encode one particular opcode sticks out:

:> pd1@0x0048a32f
│           0x0048a32f      4131d2         xor r10d, edx

This looks very interesting. Basically, in a crackme, whenever you see an xor instruction xoring 2 different value, you should take note. So let's add breakpoint there.

On to the check function. It's a really simple function that loops through something and compares this something against another something:

:> pd3@0x0048a485
│           0x0048a485      0fb6341a       movzx esi, byte [rdx + rbx]
│           0x0048a489      0fb63c19       movzx edi, byte [rcx + rbx]
│           0x0048a48d      4038fe         cmp sil, dil

Time for another breakpoint @ 0x0048a48d.

Dynamic anylsis

Let's start debugging. Once our breakpoint inside encode is hit, we note that our input (y for the first question) is xored against the first value in a byte array, currently pointed to by the rsi register. We keep that in mind and continue execution (this is probably the key).

As we hit the next breakpoint we see that, indeed, our xored input is compared against some other value. So now we have everything, we can xor the first cipher byte (0x17) against the first key byte (n = 0x6e) to get the correct first answer:

[0x00000000]> ? 0x6e^0x17
string  "y"

Now we can step through manually or use a script to extract the all answers (I was too lazy to write a script but given the length of the flag it would've been worth it I guess):

echo -e "y\nnever\nboth\nuserious\nwhat\nlightthetorch\niguessiopenit\nturntheknob\nWITHAKEYIUSEAKEY" | ./ctf.file
Welcome To The Greatest Adventure Of Your Life
In this game, I will ask you questions and you will have to answer. Some valid answers are y or n, but these are not the only ones. You will need to figure out the rest by yourself :)
Do you understand the rules?
Great. First question: Have you ever cried during sex?
Hmmm. Are you red or purple?
Are you a giraffe or a seagull?
Is the key in the room?
You are standing in a dark room and can’t see anything. There is a torch and a match. What do you do?
The torch fills the dark room with light. You see a door in front of you. What do you do?
How do you open it?
The knob doesn't turn. How do you open it?
Thank you.
The flag is r2con2019{all_your_answers_in_order_without_spaces}

[100] r2xor

An Android developer was playing around with some cryptography mechanisms to hide secrets, but maybe the method is not secure at all.

We download a file re.rada.con.ctf.r2xor.apk which is an android app.

I extraczed it's content using jadx.

The interesting Java source files are to be found inside sources/re/rada/con/ctf/r2xor/:

> l sources/re/rada/con/ctf/r2xor/
.rw-r--r--  cyrill  users  15.6 KB  Sat Aug 31 15:55:29 2019  
.rw-r--r--  cyrill  users  89.4 KB  Sat Aug 31 15:55:28 2019  
.rw-r--r--  cyrill  users     2 KB  Sat Aug 31 15:55:28 2019  

Let's quickly have a look at MainActivity class, which is more or less like a main function for the App.

First thing we see: It implements a callback when something is clicked. This calls some validate method and logs Congratulations, your flag is correct :) if validate returns 1.

The next function jadx failed to decompile. I checked the disassembly; it's some antidebug/antiroot functionality, but nothing directly related to the flag (I think).

So let's proceed with the validate function. Where is it? On the very bottom of it is declared:

public native boolean validate(String str);

But I was unable to find it's implementation in the Java source code. After a quick internet research I learned that native is used to call native function. A native function is a function that is not implemented in Java, so there must be a library containing it's implementation.

Found some promising shared object files:

find . -name *.so

Two libraries for each supported architecture. is smaller and contains anti debugging stuff, so lets focus on

As usual check strings first. One sticks out and is really interesting:

:> iz~super
006 0x00025d35 0x00025d35  11  12 (.rodata) ascii supersecure
:> axt str.supersecure 
(nofunc) 0xd533 [DATA] lea r10, str.supersecure
(nofunc) 0xd5b9 [DATA] lea rax, str.supersecure
sym.decrypt_char_const___char 0xd64f [DATA] lea r13, str.supersecure

Let's seek to this function decrypt_char and analyze it. In a nutshell it loads our supersecret as a key and XORs this key against some other bytes.

│     ╎││   0x0000d674      4132041f       xor al, byte [r15 + rbx]

The pointer to the encrypted flag is an argument to that function, so not really easy to get with static analysis. But just when I was working on it, an easier version of this challenge was release. I downloaded it and checked what has changed. Among the changes I found it interesting that the very beginning of the .rodata section changed (.rodata is where the strings are stored). In the easier challenge file, the .rodata section looks like this:

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF  comment                                                                                                                                             [0x00025cc0]
0x00025cc0  0147 130a 1c08 1d53 072d 5446 2a1e 5545  .G.....S.-TF*.UE  ; section..rodata  ; [14] -r-- section size 11616 named .rodata  ; str.S___TF  ; str.UEre_rada_con_ctf_r2xor_MainActivity                                      |
0x00025cd0  7265 2f72 6164 612f 636f 6e2f 6374 662f  re/rada/con/ctf/ 

That first string is really suspicious ;-) I guessed it was the encrypted flag and I was right.

import sys
key = "supersecure"
cipher=[0x01, 0x47, 0x13, 0x0a, 0x1c, 0x08, 0x1d, 0x53, 0x07, 0x2d, 0x54, 0x46, 0x2a, 0x1e, 0x55, 0x45, 0x2c, 0x50, 0x53, 0x2a, 0x47, 0x56, 0x10, 0x20, 0x45, 0x56, 0xf]
for i in range(0, len(cipher)):

# r2con{x0r_15_n07_50_53cU53}

[100] r2darling

New r2 plugin to remotely reverse binaries with your folks. First of all, you need to login....

We download a binary r2darling which is an ELF64 executable.

It looks like the following if we execute it:

> ./r2darling 
RadareCON Secure collaborative-REshell Login

> Checking Password ... Fail!

So, a classic crackme :-) Let's open r2, seek to the main function and get an overview. The following command shows all calls from inside the main function:

:> pdr@main~call
│ 0x00401d23      e898ffffff     call sym.my_print
│ 0x00401d35      e886ffffff     call sym.my_print
│ 0x00401d49      e8a2360100     call sym.malloc                       ; sym.malloc_hook_ini-0x310
│ 0x00401d5c      e85ffeffff     call sym.my_getpass
│ 0x00401d86      e8c5fdffff     call sym.scramble
│ 0x00401d95      e826ffffff     call sym.my_print
│ 0x00401dba      e801ffffff     call sym.my_print
│ 0x00401dc7      e834aa0300     call sym.sleep                        ; int sleep(int s)
│ 0x00401df3      e878f2ffff     call 0x401070
│ 0x00401e0b      e8b0feffff     call sym.my_print
│ 0x00401e1d      e89efeffff     call sym.my_print
│ 0x00401e34      e887feffff     call sym.my_print

Ok, get_pass followed by scramble look already quite interesting. Lets break there:

:> dcu sym.scramble 
Continue until 0x00401b50 using 1 bpsize
RadareCON Secure collaborative-REshell Login

Password:hit breakpoint at: 401b50

The function does not look complicated and at address 0x00401b9d there's an XOR, and before that at address 0x00401b8c the key byte is loaded. So we can break there and extract it:

:> db 0x00401b88; dc; x@rdi
hit breakpoint at: 401b9d
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x004a5100  1003 2e1f 101a 6e04 1852 5833 5f01 6b21  ......n..RX3_.k!
0x004a5110  0716 535d 0000 0000 0000 0000 0000 0000  ..S]............

The compare function is called at address 0x00401df3. If we break there the encrypted flag is in the rsi register (an argument to the strncmp_avx2) function:

:> x@rsi
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x004a5120  7333 6372 6574 5f70 616e 6b41 6b65 5f53  s3cret_pankAke_S
0x004a5130  3475 6333 2100 0000 0000 0000 0000 0000  4uc3!...........

This is all we need :-)

import sys
key = [0x10, 0x03, 0x2e, 0x1f, 0x10, 0x1a, 0x6e, 0x04, 0x18, 0x52, 0x58, 0x33, 0x5f, 0x01, 0x6b, 0x21, 0x07, 0x16, 0x53, 0x5d, 0x0]
cipher = [0x73, 0x33, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x6e, 0x6b, 0x41, 0x6b, 0x65, 0x5f, 0x53, 0x34, 0x75, 0x63, 0x33, 0x21]
for i in range(0, len(cipher)):

# c0Mmun1ty<3r4d4r3c0n!

[250] r2boy 2

Find Ole into the labyrinth and ask him for the flag.

Insist but don't be rude!

We download a gameboy rom

I did not use r2 to solve this challenge. I started the ROM using the mGBA emulator and had a look at the tileset. The flag is in the Tileset from the first moment you start the game: HIDR3NM4P

I failed to type it correctly into the validator and so did not get the points. But after that I thought this is too easy and started looking into the roms code. Time was wasted xD

[250] vermu 1

Find the secret flag in the upside down.

We download 2 files, vermu_elf which is a ELF64 binary and another file called level1.

level1 is a small file:

> r2 -qqc 'x304' level1 
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000000  1000 0010 0810 0000 0020 1000 0000 0050  ......... .....P
0x00000010  1000 0000 5641 bce6 b80a baad 1bab c5dd  ....VA..........
0x00000020  1214 b2c1 1b74 7fc5 2197 6555 1298 9315  .....t..!.eU....
0x00000030  df80 1529 7c7d 312b 249a 2b29 d567 0d7d  ...)|}1+$.+).g.}
0x00000040  80d8 124e 91be e865 a758 efd8 b252 18de  ...N...e.X...R..
0x00000050  4314 cbed aaff 1000 0000 0110 0000 1004  C...............
0x00000060  2010 0000 1008 1000 0010 0021 1000 0000   ..........!....
0x00000070  0433 3021 1000 0000 1610 0000 1000 2110  .30!..........!.
0x00000080  0000 0004 3330 2138 3110 0000 0036 1000  ....30!81....6..
0x00000090  0010 0021 1000 0000 0433 3021 3210 0000  ...!.....30!2...
0x000000a0  00b4 4210 0000 0000 1000 0010 0420 1000  ..B.......... ..
0x000000b0  0000 c641 1000 0000 0110 0000 1004 2134  ...A..........!4
0x000000c0  1000 0010 0420 1000 0010 0021 1000 0000  ..... .....!....
0x000000d0  0130 1000 0010 0020 1000 0000 0810 0000  .0..... ........
0x000000e0  1000 2132 1000 0000 6145 1000 0010 0421  ..!2....aE.....!
0x000000f0  1000 0000 0132 1000 0001 0d42 1000 0001  .....2.....B....
0x00000100  2410 0000 0008 1000 0000 0150 6010 0000  $..........P`...
0x00000110  011e 1000 0000 0610 0000 0001 5060 7965  ............P`ye
0x00000120  6168 210a 6e6f 7065 203a 280a ffff ffff  ah!.nope :(.....

We can now assume that this is a VM crackme because of the name of the crackme and also the level1 file looks like bytecode followed by some string data.

Reversing an entire VM can be a tedious and very time consuming task, so I always try to avoid it and find some "smart" way to solve the task. To get a first impression and overview I played around a bit, by that I mean I just stepped a bit through it using r2 debugger. It's not really obfuscated or otherwise fucked up task, so I was able to find the find the VMs handler that perform xor and cmp rather quickly.

xor is located here:

     │││    0x55fbbec02876      31c2           xor edx, eax   

cmp is located here:

     ││ │   0x55fbbec02926      39c2           cmp edx, eax    

As usual, breaking at the xor handle we will see the key, and breaking at the cmp handle we will see the encrypted flag. Let's see what we get:

import sys
key = [0x43,0x19,0x47,0xf5,0x45,0x52,0xe4,0x54,0x3a,0x22,0xed,0xeb,0x4d,0x3e,0xe4,0x8b,0x80, 0x3a, 0xde, 0x68, 0x9a, 0xaa, 0xed, 0x67, 0x6c, 0xea, 0x20, 0x7f, 0xea, 0xd6, 0x83, 0x82]
cipher = [0x31, 0x2b, 0x24, 0x9a, 0x2b, 0x29, 0xd5, 0x67, 0x0d, 0x7d, 0x80, 0x8d, 0x12, 0x4e, 0x91, 0xbe, 0xe8, 0x65, 0xa7, 0x58, 0xef, 0xd8, 0xb2, 0x52, 0x18, 0xde, 0x43, 0x14, 0xcb, 0xed, 0xaa, 0xff]
for i in range(0, len(cipher)):
# r2con{137_m3_pu5h_y0ur_5t4ck!;)}

[250] Cerberus

Hack the Gibson through this secret gateway.

Connect here: nc 1337

We download a file cerberus which is a ELF64 binary.

The binary is a server that binds to port 1337. First I connected it and played with it for a while...

> nc localhost 1337
|                        Cerberus Gate Kepper                               |
Available commands:
* cat echo env exit help ls set unset login
[!] A logged user is required
cyrill@0x00 ~/ctf/r2con/cerberus
> nc localhost 1337
|                        Cerberus Gate Kepper                               |
$login help
usage: login <username> <password>

So the goal seems to login somehow and the login command expects a username and a password as arguments. We're going do that later inside a debugger.

Basic understanding of the binary

First I looked at the main function. It's a bit lengthy and has a lot of calls to other functions. None of those function calls look interesting (most of the are just the server setup) except one:

:> pd1@0x00403cdf
│           0x00403cdf      e8ecf6ffff     call 0x4033d0

It sticks out because it's the only call to something that was not imported and r2 did not automatically define that function. When you seek there you'll notice that the greeting banner string is referenced at the very beginning, so looks like this is the servers mainloop.

We define that function because breaking there in dynamic analysis seems like a good starting point.

af mainloop@0x004033d0

At the end of our mainloop function there is another interesting line of code:

:> pd1@0x004031ec
            0x004031ec      ffd0           call rax

We can not easily tell right now where this call will end up, so let's take note of this address for dynamic anylsis.

Debugging the binary

Whenever a user connect to the server it forks, so for convenience you want to connect first and then attach to the process.

So, first terminal:

> ./cerberus

Second terminal:

> nc localhost 1337

Third terminal:

> pgrep cerberus
18972  <-- Server
19021  <-- Client
cyrill@0x00 ~/ctf/r2con/cerberus
> sudo r2 -Ad 19021

This is the base setup for solving this challenge, which actually consists of 4 stages.

Stage 1 (login)

The attached client process should now be stuck inside a syscall (expecting some user input), so in the client we type login AAAAAA AAAAAA <enter> and then start stepping in r2.

We break at our call rax instruction and continue execution:

db 0x004031ec; dc

Now rax points to Address 0x00402600 which is actually the login function. Let's define it:

af login@0x00402600

Now, by studying the strings we can confirm that this is the login function:

:> :> axt str.ACCESS_GRANTED 
login 0x4028ae [DATA] movabs rdi, str.ACCESS_GRANTED

Inside this function r2 identifies 2 calls to strncmp. Because this may be the password check, we break there, and:

:> x32@rdi
- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7fff81daf6e6  4141 4141 4141 4141 0041 4141 4141 4141  AAAAAAAA.AAAAAAA
0x7fff81daf6f6  4100 0002 0000 0000 0000 2300 0000 0000  A.........#.....
:> x32@rsi
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x004082f1  616e 306e 796d 3075 7300 496e 7472 7564  an0nym0us.Intrud
0x00408301  3372 0000 0000 0000 0000 0000 0000 0045  3r.............E

Lets try:

> nc localhost 1337
|                        Cerberus Gate Kepper                               |
$login an0nym0us Intrud3r
 ~> Verifying......ACCESS GRANTED
Welcome Neo...

Stage 2 (su command)

During stepping trough the binary I noticed that there is actually another command su, that does not show up when you type the help command. Inside fcn.00402f90 (called in the mainloop) there is a strcmp at address 0x004031aa that checks your command. If you break there you can see all the other possible commands:

:> x@rsi
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x004081a0  6578 6974 0063 6174 0065 6368 6f00 656e
0x004081b0  7600 6865 6c70 006c 7300 7365 7400 756e
0x004081c0  7365 7400 6c6f 6769 6e00 7375 0025 7300

Let's confirm that it works:

> nc 1337
|                        Cerberus Gate Kepper                               |
$login an0nym0us Intrud3r
 ~> Verifying......ACCESS GRANTED
Welcome Neo...
 ~> Getting privileges......

Stage 3 (ADMIN env var)

If we try to cat flag.txt we still fail with ERROR: permission denied. This step I did not solve by analyzing the binary further but by some lucky guesses.

I noted that if you type env there is the ADMIN variable, but by default it does not look like it's set:


I figured that I have to set this var the value true to gain real admin priviledges before su:

> nc 1337
|                        Cerberus Gate Kepper                               |
$login an0nym0us Intrud3r
 ~> Verifying......ACCESS GRANTED
Welcome Neo...
$set ADMIN true
 ~> Getting privileges......
(admin) ~cat flag.txt
This is not the flag :( but you almost got it!  Remember that not all elements might be shown... 

Stage 4 (hidden file)

The last stage is easy once you realize it ;-) The real flag is not in flag.txt that is shown when you type ls but in a hidden .flag.txt:

(admin) ~cat .flag.txt
Congratulations! Here you have the flag: r2con{the_gu4rd_0f_the_g4t3s_s3rv3_y0u}

[250] r2 License Manager

Register your Radare2 copy!

We download a binary r2licensemgr.exe which is PE32 binary.

I don't have my Windows machine with me at the moment so this is a stripped writeup.

I patched the bianry to get it working at all and in debugger:

radiff2 -D r2licensemgr_patched.exe r2licensemgr.exe
--- 0x000ac542  e97d000000
- jmp 0x82
+++ 0x000ac542  737c488d05
+ jae 0x7e
+ invalid
+ invalid
+ invalid

The strings (well, the important ones) are obfuscated using an XOR with the key aSSf4%23#$!". I decrypted some of them first:

'2023230a5d4653474a4b4f411621145b573552713678800500929355621337890625' # Application Error
'33323707464000136f4d42043d20031468535d42434413' # Radare2 License Manager
'383c261414465d435a044e077301075044405611044812733d0940054056444d521536210350043f392e2e710d3632155105445a504d5541273b0314570013' # not registered
'383c26141477535742564453733009445c125a5004420e21210357515e4a03484802363d15514113' # correctly licensed
'1420361407171c574f4837252902984619140625' # user32.dll
'22690f36464a5541424901273a3f034779405247455304610f14060b59565a' # C:\Program Files\radare2\r2.key
'ba7c47cf1a98bb78b15cf90dba') # kernel.dll
'383c261414465d435a044e077301075044405611044812733d0940054056444d521536210350043f392e2e710d3632155105445a504d5541273b03145700134c424708303a075805415a574101153c73164157515b4257444132730a5d46575d504100010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899') # not registered
'222132155c056056534b53155e596b3e765d5e465049083d3446404a46524f485841263d034c5557505741454139261540055a525354440f363748'# Crash Report
'2023230a5d4653474a4b4f411621145b573552713678800500929355621337890625' # Crash Report

This helped a lot, because now I was able to work with string XRefs. So it expects the r2 license to be in C:\Program Files\radare2\r2.key. The binary reads that file and checks it using some simple algorithm. I made up the following script to solve it:

arr = bytearray.fromhex('a495d2dde9df9495d8d2d1a491cdd3cac9c3c0c0cb9d9496a1e1d8a4bc')
flag = "r"

for c in range(0, len(arr)):
	flag += chr(arr[c]-ord(flag[c]))

# r2con{d0es_r2_need_a_l1c3nse?}

[400] r2tune

Find the secret melody!

flag format: r2con{flag}

We download a game boy rom

For this challenge I also did not use r2 :-(

I started the rom in mGBA and the flag appeared in RAM dump. The rest is left as an exercise for the reader.

[400] Doctor, how much time I have left?

A hospital has been hacked, and records relating to patients have been leaked.

After initial investigations by the hospital's Blue Team, and after speaking with some hospital employees, it has been inferred that an employee's computer has been infected by malicious email and then swung to a hospital server.

The email the employee received was as follows:

Hello, Doctor,

After the last analysis I decided to go to a private hospital and have some x-rays taken.

They told me that I might have cancer and I wanted to consult with you the next day.

I leave you attached the evidence.

**Attached document: The Blue team is analyzing attached file at the moment.

Would you like to help them?

We download a file cancer.dcm.

In this write-up I'll only need some open source tools to solve the challenge :-)


I've never seen or heard about the .dcm file extension before. Well, r2 identifies it as PE32 executable, and you can actually run it:

cyrill@0x00 ~/ctf/r2con/doctor
> r2 -qqci~class cancer.dcm 
class    PE32
cyrill@0x00 ~/ctf/r2con/doctor
> wine cancer.dcm
[!] Who am I? When you know it, you can try to execute me from the cmd. Just 4 fun

That was good enough for me as a start but we will come back to it later.

Decrypting the strings

As usual lets open the binary in r2 and have a look at the main function. Probably the first thing I noticed is that there are many rather large blocks of code that are like this one:

│           0x0040157b      c685f0feffff.  mov byte [s], 0x52          ; 'R' ; 82                                                                                                                                                      
│           0x00401582      c685f1feffff.  mov byte [var_10fh], 9                                                                                                                                                                      
│           0x00401589      c685f2feffff.  mov byte [var_10eh], 0x73    ; 's' ; 115
│           [...]
│           0x004017b2      c68541ffffff.  mov byte [var_bfh], 0x27    ; ''' ; 39                                                                                                                                                      
│           0x004017b9      c68542ffffff.  mov byte [var_beh], 0x3c    ; '<' ; 60                                                                                                                                                      
│           0x004017c0      c68543ffffff.  mov byte [var_bdh], 0                                                                                                                                                                       
│           0x004017c7      c745e4010000.  mov dword [var_1ch], 1     

Followed by a small loop:

│      ╭──> 0x004017d0      0fb695f0feff.  movzx edx, byte [s]                                                                                                                                                                         
│      ╎│   0x004017d7      8d8df0feffff   lea ecx, [s]                                                                                                                                                                                
│      ╎│   0x004017dd      8b45e4         mov eax, dword [var_1ch]                                                                                                                                                                    
│      ╎│   0x004017e0      01c8           add eax, ecx                                                                                                                                                                                
│      ╎│   0x004017e2      0fb600         movzx eax, byte [eax]                                                                                                                                                                       
│      ╎│   0x004017e5      89d1           mov ecx, edx                                                                                                                                                                                
│      ╎│   0x004017e7      31c1           xor ecx, eax                                                                                                                                                                                
│      ╎│   0x004017e9      8d95f0feffff   lea edx, [s]                                                                                                                                                                                
│      ╎│   0x004017ef      8b45e4         mov eax, dword [var_1ch]                                                                                                                                                                    
│      ╎│   0x004017f2      01d0           add eax, edx                                                                                                                                                                                
│      ╎│   0x004017f4      8808           mov byte [eax], cl                                                                                                                                                                          
│      ╎│   0x004017f6      8345e401       add dword [var_1ch], 1                                                                                                                                                                      
│      ╎│   ; CODE XREF from main @ 0x4017ce                                                                                                                                                                                           
│      ╎╰─> 0x004017fa      0fb685f0feff.  movzx eax, byte [s]                                                                                                                                                                         
│      ╎    0x00401801      0fbec0         movsx eax, al                                                                                                                                                                               
│      ╎    0x00401804      83c001         add eax, 1                                                                                                                                                                                  
│      ╎    0x00401807      3b45e4         cmp eax, dword [var_1ch]                                                                                                                                                                    
│      ╰──< 0x0040180a      7fc4           jg 0x4017d0                                                                                                                                                                                 
│           0x0040180c      8d85f0feffff   lea eax, [s]                                                                                                                                                                                
│           0x00401812      83c001         add eax, 1                                                                                                                                                                                  
│           0x00401815      890424         mov dword [esp], eax        ; const char *s                                                                                                                                                 
│           ;-- eip:                                                                                                                                                                                                                   
│           0x00401818      e86b390000     call sub.msvcrt.dll_puts    ;[1] ; int puts(const char *s)      

What this does is it simply loads all these bytes into memory and XORs them using a static key byte (0x52 in this case, see the s variable at the beginning). It's basically obfuscating the strings a bit inside the binary. If we have all these strings decrypted we can get a way better understanding of what's going on. But we don't do that manually. One easy way is to decrypt them using ESIL emulation.

We achieve that using the following r2 commands:

:> aei             # initialize ESIL VM state
:> aeim            # initialize ESIL VM stack
:> s 0x0040157b    # seek to the start of our block
:> aeip            # initialize ESIL program counter to current seek
:> aecc            # continue until call (note the call to puts after the loop=
call at 0x00401818
:> ps@eax          # print the decrypted string
[!] Who am I? When you know it, you can try to execute me from the cmd. Just 4 fun

Yay, that worked really well! Let's hack a small script together to quickly extract all the strings:

for i in 0x0040157b 0x00401827 0x00401a18 0x00401bf7 0x00401ea3 0x0040207a 0x004021a9 0x00402367 0x00402599 0x00402817 0x00402917 0x00402ac9
  echo $i `r2 -qqc "aei;aeim;s $i;aeip;aecc;ps@eax" cancer.dcm 2>&1 | grep -v "call at"`
> bash 
0x0040157b [!] Who am I? When you know it, you can try to exe
0x00401827 [+] Well, now you have the file, let's see what is
0x00401a18 [!] My fault :S
0x00401bf7 [+] Well done! now the flag is almost in your hand
0x00401ea3 [+] Well done! now the flag is in your hands...!
0x0040207a [!] Error writing file
0x004021a9 [!] Did you look for the prefix I'm looking for?
0x00402367 [+] You are a lucky guy, now you know more about D
0x00402599 [!] Are you sure you are executing from the correc
0x00402817 [!] My fault :S
0x00402917 [+] Well Done, Good luck with the next stage...
0x00402ac9 [!] Can't handle this error, please delete al file

Based on that it looks like our goal is to reach Address 0x00401ea3 at some point. We could maybe use an SMT solver here but I decided to go for some more analysis first.

Stage 1

When we currently execute the binary now the string at address 0x0040157b is printed to the terminal, which means we don't take the branch there. Before we land there we see another branch which depends on the return value of the function fcn.00401500. It is a short function:

│           0x00401500      55             push ebp
│           0x00401501      89e5           mov ebp, esp
│           0x00401503      83ec18         sub esp, 0x18
│           0x00401506      8b4508         mov eax, dword [arg_8h]
│           0x00401509      890424         mov dword [esp], eax
│           0x0040150c      a16c924000     mov eax, dword [sym.imp.SHLWAPI.DLL_PathFileExistsA] ; [0x40926c:4]=0x9622 reloc.SHLWAPI.DLL_PathFileExistsA ; "\"\x96"
│           0x00401511      ffd0           call eax
│           0x00401513      83ec04         sub esp, 4
│           0x00401516      c9             leave
╰           0x00401517      c3             ret

r2 helps us here because it understands that the call instruction at address 0x00401511 will be a DLL call to PathFileExistsA. The file that's checked will be first argument, lets check what that will be.

:> ps@[0x406008]

If the setup was more complex you could also call out ESIL to do it for you:

> r2 -Aqqc "s 0x0040154a;aei;aeim;aeip;4aes;ps@[esp]" cancer.dcm

What this does it seeks to the function call "setup" and emulates it. The first argument is now on the stack so we can just print it.

For convenience we rename the function to something useful.

afn DLL_PathFileExistsA @ fcn.00401500

Stage 2

Let's juts prepend the file stage2.dcm exists and we are at address 0x402187:

:> pd3@0x402187
│           ; CODE XREF from main @ 0x40155f
│           0x00402187      a108604000     mov eax, dword [0x406008]   ; [0x406008:4]=0x40717c eax
│           0x0040218c      890424         mov dword [esp], eax
│           0x0040218f      e86cf3ffff     call DLL_PathFileExistsA

It agains checks for the existance (according to Microsoft Docs: "Determines whether a path to a file system object such as a file or folder is valid."). So assuming we don't take the branch at 0x0040155f the next checks happens inside fcn.00403b18. This function first calls GetModuleFileNameA with the first parameter set to 0 which "retrieves the path of the executable file of the current process". The next call to PathFindFileNameA checks that this file exists and after that the file name is compared against the first arg, which is stage2.dcm. In short it checks whether the file name of the executed binary is stage2.dcm.

So let's just see what will happen if we name it correctly:

> cp cancer.dcm stage2.dcm 
cyrill@0x00 ~/ctf/r2con/doctor/writeup master 
> wine stage2.dcm
[+] Well, now you have the file, let's see what is it
[+] Well done! now the flag is almost in your hands...!

Stage 3

If you pay attention you will notice that if you run the binary like that it will actually open the cancer.dcm file and write some stuff into it. I just noticed that the file changes on disk everytime I run stage2.dcm, but let's find proof for that in the code.

We seek to where the last string that is printed get constructed and see what happens there. Since we deobfuscated the strings already we know it's at address 0x00401ea3. When we have a look at what happens just before that address we see some loops and function calls that probably take a while to understand, so let's reverse a bit more and see how we got to this block first hand. It actually starts at address 0x00401de8, where r2 found a XRef from address 0x401b9b:

│           0x00401b7d      8b45ac         mov eax, dword [var_54h]
│           0x00401b80      83c005         add eax, 5
│           0x00401b83      0fb600         movzx eax, byte [eax]
│           0x00401b86      0fb6c0         movzx eax, al
│           0x00401b89      6689857effff.  mov word [var_82h], ax
│           0x00401b90      0fb7857effff.  movzx eax, word [var_82h]
│           0x00401b97      6683f863       cmp ax, 0x63                ; 99
│       ╭─< 0x00401b9b      0f8747020000   ja 0x401de8 

So it checks some value at address var_54h + 5 to be 0x64 or greater. The easiest way to find out how this local variable get's populated is to just grep for it inside the main function:

:> pdf@main~var_54h
│           ; var uint32_t var_54h @ ebp-0x54
│     │ │   0x00401a0b      8945ac         mov dword [var_54h], eax
│     │ │   0x00401a0e      837dac00       cmp dword [var_54h], 0
│   │││ │   0x00401b5d      8b45ac         mov eax, dword [var_54h]
│   │││ │   0x00401b6e      8b45ac         mov eax, dword [var_54h]

Interesting is obviously the first hit at address 0x00401a0b where It becomes the return value of fcn.00403a36. This function returns some local variable ptr:

│    │││    0x00403b13      8b45ec         mov eax, dword [ptr]
│    │││    ; CODE XREFS from fcn.00403a36 @ 0x403a5d, 0x403ac2, 0x403ae5
│    ╰╰╰──> 0x00403b16      c9             leave
╰           0x00403b17      c3             ret

Using the same grepping as we did just before we can learn that it is a pointer to some allocated memory:

│      │    0x00403aaf      e81c170000     call sub.msvcrt.dll_calloc  ; void *calloc(size_t nmeb, size_t size)
│      │    0x00403ab4      8945ec         mov dword [ptr], eax

When you reverse the rest of this function you will learn that it more or less reads a file into this buffer. The filename is passed as an argument, and how to get that we already know:

:> ps@[0x406004]

In conclusion the byte at offset 5 in the cancer.dcm file needs to be greater than 0x63 to pass this stage (remember we are executing stage2.dcm currently). But we can not just monkey patch it, because there's more to it than just that.

In fact what happens is that some data inside cancer.dcm is incrementally decrypted every time you run stage2.dcm! To keep track of how many iterations we have done already the byte at offset 5 gets increment by one. For the sake of trying to keep this write up somewhat compact I'll skip dissecting this part.

We can use a small script to fully decrypt the flag file:

> cat
# get a clean cancer.dcm
cp stage2.dcm cancer.dcm
for i in {0..100}
	wine stage2.dcm   


cyrill@0x00 ~/ctf/r2con/doctor
> bash 
[+] Well, now you have the file, let's see what is it
[+] Well done! now the flag is almost in your hands...!


[+] Well, now you have the file, let's see what is it
[!] I need you to run me inside a debugger please =)
cyrill@0x00 ~/ctf/r2con/doctor

Stage 4

This is easy:

> r2 -d wine stage2.dcm
Process with PID 16186 started...
= attach 16186 16186
bin.baddr 0x7c000000
Using 0x7c000000
asm.bits 32
glibc.fc_offset = 0x00148
 -- Press 'C' in visual mode to toggle colors
[0xf7fc6120]> dc
hit hardware breakpoint 1 at: 7c400000
[0x7c400000]> dc
child stopped with signal 17
[+] SIGNAL 17 errno=0 addr=0x3e800003f44 code=1 ret=0
[+] signal 17 aka SIGCHLD received 0
[0xf7fac929]> dc
child stopped with signal 17
[+] SIGNAL 17 errno=0 addr=0x3e800003f48 code=1 ret=0
[+] signal 17 aka SIGCHLD received 0
[0xf7fac929]> dc
[+] Well, now you have the file, let's see what is it
hit hardware breakpoint 1 at: 403dc9
[0x00403dc9]> dc
hit hardware breakpoint 1 at: 403dc9

Wtf where are all these breakpoints coming from?

[0x00403dc9]> pd1@eip-1
            0x00403dc8      cc             int3

Ok debug traps or whatever. Note that this is in a loop that will be execute 0xff times. I guess I can either patch out the interrupt or just continue at least 256 times. The latter makes a nice oneliner:

> r2 -qqdc260dc wine stage2.dcm
Process with PID 17041 started...
hit hardware breakpoint 1 at: 403dc9
hit hardware breakpoint 1 at: 403dc9
[+] Well done! now the flag is in your hands...!
[+] signal 17 aka SIGCHLD received 0

Stage 5

It sais Well done! now the flag is in your hands...! which is nice but what does it mean? Remember the challenge description and the .dcm file extension? cancer.dcm is actually a DICOM file! There are many open DICOM viewers for Linux, I installed some of them but all failed to open the cancer.dcm. I did not know this file format before so I did quick internet research. There are 4 magic bytes at offset 0x80 that must be DICM. But it is not like that with cancer.dcm:

> r2 -nqqcx4@0x80 cancer.dcm
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000080  4b45 5921                                KEY!

This is why it cannot not be viewed. What you see is what you fix:

r2 -wnqqc "w DICM@0x80" cancer.dcm

Finally we can get the flag using our preferred DICOM image viwer \o/

Here is full script to get the flag in one shot using aliza DICOM viewer:

cp cancer.dcm stage2.dcm
for i in {0..100}
	wine stage2.dcm   
r2 -qqdc260dc wine stage2.dcm
r2 -wnqqc "w DICM@0x80" cancer.dcm
aliza cancer.dcm

# r2con{s0m3t1m3s_th1ngs_4r3_n0t_wh4t_th3y_s33m}

[400] vermu 2

You have all you need, but this time do it harder!

Please see the Solution for vermu 1, you just need to be more patient until the key and cipher appears :-)

import sys

for i in range(0, len(cipher)):

# r2con{cl4an_da_st4ck_b3fore_jmp}

[400] Pull your socks up

Be careful with moths, can make small holes in your socks.

r2 has new socks, and they are in kernel land! Pwn the kernel in this challenge and read the flag off /flag.txt.

$ nc 31337

I think this is a kernel crackme which nobody managed to solve :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment