简单栈溢出理解
前言
再次理解函数调用,简单栈溢出
参考:从0开始CTF-PWN(二)从PWN的HelloWorld-栈溢出开始
寄存器
- 函数状态主要涉及三个寄存器--esp,ebp,eip。
- esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。
- ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。
- eip 用来存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。
函数调用栈
函数调用栈是指程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
典型的函数调用栈结构如下:
函数调用过程
发生函数调用时,会先将被调用函数(callee)的参数按逆序压入栈内,这些参数会保存在调用函数(caller)的函数状态内。
然后压入被调用函数(callee)的返回地址(即调用后的下一条指令的地址),这样就保存了调用函数的eip寄存器内容。
继续压入调用函数(caller)的基址,也就是当前ebp寄存器的值,同时将ebp寄存器的值更新为当前栈顶的地址(mov ebp,esp),这样调用函数(caller)的基地址信息得以保存,后续调用完毕返回时,可以用于恢复ebp。
继续压入被调用函数的局部变量等数据。
入栈时: 被调函数参数 –> 被调函数返回地址 –> 调用者基址 –> 被调者局部变量
在压栈过程中,esp寄存器的值会逐渐减小(栈从内存高地址向地址值“生长”)。发生调用时,程序还会将被调用函数(callee)的指令地址存到eip寄存器内,这样程序就可以依序执行被调用函数的指令了。
调用结束时,栈变化的核心任务是弹出被调用函数(callee)的状态,并将整个栈恢复到调用函数(caller)的状态。首先弹出被调用函数(callee)的局部变量,然后将栈上存储的调用函数(caller)的基地址从栈内弹出,并重新保存到ebp寄存器中,这样调用函数的基地址信息得以恢复,此时栈顶会指向返回地址。最后将返回地址从栈顶弹出,并保存到eip寄存器内,这样调用函数的eip指令信息得以恢复,指向了调用函数后的下一条语句。
出栈时:被调者局部变量 –> 调用者基址 –> 被调函数返回地址 –>被调函数参数
pwndbg调试跟进
代码:
|
编译
gcc -g -m32 -O0 -fno-stack-protector -z execstack -o test test.c |
在 call func 处下断点 b *56556254
查看栈情况
看到压入的是被调函数参数
地址为 0xffffd130
stepi 进一步进行
看到栈内情况 压入了 callee返回地址
如果想利用栈溢出来覆盖参数,那么肯定要知道 gets 开始存入的地址
在gets下一步下断点c
继续运行
看到从 0xffffd0f0
开始存入输入的字符
p/d 0xffffd130 - 0xffffd0f0
得到输入处地址与函数参数地址之间的距离,然后构造垃圾数据+paylaod
达到控制函数参数的目的
嗯,目前了解就是这些了