Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 21 additions & 9 deletions angrop/chain_builder/reg_setter.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,20 +574,26 @@ def _find_relevant_gadgets(self, allow_mem_access=True, **registers):
return gadgets

def _handle_hard_regs(self, gadgets, registers, preserve_regs) -> list[RopGadget|RopBlock]: # pylint: disable=unused-argument
# handle register set that contains bad byte (so it can't be popped)
# and cannot be directly set using concrete values
hard_regs = [reg for reg, val in registers.items() if self._word_contain_badbyte(val)]
"""
handle register that cannot be popped or set using concrete values directly
there are two reasons why a register cannot be popped:
1. there is no gadget that can pop it
2. the value to set has badbyte
"""
hard_regs = {reg for reg in registers if not self._reg_setting_dict[reg]}
hard_regs |= {reg for reg, val in registers.items() if self._word_contain_badbyte(val)}
if len(hard_regs) > 1:
l.error("too many registers contain bad bytes! bail out! %s", registers)
raise RopException("too many registers contain bad bytes")
if not hard_regs:
return []
if registers[hard_regs[0]].symbolic:
hard_reg = hard_regs.pop()
if registers[hard_reg].symbolic:
return []

# if hard_regs exists, try to use concrete values to craft the value
hard_chain = []
reg = hard_regs[0]
reg = hard_reg
val = registers[reg].concreted
key = (reg, val)
if key in self.hard_chain_cache:
Expand All @@ -600,10 +606,16 @@ def _handle_hard_regs(self, gadgets, registers, preserve_regs) -> list[RopGadget
hard_chain = self._find_add_chain(gadgets, reg, val)
if hard_chain:
self.hard_chain_cache[key] = hard_chain # we cache the result even if it fails
if not hard_chain:

# FIXME: technically, we should always raise when we cannot find chains for a hard
# register. But there is an edge case: if a gadget can set a register providing a
# `modifiable_memory_range`, then it will not appear in _reg_setting_dict but the chain
# generation process will work. In this case, we proceed to the graph search.
if not hard_chain and self._word_contain_badbyte(registers[reg]):
l.error("Fail to set register: %s to: %#x", reg, val)
raise RopException("Fail to set hard registers")
registers.pop(reg)
if hard_chain:
registers.pop(reg)
return hard_chain

@staticmethod
Expand All @@ -615,7 +627,7 @@ def _find_concrete_chains(gadgets, registers):
chains.append([g])
return chains

def _find_add_chain(self, gadgets, reg, val):
def _find_add_chain(self, gadgets, reg, val) -> list[RopGadget|RopBlock]:
"""
find one chain to set one single register to a specific value using concrete values only through add/dec
"""
Expand All @@ -635,7 +647,7 @@ def _find_add_chain(self, gadgets, reg, val):
return [g1, g2]
except Exception:# pylint:disable=broad-except
pass
return None
return []

#### Gadget Filtering ####

Expand Down
22 changes: 22 additions & 0 deletions tests/test_chainbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,28 @@ def test_riscv_oop_normalization():
rb = rop.chain_builder._reg_setter.normalize_gadget(g)
assert rb is not None

def test_concrete_value_crafting():
proj = angr.load_shellcode(
"""
pop ebx; xor eax, eax; pop esi; pop ebp; ret
add eax, 0x5a5d80cd; pop ecx; ret
xor al, 0x5b; pop esi; pop edi; pop ebp; ret
""",
"i386",
load_address=0x400000,
auto_load_libs=False,
)
rop = proj.analyses.ROP()
rop.find_gadgets_single_threaded(show_progress=False)

chain = rop.set_regs(eax=0x5b)
state = chain.exec()
assert state.regs.eax.concrete_value == 0x5b

chain = rop.set_regs(eax=0x5a5d80cd)
state = chain.exec()
assert state.regs.eax.concrete_value == 0x5a5d80cd

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