昨天试过Linux系统下的BufBomb练习,结果在最后时刻被Linux操作系统的ExecShield给做了。正是由于这ExecShield,在Linux平台上,传统的Buffer Overflow已经彻底失效了。
今天我们来试下Windows平台。昨天那篇日志是我练习时留下的笔记,所以比较乱,很多地方没有讲明白。这次是做完练习之后的整理,我会尽量把步骤介绍清楚。
首先,为了在Windows平台上编译这个代码,要多引入一个malloc.h的头文件。我们先来看看最关键的代码片段。
/* $begin getbuf-c */
int getbuf()
{
char buf[12];
getxs(buf);
return 1;
}
void test()
{
int val;
printf("Type Hex string:");
val = getbuf();
printf("getbuf returned 0x%x\n", val);
}
getbuf函数直接return 1; 然后在test函数中,直接把这个返回的数字1放在val变量中并以十六进制的形式输出出来。显然这个程序在正常情况下,输出的就是0x1。而题目的要求就是让这个程序输出0xdeadbeef。方法就是利用getbuf函数没有检查用户输入长度的漏洞,通过输入特别构造的字符串,改变程序的运行过程,让程序运行任意代码。
我们在getbuf函数中加一个断点,把程序断下来。看看生产的汇编代码和程序栈中的情况。这里使用了NetBeans 7.0.1 + GCC 4.6.1作为开发、调试环境。
程序断下来之后,按如下图所示的方式,可以看到当前函数的汇编代码。
还得把内存和寄存器(就是图中的注册)窗口打开。(可恨这些都没有个快捷键,而且内存窗口只能有一个。都很让人不爽。)然后我们就可以看到汇编出来的代码。
当前行所指向的代码 lea -0x14(%ebp),%eax的意思就是取buf的起始地址。再来看看寄存器里的内容。
这时ebp的值是0x28ef98,所以buf的起始地址就是0x28ef98-0x14=0x28ef84。用户输入的字符串就是从这个地方开始存放在内存中的。我们也就是要让程序跳转到这个地址上去执行我们特别构造好的代码。
那么现在的问题是,如何让程序跳转到BUFF所在的地址上去?再来看上面的汇编代码,后面就要ret了,ret就是一次跳转,而跳转的地址就是保存在栈中的。而且,这个跳转的地址比BUFF的起始地址要大。所以BUFF里的数据如果溢出的话,是完全可以覆盖掉这个地址的。也就是可以让这个函数结束时,返回到任何地方去执行任何代码。
现在用栈里的情况,来验证上面的说法。上图的寄存器的值显示,栈顶的地址是0x28ef70。就是这块内存。如下图所示。
每行16个字节。红框即栈顶,绿框即栈底,蓝框即Buff的内存。绿框是保存的值是函数开始时入栈的ebp值。它后面的0x401476即是函数的返回地址。上图中eip寄存的值是0x40144d,这是当前指令的地址。很接近哈,这就对了,代码们本来就是在一起的。原来的返回地址后面也要用到。这里先记下。
Buff的大小是12字节,离函数返回地址还有16个字节。所以总共要输入28个字节的值。其中绿框的部分是ebp值,是函数返回要用的,所以要保留。后面的返回地址要改成Buff的起始地址。
所以我们构造的数据的后8字节应该是:c8 ef 28 00 84 ef 28 00
现在就来手写汇编代码,修改掉这个函数的返回值。从getbuf的汇编代码中可以看出,函数的返回值是保存在eax中的。所以我们的汇编代码应该是。
movl $0xdeadbeef, %eax
jmp 0xXXXXXXXX
第一行很好理解,第二行,目标是直接跳转到getbuf本来应该返回到的地方。这个地址是0x401476,但是jmp使用的是相对偏移。这两条指定占用10个字节,所以下一条指令的地址就是Buff的起始地址加10。即0x28ef84 + 0xa = 0x28ef8e。
所以偏移量就是0x401476 – 0x28ef8e = 0x1724e8。所以完整的代码就是
movl $0xdeadbeef, %eax
jmp 0x1724e8
把这两行代码放在一个文件中,比如叫,bomb.s,然后按下图所示的方式运行gcc和objdump来得到十六进制机器码。
生成的b.s文件内容如下。
Disassembly of section .text:
00000000 <.text>:
0: b8 ef be ad de mov $0xdeadbeef,%eax
5: e9 ee 24 17 00 jmp 1724f8 <.text+0x1724f8>
a: 90 nop
b: 90 nop
可以看到上面两条汇编的二进制表示,即b8 ef be ad de e9 ee 24 17 00。这就是要输入Buff的前10个字节。任务完成!我们有了前10和后8字节,总共需要28字节,中间的10个字节就可以随意了。
所以我们最终可以得到的攻击用的字符串是:
b8 ef be ad de e9 ee 24 17 00 00 00 00 00 00 00 00 00 00 00 c8 ef 28 00 84 ef 28 00
我们来运行一下看看。
作为对比,正常的执行结果应该是:
这是第二次做这个事情了,所以没有像第一次单步跟踪的过程。有兴趣的同学可以自己加个断点试试,我上面给出的结果很有可能在你的机器上不能起到攻击的效果,如果想重现,很可能要自己从头做一遍。
Window 7 + GCC 4.6.1环境攻击成功。昨天Ubuntu 11.04 + GCC 4.5.2攻击失败,Linux早在05年的时候就通过ExecShield机制让这种原始的Buffer Overflow攻击入了土。也许是配置问题,但是默认就能攻击成功也是不靠谱的。