[阅读型]CTF中linux pwn的四大基本防御措施

前言

个人比较系统与深入总结,适合有一定基础的ctfer快速阅读。
如有错误或缺失非常感谢指出!持续更新…
更新时间 2021-10-25

linux pwn中四大基本防御

TYPEgcc选项编译器默认情况
RELRO (relocation read only)”-z norelro”, “-z lazy”, “-z now”“-z lazy”(gcc-4.9)
"-z now"(gcc-7.5)
“-z lazy”(clang-10)
NX (no-execute)“-z execstack”(关闭)
"-z noexecstack"(开启)
开启
CANARY (又称 stack-protector)“-fstack-protector-all”(全部开启)
"-fno-stack-protector"(关闭)
“-fstack-protector”(gcc-4.9开启)
"-fno-stack-protector"(clang-10关闭)
PIE (position-independent executables)“-no-pie”(关闭)
"-fPIE -pie"(开启)
关闭(gcc-4.9)
开启(gcc-7.5)
关闭(clang-10)

其中PIE需要 来自操作系统的支持,称为为
ASLR(address space layout randomization)。

#查看当前ASLR开启情况 默认级别是2
cat /proc/sys/kernel/randomize_va_space
#0 no; 
#1 only randomize: stack,heap,mmap; 
#2 all (include the ELF);
#修改为0
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 

可借助pwntools查看一个elf可执行文件防御开启情况

#安装pwntools
python3 -m pip install pwntools
#checksec 由上行命令安装在 ~/.local/bin目录下
checksec --file ./vul

checksec

1. RELRO

RELRO 是与 dl(dynamic linker)在运行时的相关数据结构的保护。概括来说,将相关数据区域设置为只读权限。根据实现的程度,分为 no RELRO,partial RELRO 和 full RELRO,对应的 gcc 编译选项为"-z norelro", "-z lazy", "-z now"

#查看relro文档
man ld

Create an ELF “PT_GNU_RELRO” segment header in the object. This specifies a memory segment that should be made read-only after relocation, if supported. Specifying common-page-size smaller than the system page size will render this protection ineffective. Don’t create an ELF “PT_GNU_RELRO” segment if norelro.
(翻译)
如果设置了relro,那么会在ELF中创建"PT_GNU_RELRO"的segment;如果没设置那就没有这个segment

一个 ELF 可执行文件被执行时(execv()),首先由内核识别该二进制ELF文件,并映射到进程虚拟地址空间,然后内核把控制权交给ld-linux.so(/lib64/ld-linux-x86-64.so.2,路径由.interp指定),dl 的执行过程是在用户空间进行的。由动态链接器加载运行用户程序所需要的动态库(比如 libc.so, /lib/x86_64-linux-gnu/libc.so.6,共享库的路径有一套复杂的寻找流程),然后控制权才会转移到用户程序的main()。

#linux中共享库寻找流程的标准文档 这里不再扩展
man ld.so

简单总结可执行文件执行的流程
terminal --> kernel --> ld.so --> ELF中的_start --> libc.so中__libc_start_main() --> ELF中(.init .init_array) --> ELF中main() --> ELF中(.fini .fini_array) --> syscall exit()

在 dl 加载动态库的过程中,需要进行符号解析重定位的工作,需要在内存留下相关的数据结构(比如.got .got.plt 表存放外部函数指针,.dynamic 表存放辅助的关键数据结构),其结构中存放着重要的信息比如各个section的首地址,函数指针等。如果攻击者能够修改.got 表中的函数指针,就能够劫持控制流。

经过本机测试,在 ubuntu 16.04 LTS 中,gcc 的版本为 5.4.0,默认情况下编译出的二进制文件是 partial RELRO。在 ubuntu 18.04 LTS 中,gcc 的版本为 7.5.0,默认情况下编译出的二进制文件是 full RELRO。

Partial RELRO 中,会应用懒加载机制,二进制程序引用的外部符号只有在第一次调用的时候才去解析,故在运行期间.got.plt的内容是可写的,存放着各个引用的外部符号的地址。.init_array, .fini_array, .dynamic, .got 内容均不可写,如下图所示。红色箭头所指,其segments组只有R权限。

readelf -l ./vul

在这里插入图片描述

Full RELRO 中,所有外部符号在运行初期转交给用户程序前,即跳转到_start 前,全部完成解析。故.got.plt被合并到.got 中且为不可写,并且 Partial RELRO 中不可写的 section 仍然不可写。

其有关dl数据结构的详细介绍可参考15年 USENIX security一篇文章1,非常详细,是ret2dlresolve的起源。与ctf-wiki2不同的是,论文详细介绍了Full RELRO的绕过方法!

2. CANARY

CANARY 最初在 98 年 USENIX security 提出3,目前已经是 gcc 基本启用的安全防御措施。经测试 gcc 5.4.0 是默认开启的。CANARY 的直觉就是认为,栈溢出是顺序修改栈上的数据的,故攻击者要修改到 ret addr 这一关键数据的话,就肯定会修改到 CANARY

Alt
如上图所示一个基本的栈空间布局。如果callee的栈有溢出,那么攻击者想要溢出以至于覆盖ret addr,那么就会修改到CANARY。故callee执行前放一个固定的数在栈上,在ret之前检查CANARY的值是否被修改,如果发现被修改那么就跳转到__stack_chk_fail()。代码如下图所示,第一个红框是把fs寄存器指向的空间0x28偏移处的8个字节放在栈上;第二个红框是把该栈上的值与原来fs指向的值做比较。

在这里插入图片描述

关于fs寄存器
这里有个基本的认识,在x64中仍然继承了一堆16位的段寄存器如cs,ss,ds,es,gs,当然也包括fs。但是在64位下虚拟内存空间绰绰有余,故os的开发者一般采用平坦模式(flat mode),进程的各个段是重叠的,各个段的基地址从0开始。说白了这些本来用于表示段选择子的寄存器就没用 。所以关于这些寄存器的用途就没有一个标准。不知是否有人系统总结这些寄存器在linux,windows下各自的用途?

在intel中,fs寄存器与用户态程序中的tls(Thread Local Storage)相关,用于实现不同线程的独立存储。例如 __thread修饰的全局变量存储的位置与fs相关。

根据intel SDM描述,gs寄存器会用于SwapGS这一特别的指令。这一指令主要目的是用于64位中fast syscall的实现,方便内核开发者,用于在syscall entry的时候,快速找到内核数据的位置。

在gdb中info reg会显示所有寄存器,fs会显示0,非常奇怪,stackoverflow有人回答是因为用户态的权限不足。猜特fs可能属于特权寄存器,可能需要qemu-system级别的模拟才能看到。
info reg fs_base可以显示fs指向的内存。
或者在该进程中执行syscall(SYS_arch_prctl,ARCH_GET_FS,&fs_base);
或者安装pwndbg,输入 tls
fs_base+0x28就是CANARY

在这里插入图片描述
Alt
注意,这fs_base这块内容是可以读写的,并且其地址的offset与mmap出来的址是固定的。故在泄漏libc的情况下,可以读取到**CANARY**,甚至可以修改!

3. NX

NX 是指堆栈不可执行,在windows中称为数据执行保护(DEP, Data Execution Prevention)。如下图,左侧图表示没有开启 NX 时的内存权限情况,右侧图表示开启 NX 的情况。在没有开启的时候,数据段都有可执行权限,开启了后,只有代码段才有可执行权限。
在这里插入图片描述

一般的绕过 NX 方法是 ROP(Return-oriented programming)4。ROP的直觉就是不用 call 指令,只使用 ret 指令与代码段已有的代码,就可以构造出想要效果的指令序列,在有足够的 gadget 下,可以证明 ROP 是图灵完备的。 一般认为,PIE 和 ASLR 能有效对抗 ROP。

4. PIE & ASLR

ASLR 与 PIE 是不同的技术,两者互相补充。在二进制文件执行的时候,首先由内核解析
文件并做虚拟内存空间的映射,ASLR 是由内核完成的。
在 ubuntu 中查看与修改的方式:

cat /proc/sys/kernel/randomize_va_space

2 是默认值,表示完全开启,0 表示关闭 ASLR。
关闭 ASLR 只需将改值修改为 0 即可,

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

PIE 技术是由编译器完成。在 ubuntu 16.04 中,启用 PIE 需要加上编译选项-fPIC -pie
在ubuntu 18.04 中关闭 PIE 需要加上编译选项-no-pie

PIE 本意并不是出于安全,它本身只是一个让程序与其装载地址无关的一项技术,想编译成共享库就应该启用PIE。使用PIE 的话实际上会降低性能4。 启用PIE 在 x86 上大约有 10%的额外开销,但是在 x86_64 上只有3.6 %。这是因为在 64 位机上在指令层有优化,提供了特别的与地址无关的取址指令。

与地址无关技术的关键就是相对地址的movcall
cpu指令集提供了相对当前ip寄存器的跳转指令。很不幸访存指令并没有相对访存。

关于指令集优化的例子
mov rax, [rip+0x100]; 在amd64中,这条语句合法
然而在32位下
mov eax, [ebx+0x100]; 在i386中,这条语句合法
mov eax, [eip+0x100]; 在i386中,这条语句非法,这也是32位下PIE开销过大的原因
为了解决该非法语句,编译器先获得当前eip,再计算,再取值。
call __x86.get_pc_thunk.ax <__x86.get_pc_thunk.ax>
add eax, 0x1aa7
mov edx, dword ptr [eax + 0x30]
理论上来说上面三条汇编的语义等价与
mov edx, [eip+0x1aa7+0x30]; 但是此条汇编非法

经本机实测,在 ubuntu 16.04 LTS 中,gcc 的版本为 5.4.0,默认情况下编译出的二进制文件是没有开启 PIE。
在 ubuntu 18.04 LTS 中,gcc 的版本为 7.5.0,默认情况下编译出的二进制文件是启用 PIE。

  • 当 ASLR 关闭时,无论 PIE 是否开启,程序的各个段与共享库的在虚拟地址空间的地址
    是不变的,每一次启动进程均是一个固定值。
  • 当 ASLR 开启,PIE 关闭时,程序自身相关的段比如.txt .data .bss .got.plt 的虚拟地址是固
    定的,64bit 系统程序自身基地址固定为 0x400000,但是程序的 heap,stack 与共享库的地址是随机化的。
  • 当 ASLR 与 PIE 均开启,程序自身相关的段、heap、共享库、stack 的基地址均是随机化

CTFer比较关心的:
无论是否开启ASLR与PIE,都有下面两个结论

  • 程序自身的.txt .data .bss .got.plt 等,它们之间的偏移是固定的。知道其中一个基地址,就知道所有的基地址
  • 每次mmap()之间的偏移是固定。第二次mmap()是接着第一次的空间结尾分配出来,不会有浪费。注意共享库的空间也是通过mmap()分配的, malloc()的size超过一定阈值也会变成mmap(),故可以通过某个共享库的基地址或是mmap()的基地址推出libc的基地址。

额外补充

Fortify

开启: -D_FORTIFY_SOURCE=2
关闭: 默认关闭
开启后:
格式化字符串中的%n必须位于只读区域;
格式化字符串num$,前num个参数必须存在。


  1. Di Federico A, Cama A, Shoshitaishvili Y, Kruegel C, Vigna G. How the {ELF} Ruined Christmas. In24th {USENIX} Security Symposium ({USENIX} Security 15) 2015 (pp. 643-658). ↩︎

  2. CTF-WiKi(https://ctf-wiki.org/pwn/linux/stackoverflow/advanced-rop/ret2dlresolve/) ↩︎

  3. Cowan C, Pu C, Maier D, Walpole J, Bakke P, Beattie S, Grier A, Wagle P, Zhang Q, Hinton H.Stackguard: Automatic adaptive detection and prevention of buffer-overflow attacks. In USENIX security symposium 1998 Jan 26 (Vol. 98, pp. 63-78) ↩︎

  4. Carlini N, Wagner D. {ROP} is Still Dangerous: Breaking Modern Defenses. In23rd {USENIX} 13 Security Symposium ({USENIX} Security 14) 2014 (pp. 385-399). ↩︎ ↩︎

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值