先来谈谈栈迁移的攻击手法
很多时候,程序给的溢出空间不够,仅仅只能溢出到rbp,或者是返回地址,这个时候去构造rop链条打程序就行不通了,没有多余的空间给你去构造rop链条,那么我们可以使用栈迁移的技术,先在一个位置布置好rop链条,然后把程序的执行流迁移到我们布置好rop链条的位置,让程序执行流执行踏入我们布置好了的地方,就相当于之前的那种构造rop链条的手段,但是多了一个迁移栈步骤,这个下一篇博客会细讲
现在就是讲下最基础的利用栈迁移实现任意地址写,也就是可以改写变量的值
这里的关键一就是rbp指令,局部变量是根据rbp寻址的
这里的关键二就是leave_ret指令
leave指令的本质其实就是:
mov esp,ebp
pop ebp
ret指令的本质其实就是:
pop eip
下面我们就可以利用上面分析的原理解出这道题目
例题
可以看到,这里不存在栈溢出,不存在栈溢出的题目大概率就是用栈迁移去打
这里允许读入的数据只有0x58,而buf变量的大小有0x50,64位的程序,所以只能刚刚好溢出到rbp的地方,连返回地址的空间都没办法溢出到
可以看到当v4和passwd的值都为4660也就是0x1234的时候就可以直接拿到shell了
那么具体怎么判断的呢?我继续给出汇编代码的分析,让我们对汇编代码理解更加深刻一些
第一步,cs:passwd,也就是把passwd这个空间的值取出来,然后mov eax,cs:passwd,也就是把这个取出来的值赋值给eax
第二步,cmp eax,1234h 这里的h代表这是一个16进制的数,这里就是说把eax里面的值和16进制0x1234进行判断,如果相同的话就跳到下面的call system直接提权拿shell,不过不成功的话就跳到short loc_4012DB里面去,jnz就是指cmp判断后不等于跳转的位置
然后第三步,就是把rbp+var_4也就是rbp-4的位置赋值给eax里面,其实也就是啊,闭环了,我们输入的数据的位置,然后的步骤和上面的一样的
所以我们在打程序的时候就是需要通过这个栈迁移来实现对passwd这一个全局变量的内容进行更改,改成我们输入的0x1234,同时x的值本来就是我们输入的值,也就是rbp-4的值是0x1234,就可以把程序打下来了
首先我们需要找到passwd的地址,双击一下跟进,可以看到所在地址是0x4033cc
第二步我们需要确认一下调用scanf的时候它读取的偏移是多少,这需要根据汇编代码来看
可以看到偏移是减4,上面仔细分析过,所以偏移就是4,所以就把rbp迁移到passwd_addr + 0x4的位置,那么我们输入的数据依据rbp寻址后就是在减去0x4的位置也就是passwd的位置,也就完成了任意地址写,改写了passwd的值
知道了这些就可以构造脚本了
exp:
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
p = process("./test1")
elf = ELF("./test1")
passwd_addr = 0x04033cc + 0x4
payload = b'a'*0x50 + p64(passwd_addr)
p.recvuntil("input2:")
p.send(payload)
p.recvuntil("input1:")
p.sendline(b"4660")
p.interactive()