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

Wip/dumb tracer #33

Merged
merged 36 commits into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e25e059
allow more symbolic memory access if the architecture does not have p…
Kyle-Kyle Feb 20, 2021
07f6088
sometimes we don't want angrop to perform its toy rebase analysis
Kyle-Kyle Feb 20, 2021
eb737ed
badbytes should be added to constraints
Kyle-Kyle Feb 20, 2021
7ae7352
the old method of handling badbytes filters some good addresses becau…
Kyle-Kyle Feb 20, 2021
339f8f1
try other memory write gadget if the 'best' gadget fails
Kyle-Kyle Feb 21, 2021
3b0e64a
lol legacy python2-3 issue
Kyle-Kyle Feb 21, 2021
e1c5149
add jump gadget
Kyle-Kyle Feb 21, 2021
41e8b01
handle some weird corner cases
Kyle-Kyle Feb 21, 2021
2c69ef3
give variables names that make sense
Kyle-Kyle Feb 21, 2021
b47a2f0
catch some error
Kyle-Kyle Feb 21, 2021
7b8dbb8
never crashes during gadget analysis
Kyle-Kyle Feb 22, 2021
e4b0190
change how caching works in angrop
Kyle-Kyle Feb 22, 2021
03172b1
do now allow analyzing a gadget for more than 3 seconds
Kyle-Kyle Feb 22, 2021
52df00f
goodbye python2
Kyle-Kyle Feb 22, 2021
1b50c78
handle instruction alignment in ARM
Kyle-Kyle Feb 23, 2021
d61ac18
oops
Kyle-Kyle Feb 23, 2021
d47e5a8
add some timeout
Kyle-Kyle Feb 23, 2021
169705b
fix timeout stub
Kyle-Kyle Feb 23, 2021
f1ced8a
filter gadgets containing badbytes from the beginning
Kyle-Kyle Feb 23, 2021
0da2da9
don't nuke gadgets, make it a property
Kyle-Kyle Feb 25, 2021
de2c937
rework angrop's message display
Kyle-Kyle Feb 25, 2021
eccb0dc
Add tqdm as a dependency.
ltfish Feb 25, 2021
e046c9e
rewrite how set_regs work completely
Kyle-Kyle Feb 26, 2021
2847ef1
alright, add the old code back. use the new approach as a backup
Kyle-Kyle Feb 26, 2021
9d04da7
disable conditional execution code, for now
Kyle-Kyle Feb 26, 2021
8cdd8a3
really disable conditional execution for now
Kyle-Kyle Feb 26, 2021
40423be
wow, we just hit a case where our new algorithm can find solution and…
Kyle-Kyle Feb 26, 2021
c3cb881
now that gadgets are filtered initially, we have to account for badby…
Lukas-Dresel Feb 26, 2021
0deb7a3
Limit MIPS gadgets and eliminate the ones whose return address is not…
ltfish Mar 1, 2021
954d2b6
trigger CI
Kyle-Kyle Apr 28, 2021
9613539
make pylint happy batch 1
Kyle-Kyle Apr 28, 2021
92a1d12
handle fillers in a disgusting way
Kyle-Kyle Apr 28, 2021
5cc2bad
make pylint happy
Kyle-Kyle Apr 28, 2021
563e338
apparently the new angrop finds unexpected but valid ropchain
Kyle-Kyle Apr 28, 2021
4a4449a
add test to ensure no conditional execution in arm because we don't m…
Kyle-Kyle May 7, 2021
0c64dc5
add some tests
Kyle-Kyle May 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 116 additions & 130 deletions angrop/chain_builder.py → angrop/chain_builder/__init__.py

Large diffs are not rendered by default.

511 changes: 511 additions & 0 deletions angrop/chain_builder/reg_setter.py

Large diffs are not rendered by default.

184 changes: 135 additions & 49 deletions angrop/gadget_analyzer.py

Large diffs are not rendered by default.

181 changes: 120 additions & 61 deletions angrop/rop.py

Large diffs are not rendered by default.

41 changes: 33 additions & 8 deletions angrop/rop_chain.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from . import rop_utils
from .errors import RopException

from cle.address_translator import AT

class RopChain(object):
class RopChain:
"""
This class holds rop chains returned by the rop chain building methods such as rop.set_regs()
"""
def __init__(self, project, rop, state=None):
def __init__(self, project, rop, state=None, rebase=True, badbytes=None):
"""
rebase=False will force everything to use the addresses in angr
"""
self._p = project
self._rop = rop

Expand All @@ -18,6 +22,8 @@ def __init__(self, project, rop, state=None):
self._blank_state = self._p.factory.blank_state() if state is None else state
self._pie = self._p.loader.main_object.image_base_delta != 0
self._rebase_val = self._blank_state.solver.BVS("base", self._p.arch.bits)
self._rebase = rebase
self.badbytes = badbytes if badbytes else []

def __add__(self, other):
# need to add the values from the other's stack and the constraints to the result state
Expand All @@ -34,7 +40,7 @@ def __add__(self, other):

def add_value(self, value, needs_rebase=False):
# override rebase if its not pie
if not self._pie:
if not self._rebase or not self._pie:
needs_rebase = False
if needs_rebase:
value -= self._p.loader.main_object.mapped_base
Expand All @@ -50,6 +56,7 @@ def add_constraint(self, cons):
"""
self._blank_state.add_constraints(cons)

@rop_utils.timeout(3)
def _concretize_chain_values(self, constraints=None):
"""
we all the flexibilty of chains to have symbolic values, this helper function
Expand All @@ -68,10 +75,21 @@ def _concretize_chain_values(self, constraints=None):

concrete_vals = []
for val, needs_rebase in self._values:
# if it is int, easy
if isinstance(val, int):
concrete_vals.append((val, needs_rebase))
else:
concrete_vals.append((solver_state.solver.eval(val), needs_rebase))
continue

# if it is symbolic, make sure it does not have badbytes in it
constraints = []
# for each byte, it should not be equal to any bad bytes
for idx in range(val.length//8):
b = val.get_byte(idx)
constraints += [ b != c for c in self.badbytes]
# apply the constraints
for expr in constraints:
solver_state.solver.add(expr)
concrete_vals.append((solver_state.solver.eval(val), needs_rebase))

return concrete_vals

Expand All @@ -91,6 +109,8 @@ def payload_str(self, constraints=None, base_addr=None):
test_state.stack_push(value)
sp = test_state.regs.sp
rop_str = test_state.solver.eval(test_state.memory.load(sp, self.payload_len), cast_to=bytes)
if any(bytes([c]) in rop_str for c in self.badbytes):
raise RopException()
return rop_str

def payload_bv(self):
Expand All @@ -103,7 +123,7 @@ def payload_bv(self):
sp = test_state.regs.sp
return test_state.memory.load(sp, self.payload_len)

def print_payload_code(self, constraints=None, print_instructions=True):
def payload_code(self, constraints=None, print_instructions=True):
"""
:param print_instructions: prints the instructions that the rop gadgets use
:return: prints the code for the rop payload
Expand Down Expand Up @@ -142,7 +162,10 @@ def print_payload_code(self, constraints=None, print_instructions=True):
else:
payload += "chain += " + pack % value + instruction_code
payload += "\n"
print(payload)
return payload

def print_payload_code(self, constraints=None, print_instructions=True):
print(self.payload_code(constraints=constraints, print_instructions=print_instructions))

def copy(self):
cp = RopChain(self._p, self._rop)
Expand All @@ -152,8 +175,10 @@ def copy(self):
cp._blank_state = self._blank_state.copy()
cp._pie = self._pie
cp._rebase_val = self._rebase_val
cp._rebase = self._rebase
cp.badbytes = self.badbytes

return cp

def __str__(self):
return self.payload_str()
return self.payload_code()
10 changes: 10 additions & 0 deletions angrop/rop_gadget.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ def __init__(self, addr):
self.block_length = None
self.makes_syscall = False
self.starts_with_syscall = False
self.gadget_type = None
self.jump_reg = None
self.pc_reg = None

@property
def num_mem_access(self):
return len(self.mem_reads) + len(self.mem_writes) + len(self.mem_changes)

def __str__(self):
s = "Gadget %#x\n" % self.addr
Expand Down Expand Up @@ -154,6 +161,9 @@ def copy(self):
out.block_length = self.block_length
out.makes_syscall = self.makes_syscall
out.starts_with_syscall = self.starts_with_syscall
out.gadget_type = self.gadget_type
out.jump_reg = self.jump_reg
out.pc_reg = self.pc_reg
return out


Expand Down
29 changes: 27 additions & 2 deletions angrop/rop_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import time
import signal

import angr
import claripy

Expand Down Expand Up @@ -217,5 +220,27 @@ def step_to_unconstrained_successor(project, state, max_steps=2, allow_simproced
raise RopException("Does not get to an unconstrained successor")
return succ.unconstrained_successors[0]

except (angr.errors.AngrError, angr.errors.SimError):
raise RopException("Does not get to a single unconstrained successor")
except (angr.errors.AngrError, angr.errors.SimError) as e:
raise RopException("Does not get to a single unconstrained successor") from e

def timeout(seconds_before_timeout):
def decorate(f):
def handler(signum, frame):# pylint:disable=unused-argument
print("[angrop] Timeout")
raise RopException("[angrop] Timeout!")
def new_f(*args, **kwargs):
old = signal.signal(signal.SIGALRM, handler)
old_time_left = signal.alarm(seconds_before_timeout)
if 0 < old_time_left < seconds_before_timeout: # never lengthen existing timer
signal.alarm(old_time_left)
start_time = time.time()
try:
result = f(*args, **kwargs)
finally:
if old_time_left > 0: # deduct f's run time from the saved timer
old_time_left -= int(time.time() - start_time)
signal.signal(signal.SIGALRM, old)
signal.alarm(old_time_left)
return result
return new_f
return decorate
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
packages=['angrop'],
install_requires=[
'progressbar',
'tqdm',
'angr==9.0.gitrolling',
],
)
54 changes: 54 additions & 0 deletions tests/test_gadgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import os

import angr
import angrop # pylint: disable=unused-import

BIN_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "binaries")

def test_arm_conditional():
"""
Currently, we don't model conditional execution in arm. So we don't allow
conditional execution in arm at this moment.
"""
proj = angr.Project(os.path.join(BIN_DIR, "tests", "armel", "helloworld"))
rop = proj.analyses.ROP(rebase=False)
rop.find_gadgets_single_threaded(show_progress=False)

cond_gadget_addrs = [0x10368, 0x1036c, 0x10370, 0x10380, 0x10384, 0x1038c, 0x1039c,
0x103a0, 0x103b8, 0x103bc, 0x103c4, 0x104e8, 0x104ec]

assert all(x.addr not in cond_gadget_addrs for x in rop._gadgets)

def test_jump_gadget():
"""
Ensure it finds gadgets ending with jumps
Ensure angrop can use jump gadgets to build ROP chains
"""
proj = angr.Project(os.path.join(BIN_DIR, "tests", "mipsel", "fauxware"))
rop = proj.analyses.ROP(rebase=False)
rop.find_gadgets_single_threaded(show_progress=False)

jump_gadgets = [x for x in rop._gadgets if x.gadget_type == "jump"]
assert len(jump_gadgets) > 0

jump_regs = [x.jump_reg for x in jump_gadgets]
assert 't9' in jump_regs
assert 'ra' in jump_regs

def run_all():
functions = globals()
all_functions = dict(filter((lambda kv: kv[0].startswith('test_')), functions.items()))
for f in sorted(all_functions.keys()):
if hasattr(all_functions[f], '__call__'):
all_functions[f]()

if __name__ == "__main__":
import sys
import logging

logging.getLogger("angrop.rop").setLevel(logging.DEBUG)

if len(sys.argv) > 1:
globals()['test_' + sys.argv[1]]()
else:
run_all()
30 changes: 24 additions & 6 deletions tests/test_rop.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def execute_chain(project, chain):
def test_rop_x86_64():
b = angr.Project(os.path.join(public_bin_location, "x86_64/datadep_test"))
rop = b.analyses.ROP()
rop.find_gadgets_single_threaded()
rop.find_gadgets_single_threaded(show_progress=False)

# check gadgets
test_gadgets, _ = pickle.load(open(os.path.join(test_data_location, "datadep_test_gadgets"), "rb"))
Expand All @@ -126,7 +126,7 @@ def test_rop_x86_64():
def test_rop_i386_cgc():
b = angr.Project(os.path.join(public_bin_location, "cgc/sc1_0b32aa01_01"))
rop = b.analyses.ROP()
rop.find_gadgets_single_threaded()
rop.find_gadgets_single_threaded(show_progress=False)

# check gadgets
test_gadgets, _, _ = pickle.load(open(os.path.join(test_data_location, "0b32aa01_01_gadgets"), "rb"))
Expand All @@ -150,7 +150,7 @@ def test_rop_i386_cgc():
def test_rop_arm():
b = angr.Project(os.path.join(public_bin_location, "armel/manysum"), load_options={"auto_load_libs": False})
rop = b.analyses.ROP()
rop.find_gadgets_single_threaded()
rop.find_gadgets_single_threaded(show_progress=False)

# check gadgets
test_gadgets, _ = pickle.load(open(os.path.join(test_data_location, "arm_manysum_test_gadgets"), "rb"))
Expand All @@ -175,13 +175,31 @@ def test_rop_arm():
def test_roptest_x86_64():
p = angr.Project(os.path.join(public_bin_location, "x86_64/roptest"))
r = p.analyses.ROP()
r.find_gadgets_single_threaded()
r.find_gadgets_single_threaded(show_progress=False)
c = r.execve(b"/bin/sh")

# verifying this is a giant pain, partially because the binary is so tiny, and there's no code beyond the syscall
assert len(c._gadgets) == 8
# this is the hardcoded chain...
assert [ g.addr for g in c._gadgets ] == [ 0x4000b0, 0x4000b2, 0x4000b4, 0x4000b0, 0x4000bb, 0x4000b2, 0x4000bf, 0x4000c1 ]

# verify the chain is valid
chain_addrs = [ g.addr for g in c._gadgets ]
assert chain_addrs[1] in [0x4000b2, 0x4000bd]
assert chain_addrs[5] in [0x4000b2, 0x4000bd]
chain_addrs[1] = 0x4000b2
chain_addrs[5] = 0x4000b2
assert chain_addrs == [ 0x4000b0, 0x4000b2, 0x4000b4, 0x4000b0, 0x4000bb, 0x4000b2, 0x4000bf, 0x4000c1 ]

def test_roptest_mips():
proj = angr.Project(os.path.join(public_bin_location, "mipsel/darpa_ping"))
rop = proj.analyses.ROP()
rop.find_gadgets_single_threaded(show_progress=False)

chain = rop.set_regs(s0=0x41414141, s1=0x42424242, v0=0x43434343)
result_state = execute_chain(proj, chain)
nose.tools.assert_equal(result_state.solver.eval(result_state.regs.s0), 0x41414141)
nose.tools.assert_equal(result_state.solver.eval(result_state.regs.s1), 0x42424242)
nose.tools.assert_equal(result_state.solver.eval(result_state.regs.v0), 0x43434343)


def run_all():
functions = globals()
Expand Down