程序保护机制学习
Linux
ELF
relro
read only relocation: 由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.
- 比如说 got表,在完全开启后只读,我们无法修改修改函数 got 表的内容从而改变函数的执行过程。
gcc 选项
1 | -z norelro # 关闭 |
aslr
Address Space Layout Randomization, 这种技术使得系统上运行的进程的内存地址无法被预测,使得与这些进程有关的漏洞变得更加难以利用。配合 PIE 保护从而得到最好的效果
Linux上ASLR分为0/1/2三级,用户可以通过内核参数randomize_va_space进行等级控制,对应效果如下:
- 0:没有随机化,即关闭ASLR
- 1:保留的随机化,即共享库、栈、mmap()以及VSDO将被随机化
- 2:完全的随机化,在1的基础上,通过brk分配的内存空间(heap通过此系统调用获得)也将随机化
1 | $ cat /proc/sys/kernel/randomize_va_space |
在使用 gdb 软件调试时可以关闭此保护,从而在调试时获得的地址一致。
因为存在一定的地址随机化,所以在漏洞利用时不能使用固定的函数地址。比如没开 aslr 保护system函数(共享库中的函数)地址是 0x1234。开启aslr时system函数位置会改变,从而利用失败。
NX
No-eXecute 不可执行。和DEP(Data Execute Protector)一致
将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
gcc参数
1 | -z execstack # 栈可执行 |
bypass: 杜绝了一定写shellcode的利用,但是可以使用**ROP(Return-oriented programming)**来bypass,造成我们想要的攻击效果。
PIE
Position independent code, 位置无关代码,默认开启。
针对代码段.text, 数据段,.data,.bss等固定地址的一个防护技术。同ASLR一样,应用了PIE的程序会在每次加载时都变换加载基址,从而使位于程序本身的gadget也失效
没有PIE保护的程序,每次加载的基址都是固定的,64位上一般是0x400000。
gcc 参数
1 | -no-pie # 关闭pie |
开启aslr,我们可以获得text段的gadget,并且可以使用。但是开其pie保护后,text随机化,我们寻找的gadget也需要加上一个偏移才能使用。
- bypass
- partial write: 由于内存的页载入机制,PIE的随机化只能影响到单个内存页。通常来说,一个内存页大小为0x1000,这就意味着不管地址怎么变,某条指令的后12位是始终不变的。因此通过覆盖部分内容比如后8位从而劫持函数执行流。
- leak: 程序本身的漏洞可以泄露某些函数地址,从而获得程序加载的基地址。
- vdso/vsyscall: 系统把几个常用的无参内核调用从内核中映射到用户空间中,这就是vsyscall。在某些版本中,这个地址不会改变,并且不受保护的影响。vsyscall 内存页中包含了三个系统调用。而且这三个系统调用对程序运行基本没有影响,也就是说我们获得了三个已知地址的 ret。相当可以执行
ret
指令,获得:查看程序的映射cat /proc/<pid>/maps
。
- partial write: 由于内存的页载入机制,PIE的随机化只能影响到单个内存页。通常来说,一个内存页大小为0x1000,这就意味着不管地址怎么变,某条指令的后12位是始终不变的。因此通过覆盖部分内容比如后8位从而劫持函数执行流。
1 | __vsyscall_page: |
stack canary
当启用 canary 保护后,函数开始执行的时候会先往栈里插入 cookie 信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在栈溢出覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
Linux canary 最后一字节为 \x00
gcc 开启和关闭的参数
1 | -fno-stack-protector # 关闭,默认没有canary |
可以看这系列文章。加深对canary理解:Linux内核的FSGSBASE特性
Linux x86-64下的fs/gs段寄存器?
- 用户态使用fs寄存器引用线程的glibc TLS(TLS全称Thread Local Storage)和线程在用户态的stack canary;用户态的glibc不使用gs寄存器;应用可以自行决定是否使用该寄存器。
- 内核态使用gs寄存器引用percpu变量和进程在内核态的stack canary;内核态不使用fs寄存器。
默认stack canary使用全局符号变量 __stack_chk_guard(fs:0x28) 作为原始的canary, 在gcc/clang中均使用相同的名字
1 | typedef struct |
per-cpu 变量的引入是为了实现per-task的stack canary, 每个cpu上同时只能运行一个进程/线程, per cpu变量可以随进程的切换而切换,故通过一个per-cpu变量完全可以为每个进程/线程解引用到不同的canary地址(后续称为per-cpu canary),以实现per-task的canary.
kernel
kaslr
kernel address space layout randomize
在开启了 KASLR 的内核中,内核的代码段基地址等地址会整体偏移。
smap
Supervisor Mode Access Prevention,管理模式访问保护。
如果内核态可以访问用户态的数据,也会出现问题。比如在劫持控制流后,攻击者可以通过栈迁移将栈迁移(pop rsp类似的指令)到用户态,然后进行 ROP,进一步达到提权的目的。
在 Linux 内核中,这个防御措施的实现是与指令集架构相关的。x86 下对应的保护机制的名字为 SMAP。CR4 寄存器中的第 21 位用来标记是否开启 SMEP 保护。
x86CPU的特性,需要CPU和操作系统支持。bypass: 修改cr4
smep
Supervisor Mode Execution Prevention,管理模式执行保护。
在内核态执行代码时,可以直接执行用户态的代码。那如果攻击者控制了内核中的执行流,就可以执行处于用户态的代码。由于用户态的代码是攻击者可控的,所以更容易实施攻击。为了防范这种攻击,研究者提出当位于内核态时,不能执行用户态的代码。
在 Linux 内核中,这个防御措施的实现是与指令集架构相关的(ARM PXN)。x86下 CR4
寄存器中的第 20 位用来标记是否开启 SMEP 保护。
查看是否开启
1 | grep smap /proc/cpuinfo # 如果出现结果,说明开启 |
bypass: 修改cr4
kpti
Kernel Page Table Isolation,内核页表隔离
在 x86_64 的 PTI 机制中,内核态的用户空间内存映射部分被全部标记为不可执行。也就是说,之前不具有 SMEP 特性的硬件,如果开启了 KPTI 保护,也具有了类似于 SMEP 的特性。此外,SMAP 模拟也可以以类似的方式引入,只是现在还没有引入。因此,在目前开启了 KPTI 保护的内核中,如果没有开启 SMAP 保护,那么内核仍然可以访问用户态空间的内存,只是不能跳转到用户态空间执行 Shellcode。
bypass
- signal handler
- swapgs_restore_regs_and_return_to_usermode 函数中存在可以改变 cr3 的gadget
fgkaslr
FGKASLR 在 KASLR 基地址随机化的基础上,在加载时刻,以函数粒度重新排布内核代码。目前,FGKASLR 只支持 x86_64 架构。
FGKASLR 利用 gcc 的编译选项 -ffunction-sections
把内核中不同的函数放到不同的 section 中。 在编译的过程中,任何使用 C 语言编写的函数以及不在特殊输入节的函数都会单独作为一个节;使用汇编编写的代码会位于一个统一的节中。
如果想要开启内核的 FGKASLR,你需要开启 CONFIG_FG_KASLR=y
选项。
Kernel Stack Canary
在编译内核时,我们可以设置 CONFIG_CC_STACKPROTECTOR 选项,来开启该保护
在 x86 架构中,同一个 task 中使用相同的 Canary。
Android
安卓使用 Linux Kernel,有些保护并非特有,而是是否默认开启。
pxn
Privileged Execute-Never。内核安全特性,用来阻止内核直接执行用户空间的代码,能够极大地提升漏洞利用的难度。
和 smep 一个性质
armv8及以上版本的PXN特性由硬件支持,Android 5 arm64后开启,Android通过页表来开启PXN。
pan
smap 类似的性质
cfi
Control-Flow Integrity 控制流完整性
其核心思想是限制程序运行中的控制转移,使之始终处于原有的控制流图所限定的范围内。具体做法是通过分析程序的控制流图,获取间接转移指令(包括间接跳转、间接调用、和函数返回指令)目标的白名单,并在运行过程中,核对间接转移指令的目标是否在白名单中。控制流劫持攻击往往会违背原有的控制流图,CFI使得这种攻击行为难以实现,从而保障软件系统的安全。
内核 CFI 手动启用,x86通过此开启
1 | CONFIG_CFI_CLANG=y |
SELinux
Android 8.0之后推出厂商升级成本大大降低,8.0之后增加vendor.img镜像 ,攻击面大大减少, 很多厂商的代码不与应用层直接交互, 增加了应用和厂商代码的SELinux限制
Windows
ASLR
与 Linux 相同,ASLR 保护指的是地址随机化技术(Address Space Layout Randomization),这项技术将在程序启动时将 DLL 随机的加载到内存中的位置,这将缓解恶意程序的加载。ASLR 自 Windows 10 开始已经在系统中被配置为默认启用。
不同于linux的是,windows下的ASLR基地址在每次开机后都是一致的,关机后改变
DEP
Data Execute Protector
VirtualAlloc()
可以设置权限,设置为`
GS
这个保护类似于 Linux 中的 Canary 保护,一旦开启,会在返回地址和 BP 之前压入一个额外的 Security Cookie。系统会比较栈中的这个值和原先存放在 .data 中的值做一个比较。如果两者不吻合,说法栈中发生了溢出。
.NET
DLL 混淆级保护。
Isolation
被称为隔离保护,一旦开启,表示此程序加载时将会在一个相对独立的隔离环境中被加载,从而阻止攻击者过度提升权限。
SEH
结构化异常处理(Structured Exception Handling,简称 SEH)是一种 Windows
操作系统对错误或异常提供的处理技术。为Windows 的程序设计者提供了程序错误或异常的处理途径,使得系统更加健壮。
SafeSEH
安全结构化异常处理函数,即白名单安全沙箱,事先定义一些异常处理程序,并基于此构造安全结构化异常处理表,程序正式运行后,安全结构化异常处理表之外的异常处理程序将会被阻止运行。
SMEP SMAP
windows10 之后引入 SMEP和SMAP
KVAS
内核虚拟地址影子(Kernel Virtual Address Shadow,由微软提出的一个术语,简称KVAS),由于该特性仅允许用户模式代码访问有限的内核内存,因此能有效防范Meltdown攻击。是微软在Windows系统上对KPTI的具体实现方式
Virtualization-Based Security (VBS)
Hypervisor Protected Code Integrity (HVCI)