Skip to content

Commit

Permalink
add syscallgadget prologue to accurately track gadget effect before i…
Browse files Browse the repository at this point in the history
…nvoking the syscall
  • Loading branch information
Kyle-Kyle committed Feb 12, 2025
1 parent b0e7270 commit 7c0937c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 33 deletions.
11 changes: 9 additions & 2 deletions angrop/chain_builder/sys_caller.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,10 @@ def key_func(x):
return len(set(x.concrete_regs.keys()).intersection(registers.keys()))
gadgets = sorted(gadgets, reverse=True, key=key_func)

more = kwargs.pop('preserve_regs', set())
for gadget in gadgets:
# separate registers to args and extra_regs
to_set_regs = {x:y for x,y in registers.items() if x not in gadget.concrete_regs}
to_set_regs = {x:y for x,y in registers.items() if x not in gadget.prologue.concrete_regs}
if sysnum_reg in to_set_regs:
extra_regs = {sysnum_reg: syscall_num}
del to_set_regs[sysnum_reg]
Expand All @@ -160,9 +161,15 @@ def key_func(x):
preserve_regs = set(registers.keys()) - set(to_set_regs.keys())
if sysnum_reg in preserve_regs:
preserve_regs.remove(sysnum_reg)
more = kwargs.pop('preserve_regs', set())
preserve_regs.update(more)

# now check whether the prologue clobbers the registers
clobbered_regs = gadget.prologue.changed_regs - gadget.prologue.popped_regs
if clobbered_regs.intersection(preserve_regs):
continue
if clobbered_regs.intersection(set(to_set_regs.keys())):
continue

try:
return self._func_call(gadget, cc, args, extra_regs=extra_regs,
needs_return=needs_return, preserve_regs=preserve_regs, **kwargs)
Expand Down
90 changes: 61 additions & 29 deletions angrop/gadget_finder/gadget_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,28 @@ def filter_func(state):

return final_states, bad_states

def _at_syscall(self, state):
return self.project.factory.block(state.addr,
num_inst=1).vex.jumpkind.startswith("Ijk_Sys")

def _step_to_syscall(self, state):
"""
windup state to a state just about to make a syscall
"""

if self._at_syscall(state):
return state

simgr = state.project.factory.simgr(state)
while True:
simgr.step(num_inst=1)
if not simgr.active:
raise RuntimeError("unable to reach syscall instruction")
state = simgr.active[0]
if self._at_syscall(state):
return state
return None

@rop_utils.timeout(3)
def _analyze_gadget(self, addr, allow_conditional_branches):
l.info("Analyzing 0x%x", addr)
Expand Down Expand Up @@ -316,18 +338,7 @@ def _control_to_transit_type(ctrl_type):
case _:
raise ValueError("Unknown control type")

def _create_gadget(self, addr, init_state, final_state, ctrl_type, do_cond_branch):
# create the gadget
if ctrl_type == 'syscall' or self._does_syscall(final_state):
# gadgets that do syscall and pivoting are too complicated
if self._does_pivot(final_state):
return None
gadget = SyscallGadget(addr=addr)
elif ctrl_type == 'pivot' or self._does_pivot(final_state):
gadget = PivotGadget(addr=addr)
else:
gadget = RopGadget(addr=addr)

def _effect_analysis(self, gadget, init_state, final_state, ctrl_type, do_cond_branch):
# compute sp change
l.debug("... computing sp change")
self._compute_sp_change(init_state, final_state, gadget)
Expand All @@ -336,23 +347,24 @@ def _create_gadget(self, addr, init_state, final_state, ctrl_type, do_cond_branc
return None

# transit_type-based handling
transit_type = self._control_to_transit_type(ctrl_type)
gadget.transit_type = transit_type
arch_bits = self.project.arch.bits
match transit_type:
case 'pop_pc': # record pc_offset
idx = list(final_state.ip.variables)[0].split('_')[2]
gadget.pc_offset = int(idx) * self.project.arch.bytes
if gadget.pc_offset >= gadget.stack_change:
return None
case 'jmp_reg': # record pc_reg
gadget.pc_reg = list(final_state.ip.variables)[0].split('_', 1)[1].rsplit('-')[0]
case 'jmp_mem': # record pc_target
for a in reversed(final_state.history.actions):
if a.type == 'mem' and a.action == 'read' and a.size == arch_bits:
if (a.data.ast == final_state.ip).is_true():
gadget.pc_target = a.addr.ast
break
if ctrl_type is not None:
transit_type = self._control_to_transit_type(ctrl_type)
gadget.transit_type = transit_type
arch_bits = self.project.arch.bits
match transit_type:
case 'pop_pc': # record pc_offset
idx = list(final_state.ip.variables)[0].split('_')[2]
gadget.pc_offset = int(idx) * self.project.arch.bytes
if gadget.pc_offset >= gadget.stack_change:
return None
case 'jmp_reg': # record pc_reg
gadget.pc_reg = list(final_state.ip.variables)[0].split('_', 1)[1].rsplit('-')[0]
case 'jmp_mem': # record pc_target
for a in reversed(final_state.history.actions):
if a.type == 'mem' and a.action == 'read' and a.size == arch_bits:
if (a.data.ast == final_state.ip).is_true():
gadget.pc_target = a.addr.ast
break

# register effect analysis
l.info("... checking for controlled regs")
Expand Down Expand Up @@ -410,6 +422,26 @@ def _create_gadget(self, addr, init_state, final_state, ctrl_type, do_cond_branc

return gadget

def _create_gadget(self, addr, init_state, final_state, ctrl_type, do_cond_branch):
# create the gadget
if ctrl_type == 'syscall' or self._does_syscall(final_state):
# gadgets that do syscall and pivoting are too complicated
if self._does_pivot(final_state):
return None
prologue_state = self._step_to_syscall(init_state)
g = RopGadget(addr=addr)
if init_state.addr != prologue_state.addr:
self._effect_analysis(g, init_state, prologue_state, None, do_cond_branch)
gadget = SyscallGadget(addr=addr)
gadget.prologue = g
elif ctrl_type == 'pivot' or self._does_pivot(final_state):
gadget = PivotGadget(addr=addr)
else:
gadget = RopGadget(addr=addr)

gadget = self._effect_analysis(gadget, init_state, final_state, ctrl_type, do_cond_branch)
return gadget

def _analyze_concrete_regs(self, init_state, final_state, gadget):
"""
collect registers that are concretized after symbolically executing the block (for example, xor rax, rax)
Expand Down
17 changes: 15 additions & 2 deletions angrop/rop_gadget.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,12 @@ class SyscallGadget(RopGadget):
2. without return: syscall; xxxx
"""
def __init__(self, addr):
# the "gadget" right before the syscall invocation
# we need this to track how it affects the syscall invocation
self.prologue = None
self._project = None

super().__init__(addr)
self.makes_syscall = False

def __str__(self):
s = f"SyscallGadget {self.addr:#x}\n"
Expand All @@ -294,13 +298,22 @@ def __str__(self):
def __repr__(self):
return f"<SyscallGadget {self.addr:#x}>"

@property
def project(self):
return self._project

@project.setter
def project(self, proj):
self._project = proj
if self.prologue:
self.prologue.project = proj

@property
def can_return(self):
return self.transit_type is not None

def copy(self):
new = super().copy()
new.makes_syscall = self.makes_syscall
return new

class FunctionGadget(RopGadget):
Expand Down
5 changes: 5 additions & 0 deletions tests/test_gadgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ def test_syscall_gadget():
assert not gadget.can_return
assert len(gadget.concrete_regs) == 1 and gadget.concrete_regs.pop('rsi') == 0x81

proj = angr.Project(os.path.join(BIN_DIR, "tests", "cgc", "sc1_0b32aa01_01"), auto_load_libs=False)
rop = proj.analyses.ROP()
g = rop.analyze_gadget(0x0804843c)
assert g.prologue and isinstance(g, RopGadget)

def test_pop_pc_gadget():
proj = angr.Project(os.path.join(BIN_DIR, "tests", "mipsel", "darpa_ping"), auto_load_libs=False)
rop = proj.analyses.ROP()
Expand Down

0 comments on commit 7c0937c

Please sign in to comment.