首先得会内存、寄存器还有程序运行的规则。
存储知识:
文件地址(File Offset):数据在PE文件中的地址,文件在磁盘上存放时相对于文件开头的偏移;
虚拟内存地址:用户程序看到的存储空间,每个进程都有4G虚拟空间,以下说的内存、所有的存储空间都是虚拟内存;
物理内存地址:操作系统才能看到,用户程序不能接触;
这三个地址要层层映射
内存:
代码区:存放二进制代码
数据区:存储全局变量
堆区:动态内存空间(由程序员分配)
栈区:存放函数调用关系(缓冲区溢出就在这里发生)(由编译器分配)
重点研究栈的结构:
栈帧:每个函数有自己的栈帧,只有被调用的函数才会在系统栈开辟栈帧,调用完毕将弹出栈帧
两个寄存器(用于标示当前正在执行的函数的栈帧):
ebp:指向栈顶的底部
esp:指向栈顶的顶部
发生函数调用时栈帧的创建过程:
参数入栈
返回地址入栈
保存当前栈帧状态,也就是主调函数栈帧的ebp和esp,一般情况下只要ebp就行
把esp寄存器内容装入ebp寄存器,即创建被调用函数的栈帧作为当前栈帧
根据被调用函数需要的变量情况开辟一定大小的空间,用esp减去空间就得到当前栈帧的栈顶(从栈底到栈顶内存地址从高到底)
注意:返回地址是被调函数被调用指令的下一条指令地址,是代码区的地址。
被调函数执行结束之后弹出栈的过程是这样的:
1.esp加上局部变量占用的空间
2.弹出ebp,ebp指向调用者函数
3.返回地址存入指令寄存器eip
4.esp加上两个内存单元的大小也就是之前ebp和返回地址占用的空间
5.esp加上参数占用的空间。这样就完成了被调函数和调用者函数栈帧的转换。
明白了这些就可以开始缓冲区溢出小实验了:
#include<stdio.h>
void hack()
{
printf("hello");
//_exit(0);
return;
}
int main()
{
int a[0];
a[3]=0x004016b6;
return 0;
}
看了下图就明白了
我们知道数组是从a[0]的位置开始向后移动的,那么a[3]的位置(如图的话应该是a[2]才对,但这里a[3]才行,这是视具体的机器而定的因为还有编译优化的问题)正好是返回地址,但是给a[3]赋值为hack()函数的起始地址那么main函数就会跳转到hack()的地址执行hack函数。注意main函数也有返回地址。另外这里为什么输出两个“hello world”我还不清楚原因,如果把hack()函数的“return”改为"_exit(0)"就只输出一个"hello world",这是为什么呢,如果有大神知道原因还请赐教。
hack()函数的地址先通过正常调用一次然后用IDA工具很容易就找到了。反汇编软件爆破就要用到上面的三个存储地址的映射关系。
没想到写得这么累。
大概先这样。