ACS 2023: babyrev

Peek-a-boo

TL;DR

Flag checker with library call path explosion designed to thwart Angr's symbolic execution. This pitfall can be resolved via Angr's Dynamic Memory implementation.

Challenge Overview

babyrev is yet another flag checker program, but connects over socket to increase its complexity.

Angr is known to struggle against library calls. The use of socket to receive user input is similar to one of the common roadblocks in Angr where library functions such as scanf are used.

I came across this tutorial by Federico that uses Angr's symbolic memory to hook library calls.

Determine Start Address

To overcome this roadblock, we will try to avoid all instructions that are related to socket, and configure Angr's Simulation Manager to start directly from the flag-checking logic.

initial_state = project.factory.blank_state(addr=0x40CC5A)

Symbolic Memory

First, we create a symbolic bitvector flag that will substitute our input. In our case, the original input that recv() receives is stored in rbp-0xd0; hence we would also define our symbolic bitvector to store in the same memory address.

flag_chars = [claripy.BVS(f"c_{i}", 8) for i in range(FLAG_LEN)]
flag = claripy.Concat(*flag_chars)

initial_state.memory.store(initial_state.regs.rbp-0xd0, flag)
# Define the address at which the symbolic bitvector will be stored.

Determine Good & Bad Address

The rest is pretty much straightforward, which involves performing light-reversing to find good and bad addresses.

Solution

Angr solved this pretty quickly, taking only 4 minutes.

Flag: ACS{V2l0aCBncmVhdCBwb3dlciBjb21lcyBncmVhdCByZXNwb25zaWJpbGl0eS4gLSBTcGlkZXItbWFuIGZpbG1zCg==}

Final Script

#!/usr/bin/env python3

import angr
import claripy
import logging

FLAG_LEN = 88

find_addr  = 0x40CD50 # SUCCESS
avoid_addr = 0x40CCFA # FAILURE
start_addr = 0x40CC5A

project = angr.Project("babyrev", load_options={"auto_load_libs": False})
flag_chars = [claripy.BVS(f"c_{i}", 8) for i in range(FLAG_LEN)]
flag = claripy.Concat(*flag_chars)

logging.getLogger('angr').setLevel('INFO')
initial_state = project.factory.blank_state(
        stdin=flag,
        addr = start_addr,
        add_options={
        angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
        angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
        angr.options.LAZY_SOLVES
    }
)

initial_state.memory.store(initial_state.regs.rbp-0xd0, flag)

# Add constraints that all characters are printable
for f in flag_chars:
    initial_state.solver.add(f >= 0x20)
    initial_state.solver.add(f < 0x7f)

sm = project.factory.simulation_manager(initial_state)
sm.explore(find=find_addr, avoid=avoid_addr)

if sm.found:
    solution_state = sm.found[0]
    solution = solution_state.solver.eval(flag, cast_to=bytes)
    print(solution)
else:
    raise Exception('Could not find the solution')

Last updated