Buflab

I am a hacker!!


简述

原文指南

该任务帮助详细了解IA-32调用方式和栈组织,其涉及对可执行文件bufbomb实行一系列的缓冲区溢出攻击(buffer overflow attacks)

在本实验中,将获得有关通用的寻找操作系统和网络服务器中的安全弱点的方法的第一手经验。我们的目的是帮助您了解程序的运行时操作,并了解这种形式的安全弱点的性质,以便您在编写系统代码时避免它。我们不容忍使用这种或任何其他形式的攻击来获得对任何系统资源的未经授权的访问

在实验材料中有三个可执行文件

  • bufbomb:将要攻击的程序
  • makecookie:基于userid创建cookie,因此每个人的解决方案都会不一样
  • hex2raw:在字符串格式间转换

cookie是由八个十六进制数字组成的字符串,它(概率很高)是userid所独有的,如:

bufbomb程序使用下面的函数读入一个字符串

1
2
3
4
5
6
7
8
9
/* Buffer size for getbuf */ 
#define NORMAL_BUFFER_SIZE 32

int getbuf()
{
char buf[NORMAL_BUFFER_SIZE];
Gets(buf);
return 1;
}

Gets函数将读入的字符串写入目标地址,但是无法判断缓冲区对于输入字符串是否够大

我们的任务就是编写特殊的输入字符串,使得程序作出不一样的事情,这就叫exploit string

bufbomb函数有一些有用的参数

  • -u <userid>:该函数的一些特征会根据userid作出调整
  • -h:一些可能的命令行参数
  • -n:使用Nitro模式,将在Level 4中使用
  • -s:提交

Candle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test() 
{
int val;
/* Put canary on stack to detect possible corruption */
volatile int local = uniqueval();

val = getbuf();

/* Check for corrupted stack */
if (local != uniqueval()) {
printf("Sabotaged!: the stack has been corrupted\n");
}
else if (val == cookie) {
printf("Boom!: getbuf returned 0x%x\n",val);
validate(3);
}else{
printf("Dud: getbuf returned 0x%x\n",val);
}
}

当getbuf执行返回语句时,程序会返回上面的test函数执行,任务就是改变这种行为,使得进入下面的函数

1
2
3
4
5
6
void smoke() 
{
printf("Smoke!: You called smoke()\n");
validate(0);
exit(0);
}

反汇编查看bufbomb代码,并形成如下状态的运行栈
也就说缓冲区起始位置距离返回地址的位置为0x28,也就是40个字节,因此填充40个字节符号后,将smoke函数的起始地址填入即可,注意地址要按小端法表示

Sparkler

1
2
3
4
5
6
7
8
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);
}

level 1实验的要求和level 0相似,但它需要程序从getbuf()返回后执行fizz(int val)函数,并且为fizz(int val)函数传递你自己独有的cookie参数。

调用者负责将参数压入到栈上,参数位置是(%esp),即栈顶所指的位置。从fizz的反汇编代码中也看出参数应该是在%eax,也就是在0x8(%ebp)

要传入的参数就是userID对应到的cookie,如下所示

得到的攻击字符串自然如下所示

Sparkler GET!

Firecracker

1
2
3
4
5
6
7
8
9
10
int global_value = 0;
void bang(int val)
{
if (global_value == cookie) {
printf("Bang!: You set global_value to 0x%x\n",global_value);
validate(2);
} else
printf("Misfire: global_value = 0x%x\n",global_value);
exit(0);
}

更复杂的缓冲区攻击形式包括提供一个字符串,对实际的机器指令进行编码。然后,攻击字符串将用堆栈上这些指令的起始地址覆盖返回指针。当调用函数(在本例中为getbuf)执行其ret指令时,程序将开始执行堆栈上的指令,而不是返回。有了这种形式的攻击,你可以让程序做几乎任何事情。您放置在堆栈上的代码称为利用代码。不过,这种攻击方式很棘手,因为你必须把机器代码放到堆栈上,并将返回指针设置为此代码的开头。

global_value是一个全局变量,它没有储存在栈里面。所以在程序执行过程中,只能通过赋值语句来改变global_value的值。即这次我们不仅要让函数跳到bang中,而且要模拟一个函数调用来先进行赋值。

因此思路就是先覆盖返回地址,使其跳转到缓冲区中的攻击指令(也就是我们编写的函数),在这个函数之后,再跳转到bang函数中

新的函数如下所示,先获取已有的cookie,再将其复制到global_value上,最后将bang的地址推入栈中,以返回该函数

我们选择将该攻击函数代码放在缓冲区的起始位置,因此要寻找buf的起始位置是多少,如下所示

和上面题目不太一样的一点就是现在我们要返回到我们编写的这个函数位置,最后完整的指令如下所示

Firecracker GET!

Dynamite

这次要求getbuf调用后,返回到test当中,但是不能破坏为test函数维护的堆栈状态(test函数加了堆栈状态检测),同时要让test函数调用getbuf后的返回值(val)为自己的cookie。

由于缓冲区溢出时破坏了%ebp的值,需要写入代码修复%ebp,因此先获得原来的%ebp的值,如下所示

和之前的做法一样,在攻击字符串中写入一下代码,首先将cookie作为返回值传给test,然后修复%ebp,在将test调用getbuf的下一条指令推入栈,最后返回

得到对应的完整攻击代码

Dynamite GET!

Nitroglycerin

如书中所说,每次运行时,给定过程使用的确切堆栈位置会有所不同。攻击方法就是在实际攻击代码前插入很长一段nop操作(no operation),执行这种指令除了PC加一外,使其指向下一条指令,没有任何效果。只要攻击者能够猜中这段序列的某个地址,就能达到攻击代码。这个序列就叫空操作雪橇(nop sled)

当您使用命令行标志”-n”运行时,它将在”Nitro”模式下运行。程序不是调用函数getbuf,而是调用稍有不同的函数getbufn。此函数类似于getbuf,只是它具有512个字符的缓冲区。调用getbufn的代码首先在堆栈上分配随机数量的存储。每次执行时,会发现%ebp%ebp-0x208则是buf的首地址)的值都是不一样的,如下所示

由于不能确定buf首地址的位置,就不知道修改的跳转地址应该是什么,也就不知道攻击代码应该放在哪里。对于这种栈随机化,使用的就是nop sled方法。要做的依旧是提供一个利用字符串,该字符串将导致getbufn将您的cookie返回test,而不是值1。您可以在测试代码中看到,这将导致程序转到”KABOOM!”。其他功能则和Dynamite一样。

先通过上图来确定buf首地址可能的位置,设置跳转地址为该值,如下图最后一行所示,不管buf首地址是什么,只要能跳转到509个nop操作(也就是90)中的其中一个,PC就能执行到存放在buf中的攻击代码

攻击代码和上面的题目类似,首先将cookie作为返回值,然后修复%ebp,然后将调用getbufn的下一条语句推入栈中

Nitroglycerin GET!