Skip to content

Commit

Permalink
utilize all the concrete values in syscall preamble
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle-Kyle committed Mar 23, 2024
1 parent fc4e490 commit 7bfdf15
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 23 deletions.
49 changes: 36 additions & 13 deletions angrop/chain_builder/sys_caller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ def cmp(g1, g2):
if not g1.can_return and g2.can_return:
return 1

if g1.starts_with_syscall and not g2.starts_with_syscall:
if g1.num_mem_access < g2.num_mem_access:
return -1
if g2.starts_with_syscall and not g1.starts_with_syscall:
if g1.num_mem_access > g2.num_mem_access:
return 1

if g1.stack_change < g2.stack_change:
Expand All @@ -29,6 +29,7 @@ def cmp(g1, g2):
if g1.block_length > g2.block_length:
return 1
return 0

class SysCaller(FuncCaller):
"""
handle linux system calls invocations, only support i386 and x86_64 atm
Expand Down Expand Up @@ -106,8 +107,6 @@ def execve(self, path=None, path_addr=None):

return chain + chain2

# TODO handle mess ups by _find_reg_setting_gadgets and see if we can set a register in a syscall preamble
# or if a register value is explicitly set to just the right value
def do_syscall(self, syscall_num, args, needs_return=True, **kwargs):
"""
build a rop chain which performs the requested system call with the arguments set to 'registers' before
Expand All @@ -118,23 +117,47 @@ def do_syscall(self, syscall_num, args, needs_return=True, **kwargs):
:param needs_return: whether to continue the ROP after invoking the syscall
:return: a RopChain which makes the system with the requested register contents
"""
if not self.syscall_gadgets:
raise RopException("target does not contain syscall gadget!")

# set the system call number
extra_regs = {}
extra_regs[self.project.arch.register_names[self.project.arch.syscall_num_offset]] = syscall_num
cc = angr.SYSCALL_CC[self.project.arch.name]["default"](self.project.arch)

# find small stack change syscall gadget that also fits the stack arguments we want
# FIXME: does any arch/OS take syscall arguments on stack? (windows? sysenter?)
if len(args) > len(cc.ARG_REGS):
raise NotImplementedError("Currently, we can't handle on stack system call arguments!")
registers = {}
for arg, reg in zip(args, cc.ARG_REGS):
registers[reg] = arg

sysnum_reg = self.project.arch.register_names[self.project.arch.syscall_num_offset]
registers[sysnum_reg] = syscall_num

# do per-request gadget filtering
gadgets = self.syscall_gadgets
if needs_return:
gadgets = [x for x in gadgets if x.can_return]
gadgets = [x for x in gadgets if
all(y not in registers or x.concrete_regs[y] == registers[y] for y in x.concrete_regs)]
gadgets = sorted(gadgets, reverse=True, key=lambda x: len(set(x.concrete_regs.keys()).intersection(registers.keys())))

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}
if sysnum_reg in to_set_regs:
extra_regs = {sysnum_reg: syscall_num}
del to_set_regs[sysnum_reg]
else:
extra_regs = {}
preserve_regs = set(registers.keys()) - set(to_set_regs.keys())
if sysnum_reg in preserve_regs:
preserve_regs.remove(sysnum_reg)
self.project.factory.block(gadget.addr).pp()

if not self.syscall_gadgets:
raise RopException("target does not contain syscall gadget!")

for gadget in self.syscall_gadgets:
if needs_return and not gadget.can_return:
continue
try:
return self._func_call(gadget, cc, args, extra_regs=extra_regs,
needs_return=needs_return, **kwargs)
needs_return=needs_return, preserve_regs=preserve_regs, **kwargs)
except Exception: # pylint: disable=broad-exception-caught
continue

Expand Down
20 changes: 12 additions & 8 deletions angrop/gadget_finder/gadget_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def _create_gadget(self, addr, init_state, final_state, ctrl_type):
self._check_reg_movers(init_state, final_state, reg_reads, gadget)

# check concretized registers
self._analyze_concrete_regs(final_state, gadget)
self._analyze_concrete_regs(init_state, final_state, gadget)

# check mem accesses
l.debug("... analyzing mem accesses")
Expand All @@ -316,10 +316,14 @@ def _create_gadget(self, addr, init_state, final_state, ctrl_type):

return gadget

def _analyze_concrete_regs(self, state, 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)
"""
if type(gadget) == SyscallGadget:
state = self._windup_to_presyscall_state(final_state, init_state)
else:
state = final_state
for reg in self.arch.reg_set:
val = state.registers.load(reg)
if val.symbolic:
Expand Down Expand Up @@ -804,20 +808,20 @@ def _starts_with_syscall(self, addr):

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

def _windup_to_presyscall_state(self, symbolic_p, symbolic_state):
def _windup_to_presyscall_state(self, final_state, init_state):
"""
Retrieve the state of a gadget just before the syscall is made
:param symbolic_p: the stepped path, symbolic_state is an ancestor of it.
:param symbolic_state: input state for testing
"""

if self._does_syscall(symbolic_p):
if self._does_syscall(final_state) or self.is_in_kernel(final_state):
# step up until the syscall and save the possible syscall numbers into the gadget
prev = cur = symbolic_state
while not self._does_syscall(cur):
succ = self.project.factory.successors(cur)
prev = cur = init_state.copy()
while not self._does_syscall(cur) and not self.is_in_kernel(cur):
tmp = rop_utils.step_one_inst(self.project, cur.copy(), stop_at_syscall=True)
prev = cur
cur = succ.flat_successors[0]
cur = tmp
return prev.copy()

raise RopException("Gadget passed to _windup_to_presyscall_state does not make a syscall")
Expand Down
5 changes: 3 additions & 2 deletions angrop/rop_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,9 @@ def step_to_unconstrained_successor(project, state, max_steps=2, allow_simproced
if not allow_simprocedures and project.is_hooked(succ.flat_successors[0].addr):
# it cannot be a syscall as now syscalls are not explicitly hooked
raise RopException("Skipping simprocedure")
return step_to_unconstrained_successor(project, succ.flat_successors[0],
max_steps-1, allow_simprocedures)
return step_to_unconstrained_successor(project, succ.flat_successors[0], max_steps=max_steps-1,
allow_simprocedures=allow_simprocedures, stop_at_syscall=stop_at_syscall,
precise_action=precise_action)
if len(succ.flat_successors) == 1 and max_steps == 0:
raise RopException("Does not get to an unconstrained successor")
return succ.unconstrained_successors[0]
Expand Down
15 changes: 15 additions & 0 deletions tests/test_chainbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ def test_i386_syscall():
state = chain.exec()
assert state.posix.dumps(1) == b'/usr/share/locale'

def test_x86_64_syscall():
cache_path = os.path.join(CACHE_DIR, "amd64_glibc_2.19")
proj = angr.Project(os.path.join(BIN_DIR, "tests", "x86_64", "libc.so.6"), auto_load_libs=False)
rop = proj.analyses.ROP()

if os.path.exists(cache_path):
rop.load_gadgets(cache_path)
else:
rop.find_gadgets()
rop.save_gadgets(cache_path)

gadget = rop.analyze_gadget(0x536715)
rop.chain_builder._sys_caller.syscall_gadgets = [gadget]
chain = rop.do_syscall(0xca, [0, 0x81], needs_return=False)

def test_preserve_regs():
cache_path = os.path.join(CACHE_DIR, "1after909")
proj = angr.Project(os.path.join(BIN_DIR, "tests", "x86_64", "1after909"), auto_load_libs=False)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_gadgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,27 @@ def test_syscall_gadget():
assert gadget.stack_change == 0
assert not gadget.can_return

proj = angr.Project(os.path.join(BIN_DIR, "tests", "x86_64", "libc.so.6"), auto_load_libs=False)
rop = proj.analyses.ROP(fast_mode=False)

gadget = rop.analyze_gadget(0x4c1330)
assert type(gadget) == SyscallGadget
assert gadget.stack_change == 0
assert not gadget.can_return
assert len(gadget.concrete_regs) == 1 and gadget.concrete_regs.pop('rax') == 0x3b

gadget = rop.analyze_gadget(0x4c1437)
assert type(gadget) == SyscallGadget
assert gadget.stack_change == 0
assert not gadget.can_return
assert len(gadget.concrete_regs) == 1 and gadget.concrete_regs.pop('rax') == 0x3b

gadget = rop.analyze_gadget(0x536715)
assert type(gadget) == SyscallGadget
assert gadget.stack_change == 0
assert not gadget.can_return
assert len(gadget.concrete_regs) == 1 and gadget.concrete_regs.pop('rsi') == 0x81

def run_all():
functions = globals()
all_functions = {x:y for x, y in functions.items() if x.startswith('test_')}
Expand Down

0 comments on commit 7bfdf15

Please sign in to comment.