angr / angrop

BSD 2-Clause "Simplified" License
606 stars 71 forks source link

make chains with retn 0x10 portable #108

Closed astewart-bah closed 5 months ago

astewart-bah commented 6 months ago

Bug: chains including x86_64 retn 0x10 instructions not terminated in a recoverable position.

We used this python script against the angrop_retn_test binary:

import angr, angrop, multiprocessing
p = angr.Project("angrop_retn_test", auto_load_libs=False)
rop = p.analyses.ROP(fast_mode=False, only_check_near_rets=False)
rop.find_gadgets(processes=multiprocessing.cpu_count())
chain1 = rop.do_syscall(3, [0])
chain2 = rop.do_syscall(0x27, [0])
final_chain = chain1 + chain2
print(chain1)
print(chain2)
print(final_chain)

This is the resulting chain1:

chain = b""
chain += p64(0x401137)  # pop rax; pop rdi; ret
chain += p64(0x3)
chain += p64(0x0)
chain += p64(0x40113a)  # syscall
chain += p64(0x0)
chain += p64(0x0)
chain += p64(0x0)

Note how the value immediately following the syscall is still p64(0x0).

It looks like the next_pc is not populated until concretized and until another gadget is appended to the end of the chain.

It was our understanding that chains should always end in a state where the next value on the stack should be PC. This was throwing off some of our calculations, so we made this branch.

Since you already wrote some impressive logic to overwrite a next_pc value when chains are added together, we thought we would take advantage of this. We decided to add a simple rop.shift(arch.bytes) chain (which should be as simple as a single ret gadget) to the chain currently trying to be concretized, and then concretize the conglomerate chain instead.

This is chain1 after our modifications:

chain = b""
chain += p64(0x401137)  # pop rax; pop rdi; ret
chain += p64(0x3)
chain += p64(0x0)
chain += p64(0x40113a)  # syscall
chain += p64(0x401076)  # nop word ptr cs:[rax + rax]; endbr64 ; ret
chain += p64(0x0)
chain += p64(0x0)
Kyle-Kyle commented 5 months ago

Thank you for the PR! This is great!