Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/add to mem #75

Merged
merged 9 commits into from
Feb 6, 2024
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
Loading