diff --git a/angrop/gadget_finder/gadget_analyzer.py b/angrop/gadget_finder/gadget_analyzer.py index 9aae7a7..20f57d5 100644 --- a/angrop/gadget_finder/gadget_analyzer.py +++ b/angrop/gadget_finder/gadget_analyzer.py @@ -710,6 +710,8 @@ def _build_mem_access(self, a, gadget, init_state, final_state): mem_access.addr_dependencies = rop_utils.get_ast_dependency(a.addr.ast) mem_access.addr_controllers = rop_utils.get_ast_controllers(init_state, a.addr.ast, mem_access.addr_dependencies) + mem_access.addr_offset = rop_utils.get_ast_const_offset(init_state, a.addr.ast, + mem_access.addr_dependencies) # case 3: the symbolic address comes from controlled stack elif all(x.startswith("symbolic_stack") for x in a.addr.ast.variables): mem_access.addr_stack_controllers = set(a.addr.ast.variables) diff --git a/angrop/rop_gadget.py b/angrop/rop_gadget.py index 7564eac..3f3d8aa 100644 --- a/angrop/rop_gadget.py +++ b/angrop/rop_gadget.py @@ -6,6 +6,7 @@ class RopMemAccess: Attributes: addr_dependencies (set): All the registers that affect the memory address. addr_controller (set): All the registers that can determine the symbolic memory access address by itself + addr_offset (int): Constant offset in the memory address relative to register(s) addr_stack_controller (set): all the controlled gadgets on the stack that can determine the address by itself data_dependencies (set): All the registers that affect the data written. data_controller (set): All the registers that can determine the symbolic data by itself @@ -17,6 +18,7 @@ class RopMemAccess: def __init__(self): self.addr_dependencies = set() self.addr_controllers = set() + self.addr_offset: int | None = None self.addr_stack_controllers = set() self.data_dependencies = set() self.data_controllers = set() diff --git a/angrop/rop_utils.py b/angrop/rop_utils.py index 40fc45a..9eb7472 100644 --- a/angrop/rop_utils.py +++ b/angrop/rop_utils.py @@ -68,6 +68,28 @@ def get_ast_controllers(state, ast, reg_deps) -> set: return controllers +def get_ast_const_offset(state, ast, reg_deps) -> int: + """ + Gets the constant offset for a memory access + :param state: the input state + :param ast: the ast of which we are trying to analyze controllers + :param reg_deps: All registers which it depends on + :return: Constant value + """ + size = ast.size() + zero_val = claripy.BVV(0, size) + + # Replace symbolic values with zero to get the constant value + # This is faster than eval with extra contraints + for reg in reg_deps: + reg_val = state.registers.load(reg) + ast = claripy.algorithm.replace( + expr=ast, old=reg_val, new=zero_val) + + assert not ast.symbolic + return state.solver.eval(ast) + + def unconstrained_check(state, ast, extra_constraints=None): """ Attempts to check if an ast is completely unconstrained diff --git a/tests/test_gadgets.py b/tests/test_gadgets.py index 52937b5..78c2e1c 100644 --- a/tests/test_gadgets.py +++ b/tests/test_gadgets.py @@ -271,6 +271,14 @@ def test_syscall_gadget(): assert not gadget.can_return assert len(gadget.concrete_regs) == 1 and gadget.concrete_regs.pop('rax') == 0x3b + gadget = rop.analyze_gadget(0x521cef) + assert type(gadget) == RopGadget + assert len(gadget.mem_writes) == 1 + mem_write = gadget.mem_writes[0] + assert mem_write.addr_offset == 0x68 + assert len(mem_write.addr_controllers) == 1 and 'rdx' in mem_write.addr_controllers + assert len(mem_write.data_controllers) == 1 and 'rcx' in mem_write.data_controllers + gadget = rop.analyze_gadget(0x4c1437) assert type(gadget) == SyscallGadget assert gadget.stack_change == 0