PXN简介
PXN全称 Privileged Execute-Never,是一种内核安全特性,用来阻止内核直接执行用户空间的代码,能够极大地提升漏洞利用的难度。
根据kernelsec网站的描述,armv8及以上版本的PXN特性由硬件支持,各平台和架构的PXN特性见下图:
(表格内容来自Linux Kernel Security Subsystem)
结合对linux源码(内核版本5.10.41)的分析可以发现,PXN和UXN机制均通过寄存器中的一个位进行控制,具体实现如下:
arch/arm64/include/asm/pgtable-hwdef.h
/*
* Level 3 descriptor (PTE).
*/
#define PTE_PXN (_AT(pteval_t, 1) << 53) /* Privileged XN */
#define PTE_UXN (_AT(pteval_t, 1) << 54) /* User XN */
因为PXN特性取决于硬件平台是否支持,所以即使我们看到代码中有对应的实现,并不代表这个功能在系统上是有效的,因此需要进一步构造测试案例来验证。
构造测试案例
编写内核模块,在内核模块中创建proc文件系统,通过proc_write接口向内核空间传递用户空间的代码地址,然后尝试在内核空间中直接执行来自用户空间的代码。
kernel模块主要逻辑实现如下:
static ssize_t mywrite(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
char buf[MAX_LENGTH];
typedef void (*MY_FUNC_P)(void);
printk(KERN_INFO "********************* dump stack **********************\n");
dump_stack();
((MY_FUNC_P)ubuf)();
return count;
}
用户空间主要逻辑实现如下:
void
payload(void)
{
if (getuid() == 0) {
printf("[+] PXN is gone, how dare you!\n");
execl("/bin/sh", "sh", NULL);
} else {
warnx("this will never be execute.");
}
_exit(0);
}
extern uint32_t shellCode[];
asm
(
" .text\n"
" .align 2\n"
" .globl shellCode\n\t"
"shellCode:\n\t"
"bl #payload\n\t"
);
void
trigger_vuln(int fd, int canary)
{
#define MAX_PAYLOAD (MAX + 4 * 4 )
char buf[MAX_PAYLOAD];
memset(buf, 0, sizeof(buf));
void * pc = buf ;
*(void **)pc = (void *) shellCode;
write(fd, buf, sizeof(buf) );
printf("[+] write done, %d bytes\n", sizeof(buf));
}
通过执行测试案例,我们的预期结果是kernel在执行用户空间代码时抛出异常提示,但如果用户空间的测试程序执行之后输出"[+] PXN is gone, how dare you!\n",则表示PXN没有生效。
测试结果
安装内核模块:
vcu-sxxx:/home# insmod lkm_pxn.ko
vcu-sxxx:/home# [ 49.457010] hello...
执行用户空间程序:
vcu-sxxxxx:/home# chmod +x uspace
vcu-sxxxxx:/home# ./uspace
[ 61.084617] ********************* dump stack **********************
[ 61.084642] CPU: 3 PID: 1474 Comm: uspace Tainted: G O 5.10.41-rt42+g5c4c385db992 #1
[ 61.084658] Hardware name: ARM vcu-sxxxxx(DT)
[ 61.084663] Call trace:
[ 61.084665] dump_backtrace+0x0/0x180
[ 61.084686] show_stack+0x18/0x70
[ 61.084695] dump_stack+0xd0/0x12c
[ 61.084707] mywrite+0x2c/0xd8 [lkm_pxn]
[ 61.084720] proc_reg_write+0xa8/0xec
[ 61.084731] vfs_write+0xf0/0x2b0
[ 61.084743] ksys_write+0x58/0xe0
[ 61.084751] __arm64_sys_write+0x20/0x30
[ 61.084760] el0_svc_common.constprop.0+0x78/0x1a0
[ 61.084772] do_el0_svc+0x24/0x90
[ 61.084780] el0_svc+0x14/0x20
[ 61.084790] el0_sync_handler+0x1a4/0x1b0
[ 61.084797] el0_sync+0x180/0x1c0
[ 61.084810] Unable to handle kernel execution of user memory at virtual address 0000007fd808ce58
[ 61.165015] Mem abort info:
[ 61.165014] printk: console [ttyLF0]: printing thread stopped
[ 61.167933] ESR = 0x8600000f
[ 61.176923] EC = 0x21: IABT (current EL), IL = 32 bits
[ 61.182402] SET = 0, FnV = 0
[ 61.185549] EA = 0, S1PTW = 0
[ 61.188788] user pgtable: 4k pages, 39-bit VAs, pgdp=000000009709d000
[ 61.195406] [0000007fd808ce58] pgd=000000008c39b003, p4d=000000008c39b003, pud=000000008c39b003, pmd=0000000081f30003, pte=00e8000086663f43
[ 61.208308] Internal error: Oops: 8600000f [#1] PREEMPT_RT SMP
[ 61.214289] Modules linked in: lkm_pxn(O) pfeng(O)
[ 61.219205] CPU: 3 PID: 1474 Comm: uspace Tainted: G O 5.10.41-rt42+g5c4c385db992 #1
[ 61.228482] Hardware name: ARM XXXXX (DT)
[ 61.233300] pstate: 40000005 (nZcv daif -PAN -UAO -TCO BTYPE=--)
[ 61.239457] pc : 0x7fd808ce58
[ 61.242501] lr : mywrite+0xb4/0xd8 [lkm_pxn]
[ 61.246882] sp : ffffffc01235b980
[ 61.250275] x29: ffffffc01235b980 x28: ffffff8001d29a80
[ 61.255721] x27: 0000000000000000 x26: 0000000000000000
[ 61.261165] x25: 0000000000000000 x24: 0000000000000000
[ 61.266609] x23: 0000000000000000 x22: ffffffc01235be38
[ 61.272052] x21: 0000007fd808ce58 x20: 0000007fd808ce58
[ 61.277497] x19: 0000000000000050 x18: 00000000fffffffa
[ 61.282941] x17: 0000000000000000 x16: 0000000000000000
[ 61.288385] x15: 0000000000000020 x14: 4141414141414141
[ 61.293828] x13: 4141414141414141 x12: 4141414141414141
[ 61.299272] x11: 4141414141414141 x10: 4141414141414141
[ 61.304715] x9 : 4141414141414141 x8 : 4141414141414141
[ 61.310160] x7 : 4141414141414141 x6 : ffffffc01235b9f0
[ 61.315603] x5 : ffffffc01235b9f0 x4 : 0000000000000008
[ 61.321048] x3 : 858d0cd041414141 x2 : 0000000000000000
[ 61.326493] x1 : 0000007fd808cea8 x0 : 0000000000000000
[ 61.331938] Call trace:
[ 61.334442] 0x7fd808ce58
[ 61.337127] proc_reg_write+0xa8/0xec
[ 61.340886] vfs_write+0xf0/0x2b0
[ 61.344289] ksys_write+0x58/0xe0
[ 61.347688] __arm64_sys_write+0x20/0x30
[ 61.351709] el0_svc_common.constprop.0+0x78/0x1a0
[ 61.356623] do_el0_svc+0x24/0x90
[ 61.360020] el0_svc+0x14/0x20
[ 61.363154] el0_sync_handler+0x1a4/0x1b0
[ 61.367262] el0_sync+0x180/0x1c0
[ 61.370667] Code: 00000000 00000000 000000ac 00000000 (41414141)
[ 61.376915] ---[ end trace 0000000000000002 ]---
Segmentation fault
从日志“Unable to handle kernel execution of user memory at virtual address”可以确认,内核PXN机制阻止了此次用户空间代码的执行。