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

您所在的位置:网站首页 pwn攻击 [阅读型]CTF中linux pwn的四大基本防御措施

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

2024-03-25 10:37| 来源: 网络整理| 查看: 265

目录 前言linux pwn中四大基本防御1. RELRO2. CANARY3. NX4. PIE & ASLR 额外补充Fortify

前言

个人比较系统与深入总结,适合有一定基础的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 位机上在指令层有优化,提供了特别的与地址无关的取址指令。

与地址无关技术的关键就是相对地址的mov与call。 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 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个参数必须存在。

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). ↩︎

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

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) ↩︎

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3