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 泄漏

绕过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
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

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 和普通用户都可以读取.
    https://www.freebuf.com/vuls/397566.html

__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/

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
在分配堆内存时会先对申请/释放的内存区域进行清零

参考链接