Follow-up to #34 / #35.
The side-effect refresh added in RopChain._construct_ropchain.backtrack (PR #35) zeroes the clobber counter of a gadget's dst whenever a step writes it, so later steps can reuse that register:
norm_dst = arch.normalize_reg(gad.dst) if gad.dst else None
saved_dst = side_effected[norm_dst] if norm_dst else 0
if saved_dst:
side_effected[norm_dst] = 0
This is correct for register writes (mov dst, src, lc(dst), …), but wrong for store operations.
Problem
st is mov [dst], src, so gad.dst holds the base register of the address ([rax] → rax). That register is not written — it is consumed as an address. Zeroing its clobber count marks it as "freshly produced" when it is not, so a stale clobber from an earlier step can be silently masked, potentially yielding a ROP chain where the store's address register no longer holds the expected value.
Not a regression (a store's dst was never protected by the side-effect guard, which only checks effective_src), but the refresh should not apply here.
Suggested fix
Only apply the refresh when dst is a register actually written by the gadget, not a memory base. Options:
- skip the refresh when the matched operation's
dst operand is op_mem, or
- guard on
regs_write (only refresh if norm_dst is in the gadget's written registers).
Note: a regression test for the correct store behaviour should accompany the fix (the existing tests/test_ropchain.py::test_explicit_dst_clears_clobbered_register covers the register-write case).
Follow-up to #34 / #35.
The side-effect refresh added in
RopChain._construct_ropchain.backtrack(PR #35) zeroes the clobber counter of a gadget'sdstwhenever a step writes it, so later steps can reuse that register:This is correct for register writes (
mov dst, src,lc(dst), …), but wrong for store operations.Problem
stismov [dst], src, sogad.dstholds the base register of the address ([rax]→rax). That register is not written — it is consumed as an address. Zeroing its clobber count marks it as "freshly produced" when it is not, so a stale clobber from an earlier step can be silently masked, potentially yielding a ROP chain where the store's address register no longer holds the expected value.Not a regression (a store's
dstwas never protected by the side-effect guard, which only checkseffective_src), but the refresh should not apply here.Suggested fix
Only apply the refresh when
dstis a register actually written by the gadget, not a memory base. Options:dstoperand isop_mem, orregs_write(only refresh ifnorm_dstis in the gadget's written registers).Note: a regression test for the correct store behaviour should accompany the fix (the existing
tests/test_ropchain.py::test_explicit_dst_clears_clobbered_registercovers the register-write case).