前序:
栈劫持概念:当存在栈溢出但溢出长度不够容纳payload时,就需要采用栈劫持。一般这种情况下,溢出时仅能够且刚好覆盖ebp,ret,但还需要设置参数。这时原来的栈空间不足,所以要构造一个新的栈空间来放payload,可以理解为劫持程序执行流跳转到自己构造的payload的栈中执行程序,也叫做栈迁移。
原理:函数调用时形成栈帧,栈执行命令时是从esp指针处向ebp指向的位置依次执行,正常情况下退栈的操作是:esp指向ebp的位置,ebp指向ebp里的内容所指向的位置;当遇见leave ret 命令时,就相当于执行mov esp,ebp ; pop ebp;(手动分隔) ret;pop,eip ;作用就是将ebp指向的位置给esp,ebp指向ebp里的内容所指向的位置。 如果我们提前控制了ebp的值那么当遇见leave ret指令的时候,ebp的值就会给到esp,由于esp里的内容会给到eip,所以就可以通过控制esp里的内容,实现控制程序执行流。
漏洞利用
存在栈溢出,但可利用栈空间不够,采用栈劫持
栈溢出长度不足来直接 ROP,把栈迁移到别的地方来构造新的ROP链,一般利用leave_ret来进行栈迁移
leave
//move esp ebp 将ebp指向的地址给esp
//pop ebp 将esp指向的地址存放的值赋值给ebp
ret
//pop eip 将esp指向的地址存放的值赋值给eip
举个栗题:
ciscn_2019_es_2
32位程序,并且没有开启栈保护。
ida分析main函数。
但是栈劫持的前提是知道缓冲区s一开始读入的栈地址,但s是一个定义在函数里的局部变量char s[40],我们无法知道其地址,但是我们知道s地址与函数基地址ebp的偏移量是一定的,不会变化,因此只要知道了ebp地址就能知道s的地址,也同时可以通过计算知道我们输入的内容的具体位置。
继续思路:
存在栈溢出漏洞,但是由于可供溢出的空间太小,我们选择采用栈劫持,采用栈迁移必须要使用到leave, ret组合指令。
程序中存在system函数,我们可以在内存中存放一个字符串/bin/sh,并且我们可以泄露ebp地址来计算出该字符串在内存中的地址,我们就可以构造并执行system('/bin/sh')。
那么首先要做的就是利用第一次read泄露ebp地址。
1:第一次read泄露ebp地址
我们都知道,ebp(我们将此称为ebp1)所指向的内存中存放的是上一次压入的ebp(我们将此称为ebp2),我们可以通过第一次的read函数和printf函数将该内容打印出来,但是打印出来之后的ebp2无法直接来使用,原因是我们知道ebp1到buf的距离是0x28,而我们不知道ebp2到s起始地址的距离是多少,这时候我们需要动态地调试一下程序,找到ebp2到s起始地址之间的距离。
2:第二次read利用leave ret劫持程序流到s起始地址,这里也就是劫持到ebpaddr-0x38的地址。
现在来找下leave ret 地址:
完整exp如下:
剖析一下程序流执行:
发送的payload2数据流的初始状态:
第一次执行leave_ret : mov esp ,ebp
第一次执行leave_ret :pop ebp
第一次leave ret :pop eip:
eip寄存器存的是leave ret的地址,程序接下来又要执行leave ret 开始第二次leave ret:
第二次leave ret :mov esp ,ebp
第二次leave ret:pop ebp
第二次leave ret :pop eip
Comments | NOTHING