首先让我们了解一下函数调用栈的原理。函数调用栈是指程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
函数状态主要涉及三个寄存器--esp,ebp,eip。esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。
函数调用发生时栈的变化。首先被调用函数的参数压入栈中,然后是调用函数的返回地址压入栈中,最后是被调用函数的局部变量等数据压入栈中。
注意函数调用前后寄存器的指向的变化。函数调用结束时过程与此相反。
shellcode题解思路
在函数调用或者结束时,程序的控制权会在函数状态之间发生跳转,这才可以通过修改函数状态来实现攻击。我们的目的就是让eip载入攻击指令的地址。而本题shellcode就是修改返回地址,让eip指向溢出数据中的一段攻击指令,即载入攻击指令的地址。要完成的任务包括:在溢出数据内包含一段攻击指令,用攻击指令的起始地址覆盖掉返回地址。溢出数据组成为payload : padding1 + address of shellcode + padding2 + shellcode 这里的shellcode指的是攻击指令。
1处的数据可以随意填充,但要注意利用字符串程序输入溢出数据不要包含“\x00”,长度应该刚好覆盖函数的基地址。可以用调试工具gdb查看汇编代码来确定这个距离,也可以在运行程序时用不断增加输入长度的方法来试探。2处数据也可以随意填充,长度可以任意。接下来需要确定攻击指令的起始地址。我们在调试工具里查看返回地址的位置并不准确。解决方法是在2中填充若干长度的\x90这个机器码对应的指令是告诉CPU什么也不做,然后跳到下一条指令。这样我们就可以通过这种办法来配合试验shellcode的起始地址。解决完上述问题,我们就可以拼接出最终的溢出数据来,输入程序中执行了。但是这种方法生效的前提有两个。第一,在函数调用栈上的数据要有可执行的权限。第二,关闭内存的布局随机化ASLR
本文章仅用于理解题的原理,具体解决还需要实战来解决。