Skip to content

Commit

Permalink
Merge pull request #71 from angr/feat/reg_mover
Browse files Browse the repository at this point in the history
Feat/reg mover
  • Loading branch information
Kyle-Kyle authored Jan 31, 2024
2 parents 8ce4b72 + 387e0f3 commit 48fe183
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 209 deletions.
24 changes: 18 additions & 6 deletions angrop/chain_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import claripy

from .reg_setter import RegSetter
from .reg_mover import RegMover
from .mem_writer import MemWriter
from .. import rop_utils
from .. import common
Expand All @@ -23,7 +24,7 @@ class ChainBuilder:
This class provides functions to generate common ropchains based on existing gadgets.
"""

def __init__(self, project, gadgets, duplicates, reg_list, base_pointer, badbytes, roparg_filler, rebase=True):
def __init__(self, project, gadgets, duplicates, reg_list, base_pointer, badbytes, roparg_filler):
"""
Initializes the chain builder.
Expand All @@ -43,7 +44,6 @@ def __init__(self, project, gadgets, duplicates, reg_list, base_pointer, badbyte
self._base_pointer = base_pointer
self.badbytes = badbytes
self._roparg_filler = roparg_filler
self._rebase = rebase

self._syscall_instruction = None
if self.project.arch.linux_name == "x86_64":
Expand All @@ -67,9 +67,11 @@ def __init__(self, project, gadgets, duplicates, reg_list, base_pointer, badbyte
self._filtered_reg_gadgets = None

self._reg_setter = RegSetter(project, gadgets, reg_list=reg_list, badbytes=badbytes,
rebase=self._rebase, filler=self._roparg_filler)
filler=self._roparg_filler)
self._reg_mover = RegMover(project, gadgets, reg_list=reg_list, badbytes=badbytes,
filler=self._roparg_filler)
self._mem_writer = MemWriter(project, self._reg_setter, base_pointer, gadgets, badbytes=badbytes,
rebase=self._rebase, filler=self._roparg_filler)
filler=self._roparg_filler)

def _contain_badbyte(self, ptr):
"""
Expand Down Expand Up @@ -121,6 +123,16 @@ def set_regs(self, *args, **kwargs):

return self._reg_setter.run(*args, **kwargs)

def move_regs(self, **registers):
"""
: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
example:
chain = rop.move_regs(rax='rcx', rcx='rbx')
"""
return self._reg_mover.run(**registers)

def _func_call(self, func_gadget, cc, args, extra_regs=None, modifiable_memory_range=None, ignore_registers=None,
use_partial_controllers=False, needs_return=True):
assert type(args) in [list, tuple], "function arguments must be a list or tuple!"
Expand Down Expand Up @@ -148,7 +160,7 @@ def _func_call(self, func_gadget, cc, args, extra_regs=None, modifiable_memory_r
# invoke the function
chain.add_gadget(func_gadget)
for _ in range(func_gadget.stack_change//arch_bytes-1):
chain.add_value(self._get_fill_val(), needs_rebase=False)
chain.add_value(self._get_fill_val())

# we are done here if there is no stack arguments
if not stack_arguments:
Expand Down Expand Up @@ -184,7 +196,7 @@ def _func_call(self, func_gadget, cc, args, extra_regs=None, modifiable_memory_r
chain.add_gadget(stack_cleaner)
stack_arguments += [self._get_fill_val()]*(stack_cleaner.stack_change//arch_bytes - len(stack_arguments)-1)
for arg in stack_arguments:
chain.add_value(arg, needs_rebase=False)
chain.add_value(arg)

return chain

Expand Down
154 changes: 154 additions & 0 deletions angrop/chain_builder/builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import struct
from functools import cmp_to_key

import claripy

from .. import rop_utils
from ..errors import RopException
from ..rop_value import RopValue
from ..rop_chain import RopChain

class Builder:
"""
a generic class to bootstrap more complicated chain building functionality
"""
def __init__(self, project, reg_list=None, badbytes=None, filler=None):
self.project = project
self._reg_set = set(reg_list)
self._badbytes = badbytes
self._roparg_filler = filler

@staticmethod
def _sort_chains(chains):
def cmp_func(chain1, chain2):
stack_change1 = sum(x.stack_change for x in chain1)
stack_change2 = sum(x.stack_change for x in chain2)
if stack_change1 > stack_change2:
return 1
elif stack_change1 < stack_change2:
return -1

num_mem_access1 = sum(x.num_mem_access for x in chain1)
num_mem_access2 = sum(x.num_mem_access for x in chain2)
if num_mem_access1 > num_mem_access2:
return 1
if num_mem_access1 < num_mem_access2:
return -1
return 0
return sorted(chains, key=cmp_to_key(cmp_func))

def _word_contain_badbyte(self, ptr):
"""
check if a pointer contains any bad byte
"""
if isinstance(ptr, RopValue):
if ptr.symbolic:
return False
else:
ptr = ptr.concreted
raw_bytes = struct.pack(self.project.arch.struct_fmt(), ptr)
if any(x in raw_bytes for x in self._badbytes):
return True
return False

@rop_utils.timeout(2)
def _build_reg_setting_chain(self, gadgets, modifiable_memory_range, register_dict, stack_change):
"""
This function figures out the actual values needed in the chain
for a particular set of gadgets and register values
This is done by stepping a symbolic state through each gadget
then constraining the final registers to the values that were requested
FIXME: trim this disgusting function
"""

# create a symbolic state
test_symbolic_state = rop_utils.make_symbolic_state(self.project, self._reg_set)
addrs = [g.addr for g in gadgets]
addrs.append(test_symbolic_state.solver.BVS("next_addr", self.project.arch.bits))

arch_bytes = self.project.arch.bytes
arch_endness = self.project.arch.memory_endness

# emulate a 'pop pc' of the first gadget
state = test_symbolic_state
state.regs.ip = addrs[0]
# the stack pointer must begin pointing to our first gadget
state.add_constraints(state.memory.load(state.regs.sp, arch_bytes, endness=arch_endness) == addrs[0])
# push the stack pointer down, like a pop would do
state.regs.sp += arch_bytes
state.solver._solver.timeout = 5000

# step through each gadget
# for each gadget, constrain memory addresses and add constraints for the successor
for addr in addrs[1:]:
succ = rop_utils.step_to_unconstrained_successor(self.project, state)
state.add_constraints(succ.regs.ip == addr)
# constrain reads/writes
for a in succ.log.actions:
if a.type == "mem" and a.addr.ast.symbolic:
if modifiable_memory_range is None:
raise RopException("Symbolic memory address when there shouldnt have been")
test_symbolic_state.add_constraints(a.addr.ast >= modifiable_memory_range[0])
test_symbolic_state.add_constraints(a.addr.ast < modifiable_memory_range[1])
test_symbolic_state.add_constraints(succ.regs.ip == addr)
# get to the unconstrained successor
state = rop_utils.step_to_unconstrained_successor(self.project, state)

# re-adjuest the stack pointer
sp = test_symbolic_state.regs.sp
sp -= arch_bytes
bytes_per_pop = arch_bytes

# constrain the final registers
rebase_state = test_symbolic_state.copy()
var_dict = {}
for r, v in register_dict.items():
var = claripy.BVS(r, self.project.arch.bits)
var_name = var._encoded_name.decode()
var_dict[var_name] = v
test_symbolic_state.add_constraints(state.registers.load(r) == var)
test_symbolic_state.add_constraints(var == v.data)

# constrain the "filler" values
if self._roparg_filler is not None:
for i in range(stack_change // bytes_per_pop):
sym_word = test_symbolic_state.memory.load(sp + bytes_per_pop*i, bytes_per_pop,
endness=self.project.arch.memory_endness)
# check if we can constrain val to be the roparg_filler
if test_symbolic_state.solver.satisfiable((sym_word == self._roparg_filler,)) and \
rebase_state.solver.satisfiable((sym_word == self._roparg_filler,)):
# constrain the val to be the roparg_filler
test_symbolic_state.add_constraints(sym_word == self._roparg_filler)
rebase_state.add_constraints(sym_word == self._roparg_filler)

# create the ropchain
chain = RopChain(self.project, self, state=test_symbolic_state.copy(),
badbytes=self._badbytes)

# iterate through the stack values that need to be in the chain
for i in range(stack_change // bytes_per_pop):
sym_word = test_symbolic_state.memory.load(sp + bytes_per_pop*i, bytes_per_pop,
endness=self.project.arch.memory_endness)

val = test_symbolic_state.solver.eval(sym_word)
if len(gadgets) > 0 and val == gadgets[0].addr:
chain.add_gadget(gadgets[0])
gadgets = gadgets[1:]
else:
# propagate the initial RopValue provided by users to preserve info like rebase
var = sym_word
for c in test_symbolic_state.solver.constraints:
if len(c.variables) != 2: # it is always xx == yy
continue
if not sym_word.variables.intersection(c.variables):
continue
var_name = set(c.variables - sym_word.variables).pop()
if var_name not in var_dict:
continue
var = var_dict[var_name]
break
chain.add_value(var)

if len(gadgets) > 0:
raise RopException("Didnt find all gadget addresses, something must've broke")
return chain
7 changes: 3 additions & 4 deletions angrop/chain_builder/mem_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ class MemWriter:
using various techniques
TODO: add a testcase for add_mem
"""
def __init__(self, project, reg_setter, base_pointer, gadgets, badbytes=None, rebase=False, filler=None):
def __init__(self, project, reg_setter, base_pointer, gadgets, badbytes=None, filler=None):
self.project = project
self.badbytes = badbytes

self._reg_setter = reg_setter
self._base_pointer = base_pointer
self._rebase = rebase
self._mem_write_gadgets = self._get_all_mem_write_gadgets(gadgets)
self._mem_change_gadgets = self._get_all_mem_change_gadgets(gadgets)
self._test_symbolic_state = rop_utils.make_symbolic_state(self.project, self._reg_setter._reg_set)
Expand Down Expand Up @@ -399,7 +398,7 @@ def _write_to_mem_with_gadget(self, gadget, addr_val, data, use_partial_controll

bytes_per_pop = self.project.arch.bytes
for _ in range(gadget.stack_change // bytes_per_pop - 1):
chain.add_value(self._get_fill_val(), needs_rebase=False)
chain.add_value(self._get_fill_val())

# verify the write actually works
state = chain.exec()
Expand Down Expand Up @@ -478,7 +477,7 @@ def _change_mem_with_gadget(self, gadget, addr, data_size, final_val=None, diffe

bytes_per_pop = self.project.arch.bytes
for _ in range(gadget.stack_change // bytes_per_pop - 1):
chain.add_value(self._get_fill_val(), needs_rebase=False)
chain.add_value(self._get_fill_val())
return chain

def _get_fill_val(self):
Expand Down
Loading

0 comments on commit 48fe183

Please sign in to comment.