This is the Write-Up from the CTF codegate 2020 teaser.
The problem is basically a brainfuck emulator, compromised of a python script main.py
and a shared object runtime.so
. These scripts use the llvmlite
to introduce JIT into it.
according to the dockerfile, the flag is located at /home/user/flag
, so we need to pwn the babyllvm to get a shell.
The memory of this brainfuck emulator is stored in a array DATA in the runtime.so
.
And it's size is limited. For every single memory access, ptrBoundCheck
function is called from the runtime. This function, with the help of the python script, halts the program when you try to use a out-of-bounds pointer on memory access.
However, there is a concept of a whitelist in the main.py
. If the requested address of a memory access is in the whitelist, it's check (a.k.a. ptrBoundCheck
) is omitted. Normally, the addition of whitelist is always coupled with a successful ptrBoundCheck
.
if not is_safe(rel_pos, whitelist_cpy):
sptr = builder.load(sptr_ptr)
cur = builder.ptrtoint(dptr, i64)
start = builder.ptrtoint(sptr, i64)
bound = builder.add(start, llvmIR.Constant(i64, 0x3000))
builder.call(ptrBoundCheck, [start, bound, cur])
whitelist_cpy = whitelist_add(whitelist_cpy, rel_pos)
This is not really useful, because it can only add useless whitelists. So, we read the main.py
again and found this piece of code.
def codegen(self, module, whitelist=None):
main_routine = findFunctionByName(module, "main_routine")
if (self.isLinear == True):
#A lot of code was here.
else:
# create all blocks
headb = self.head.codegen(module)
br1b = self.br1.codegen(module, (0, 0))
br2b = self.br2.codegen(module, (0, 0))
The second argument of the codegen is whitelist. When the code has a bracket in it, It splices the code into three, a head domain with pre-bracket codes, a br1 domain with endo-bracket codes, and a br2 domain with post-bracket codes.
In short, position right after branching is always whitelisted. Now we can access other memory regions by using brackets.
By moving the pointer backwards, we can access the got section of runtime.so
.
Using the .
of the brainfuck, we can easily get a leak of libc. Then, we calculated an offset to the one-shot gadget, overwriting the libc's got.
Please read wrapper.py
for more details.