栈,众所周知,学习pwn非常重要的一部分。
经历了许多天的学习,这个折磨了我很久、但又充满谜团的问题,终于有了答案。
前提知识:
使用call指令调用一个函数,在进入函数之前,要完成一些工作:
1.将call指令下一行指令的地址压入栈,这个地址也就是所谓的返回地址。
2.将ebp的地址压入栈中
以攻防世界的pwn题 《level2》为例:
ida反汇编后查看伪代码;
在pwndbg上调试:
在main函数处下一个断点,单步运行:
此时ip指向 call vulnerable_function
(能从图上看出)
也就是调用vulnerable函数。
我们看看下一条指令的地址:
0x8048496 <main+22>
注意这里,我们会和下文对比。
看看栈的情况:
此时,我列个表大家看一下:(省略地址中的f)
地址 | 内容 | |
---|---|---|
ebp | 0xf…d128 | |
esp | 0xf…d120 |
0x120=(十进制)288
再一次单步运行:
此时已经运行到vulnerable函数内。
看看堆栈:
地址 | 内容 | |
---|---|---|
ebp | 0xf…d128 | |
esp | 0xf…d11c | 0x8048496 |
0x11c=(十进制)284
而0x8048496刚好就是在main函数中call指令的下一条指令,也就是返回地址。
esp的变化:
由0xf…d120到0xf…d11c。
0x120=(十进制)288
0x11c=(十进制)284
288-284=4
4个字节,也就是存储了call指令下一条指令的地址,
也即存储了返回地址。
再次单步运行:
执行了 push ebp的指令。
ebp被压进了栈里。
让我们看看这个压进去的东西是什么:
0x11c-0x118=(十进制)4
压进去一个地址。
地址 | 内容 | |
---|---|---|
ebp | 0xf…d128 | |
esp | 0xf…d118 | 0xffffd128 |
0xffffd128刚好就是现在ebp的地址。
再单步运行:
可以看到,执行了 mov ebp,esp 指令。
此时栈中esp和ebp指向同一个地址,也就是保存的上一个ebp的地址。
也就是说,ebp指向的地址就是上一个ebp的地址。
显然,此时,我们完全进入vulnerable函数,为vulnerable函数建立了新的栈。
上面介绍的步骤就是为进入这个函数并创建一个新栈所做的工作。
2021.7.28
PS:
在进入volunteer函数时,我们可以看到,并没有显示“将返回地址压入栈中的操作”,但我们已进入volunteer函数后,就发现了这个操作已经被执行了。
这几个操作可以通过栈的变化来看出。
从:刚开始,还没有操作。
愚见:ebp指向的0x0,和上面的0x1应该是参数。(不一定对啊,我学习学习再修改)
到:先压入返回地址。
再到:压入ebp。
再到:mov ebp,esp
这么一个过程完成了,我们就创建并进入volunteer函数的栈区了。