r2boy1
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
r2boy2
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:
-
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.
-
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 holdingq
in order to constantly start the dialog with the character that knows about the flag. Lets’ check out what happens after a few seconds:
r2baby
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://127.0.0.1:1337
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:
sym.go.main.main
: This function reads user input and calls the following two functions.sym.go.main.encode
: This “encodes” the user input and returns it.sym.go.main.check
: This compares the user input with the expected value. The application aborts in case the two values don’t match.
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:
- Check whether the length of both strings match
- 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:
- Set a breakpoint in the
encode()
function before the key is being used (0x0048a32b
). - Read the currently used key with
pxq@rsi
- Set a breakpoint in
check()
at0x0048a46a
(length check) and0x0048a48d
(comparing). - Skip the length check by writing the expected value to the register.
- Read the expected value from the register, again with
pxq
. - 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
r2darling
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
Cerberus
Connecting to this challenge drops the user into a pseudo shell. The help
function prints these commands:
$help
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:
#!/usr/bin/python2.7
from pwn import *
io = remote("206.189.109.0", 1337)
print io.recvuntil("$")
io.sendline("login an0nym0us Intrud3r")
print io.recvuntil("$")
io.sendline("set ADMIN true")
print io.recvuntil("$")
io.sendline("su")
print io.recvuntil("admin")
io.interactive()
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 :)