函数调用栈
*发生函数调用时,调用函数保存在站内,被调用函数被压入调用栈的栈顶
*调用结束时栈顶函数状态被弹出,栈顶恢复调用状态
栈的结构
*可通过将esp值增减来调用local variables存储局部变量,从而实现被调函数的压入和弹出
函数调用过程
先将被调函数的参数逆序压入main函数的栈中,执行完调用函数后,再压将main函数的return地址压入return address中,在调用函数时,主函数main指针被存在caller‘s ebp中,通过(←esp)指向。在函数调用执行完毕之后,调用栈通过改变esp的值将原先存储在local variables的子函数清空(此处可见上一条解释),在弹出调用函数后,caller’s ebp中存储的main函数的内存地址传入(←ebp)中,最后结束整个调用过程,栈恢复原来状态.
函数调用栈工作方式
1)先调用push,mov指令,push将%ebp推入栈,mov将esp抬高到ebp的位置
2)sub esp移动esp来开辟栈空间
3)push将参数反向压入栈中
4)call指令,将eip移到目标代码位置,同时存储下一条指令的地址到return address
5)call之后进入被调函数(此时栈重新用于执行被调函数),执行被调函数的第一条push ebp指令后,父函数被存入%ebp in caller中
6)执行mov,将esp的值赋给sbp,把ebp抬到新的栈底.
7)执行函数的运算指令,将函数返回值存到eax寄存器中
8)完成计算后,执行pop ebp,因被调函数(子函数)不存在局部变量,所以在最后esp与ebp在同一个指向上,不需要leave指令移动esp到ebp(若存在局部变量,则esp与ebp不在同一点,因为需要留出栈空间存放变量),pop ebp将esp指向的父函数的原先的ebp值存入ebp中,所以ebp会回到原先指向的位置,esp再自动抬高一个字长,最后执行return指令,将下方的esp再抬高一个字长,并将esp原来指向的值存入esp中,此时被调函数结束,return address中存储的子函数的返回值被写入eip中,eip回到原先父函数的位置
9)调用add指令清空数据,再利用mov,addl等将主函数的返回值存入eax中
10)执行leave指令,ret结束(与子函数对照理解)
缓冲区溢出(Butter overfow)
*其本质是向定长的缓冲区中写入超长的数据,造成超出的数据覆写了合法内存区域
*包含栈溢出(最常见),堆溢出,BSS溢出
PWN工具
*IDA pro(静态调试)
*pwntools
*pwndbg(动态调试)
*checkesc(pwn第一步,了解程序相关保护措施)
*ROPgadget
*one_gadget(自动获得shell)