软安实验2:栈溢出
overflow_var
源码
1 |
|
验证缓冲区溢出
运行程序

通过IDA找到调用 strcmp 和 strcpy 的位置

在ollydbg里定位到这里,并添加注释。调用strcmp和strcpy处下断点。

先尝试输入一个不会溢出的错误的密码,如“123”。
F9执行至断点处,再F8单步步过。看到eax变为FFFFFF(即-1),这是输入小于密码的返回值。

再F8运行至call strcpy的下一条命令
观察到堆栈区保存了存放“123”的地址。在数据区追踪,可以看到内存里“123/0”以空字符串结束。

再输入正确密码“1234567”,eax的返回值变为0,表示密码正确。


再构造会产生溢出的密码,如“AAAAAAAAAAAAAAAAAAAAAA”。
strcpy返回结果是1,表示输入大于密码。

在数据区追踪,看到原本存放EAX的位置已经被溢出的“A”覆盖。

由此可验证栈溢出的发生。
栈帧内存分布图与攻击逻辑


淹没相邻变量改变程序流
构造8位的输入,如“12345678”。可以看到strcmp比较完返回的是1。

由于输入为8位,正好等于buffer大小,因此最后的终结符“/0”覆盖掉下一位,使authenticated变为0,正好满足密码正确时的输出。


淹没返回地址改变程序流
计算偏移量
总填充长度=buffer[8](8)+authenticated(4)+Saved EBP(4)=16字节
找到目标地址(congratulation)
直接查找字符串

记下401116。

构造输入:16位填充+0x401116
地址以小端序写入。可以构造“A”×16+“\x16\x11\x40\x00”
打开010 Editor,输入16个A,再ctrl+H切换到16进制视图,写下16114000,再ctrl+H切换回文本试图。全选复制。



overflow_ret
源码
1 |
|
验证缓冲区溢出
用IDA打开exe,发现这个程序是读取txt文件中的密码进行验证的。

先准备一个密码文件。

找到strcmp和strcpy的位置。

下断点并添加注释

跟overflow_var非常类似,只要输入字符串大于等于8位,就可以覆盖到authenticated

例如构造输入“111111111111111111”

栈帧内存分布图与攻击逻辑


淹没相邻变量改变程序流
还是构造输入“12345678”,让“/0”去覆盖authenticated的最低位


淹没返回地址改变程序流
偏移量:总填充长度=buffer[8](8)+authenticated(4)+Saved EBP(4)=16字节
目标地址:40112F

使用010 Editor构造输入


StackOverrun.exe
用IDA查看,发现需要输入命令行参数。

使用Ollydbg打开,调试→参数,输入参数后重载开始调试。


运行

IP地址分析
分析PE文件格式 (IDA)
先打开view→Open subviews→segments

双击进入.text代码段

从IDA底部状态栏可以看到:代码段的文件偏移为0x1000。同时,IDA显示.text区段的起始地址为0x00401000

分析内存加载 (Ollydbg)
为了从动态分析的角度验证 IDA 的静态分析结果,使用 Ollydbg 加载 StackOverrun.exe 并查看其内存映射。

此动态分析结果与 IDA 的静态分析结果(文件偏移 0x1000,虚拟地址 0x00401000) 完全一致。
Ollydbg 确认了程序的基地址为 0x00400000,而代码段的实际加载地址为 0x00401000。这清晰地展示了 PE 文件从磁盘(文件偏移 0x1000)加载到内存(虚拟地址 0x00401000)的地址变化过程。
详细分析函数调用
定位 call foo:查找字符串,双击“Address of foo = %p”,进入到main函数中调用foo函数的地方。

在call 00401000(调用foo函数)处下断点。

状态 1:即将执行 call foo
- EIP (ip):
0040109B,指向call 00401000这条指令,即将执行。 - ESP (sp):
0019FF24,是main函数当前的栈顶。 - EBP (bp):
0019FF74,是main函数的栈帧基址。

状态 2:刚进入 foo 函数(call 执行后)

- EIP:00401000,指向 foo 函数第一条指令
sub esp,0ch。表明call指令已成功执行,程序流已从main函数跳转到了foo函数的开头。 - EBP:仍然是0019FF74。因为
foo函数建立自己栈帧的指令(push ebp,mov ebp, esp)还没有被执行。EBP目前仍然指向main函数的栈帧基址。 - ESP:0019FF20。
ESP的值减少了 4 字节(0x24->0x20)。这是因为call指令自动将返回地址压入了栈顶。 - 栈 (Stack) 分析:右下角的栈窗口,
ESP指向的地址0019FF20处,存放的值是0040109B,正是 “状态 1” 中call指令的下一条指令的地址(即add esp, 14),证明了call指令已将正确的返回地址(0040109B)压入栈顶,ESP现在正指向它。
状态 3:foo 函数栈帧建立

- EIP:00401005,指向
push 00406070指令(即printf的 “My stack looks like…” 字符串参数),程序即将开始执行函数的主体 - EBP:0019FF74,这个函数被编译器优化了,没有使用
EBP作为栈帧指针,所以EBP的值在foo函数内部保持不变。 - ESP:0019FF0C,
ESP的值总共减少了0x14字节(0x20 - 0x0C = 20) - 栈 (Stack) 变化分析:
ESP从0019FF20变为0019FF0C,这是三条指令共同作用的结果:sub esp, 0Ch:ESP向下移动 12 字节(0xC),为局部变量buf[10]腾出空间。ESP变为0019FF14。push esi:ESI寄存器的值 (004010E1) 被压入栈。ESP减 4,变为0019FF10。push edi:EDI寄存器的值 (004010E1) 被压入栈。ESP再减 4,变为0019FF0C。
查看栈窗口:
0019FF0C(栈顶):存放着004010E1(刚压入的EDI)。0019FF10:存放着004010E1(压入的ESI)。0019FF20:返回地址 (0040109B) 仍然安全地保存在栈的上方。
程序功能推测
通过对 StackOverrun.exe 的静态与动态分析,推测其功能如下:
- 暴露漏洞:程序通过
foo函数接收一个命令行参数,并使用不安全的strcpy函数将其复制到一个 10 字节的局部缓冲区buf[10]。此操作未进行边界检查,故意暴露了一个栈缓冲区溢出漏洞。 - 提供目标:程序包含一个
bar函数(用于打印 “Augh! I’ve been hacked!”),该函数在正常执行流程中永远不会被调用。 - 辅助利用:程序在启动时会主动打印
foo函数和bar函数的内存地址,这为漏洞利用提供了关键信息。
结论: StackOverrun.exe 是一个专门为本实验设计的 “靶子”程序 。其全部功能就是为了让实验者利用其栈溢出漏洞,劫持程序流程,转而去执行在正常情况下无法访问的 bar 函数。
修改StackOverrun程序的流程
返回地址覆盖为bar函数起始地址
- 填充 (Padding):使用 12 个任意字符,例如
AAAAAAAAAAAA。 - 覆盖地址 (Overwrite Address):使用
bar函数的绝对地址0x00401060。由于 x86 架构使用小端序 (Little-Endian) 存储,该地址在内存中表示为\x60\x10\x40\x00 - 最终 Payload:
ABCDEFGHABCD+\x60\x10\x40\x00(共 16 字节)。
攻击逻辑:
- 输入与溢出:将构造好的 Payload 作为命令行参数传递给
StackOverrun.exe。foo函数内部的strcpy将此 Payload 复制到栈上的buf缓冲区。 - 覆盖返回地址:
strcpy首先复制 12 字节的填充 (ABCDEFGHABCD),刚好填满buf的空间。随后,它继续复制bar函数的地址 (\x60\x10\x40\x00),这 4 个字节精确地覆盖了栈上原来存放foo函数返回地址的位置。strcpy在复制完最后一个字节\x00后停止。 - 劫持控制流:当
foo函数执行完毕,准备返回时,执行retn指令。CPU 从栈顶弹出地址并准备跳转。此时,栈顶的地址不再是原始的返回地址,而是被覆盖上去的bar函数地址0x00401060。 - 执行目标函数:CPU 将
EIP指针设置为0x00401060,直接跳转到bar函数的入口点并开始执行。 - 成功调用:
bar函数执行其内部逻辑,调用printf打印出 “Augh! I’ve been hacked!”。
“坏字符”问题:虽然 bar 函数地址 0x00401060 包含空字节 \x00,但它恰好位于地址的最高位(小端序表示的最后一个字节)。strcpy 在复制完包含 \x00 的完整地址、成功覆盖返回地址之后才遇到 \x00 并停止,因此未影响攻击效果。


原始栈布局与攻击逻辑


