# Imports
import angr
import claripy
We push the input values to the stack and then pop them right before the function exits. Since no value is pushed onto the stack during the execution of the function, we can safely manipulate the stack in the way we planned.
Load the binary.
project = angr.Project('bomb64')
Define the address for the start of the execution path.
# The address where the symbolic execution shall begin. It is the beginning of the phase_5 function.
addr_start = 0x401062
# The address of the return block of phase_5.
addr_target = 0x4010D9
# The address of the first instruction of the explode_bomb function, which is to be avoided.
addr_bomb = 0x40143A
Define a blank state for the simulation, a state with most of it's data uninitialized. Pass the address where the state is initialized, along with the user input to be given via standard input.
state = project.factory.blank_state(addr=addr_start)
Move a symbolic value into the relevant register.
# A BitVectorSymbol is declared of max allowed lenght of 6 characters * 8 bits.
# Length of input in restricted at 6.
user_input = claripy.BVS('user_input', 8 * 6)
Store the string in the memory and then save it's address in the register so as to pass it as a parameter.
state.memory.store(0x6038c0, user_input)
state.regs.rdi = 0x6038c0
Create a simulation manager with this blank state that would help us manage the symbolic execution.
simgr = project.factory.simulation_manager(state)
We call the explore method of the simulation manager, tasked with finding an execution path that reaches the target address and avoids the address which explodes the bomb.
simgr.explore(find=addr_target, avoid=addr_bomb, enable_veritesting=True)
We dereference the execution path "found" by the simulation manager and dump the stack.
found = simgr.found[0]
found.solver.eval(user_input, cast_to=bytes)
Angr gets us the bare essential required to reach the target address. Since the and
operation was being done on the lower nibble only, Angr got us the values required for the lower nibble. Adding a 0x40
to all the bytes to convert it into an ASCII form so it could be given as an input by the user:
chr(0x49)+chr(0x4f)+chr(0x4e)+chr(0x45)+chr(0x46)+chr(0x47)
And that's the string we have to give as input to pass the fifth phase.