程序保护机制

程序保护机制学习

Linux

ELF

relro

read only relocation: 由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.

  • 比如说 got表,在完全开启后只读,我们无法修改修改函数 got 表的内容从而改变函数的执行过程。

gcc 选项

1
2
3
-z norelro   # 关闭
-z lazy # 部分开启
-z now # 完全开启

aslr

Address Space Layout Randomization, 这种技术使得系统上运行的进程的内存地址无法被预测,使得与这些进程有关的漏洞变得更加难以利用。配合 PIE 保护从而得到最好的效果

Linux上ASLR分为0/1/2三级,用户可以通过内核参数randomize_va_space进行等级控制,对应效果如下:

  • 0:没有随机化,即关闭ASLR
  • 1:保留的随机化,即共享库、栈、mmap()以及VSDO将被随机化
  • 2:完全的随机化,在1的基础上,通过brk分配的内存空间(heap通过此系统调用获得)也将随机化
1
2
3
$ cat  /proc/sys/kernel/randomize_va_space
2
echo 0 > /proc/sys/kernel/randomize_va_space

在使用 gdb 软件调试时可以关闭此保护,从而在调试时获得的地址一致。

因为存在一定的地址随机化,所以在漏洞利用时不能使用固定的函数地址。比如没开 aslr 保护system函数(共享库中的函数)地址是 0x1234。开启aslr时system函数位置会改变,从而利用失败。

NX

No-eXecute 不可执行。和DEP(Data Execute Protector)一致

将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

gcc参数

1
2
-z execstack    # 栈可执行
-z noexecstack # 开启

bypass: 杜绝了一定写shellcode的利用,但是可以使用**ROP(Return-oriented programming)**来bypass,造成我们想要的攻击效果。

PIE

Position independent code, 位置无关代码,默认开启。
针对代码段.text, 数据段,.data,.bss等固定地址的一个防护技术。同ASLR一样,应用了PIE的程序会在每次加载时都变换加载基址,从而使位于程序本身的gadget也失效

没有PIE保护的程序,每次加载的基址都是固定的,64位上一般是0x400000。

gcc 参数

1
2
-no-pie    # 关闭pie
-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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__vsyscall_page:
mov $__NR_gettimeofday, %rax
syscall
ret

.balign 1024, 0xcc
mov $__NR_time, %rax
syscall
ret

.balign 1024, 0xcc
mov $__NR_getcpu, %rax
syscall
ret

stack canary

当启用 canary 保护后,函数开始执行的时候会先往栈里插入 cookie 信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在栈溢出覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。

Linux canary 最后一字节为 \x00

gcc 开启和关闭的参数

1
2
3
4
5
-fno-stack-protector   # 关闭,默认没有canary

-fstack-protector # 保护函数中通过alloca()分配缓存以及存在大于8字节的buffer。保护能力有限,不会保护所有的函数
-fstack-protector-all # 启用堆栈保护,为所有函数插入保护代码
-fstack-protector-strong # 编译参数让保护的范围更广

可以看这系列文章。加深对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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
unsigned long int unused_vgetcpu_cache[2];
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
int __glibc_unused1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
/* The marker for the current shadow stack. */
unsigned long long int ssp_base;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));

void *__padding[8];
} tcbhead_t;

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)

参考文章