r2con 2019 CTF Writeups

r2 radare2 ctf reverse-engineering


The first GameBoy challenge was rather easy. The idea was to talk to the Pancake character in-game in order to get the flag. The problem is that Pancake chills behind a wall. One possible solution was to glitch through the wall, however I’ve solved this using static analysis.

Going through the strings and searching for a possible dialog yields interesting strings:

[0x00054075]> izzq~pancake
0x54016 37 36 Find pancake\nthrough a game\nglitch!
0x5403c 30 29 I am pancake, the\nr2 prophet.

Inspecting this region directly yields the flag in plain text:

[0x00054075]> pxq@0x5403c
0x0005403c  0x6e6170206d612049  0x6874202c656b6163   I am pancake, th
0x0005404c  0x6f72702032720a65  0x5202002e74656870   e.r2 prophet...R
0x0005405c  0x2e7265626d656d65  0x7065654b02002e2e   emember.....Keep
0x0005406c  0x00200a6d6c616320  0x776c6120646e6102    calm. ..and alw
0x0005407c  0x2065737520737961  0x206d6f72660a3272   ays use r2.from
0x0005408c  0x6568030021746967  0x7320656874206572   git!..here the s
0x0005409c  0x6f740a7465726365  0x7320726576656e20   ecret.to never s
0x000540ac  0x32720a3a7065656c  0x7b02006e6f630200   leep:.r2..con..{
0x000540bc  0x75705f7961790200  0x30665f656b346b6e   ..yay_punk4ke_f0
0x000540cc  0x007d02000a646e77  0x6d61472077654e02   wnd...}..New Gam


First of all, I’m not sure whether I’ve solved this in the intended way :D The challenge was to talk to another in-game character and get the flag that way. It turns out that the character won’t give out the flag if just you ask him. All he answers is: Don't ask for the flag!. Too bad.

I’ve received the flag using two methods and didn’t use radare2 at all:

  1. The game can be debugged using bgb. During a debugging session it’s possible to read the loaded image data that’s being used to display the game environment. This data contains the flag, stored as map image data.

  2. I’ve emulated the ROM using vbam. This emulator implements a convenient feature that allows users to speed up time while holding the space key. All I did was holding the space key while also holding q in order to constantly start the dialog with the character that knows about the flag. Lets’ check out what happens after a few seconds:



This ARM64 challenge prints out the flag that’s derived from user input after passing some checks that have to be reversed to let the input pass. My setup was to use QEMU to emulate the ARM binary:

qemu-aarch64-static -g 1337 ./babyr2

The -g parameter spawns a GDB server that can be accessed with r2:

r2 -a arm -b 64 -d gdb://

This way, the checks can be observed dynamically. Setting breakpoints didn’t work, so I’ve used dcu <address> instead.

Let’s analyze the checks, directly from the main() function.

First check:

; argc
str w0, [arg_2ch]
ldr w0, [arg_2ch]
cmp w0, 4

This is a basic check that’s ensures that three command line arguments are passed. Remember that argv[0] is the name of the executable itself.

Second Check:

movz w0, 0xcafe
cmp w1, 0

It seems that the first argument has to be 0xcafe. So let’s pass 51966, its decimal representation, as the first argument.

Third and Fourth Check:

<Weird Stuff>
cmp w1, 3
<Other Stuff>
cmp w1, 8

I’ve fiddled around with the second argument in order to pass both checks and didn’t bother analyzing in depth’s going on. I’ve used 51977 for this. I’ve skipped the last check in order to just print the calculated value after passing 123 as last argument:

[0x00400508]> dr x0 = 0x0 # Set register value to the expected value
[0x00400508]> dc
ok,ok! you are not a baby r2 reverser!
The key to be a g00d reverser is: r2con{c0ffe7}

So it seems pretty obvious that the correct flag has to be r2con{c0ffee}.

Land of Ecodelia

This is a text based adventure that prints the flag after answering all questions correctly. In fact, the string

The flag is r2con2019{all_your_answers_in_order_without_spaces}

is even embedded in the binary.

Let’s check out some functions:

What’s really happening in the sym.go.main.encode function is that it uses an embedded key to XOR encrypt the input:

0x0048a32b      0fb61416       movzx edx, byte [rsi + rdx]
0x0048a32f      4131d2         xor r10d, edx

The RSI register points to the key and RDX is the current offset in the key. It’s important to note that for each question an individual key is being used. The keys are defined here:


The look as if they were the answers to the questions but they are not :)

The check() function performs two checks:

  1. Check whether the length of both strings match
  2. Compare the values byte by byte:
0x0048a485      0fb6341a       movzx esi, byte [rdx + rbx] # Value A
0x0048a489      0fb63c19       movzx edi, byte [rcx + rbx] # Value B
0x0048a48d      4038fe         cmp sil, dil # CMP RSI, RDI

My solution was to:

  1. Set a breakpoint in the encode() function before the key is being used (0x0048a32b).
  2. Read the currently used key with pxq@rsi
  3. Set a breakpoint in check() at 0x0048a46a (length check) and 0x0048a48d (comparing).
  4. Skip the length check by writing the expected value to the register.
  5. Read the expected value from the register, again with pxq.
  6. Use CyberChef to decrypt the expected value using the current key:


I’ve performed these steps for every question. I’m sure there are more elegant solutions but it worked anyway :D



You need to enter the correct password in order to print the flag. While checking out main(), this caught my attention:

0x00401de0      b820000000     mov eax, 0x20
0x00401de5      89c2           mov edx, eax
0x00401de7      488b7de8       mov rdi, qword [var_18h]
0x00401deb      488b3425f850.  mov rsi, qword [obj.pass] # <-- pass looks good
0x00401df3      e878f2ffff     call 0x401070 # String compare
0x00401df8      83f800         cmp eax, 0
0x00401dfb      0f8529000000   jne 0x401e2a # Not correct

The obvious thing to do is to print the value of obj.pass as soon as its loaded into RSI:

0x004a5120  0x705f746572633373  0x535f656b416b6e61   s3cret_pankAke_S
0x004a5130  0x0000002133637534  0x0000000000000000   4uc3!

I’ve tried to submit this as a flag but nope. However, after using this string as input for the application, the correct flag can be read while executing the string comparison function listed above (0x401070).

[0x0041eb5a]> pdf
0x0041eb52      0fb60417       movzx eax, byte [rdi + rdx]
0x0041eb56      0fb61416       movzx edx, byte [rsi + rdx]
0x0041eb5a      29d0           sub eax, edx
[0x0041eb5a]> pxq@rdi
0x01bcab40  0x74316e756d4d3063  0x7234643472333c79   c0Mmun1ty<3r4d4r
0x01bcab50  0x031000216e306333  0x5218046e1a101f2e   3c0n!.......n..R


Connecting to this challenge drops the user into a pseudo shell. The help function prints these commands:

Available commands:
* cat echo env exit help ls set unset login

After trying to log in, I’ve found that the fcn.00402600 function performs the login checks:

[0x004027e9]> izzq~DENIED
0x406197 15 14 ACCESS DENIED\n
[0x004027e9]> axt 0x406197
fcn.00402600 0x4027c7 [DATA] movabs rdi, str.ACCESS_DENIED

I’ve pulled the correct login credentials directly from memory by setting a breakpoint before the string comparison function:


The correct user name is an0nym0us and the password is Intrud3r. After logging in, there’s a file present called flag.txt but it can’t be read by the current user. After executing env it shows that there’s an environment variable present called ADMIN with no value set.

I decided to search for this string in the whole process memory and found two interesting things:

First, there seems to be an additional su command that isn’t printed by the help function:

0x004081a0  0x7461630074697865  0x6e65006f68636500   exit.cat.echo.en
0x004081b0  0x6c00706c65680076  0x6e75007465730073   v.help.ls.set.un
0x004081c0  0x69676f6c00746573  0x007325007573006e   set.login.su.%s.
0x004081d0  0x286d32333b305b1b  0x7e20296e696d6461   .[0;32m(admin) ~
0x004081e0  0x0000000000002400  0x0000000000000000   .$..............

The su command is present in memory along with a string that looks just like an administrative shell prompt.

Also, setting the ADMIN environment variable to true seems to enable the admin mode:

0x00408280  0x74746547203e7e20  0x7669727020676e69    ~> Getting priv
0x00408290  0x2e2e736567656c69  0x004e494d4441002e   ileges....ADMIN.
0x004082a0  0x00000a0065757274  0x0000000000000000   true............

The script to log in as administrator is therefore:


from pwn import *

io = remote("", 1337)
print io.recvuntil("$")
io.sendline("login an0nym0us Intrud3r")
print io.recvuntil("$")
io.sendline("set ADMIN true")
print io.recvuntil("$")
print io.recvuntil("admin")

Maybe the flag can be read now:


Of course not. Because of maximum trollage you have to read the hidden .flag.txt file that isn’t displayed after executing ls. Remember, ls -la doesn’t work in this pseudo shell :)

This is the correct flag:


Thanks for setting up this year’s r2CTF :)

Analysis of Satisfyer Toys: Discovering an Authentication Bypass with r2 and Frida

radare2 r2 frida r2frida reverse-engineering web vulnerability

Haxxoring a Hisense Smart TV

exploitation reverse-engineering vulnerability

Game Hacking #3: Hooking Direct3D EndScene()

reverse-engineering c++ binary gamehacking hooking