利用Linux内核CVE-2021-26708漏洞研究LKRG防护机制

本文详细分析了如何利用Linux内核CVE-2021-26708漏洞绕过LKRG运行时防护机制,包括ROP链构造、栈转移技术和内核代码补丁技术,展示了从漏洞利用到防护绕过的完整攻击链。

利用Linux内核CVE-2021-26708漏洞研究LKRG防护机制

研究背景

在本文中,我将继续研究Linux内核中的CVE-2021-26708漏洞。我改进了漏洞利用原型,并以此从攻击者角度研究了Linux内核运行时防护机制(LKRG)。我将介绍如何发现新的LKRG绕过方法,以及如何进行负责任的漏洞披露。

为什么继续研究

在第一篇文章中,我描述了在x86_64平台Fedora 33 Server上进行本地权限提升的漏洞利用原型。我解释了Linux内核虚拟套接字实现中的竞争条件如何导致四个字节的内核内存损坏,并展示了攻击者如何逐步将这种错误转化为内核内存的任意读写,从而提升系统权限。但这种权限提升方式存在一些限制,阻碍了我在LKRG保护下的系统中进行实验。

寄存器控制分析

首先,我重新查看了控制流劫持时的处理器寄存器状态。在skb_zcopy_clear()函数中设置断点,该函数调用destructor_arg中的回调处理程序:

1
2
3
$ gdb vmlinux
gdb-peda$ target remote :1234
gdb-peda$ break ./include/linux/skbuff.h:1481

调试器显示在控制流劫持前,RBP寄存器包含skb_shared_info的地址,这给了我成功的希望,因为它指向攻击者控制的内存。

JOP gadget的消失

我发现了许多涉及RBP的JOP gadget,例如:

1
0xffffffff81711d33 : xchg eax, esp ; jmp qword ptr [rbp + 0x48]

但当我尝试在控制流劫持时执行这个gadget时,内核意外出现了页面错误。通过调试发现,Linux内核可以在运行时修改自己的代码,CONFIG_DYNAMIC_FTRACE机制修改了acpi_idle_lpi_enter()函数的代码。

内存转储分析

为了避免这种情况,我决定在实时虚拟机内核内存中搜索所需的ROP/JOP gadget。首先确定内核代码在内存中的位置:

1
2
3
4
[root@localhost ~]# grep "_text" /proc/kallsyms
ffffffff81000000 T _text
[root@localhost ~]# grep "_etext" /proc/kallsyms
ffffffff81e026d7 T _etext

然后使用gdb-peda的dumpmem命令转储内存:

1
gdb-peda$ dumpmem kerndump 0xffffffff81000000 0xffffffff81e03000

JOP/ROP链构造

通过分析内存中的gadget,我构建了以下JOP/ROP链来实现栈转移:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* JOP/ROP gadget chain for stack pivoting: */

/* mov ecx, esp ; cwde ; jmp qword ptr [rbp + 0x48] */
#define STACK_PIVOT_1_MOV_ECX_ESP_JMP (0xFFFFFFFF81768A43lu + kaslr_offset)

/* push rdi ; jmp qword ptr [rbp - 0x75] */
#define STACK_PIVOT_2_PUSH_RDI_JMP (0xFFFFFFFF81B5FD0Alu + kaslr_offset)

/* pop rsp ; pop rbx ; ret */
#define STACK_PIVOT_3_POP_RSP_POP_RBX_RET (0xFFFFFFFF8165E33Flu + kaslr_offset)

权限提升ROP链

成功将内核栈转移到受控内存区域后,我快速组装了权限提升ROP链:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
unsigned long *rop_gadget = (unsigned long *)(xattr_addr + MY_UINFO_OFFSET + 8);
int i = 0;

#define ROP_POP_RAX_RET (0xFFFFFFFF81015BF4lu + kaslr_offset)
#define ROP_MOV_QWORD_PTR_RAX_0_RET (0xFFFFFFFF8112E6D7lu + kaslr_offset)

/* 1. Perform privilege escalation */
rop_gadget[i++] = ROP_POP_RAX_RET; /* pop rax ; ret */
rop_gadget[i++] = owner_cred + CRED_UID_GID_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_0_RET; /* mov qword ptr [rax], 0 ; ret */
rop_gadget[i++] = ROP_POP_RAX_RET; /* pop rax ; ret */
rop_gadget[i++] = owner_cred + CRED_EUID_EGID_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_0_RET; /* mov qword ptr [rax], 0 ; ret */

测试LKRG防护

Linux Kernel Runtime Guard (LKRG)是一个非常有趣的项目,它提供了一个内核模块,在系统运行过程中检查内核完整性并抵御漏洞利用。LKRG通过特征行为和特定数据损坏来检测内核利用。

LKRG检测以下行为:

  • 通过commit_creds()调用或重写struct cred进行的未授权权限提升
  • 进程隔离破坏和namespace逃逸
  • 未授权的处理器状态更改(如x86_64上的SMEP和SMAP禁用)
  • Linux内核.text和.rodata节中的数据非法修改
  • stack pivoting和ROP技术的执行

成功的LKRG攻击

最终我成功设计出针对LKRG的有效攻击。通过研究其代码,我找到了两个负责主要功能的关键函数:p_check_integrity()(执行内核代码完整性检查)和p_cmp_creds()(将系统进程权限与LKRG内部数据库进行比较并检测未授权权限提升)。

我决定直接攻击,从利用程序的ROP链中重写这两个函数的代码。我使用字节0x48 0x31 0xc0 0xc3来实现,这些字节代表指令xor rax, rax ; ret,即return 0。

最终ROP链

完整的ROP链包括以下部分:

  1. 保存RSP寄存器值
  2. 销毁LKRG:第一部分 - 查找p_cmp_creds函数
  3. 条件处理 - 准备补丁代码
  4. 使用text_poke()函数重写内核代码
  5. 对p_check_integrity()函数重复相同过程
  6. 执行权限提升
  7. 恢复RSP寄存器并继续执行

负责任的披露

我在2021年6月10日向Adam Zabrocki和Alexander Peslyak (Solar Designer)报告了我的LKRG实验结果。我们详细讨论了我的LKRG绕过方法,并交换了对项目整体的看法。

经Adam和Alexander允许,我于7月3日在lkrg-users公开邮件列表中发布了我的研究成果。在本文发布时,我的攻击方法仍然有效。要提供保护,需要对LKRG架构进行重新设计,这已在未来计划中。

结论

在本文中,我描述了我如何改进针对Linux内核CVE-2021-26708漏洞的利用原型。这是一个有趣的研究,涉及大量面向返回编程和汇编语言的实践。我在运行系统的内存中搜索ROP/JOP gadget,并在受限条件下成功执行了内核栈转移。我还从攻击者角度分析了Linux Kernel Runtime Guard防护机制,开发了新的LKRG攻击方法,并向该项目开发团队提供了我的研究成果。

我相信这篇文章对Linux开发社区会有很大帮助,因为它反映了内核安全的许多实践方面。我还要感谢Positive Technologies公司给我进行这项研究的机会。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计