Skip to content

Commit

Permalink
Merge pull request #75 from angr/fix/add_to_mem
Browse files Browse the repository at this point in the history
Fix/add to mem
  • Loading branch information
Kyle-Kyle authored Feb 6, 2024
2 parents 9099dc3 + 13375b6 commit b1d7aa8
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 140 deletions.
13 changes: 12 additions & 1 deletion angrop/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ class ARM(ROPArch):
def __init__(self, project, kernel_mode=False):
super().__init__(project, kernel_mode=kernel_mode)
self.is_thumb = False # by default, we don't use thumb mode
self.alignment = self.project.arch.bytes

def set_thumb(self):
self.is_thumb = True
self.alignment = 2

def set_arm(self):
self.is_thumb = False
self.alignment = self.project.arch.bytes

def block_make_sense(self, block):
# disable conditional jumps, for now
Expand All @@ -66,7 +75,9 @@ def block_make_sense(self, block):
return True

class MIPS(ROPArch):
pass
def __init__(self, project, kernel_mode=False):
super().__init__(project, kernel_mode=kernel_mode)
self.alignment = self.project.arch.bytes

def get_arch(project, kernel_mode=False):
name = project.arch.name
Expand Down
6 changes: 4 additions & 2 deletions angrop/chain_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, project, gadgets, arch, badbytes, roparg_filler):

def set_regs(self, *args, **kwargs):
"""
:param preserve_regs: set of registers to preserve, e.g. ('eax', 'ebx')
:param registers: dict of registers to values
:return: a chain which will set the registers to the requested values
Expand All @@ -51,6 +52,7 @@ def set_regs(self, *args, **kwargs):

def move_regs(self, **registers):
"""
:param preserve_regs: set of registers to preserve, e.g. ('eax', 'ebx')
:param registers: dict of registers, key is the destination register, value is the source register
:return: a chain which will set the registers to the requested registers
Expand Down Expand Up @@ -87,7 +89,7 @@ def func_call(self, address, args, **kwargs):
"""
:param address: address or name of function to call
:param args: a list/tuple of arguments to the function
:param preserve_regs: list of registers which shouldn't be set
:param preserve_regs: set of registers to preserve, e.g. ('eax', 'ebx')
:param needs_return: whether to continue the ROP after invoking the function
:return: a RopChain which inovkes the function with the arguments
"""
Expand All @@ -99,7 +101,7 @@ def do_syscall(self, syscall_num, args, needs_return=True, **kwargs):
the call is made
:param syscall_num: the syscall number to execute
:param args: the register values to have set at system call time
:param preserve_regs: list of registers which shouldn't be set
:param preserve_regs: set of registers to preserve, e.g. ('eax', 'ebx')
:param needs_return: whether to continue the ROP after invoking the syscall
:return: a RopChain which makes the system with the requested register contents
"""
Expand Down
44 changes: 31 additions & 13 deletions angrop/chain_builder/mem_changer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class MemChanger(Builder):
def __init__(self, chain_builder):
super().__init__(chain_builder)
self._mem_change_gadgets = self._get_all_mem_change_gadgets(self.chain_builder.gadgets)
self._mem_add_gadgets = self._get_all_mem_add_gadgets()

def _set_regs(self, *args, **kwargs):
return self.chain_builder._reg_setter.run(*args, **kwargs)
Expand All @@ -30,19 +31,23 @@ def _get_all_mem_change_gadgets(gadgets):
if g.stack_change <= 0:
continue
for m_access in g.mem_changes:
# assume we need intersection of addr_dependencies and data_dependencies to be 0
if m_access.addr_controllable() and m_access.data_controllable() and m_access.addr_data_independent():
possible_gadgets.add(g)
return possible_gadgets

def _get_all_mem_add_gadgets(self):
return {x for x in self._mem_change_gadgets if x.mem_changes[0].op in ('__add__', '__sub__')}

def add_to_mem(self, addr, value, data_size=None):
# assume we need intersection of addr_dependencies and data_dependencies to be 0
# TODO could allow mem_reads as long as we control the address?

if data_size is None:
data_size = self.project.arch.bits

possible_gadgets = {x for x in self._mem_change_gadgets
if x.mem_changes[0].op in ('__add__', '__sub__') and x.mem_changes[0].data_size == data_size}
possible_gadgets = {x for x in self._mem_add_gadgets if x.mem_changes[0].data_size == data_size}
if not possible_gadgets:
raise RopException("Fail to find any gadget that can perform memory adding...")

# get the data from trying to set all the registers
registers = dict((reg, 0x41) for reg in self.chain_builder.arch.reg_set)
Expand All @@ -69,10 +74,23 @@ def add_to_mem(self, addr, value, data_size=None):
l.debug("Now building the mem add chain")

# build the chain
chain = self._change_mem_with_gadget(best_gadget, addr, data_size, difference=value)
chain = self._add_mem_with_gadget(best_gadget, addr, data_size, difference=value)

# verify the chain actually works
chain2 = chain.copy()
chain2._blank_state.memory.store(addr.data, 0x42424242, self.project.arch.bytes)
state = chain2.exec()
sim_data = state.memory.load(addr.data, self.project.arch.bytes)
if not state.solver.eval(sim_data == 0x42424242 + value.data):
raise RopException("memory add fails - 1")
# the next pc must come from the stack
if len(state.regs.pc.variables) != 1:
raise RopException("memory add fails - 2")
if not set(state.regs.pc.variables).pop().startswith("symbolic_stack"):
raise RopException("memory add fails - 3")
return chain

def _change_mem_with_gadget(self, gadget, addr, data_size, final_val=None, difference=None):
def _add_mem_with_gadget(self, gadget, addr, data_size, final_val=None, difference=None):
# sanity check for simple gadget
if len(gadget.mem_writes) + len(gadget.mem_changes) != 1 or len(gadget.mem_reads) != 0:
raise RopException("too many memory accesses for my lazy implementation")
Expand All @@ -87,9 +105,9 @@ def _change_mem_with_gadget(self, gadget, addr, data_size, final_val=None, diffe
test_state = self.make_sim_state(gadget.addr)

if difference is not None:
test_state.memory.store(addr, test_state.solver.BVV(~difference, data_size)) # pylint:disable=invalid-unary-operand-type
test_state.memory.store(addr.concreted, test_state.solver.BVV(~(difference.concreted), data_size)) # pylint:disable=invalid-unary-operand-type
if final_val is not None:
test_state.memory.store(addr, test_state.solver.BVV(~final_val, data_size)) # pylint:disable=invalid-unary-operand-type
test_state.memory.store(addr.concreted, test_state.solver.BVV(~final_val, data_size)) # pylint:disable=invalid-unary-operand-type

# step the gadget
pre_gadget_state = test_state
Expand All @@ -109,20 +127,20 @@ def _change_mem_with_gadget(self, gadget, addr, data_size, final_val=None, diffe
raise RopException("Couldn't find the matching action")

# constrain the addr
test_state.add_constraints(the_action.addr.ast == addr)
pre_gadget_state.add_constraints(the_action.addr.ast == addr)
test_state.add_constraints(the_action.addr.ast == addr.concreted)
pre_gadget_state.add_constraints(the_action.addr.ast == addr.concreted)
pre_gadget_state.options.discard(angr.options.AVOID_MULTIVALUED_WRITES)
pre_gadget_state.options.discard(angr.options.AVOID_MULTIVALUED_READS)
state = rop_utils.step_to_unconstrained_successor(self.project, pre_gadget_state)

# constrain the data
if final_val is not None:
test_state.add_constraints(state.memory.load(addr, data_size//8, endness=arch_endness) ==
test_state.add_constraints(state.memory.load(addr.concreted, data_size//8, endness=arch_endness) ==
test_state.solver.BVV(final_val, data_size))
if difference is not None:
test_state.add_constraints(state.memory.load(addr, data_size//8, endness=arch_endness) -
test_state.memory.load(addr, data_size//8, endness=arch_endness) ==
test_state.solver.BVV(difference, data_size))
test_state.add_constraints(state.memory.load(addr.concreted, data_size//8, endness=arch_endness) -
test_state.memory.load(addr.concreted, data_size//8, endness=arch_endness) ==
test_state.solver.BVV(difference.concreted, data_size))

# get the actual register values
all_deps = list(mem_change.addr_dependencies) + list(mem_change.data_dependencies)
Expand Down
5 changes: 5 additions & 0 deletions angrop/chain_builder/mem_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,9 @@ def _write_to_mem_with_gadget(self, gadget, addr_val, data, use_partial_controll
sim_data = state.memory.load(addr_val.data, len(data))
if not state.solver.eval(sim_data == data):
raise RopException("memory write fails")
# the next pc must come from the stack
if len(state.regs.pc.variables) != 1:
return False
if not set(state.regs.pc.variables).pop().startswith("symbolic_stack"):
return False
return chain
5 changes: 5 additions & 0 deletions angrop/chain_builder/reg_mover.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ def verify(self, chain, preserve_regs, registers):
if reg_name in preserve_regs:
l.exception("Somehow angrop thinks \n%s\n can be used for the chain generation.", chain_str)
return False
# the next pc must come from the stack
if len(state.regs.pc.variables) != 1:
return False
if not set(state.regs.pc.variables).pop().startswith("symbolic_stack"):
return False
return True

def _recursively_find_chains(self, gadgets, chain, hard_preserve_regs, todo_moves):
Expand Down
9 changes: 7 additions & 2 deletions angrop/chain_builder/reg_setter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ def verify(self, chain, preserve_regs, registers):
offset -= act.offset % self.project.arch.bytes
reg_name = self.project.arch.register_size_names[offset, self.project.arch.bytes]
if reg_name in preserve_regs:
l.exception("Somehow angrop thinks \n%s\n can be used for the chain generation.", chain_str)
l.exception("Somehow angrop thinks \n%s\n can be used for the chain generation - 1.", chain_str)
return False
if bv.symbolic or state.solver.eval(bv != val.data):
l.exception("Somehow angrop thinks \n%s\n can be used for the chain generation.", chain_str)
l.exception("Somehow angrop thinks \n%s\n can be used for the chain generation - 2.", chain_str)
return False
# the next pc must come from the stack
if len(state.regs.pc.variables) != 1:
return False
if not set(state.regs.pc.variables).pop().startswith("symbolic_stack"):
return False
return True

def run(self, modifiable_memory_range=None, use_partial_controllers=False, preserve_regs=None, **registers):
Expand Down
Loading

0 comments on commit b1d7aa8

Please sign in to comment.