内核pwn入门
-
五一之前做了一下GHCTF2024的内核入门题,好像有点摸到门道了
前提介绍
内核与用户pwn的区别
- 内核pwn和用户pwn的区别如下:
- 获取权限:最大的区别就是用户的pwn是拼凑
system('/bin/sh')
,这样以后就可以getshell
。而内核的pwn是提权,各种操作后将Linux的操作权限从用户变成root权限。 - 代码量:用户pwn大多都对程序进行攻击,内核的pwn是对操作系统的内核,攻击对象由程序这层转变为操作系统这层。这就意味着需要阅读内核代码,这代码量往往比用户pwn大得多。也需要更扎实的操作系统理论知识。
- 保护机制:内核的保护机制也和程序的保护机制用差别。
- 获取权限:最大的区别就是用户的pwn是拼凑
内核pwn的题型
- 内核pwn主要是寻找偏硬件的程序漏洞。接下来就对内核pwn的题型进行初步的分类。
- 我们一般入门都是先对
/dev
目录下的设备驱动进行利用,并且CTF
关于内核的出题一般来说就是出/dev
目录下面的题目。
- 而如果按照漏洞造成的结果,内核漏洞就可以简单分为如下三类:
提权
、内核任意执行
、逃逸
内核pwn环境搭建
- 接下来就来安装一下内核pwn利用的相关调试环境。
qemu的安装
- 在内核
pwn
题中会出现一个.sh
脚本,这个脚本用qemu
使用的题目的内核程序。
1 |
|
- 所以我们需要使用
qemu
这个虚拟化软件,这样我们就可以在本地中启动题目给的内核脚本文件。 - 接下来使用如下命令安装qemu程序。
1 | sudo apt update |
- 安装完之后运行如下命令查看一下
qemu
是否安装完成
1 | qemu-system-x86_64 --version |
busbox文件系统
-
选择使用
busbox
作为内核调试的文件系统环境有以下几点好处:-
当做内核开发和研究的时候,并不需要准备完备的文件系统,那样太复杂也很占存储空间,busybox对于kernel开发和调试来说正好合适
-
当进行跨平台内核调试时,用完备的ext4系统,运行非常慢,busybox主要是为了嵌入式之类的运算能力弱的设备
-
qemu-system的纯软件模拟非常慢,busybox刚好合适
-
-
可以直接选择包管理器一键安装
1 | sudo apt update |
- 也可以选择下载源码后本地编译安装(建议:busybox默认是动态编译,但是这里需要的是静态编译,如果动态编译的话会让文件系统变得很大)
- 先下载一下编译需要的依赖
1 | sudo apt update |
- 然后使用命令去官网上下载busybox源码
1 | wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 |
- 下载之后解压缩文件
1 | tar xjf busybox-1.36.1.tar.bz2 |
-
然后进行编译安装
make xxxxxconfig
,busybox提供了几种编译配置-
defconfig(默认配置)
-
allyesconfig(最大配置)
-
allnoconfig(最小配置)
-
这里我们一般选择默认配置
-
-
然后是
make menuconfig
,选择静态编译,当你认为上述配置中还有不满意的地方,可以进行微调,加入或去除某些命令。make menuconfig
进入后选择安装位置,进入设置
- 选择静态链接
- 然后也可以选择这个修改安装目录
- 选择保存你所选择的配置
- 最后就是编译安装了
1 | 在x86_64下只要输入该指令即可:make -j4 |
- 编译好了之后进行安装
1 | make install |
- 安装好后找到你之前配置的安装路径
内核pwn附件介绍
-
内核pwn不同于用户模式下的pwn,用户模式下的pwn最多给三个文件,
ld
、libc
、程序
,而内核pwn给的文件就有点多。 -
以2024年9月的长城杯一个内核pwn的题目做介绍,刚好试试能不能复现一下。
-
附件如下:https://wwsq.lanzoue.com/iZCI929l3qli 密码:ghfb
-
题目给的附件结构如下:
1 | . |
内核文件–bzlmage
-
bzlmage这个还是比较熟悉的,之前在重新编译wsl内核的时候看见过该程序,大概知道这个是内核,但是还没具体了解
-
bzlmage
这个是压缩后的Linux内核的镜像文件,它是一种大于传统的zImage
格式的内核镜像。bzImage
是 Linux 内核的引导镜像,用于引导系统启动。- 在内核pwn中,如果要开发一个远程漏洞利用脚本,理解
bzImage
的结构和启动过程可能会有助于理解漏洞的触发条件以及内核的内存布局。 - 可能会要对
bzImage
进行逆向,以深入分析内核的行为、检测安全漏洞。 - 使用
balmage
是比较难找gadget,这时候需要使用工具将该压缩后的内核文件解压成vmlinux
文件,可以使用ropper在提取的vmlinux中搜寻gadget,ropper比ROPgadget快很多,所以需要安装ropper - 将
bzlmage
这个提取出vmlinux
的工具网站如下。https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux,提取操作在下面
-
vmlinux
是未压缩的Linux内核映像,包含完整的内核代码段和数据段。- 通常包含调试符号,能够通过
gdb
等调试器加载进行符号化调试。 - 可以直接通过ROP工具或手动查找gadget,比如用
ROPgadget
、ROPg
等工具搜索gadgets。
- 通常包含调试符号,能够通过
启动脚本--start.sh
- start.sh其实就是一些qemu的启动命令。
1 |
|
- 给这个脚本附加权限
chmod +x start.sh
后然后运行这个脚本就可以启动该内核环境
初始RAM磁盘文件–rootfs.cpio
- 这个文件与内核文件一样重要,这里面也存在几个很重要的文件
- 之后会介绍如何将一个文件夹打包成这个文件,因为我们需要将静态编译好的用c语言写的
exp
文件放入这个文件中,然后再打包这个文件,学会打包这个文件是很重要的 - 下面先介绍几个比较重要的文件(可能不全,还需要待补充)
初始化文件–init
-
init
文件是系统启动时执行的第一个用户空间进程(PID 1)。它负责初始化系统,设置环境并启动其他进程。对init
文件的分析可以帮助你理解系统的启动流程和配置。 -
init
文件中包含的脚本和命令决定了系统如何挂载文件系统、设置网络、启动服务等。这些操作通常涉及到与内核的交互,并可能暴露潜在的漏洞或不安全的配置。 -
通过分析
init
文件,你可以获得有关如何启动和配置系统的信息。这些信息有助于你确定如何在内核或内核模块中寻找潜在的漏洞。 -
接下来我们来看一下
init
这个文件里面的内容- 从init里面的内容就可以比较快速的找出一些漏洞比如
test.ko
模块的代码和/dev/test
设备的权限配置可能包含可利用的安全漏洞。- 并且知道了该系统启动时是以uid为1000的用户身份,而不是root身份
- 从init里面的内容就可以比较快速的找出一些漏洞比如
1 |
|
内核模块–test.ko(目前)
.ko
文件表示这是一个可加载的内核对象文件,通常用于扩展内核的功能而不需要重新编译整个内核- 基本作用:内核模块可以增加内核的功能,如支持新的硬件设备、文件系统、网络协议等。
- 作为驱动:
.ko
文件是设备驱动程序,它们允许操作系统与硬件设备进行交互。
解压bzlmage
- 先在linux下,先使用wget拉取该仓库里面的内容
1 | wget https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux |
- 先chmod给执行权限
- 然后执行命令,即可解压,注意本题解压这个
bzImage
文件会出现extract-vmlinux: Cannot find vmlinux.
- 所以我换了一个
CISCN2017-babydriver
的bzImage
进行解压
- 所以我换了一个
1 | ./extract-vmlinux ./bzImage > vmlinux |
安装使用Ropper
- 安装
Ropper
1 | pip3 install ropper |
- 下载好后输入命令,看看Ropper是否安装好了
1 | ropper --help |
- 使用如下命令查找
rop
链
1 | ropper --file vmlinux |
- 过滤和排序 ROP gadgets,过程很慢,因为文件量很大
1 | ropper --file vmlinux --search "pop" |
打包.cpio文件
- 打包.cpio文件过程如下,现在模拟一下编写好
exp
如何进行提权。 .cpio
文件可以直接用zip等解压,也可以在Linux下使用cpio
命令解压

- 在Linux下解压cpio文件,需要先创建一个文件夹用来存放解压后的文件,因为使用cpio指令解压后的文件是分散的。
1 | mkdir rootfs |
- 然后进入该文件夹
1 | cd rootfs |
- 进入文件夹后使用cpio命令解压cpio文件
1 | cpio -id < ../rootfs.cpio |
-i
:解包模式。
-d
:在解包过程中创建目录。
< archive.cpio
:从 archive.cpio
文件中读取数据。
- 先将编写好的exp进行静态编译一下,然后编译成为二进制的文件,放入解压后的
.cpio
文件中,然后
- 然后在Linux下输入该命令
1 | find ./rootfs1 -print | cpio -ov > rootfs1.cpio |
查看.ko文件的保护
-
ko
是内核题比较重要的文件,有时候漏洞可能就是由这里面的代码造成的,所以会存在一些保护机制,这就需要查看一下保护机制 -
就直接使用
checksec
查看保护就行
1 | checksec test.ko |
漏洞利用
提权
-
cred结构体:kernel使用cred结构体记录了进程的权限,如果能劫持或伪造cred结构体,就能改变当前进程的权限。(这里可能不太详细,之后再来补充)
-
Linux源码网站:/pub/linux/kernel/ 的索引
-
查看源码,我查看的内核版本是
linux-6.9
版本的内核,然后cred
结构体在include/linux/cred.h
文件下- 一般而言,我们需要想办法将uid和gid设置为0(root的uid和gid均为0)
- 在 Linux 操作系统中,UID(User Identifier,用户标识符)是一个唯一的整数,用于标识系统中的每个用户账户。
- 在 Linux 操作系统中,GID(Group Identifier,组标识符)是一个唯一的整数,用于标识系统中的每个组。
1 | /* |
本地打内核
- 用c写exp然后静态编译成可执行文件,再添加到cpio文件夹下,然后启动环境,运行exp,如果
exp
能通运行之后就是root
权限,就有权限去打开flag
文件 - 在使用静态编译的时候,建议使用
musl-gcc
进行静态编译,这样编译后的文件会比较小。一般来说简单的exp.c
编译后的文件大小为几十kb
1 | musl-gcc -static -Os -o bout bout.c |
- 如果使用
gcc
进行静态编译,原本几十kb
的文件就会变成几百kb
,这样在发送这个文件到远程的时候就会出现发包量太大,从而导致一些包丢了,远程就利用不了。 - 接下来就是一个简单的
exp.c
文件的例子:
1 |
|
远程打内核
-
远程打内核还是使用
python
写脚本进行漏洞利用,但是我们要用到之前编译好的exp
文件。一般来说在远程的系统上都会有echo
和base64 -d
这个命令。 -
这时我们就需要将编译好的
exp
文件,使用base64
编码,使用命令将这一堆base64
放到远程靶机的一个文件中。 -
然后再使用将该文件的base64全部解码到另一个文件中
-
这时我们再使用
chmod
命令将远程的这个可执行文件赋予可执行权限,执行该文件后我们就成功提权了。 -
这里给出一个例子。
1 | import base64 |
- exp利用效果如下:
- 运行
exp
后就可以提权了
远程接收问题
- 在打内核的时候会碰到远程接收问题,也就是在py脚本中如果没有设置一个环境变量,就会导致在接收的时候出现如下无回显的问题。
- 这个时候我们就需要再导入
pwntools
这个模块之前设置环境变量:os.environ['PWNLIB_NOTERM'] = '1'
,具体如下:
1 | import base64 |
- 设置完这个环境变量之后就可以解决这个问题