QEMU上可以开的保护及各种绕过方法

KASLR (CONFIG_RANDOMIZE_BASE)

KASLR(Kernel Address Space Layout Randomize, 内核地址空间布局随机化),开启后,允许kernel image加载到VMALLOC区域的任何位置。在未开启KASLR保护机制时,内核代码段的基址为 0xffffffff81000000direct mapping area 的基址为 0xffff888000000000

查看方法

  • 内核启动参数是否包含kaslr参数;
  • 读/proc/kallsyms,查看是否对应未开启KASLR的基址,或者多开几次看地址是否相同。

绕过1

通过利用漏洞实现任意地址读,泄漏page_offset_base;
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x07-Kernel-Heap-Arbitrary-Address-Allocation

  • 常见结构体泄漏:msg_msg
  • 在 0x9d000 处泄漏内核地址
  • copy_to_user 爆破:通过结构体利用任意读时,如果设置地址是无效地址,copy_to_user 会返回非0值,此时 read_ldt (以 ldt_struct 为例)返回值是 -EFAULT,当 read_ldt 执行成功,说明命中内核空间。

绕过2

oops=panic 参数:内核oops时会直接panic
通过Kernel Oops的打印信息中包含的地址来泄漏,(没有开oops=panic)
oops=panic 在内核中由 panic_on_oops 标志控制,并且该变量与 .text 段相邻,如果可以修改该变量值,即可实现 oops 的信息泄漏,如果存在 CFH( Control Flow Hijacking) 攻击原语,则可以通过调用 gadgets 来修改,当然前提是已知KASLR text base,这里可以用zen2的 prefetch 来获取(retbleed)。

绕过3

读/proc/kallsyms

绕过4

hardware side channel,根据目标机器上的 cpu 型号来进行特定攻击。

ret2bpf

Achieve container escape

FG-KASLR (CONFIG_FG_KASLR & CONFIG_MODULE_FG_KASLR)

Function Granular Kernel Address Space Layout Randomization细粒度的 kaslr,函数级别上的 KASLR 优化。该保护只是在代码段打乱顺序,在数据段偏移不变,例如 commit_creds 函数的偏移改变但是 init_cred 的偏移不变。
https://ctf-wiki.org/pwn/linux/kernel-mode/defense/randomization/fgkaslr/##

查看方法

读/proc/kallsyms,符号顺序是随机的

绕过

根据 FGKASLR 的特点,我们可以发现它具有以下缺陷

  • 函数粒度随机化,如果函数内的某个地址知道了,函数内部的相对地址也就知道了。
  • .text 节区不参与函数随机化。因此,一旦知道其中的某个地址,就可以获取该节区所有的地址。有意思的是系统调用的入口代码都在该节区内,主要是因为这些代码都是汇编代码。此外,该节区具有以下一些不错的 gadget
    • swapgs_restore_regs_and_return_to_usermode,该部分的代码可以帮助我们绕过 KPTI 防护
    • memcpy 内存拷贝
    • sync_regs,可以把 RAX 放到 RDI 中
  • __ksymtab 相对于内核镜像的偏移是固定的。因此,如果我们可以泄露数据,那就可以泄露出其它的符号地址,如 prepare_kernel_cred、commit_creds。具体方式如下
    • 基于内核镜像地址获取 __ksymtab 地址
    • 基于 __ksymtab 获取对应符号记录项的地址
    • 根据符号记录项中具体的内容来获取对应符号的地址
  • data 节区相对于内核镜像的偏移也是固定的。因此在获取了内核镜像的基地址后,就可以计算出数据区数据的地址。这个节区有一些可以重点关注的数据
    • modprobe_path
__ksymtab 格式 

__ksymtab 中每个记录项的名字的格式为 __ksymtab_func_name,以 prepare_kernel_cred 为例,对应的记录项的名字为__ksymtab_prepare_kernel_cred,因此,我们可以直接通过该名字在 IDA 里找到对应的位置,如下

1
2
3
__ksymtab:FFFFFFFF81F8D4FC __ksymtab_prepare_kernel_cred dd 0FF5392F4h 
__ksymtab:FFFFFFFF81F8D500 dd 134B2h
__ksymtab:FFFFFFFF81F8D504 dd 1783Eh

__ksymtab 每一项的结构为x

1
2
3
4
5
struct kernel_symbol {
int value_offset;
int name_offset;
int namespace_offset;
};

第一个表项记录了重定位表项相对于当前地址的偏移。那么,prepare_kernel_cred 的地址应该为 0xFFFFFFFF81F8D4FC-(2**32-0xFF5392F4)=0xffffffff814c67f0。实际上也确实如此。

1
2
.text.prepare_kernel_cred:FFFFFFFF814C67F0                 public prepare_kernel_cred 
.text.prepare_kernel_cred:FFFFFFFF814C67F0 prepare_kernel_cred proc near ; CODE XREF: sub_FFFFFFFF814A5ED5+52↑p

例题:Kernel-Pwn-FGKASLR保护绕过

SMEP/SMAP

SMEP(Supervisor Mode Execution Prevention), SMAP(Supervisor Mode Accession Prevention)

查看方法

1
2
grep smep /proc/cpuinfo
grep smap /proc/cpuinfo

绕过1

修改CR4寄存器
把 CR4 寄存器中的第 20 位置为 0 后,我们就可以执行用户态的代码。一般而言,我们会使用 0x6f0 来设置 CR4,这样 SMAP 和 SMEP 都会被关闭。

内核中修改 cr4 的代码最终会调用到 native_write_cr4,当我们能够劫持控制流后,我们可以执行内核中的 gadget 来修改 CR4。从另外一个维度来看,内核中存在固定的修改 cr4 的代码,比如在 refresh_pce 函数、set_tsc_mode 等函数里都有。
https://ctf-wiki.org/pwn/linux/kernel-mode/defense/isolation/user-kernel/user-code-execution/
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#ret2usr-with-SMAP-SMEP-BYPASS

绕过2

访问Direct Mapping Area
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x03-Kernel-ROP-ret2dir

绕过3

copy_from/to_user
在劫持控制流后,攻击者可以调用 copy_from_user 和 copy_to_user 来访问用户态的内存。这两个函数会临时清空禁止访问用户态内存的标志。
https://ctf-wiki.org/pwn/linux/kernel-mode/defense/isolation/user-kernel/user-data-access/#copy_fromto_user
https://www.freebuf.com/vuls/397566.html

KPTI

KPTI(Kernel Page Table Isolation)

查看方法

1
2
3
4
5
6
dmesg | grep 'page table' 
[ 0.000000] Kernel/User page tables isolation: enabled

cat /proc/cpuinfo | grep pti
fpu_exception : yes
flags : ... pti smep smap

绕过1

swapgs and return to usermode
https://ctf-wiki.org/pwn/linux/kernel-mode/defense/isolation/user-kernel/kpti/
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#%E8%BF%94%E5%9B%9E%E7%94%A8%E6%88%B7%E6%80%81-with-KPTI-bypass

绕过2

signal handler
我们也可以考虑在用户态注册 signal handler 来执行位于用户态的代码。在这种方式下,我们无需切换页表。比如注册signal handler捕获段错误,然后执行用户态代码
hxp2020 kernel-rop

Canary (CONFIG_STACKPROTECTOR)

Kernel Canary

查看方法

  1. checksec
  2. 人工分析二进制文件,看函数中是否有保存和检查 Canary 的代码

绕过

可以发现,x86 架构下 Canary 实现的特点是同一个 task 共享 Canary。
根据 x86 架构下 Canary 实现的特点,我们只要泄漏了一次系统调用中的 Canary,同一 task 的其它系统调用中的 Canary 也就都被泄漏了。
通过漏洞泄漏canary即可
 内核中的canary的值通常取自gs段寄存器某个固定偏移处(%gs:40)的值,可以直接绕过

Dmesg Restrictions

通过设置/proc/sys/kernel/dmesg_restrict为1, 可以将dmesg输出的信息视为敏感信息(默认为0)

Kernel Address Display Restriction

内核提供控制变量 /proc/sys/kernel/kptr_restrict 用于控制内核的一些输出打印。

  • kptr_restrict == 2 :内核将符号地址打印为全 0 , root 和普通用户都没有权限.
  • kptr_restrict == 1 : root 用户有权限读取,普通用户没有权限.
  • kptr_restrict == 0 : root 和普通用户都可以读取.

__ro_after_init

Linux 内核中有很多数据都只会在 __init 阶段被初始化,而且之后不会被改变。使用 __ro_after_init 标记的内存,在 init 阶段结束后,不能够被再次修改。
例如下面a3博客里在泄漏内核基址时提到的部分,该内容中.realmode_init成员的内容即不能修改。

1
2
3
4
5
struct x86_platform_ops x86_platform __ro_after_init = {  
/* ... */
.realmode_init = init_real_mode,
/* ... */
};

绕过

我们可以使用 set_memory_rw(unsigned long addr, int numpages) 来修改对应页的权限。

mmap_min_addr

mmap_min_addr 是用来对抗 NULL Pointer Dereference 的,指定用户进程通过 mmap 可以使用的最低的虚拟内存地址。

1
2
cat /proc/sys/vm/mmap_min_addr
65536

一些Linux的编译config的作用(可能不能绕过)

CONFIG_SLAB_FREELIST_HARDENED

堆保护机制,类似与glibc的safe-linking机制,对freelist的指针进行异或加密

查看方法

freelist的指针丑不丑

绕过

https://pwn.college/software-exploitation/kernel-exploitation/
例题:强网杯2021线上赛 - notebook

CONFIG_SLAB_FREELIST_RANDOM

堆保护机制,定义random_seq,打乱freelist的顺序,但是后续申请释放仍然为LIFO

查看方法

多申请几次,查看申请到的堆块后三位偏移是否会变化

绕过

CONFIG_HARDENED_USERCOPY

内存保护机制,copy_from_user(),copy_to_user()

This series, which adds CONFIG_HARDENED_USERCOPY, checks that objects
being copied to/from userspace meet certain criteria:
- if address is a heap object, the size must not exceed the object’s
allocated size. (This will catch all kinds of heap overflow flaws.)
- if address range is in the current process stack, it must be within the
current stack frame (if such checking is possible) or at least entirely
within the current process’s stack. (This could catch large lengths that
would have extended beyond the current process stack, or overflows if
their length extends back into the original stack.)
- if the address range is part of kernel data, rodata, or bss, allow it.
- if address range is page-allocated, that it doesn’t span multiple
allocations.
- if address is within the kernel text, reject it.
- everything else is accepted

https://pwn.college/software-exploitation/kernel-exploitation/

CONFIG_STATIC_USERMODEHELPER

modprobe_path, core_pattern相关,如果设置该配置,则在执行call_usermodeprobe_setup()函数时会执行该配置项对应的CONFIG_STATIC_USERMODEHELPER_PATH路径,此时修改modprobe_path虽然还能覆写,但攻击无效。

  • modprobe_path 验证修改路径: /proc/sys/kernel/modprobe
  • core_pattern 验证修改路径:/proc/sys/kernel/core_pattern

查看方法

https://github.com/smallkirby/kernelpwn/blob/master/important_config/STATIC_USERMODEHELPER.md
https://www.kernelconfig.io/config_static_usermodehelper
注:linux 6.2后不会在自动装载 binfmt_misc 内核模块,从而导致无法直接利用 modprobe_path,但是仍然存在一些非特权的系统调用可以实现该模块的自动装载,例如:

1
socket(AF_INET, SOCK_STREAM, 132)

CONFIG_MEMCG_KMEM

GFP_KERNEL & GFP_KERNEL_ACCOUNT 的隔离
開啟 CONFIG_MEMCG_KMEM,且 malloc 時傳入 GFP_KERNEL_ACCOUNT,便會新增另一組 cache kmalloc-cg-x 以隔離重要內容。
在5.9 版本之前GFP_KERNEL 与 GFP_KERNEL_ACCOUNT 存在隔离机制,在 这个 commit 中取消了隔离机制,自内核版本 5.14 起,在 这个 commit 当中又重新引入

绕过

  • 利用cross-cache攻击内核结构体:sk_buff, pipe_buffer, msg_msg, cred等

SLAB_ACCOUNT flag

在使用 kmem_cache_create 创建一个 cache 时,传递了 SLAB_ACCOUNT 标记,那么这个 cache 就会单独存在,不会与其它相同大小的 cache 合并。
许多结构体(如 cred 结构体)对应的堆块并不单独存在,会和相同大小的堆块使用相同的 cache

CONFIG_RANDOMIZE_KSTACK_OFFSET

内核栈空间(在pt_regs后)偏移随机化
对应内核启动参数:”randomize_kstack_offset=on/off”

查看方法

绕过

https://www.kernelconfig.io/config_randomize_kstack_offset?arch=x86&kernelversion=6.15.10

CONFIG_CFI_CLANG

对间接函数调用添加了运行时检查

查看方法

绕过

直接覆写内核调用栈的物理地址构造ROP;
USMA
D3CTF2023_d3kcache

CONFIG_BINFMT_MISC

查看方法

是否可以使用modprobe_path提权路径
查看是否存在”binfmt-%04”字符串

INIT_ON_ALLOC_DEFAULT_ON & CONFIG_INIT_ON_FREE_DEFAULT_ON

内核启动参数:init_on_alloc=1,init_on_free=1
在分配堆内存时会先对申请/释放的内存区域进行清零

其他防护

sysctl_unprivileged_userfaultfd

针对userfaultfd的防护机制,在linux-5.11以及以后将其初始化为0,只有root权限才能进行该系统调用。

addr_limit

arm架构下的一种方式,规定了用户空间进程可以访问的地址空间,如果设置成 -1 则可以访问整个地址空间,一般位于 thread_info+8 的位置

UAO (User Access Override)

armv8.2 下的一种防护方式,用于在 addr_limit 为 -1 的时候防止访问用户空间地址内容。
UAO (User Access Override) as a mitigation against addr_limit overwrites

参考链接