栈迁移
栈迁移
山林川泽什么是栈迁移?
在了解栈迁移之前,让我们先来了解一下什么是栈溢出
栈是C语言中一种重要的数据结构,在C语言中,函数调用时会利用栈来管理局部变量、返回地址等信息。但是实际上,计算机程序的运行都依赖于函数调用栈。同时,栈也常用于一些算法的实现,如果对栈结构不了解,请移步我之前的博客。
栈溢出是指在程序运行过程中,栈帧被填满,栈空间不足以接收多出来的数据,导致这部分数据溢出覆盖了栈空间原有的地址
例如get()函数,就是常见的栈溢出点
因此,栈溢出能使我们覆盖栈上某些区域的值,甚至是当前函数的返回地址ret,一旦ret覆盖为某个奇怪的值,例如0xabcdef,当函数调用结束恢复调用现场时,程序会跳转到内存中的0xabcdef处。此刻,内核会立刻告诉我们“SLGSEV”,即常见的段错误
问题来了,如果这个值是一个合法地址呢,如果它是程序中某个函数甚至是shellcode的地址呢,因此,一旦缓冲区变量可以被恶意用户控制,而且栈空间足够大,程序原有的执行流很可能会被破坏,使得攻击者提权拿到shell
在完成栈溢出攻击的过程中,有一个充分条件是(栈上有足够的地方让攻击者进行布局)。通常的函数栈剩余空间是足够放置一些恶意指令的,但也有少数情况,例如仅能容纳一个ret和一个ebp。此时,一般的栈溢出攻击方法将由于空间太小无法施展身手(ret2text,ret2shellcode,ret2libc,ret2syscall)
既然此处的函数栈无法放下恶意代码,那不妨另换一处来打。这便是栈迁移
首先让我们回顾一个函数在被调用以及结束时的汇编代码以及栈的变化
上层函数的执行视角
1 | push some args |
1 | call: |
如图所示,当上层函数调用union函数,即eip执行到call union指令时,call指令以及union函数开头的指令依次做了如下事情来【保护现场】:
1.牢记union函数结束后应该从哪里继续执行(保存当前eip下面的位置到栈中,eip:指令指针寄存器,它用于储存下一条要执行的指令的地址)
2.牢记上层函数的栈底位置,即保存当前ebp的内容到栈中,即为old ebp(假如C语言中调用scanf函数,old ebp就是main函数的栈底)
3.牢记union函数栈开始的位置,即保存当前栈顶的内容到ebp,便于union函数栈内的寻址
我们认为函数调用时的call指令相当于push ret address;jmp func。进入被调用函数后,由调用函数向栈内压入old ebp。然后将ebp指针指向esp位置(实际上是开启被调函数的程序栈(栈帧创建完成))。然后被调用函数执行完毕后但将要返回前会有leave;ret指令,这两句指令就是栈迁移的核心部分。现在我们来分别讲解一下这两句,leave指令相当于清理栈,然后将之前保存的old ebp给pop到ebp指针中。ret指令:转至ret address的位置。
因此,我们可以将ebp覆盖成任意地址,也就是可以控制栈底指针。栈顶指针esp该如何控制呢,那就再来一次leave ;ret
在第一个leave;ret指令后,ebp指针指向了伪造的栈地址,第二个leave指令会将esp指向ebp的位置。两个leave ret指令我们就可以控制三个指针ebp,esp,eip,即我们控制了栈底,栈顶,PC三个指针,现在我们可以控制栈和程序流了。
如图
Step1:首先确定缓冲区变量在溢出时,至少能覆盖站上ebp和ret两个位置。之后,选取栈要被劫持到的地址;例如,若能在bss等内存段上执行shellcode,则可将栈迁移到shellcode开始处。记该地址为HijackAddr
1 | |____________| |______________| |
Step2:寻找程序中一段leave ret gadget的地址,记该地址为LeaveRetAddr。
Step3:设置缓冲区变量,使其将栈上ebp覆盖为HjickAddr-4(一般来说让payload1等于到buf—->ebp的距离,然后payload2调用system(“/bin/sh”))
Step4:程序执行至函数结束时,将依次发生如下事件:
执行mov esp;ebp,还原栈顶指针至当前函数栈底;此时esp指向栈上被篡改的ebp数据,即HijackAddr-4
执行指令 pop ebp,将篡改的HijackAddr-4放入ebp寄存器内;此时esp上移,指向栈上被篡改的ret数据(LeaveRetAddr)
//leave指令执行
1 | |____________| |______________| |
执行指令:pop eip,将LeaveRetAddr放入eip寄存器内,篡改执行流,以执行第二遍leave指令
执行指令:(第二遍的leave指令):mov esp,ebp,将HijackAddr-4移入esp寄存器内,即栈顶指针被劫持指向了HijackAddr-4,发生了栈的迁移
1 | |____________| |______________| |
执行指令:(第二遍的leave指令)(因为ret的地址被leave_ret覆盖,所以在pop eip后程序跳转到leave ret指令处):pop ebp,ebp寄存器仍然是HijackAddr-4 ,但此时esp被拉高四个字节,指向HijackAddr
执行指令:pop eip,将Hijackaddr移入eip内,成功篡改执行流至shellcode区域(或gadgets链):
Step5:程序执行shellcode,攻击结束