Kernel PWN保护机制总结
QEMU上可以开的保护及各种绕过方法
KASLR (CONFIG_RANDOMIZE_BASE)
KASLR(Kernel Address Space Layout Randomize, 内核地址空间布局随机化),开启后,允许kernel image
加载到VMALLOC
区域的任何位置。在未开启KASLR保护机制时,内核代码段的基址为 0xffffffff81000000
,direct 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 泄漏
绕过2
通过Kernel Oops的打印信息中包含的地址来泄漏,(没有开oops=panic)
绕过3
读/proc/kallsyms
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 | __ksymtab:FFFFFFFF81F8D4FC __ksymtab_prepare_kernel_cred dd 0FF5392F4h |
__ksymtab
每一项的结构为x
1 | struct kernel_symbol { |
第一个表项记录了重定位表项相对于当前地址的偏移。那么,prepare_kernel_cred
的地址应该为 0xFFFFFFFF81F8D4FC-(2**32-0xFF5392F4)=0xffffffff814c67f0
。实际上也确实如此。
1 | .text.prepare_kernel_cred:FFFFFFFF814C67F0 public prepare_kernel_cred |
SMEP/SMAP
SMEP(Supervisor Mode Execution Prevention), SMAP(Supervisor Mode Accession Prevention)
查看方法
1 | grep smep /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
KPTI
KPTI(Kernel Page Table Isolation)
查看方法
1 | dmesg | grep 'page table' |
绕过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
查看方法
checksec
- 人工分析二进制文件,看函数中是否有保存和检查 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 和普通用户都可以读取.
https://www.freebuf.com/vuls/397566.html
__ro_after_init
Linux 内核中有很多数据都只会在 __init
阶段被初始化,而且之后不会被改变。使用 __ro_after_init
标记的内存,在 init 阶段结束后,不能够被再次修改。
例如下面a3博客里在泄漏内核基址时提到的部分,该内容中.realmode_init成员的内容即不能修改。
1 | struct x86_platform_ops x86_platform __ro_after_init = { |
绕过
我们可以使用 set_memory_rw(unsigned long addr, int numpages)
来修改对应页的权限。
mmap_min_addr
mmap_min_addr 是用来对抗 NULL Pointer Dereference 的,指定用户进程通过 mmap 可以使用的最低的虚拟内存地址。
1 | cat /proc/sys/vm/mmap_min_addr |
一些Linux的编译config的作用(可能不能绕过)
CONFIG_SLAB_FREELIST_HARDENED
堆保护机制,类似与glibc的safe-linking机制,对freelist的指针进行异或加密
查看方法
freelist的指针丑不丑
绕过
https://pwn.college/software-exploitation/kernel-exploitation/
CONFIG_SLAB_FREELIST_RANDOM
堆保护机制,定义random_seq,打乱freelist的顺序,但是后续申请释放仍然为LIFO
查看方法
多申请几次,查看申请到的堆块后三位偏移是否会变化
绕过
打乱顺序仅限于申请内存时,后续释放后再申请仍然为LIFO;
https://pwn.college/software-exploitation/kernel-exploitation/
CONFIG_HARDENED_USERCOPY
内存保护机制,copy_from_user(),copy_to_user()
https://pwn.college/software-exploitation/kernel-exploitation/
CONFIG_STATIC_USERMODEHELPER
与modprobe_path
, core_pattern
相关,如果设置该配置,则在执行call_usermodeprobe_setup()函数时会执行该配置项对应的CONFIG_STATIC_USERMODEHELPER_PATH
路径,此时修改modprobe_path虽然还能覆写,但攻击无效。
查看方法
https://github.com/smallkirby/kernelpwn/blob/master/important_config/STATIC_USERMODEHELPER.md
https://www.kernelconfig.io/config_static_usermodehelper
CONFIG_MEMCG_KMEM
GFP_KERNEL & GFP_KERNEL_ACCOUNT 的隔离
開啟 CONFIG_MEMCG_KMEM,且 malloc 時傳入 GFP_KERNEL_ACCOUNT
,便會新增另一組 cache kmalloc-cg-x
以隔離重要內容。
绕过
- 利用其他结构体:sk_buff, pipe_buffer, msg_msg
- cross-cache
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
在分配堆内存时会先对申请/释放的内存区域进行清零