Skip to content

Commit 3f48c75

Browse files
committed
enhance reg_setter without the previous jmp_reg hack
1 parent df6a398 commit 3f48c75

File tree

3 files changed

+91
-10
lines changed

3 files changed

+91
-10
lines changed

angrop/chain_builder/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ def set_roparg_filler(self, roparg_filler):
160160
self.roparg_filler = roparg_filler
161161

162162
def update(self):
163-
self._reg_setter.update()
164163
self._reg_mover.update()
164+
self._reg_setter.update()
165165
self._mem_writer.update()
166166
self._mem_changer.update()
167167
self._func_caller.update()
@@ -171,6 +171,7 @@ def update(self):
171171
self._shifter.update()
172172

173173
self._reg_mover.advanced_update()
174+
self._reg_setter.advanced_update()
174175

175176
# should also be able to do execve by providing writable memory
176177
# todo pass values to setregs as symbolic variables

angrop/chain_builder/reg_setter.py

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,28 @@ def __init__(self, chain_builder):
2929
self.hard_chain_cache = None
3030
# Estimate of how difficult it is to set each register.
3131
self._reg_weights = None
32+
self._reg_setting_dict = None
33+
34+
def _insert_to_reg_dict(self, gs):
35+
for rb in gs:
36+
for reg in rb.popped_regs:
37+
self._reg_setting_dict[reg].append(rb)
38+
for reg in self._reg_setting_dict:
39+
lst = self._reg_setting_dict[reg]
40+
self._reg_setting_dict[reg] = sorted(lst, key=lambda x: x.stack_change)
3241

3342
def update(self):
3443
self._reg_setting_gadgets = self.filter_gadgets(self.chain_builder.gadgets)
3544

45+
# update reg_setting_dict
46+
self._reg_setting_dict = defaultdict(list)
47+
for g in self._reg_setting_gadgets:
48+
if not g.self_contained:
49+
continue
50+
for reg in g.popped_regs:
51+
self._reg_setting_dict[reg].append(g)
52+
self._insert_to_reg_dict([]) # sort reg dict
53+
3654
reg_pops = Counter()
3755
for gadget in self._reg_setting_gadgets:
3856
reg_pops.update(gadget.popped_regs)
@@ -43,7 +61,43 @@ def update(self):
4361

4462
self.hard_chain_cache = {}
4563

46-
## now we have a functional RegSetter, check whether we can do better
64+
def advanced_update(self):
65+
# now we have a functional RegSetter, check whether we can do better
66+
67+
# first, TODO: see whether we can use reg_mover to set hard-registers
68+
69+
# second, see whether we can use non-self-contained gadgets to reduce stack-change requirements
70+
# TODO: currently, we only support jmp_reg gadgets
71+
new_rop_blocks = set()
72+
for gadget in self._reg_setting_gadgets:
73+
if gadget.self_contained:
74+
continue
75+
if gadget.has_conditional_branch:
76+
continue
77+
if gadget.transit_type != 'jmp_reg':
78+
continue
79+
stack_change = gadget.stack_change
80+
if gadget.pc_reg not in self._reg_setting_dict:
81+
continue
82+
pc_setter = self._reg_setting_dict[gadget.pc_reg][0]
83+
pc_setter_sc = pc_setter.stack_change
84+
85+
for reg in gadget.popped_regs:
86+
if gadget.pc_reg not in self._reg_setting_dict:
87+
continue
88+
total_sc = stack_change + pc_setter_sc
89+
reg_sc = self._reg_setting_dict[reg][0].stack_change if reg in self._reg_setting_dict else 0xffffffff
90+
if total_sc > reg_sc:
91+
continue
92+
93+
assert isinstance(pc_setter, RopGadget)
94+
try:
95+
chain = self._build_reg_setting_chain([pc_setter, gadget], None, {}, total_sc)
96+
rb = RopBlock.from_chain(chain)
97+
new_rop_blocks.add(rb)
98+
except RopException:
99+
pass
100+
self._insert_to_reg_dict(new_rop_blocks)
47101

48102
def verify(self, chain, preserve_regs, registers):
49103
"""
@@ -80,6 +134,17 @@ def verify(self, chain, preserve_regs, registers):
80134
pc_var = set(state.regs.pc.variables).pop()
81135
return pc_var.startswith("next_pc")
82136

137+
def _mixins_to_gadgets(self, mixins):
138+
gadgets = []
139+
for mixin in mixins:
140+
if isinstance(mixin, RopGadget):
141+
gadgets.append(mixin)
142+
elif isinstance(mixin, RopBlock):
143+
gadgets += mixin._gadgets
144+
else:
145+
raise
146+
return gadgets
147+
83148
def run(self, modifiable_memory_range=None, preserve_regs=None, max_length=10, **registers):
84149
if len(registers) == 0:
85150
return RopChain(self.project, None, badbytes=self.badbytes)
@@ -99,6 +164,7 @@ def run(self, modifiable_memory_range=None, preserve_regs=None, max_length=10, *
99164
l.debug("building reg_setting chain with chain:\n%s", chain_str)
100165
stack_change = sum(x.stack_change for x in gadgets)
101166
try:
167+
gadgets = self._mixins_to_gadgets(gadgets)
102168
chain = self._build_reg_setting_chain(gadgets, modifiable_memory_range,
103169
registers, stack_change)
104170
chain._concretize_chain_values(timeout=len(chain._values)*3)
@@ -155,12 +221,9 @@ def _tuple_to_gadgets(data, reg_tuple):
155221
@staticmethod
156222
def _verify_chain(chain, regs):
157223
"""
158-
make sure the new chain can control the registers
224+
make sure the new chain does not do bad memory accesses
159225
"""
160226
g = chain[-1]
161-
if g.transit_type == 'jmp_reg':
162-
return g.pc_reg in regs
163-
164227
# make sure all memory access can be forced to happen on valid addresses
165228
# don't need to consider constant addr or addr popped from stack
166229
for mem_access in g.mem_reads + g.mem_writes + g.mem_changes:
@@ -195,7 +258,7 @@ def find_candidate_chains_graph_search(self, modifiable_memory_range=None, use_p
195258
partial_controllers = self._get_sufficient_partial_controllers(registers)
196259

197260
# filter reg setting gadgets
198-
gadgets = set(g for g in self._reg_setting_gadgets if g.self_contained)
261+
gadgets = self._find_relevant_gadgets(**registers)
199262
for s in partial_controllers.values():
200263
gadgets.update(s)
201264
gadgets = list(gadgets)
@@ -395,11 +458,15 @@ def _check_if_sufficient_partial_control(self, gadget, reg, value):
395458
def _find_relevant_gadgets(self, **registers):
396459
"""
397460
find gadgets that may pop/load/change requested registers
398-
exclude gadgets that do symbolic memory access
399461
"""
400-
gadgets = set({})
462+
gadgets = set()
463+
464+
# this step will add crafted rop_blocks as well
465+
for reg in registers:
466+
gadgets.update(self._reg_setting_dict[reg])
467+
401468
for g in self._reg_setting_gadgets:
402-
if g.has_symbolic_access():
469+
if not g.self_contained:
403470
continue
404471
for reg in registers:
405472
if reg in g.popped_regs:
@@ -683,6 +750,7 @@ def _same_effect(self, g1, g2):
683750
def filter_gadgets(self, gadgets):
684751
"""
685752
process gadgets based on their effects
753+
exclude gadgets that do symbolic memory access
686754
"""
687755
bests = set()
688756
gadgets = set(gadgets)
@@ -693,4 +761,5 @@ def filter_gadgets(self, gadgets):
693761
bests = bests.union(self._filter_gadgets(equal_class))
694762

695763
gadgets -= equal_class
764+
bests = set(g for g in bests if not g.has_symbolic_access())
696765
return bests

angrop/rop_block.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,17 @@ def from_gadget_list(gs, builder):
183183
RopBlock._analyze_effect(rb)
184184
return rb
185185

186+
@staticmethod
187+
def from_chain(chain):
188+
state = chain._blank_state.copy()
189+
badbytes = chain._builder.badbytes
190+
rb = RopBlock(chain._p, chain._builder, state=state, badbytes=badbytes)
191+
rb._gadgets = chain._gadgets.copy()
192+
rb._values = chain._values.copy()
193+
rb._payload_len = chain._payload_len
194+
RopBlock._analyze_effect(rb)
195+
return rb
196+
186197
def has_symbolic_access(self):
187198
accesses = set(self.mem_reads + self.mem_writes + self.mem_changes)
188199
return any(x.is_symbolic_access() for x in accesses)

0 commit comments

Comments
 (0)