06 Angr Symbolic Dynamic Memory¶
Analyzing the Binary¶
Lets take a look at the main function.
In 05 we were able to overwrite the memory directly because we knew the address. We can't do that this time because the program is using malloc
. Which allocates memory and then returns a pointer: man.
We can't overwrite the addresses in memory, but we can overwrite the pointers. What we will have to do is write our bitvectors to an open memory position and then overwrite buffer0
and buffer1
with the addresses to our input.
Building the Script¶
Nothing new here.
path_to_binary = "./06_angr_symbolic_dynamic_memory"
project = angr.Project(path_to_binary)
start_address = 0x08048699
initial_state = project.factory.blank_state(
addr=start_address,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
},
)
Now lets create our two bitvectors.
input_length_in_bits = 8 * 8
password0 = claripy.BVS("password0", input_length_in_bits)
password1 = claripy.BVS("password1", input_length_in_bits)
We need to determine four addresses. The addresses for buffer0
and buffer1
, also the addresses where we are going to store our input. I don't know of a good strategy for picking an unused portion of memory... would love to hear if there is a best practice for this. Until I have a better way, I just go to an unused portion and call it good. For this I decided to use this region of memory:
With the location decided, we define our four addresses. Then store the bitvectors to the memory locations, then store pointers into the two buffers. Instead of putting in the endianess myself, I will rely on Angr to provide that info.
mem_address0 = 0x804D020
mem_address1 = 0x804D030
buffer0_addr = 0xABCC8A4
buffer1_addr = 0xABCC8AC
initial_state.memory.store(mem_address0, password0)
initial_state.memory.store(mem_address1, password1)
initial_state.memory.store(buffer0_addr, mem_address0, 8, endness=project.arch.memory_endness)
initial_state.memory.store(buffer1_addr, mem_address1, 8, endness=project.arch.memory_endness)
Create the simulation manager and let it go, and we are done with another one!
Final Script¶
import sys
import angr
import claripy
import pwn
def run_binary(solution, path_to_binary):
if type(solution) == str:
solution = bytes(solution, "utf-8")
print(f"[+] Solution found: {solution.decode()}")
print(" [|] Running binary")
elf = pwn.ELF(path_to_binary, checksec=False)
pty = pwn.process.PTY
io = elf.process(stdin=pty, stdout=pty, level="warn")
io.recvuntil(b":")
io.sendline(solution)
output = io.recvline().decode().splitlines()[0].strip()
print(f" [+] Output: {output}")
def main(argv):
path_to_binary = "./06_angr_symbolic_dynamic_memory"
project = angr.Project(path_to_binary)
start_address = 0x08048699
initial_state = project.factory.blank_state(
addr=start_address,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS,
},
)
input_length_in_bits = 8 * 8
password0 = claripy.BVS("password0", input_length_in_bits)
password1 = claripy.BVS("password1", input_length_in_bits)
mem_address0 = 0x804D020
mem_address1 = 0x804D030
buffer0_addr = 0xABCC8A4
buffer1_addr = 0xABCC8AC
initial_state.memory.store(mem_address0, password0)
initial_state.memory.store(mem_address1, password1)
initial_state.memory.store(buffer0_addr, mem_address0, 8, endness=project.arch.memory_endness)
initial_state.memory.store(buffer1_addr, mem_address1, 8, endness=project.arch.memory_endness)
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.solver.eval(password0, cast_to=bytes).decode()
solution1 = solution_state.solver.eval(password1, cast_to=bytes).decode()
solution = " ".join([solution0, solution1])
run_binary(solution, path_to_binary)
else:
raise Exception("Could not find the solution")
if __name__ == "__main__":
main(sys.argv)