Bleichenbacher's Ghost

Hey man, computer security is hard.

Google CTF 2020 | Reversing | Beginner

Posted on — Sep 5, 2020;   Reading Time — 2 minutes

Disclosure: I didn’t solve this challenge during the CTF.

First, run file to know what we are dealing with

reversing/beginner/solution » file ../a.out
../a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e3a5d8dc3eee0e960c602b9b2207150c91dc9dff, for GNU/Linux 3.2.0, not stripped

Running the executable, we see that it asks for a flag.

reversing/beginner/solution » ./../a.out
Flag: 123
FAILURE

This seems like a classic crack-me, let’s first try with some symbolic execution tools.

Attempt 1: Using Manticore

Create a pipenv and install manticore using pip install "manticore[native]" and run manticore ./../a.out. It fails because manticore does not recognize some of the instructions. It seems like this binary is making use of some vector instructions. Shit, let’s try with angr.

Attempt 2: Using Angr

In the same pipenv (or, if you wish, in a new pipenv) install angr using pip install angr. Let’s see if angr can run the binary with this basic script.

import angr


def get_flag():
    p = angr.Project("../a.out")
    st = p.factory.entry_state()
    sm = p.factory.simulation_manager(st)
    sm.run()
    return "xxx"

if __name__ == "__main__":
    flag = get_flag()
    print(flag)

It seems like angr can execute this binary. Sweet. Now, let’s open this binary in r2 to get an idea of what this binary does.

Open binary in r2 with r2 ./../a.out, analyze all with aaa, seek to main with s main and try to disassemble it with pdf.

The figure shows the disassembly of main that can be obtained from running `s main` and `pdf` in r2.
disassembly of main

We notice that the binary reads 15 characters, does a strncmp and based on the result, either jumps to FAILURE or jumps to SUCCESS. We want to trigger the SUCCESS case.

First, let’s create a 15-character symbolic string (I adapted this from the example in the docs)

# 15-character symbolic string
flag_chars = [claripy.BVS("flag_%d" % i, 8) for i in range(15)]
# Append a newline at the end of the first input
flag = claripy.Concat(*flag_chars + [claripy.BVV(b"\n")])

Again, following the example in the docs let’s initialize the initial state and add the constraint that the flag characters are printable ASCII characters.

# enable unicorn engine for fast efficient solving
st = p.factory.full_init_state(
        args=['./../a.out'],
        add_options=angr.options.unicorn,
        stdin=flag
       )

# constrain the flag characters to be a printable ascii characters.
for k in flag_chars:
    st.solver.add(k < 0x7f)
    st.solver.add(k > 0x20)

As in the basic script, let’s construct a simulation manager with the initial state (with the constraints) and run it.

sm = p.factory.simulation_manager(st)
sm.run()

Yet again, following the example in the docs let’s grab the first final state with SUCCESS in stdout, parse it into a string, and return that.

# Output the first final state with `SUCCESS` in the stdout
y = []
for x in sm.deadended:
    if b"SUCCESS" in x.posix.dumps(1):
        flag_out = x.posix.dumps(0)
        # parse it into a string
        flag = "".join([chr(flag_out[i]) for i in range(0, len(flag_out))]).strip()
        return flag

Running this (you can find the full script here), we get

(solution) reversing/beginner/solution » python sol.py
WARNING | 2020-09-05 22:55:20,499 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
CTF{S1MDf0rM3!}

Yay!

Ending Notes. In retrospect, this wasn’t that hard, I could’ve done it during the CTF… (kicking myself) I tried to solve this using r2 and paper which was harder. Another approach to solve this that I learnt from looking at writeups was to use Ghidra; here is an excellent writeup that uses Ghidra to solve this challenge.