前提介绍

内核与用户pwn的区别

  • 内核pwn和用户pwn的区别如下:
    • 获取权限:最大的区别就是用户的pwn是拼凑system('/bin/sh'),这样以后就可以getshell。而内核的pwn是提权,各种操作后将Linux的操作权限从用户变成root权限。
    • 代码量:用户pwn大多都对程序进行攻击,内核的pwn是对操作系统的内核,攻击对象由程序这层转变为操作系统这层。这就意味着需要阅读内核代码,这代码量往往比用户pwn大得多。也需要更扎实的操作系统理论知识。
    • 保护机制:内核的保护机制也和程序的保护机制用差别。

基础1

搭建内核pwn的环境

  • 这前面的文章有写,但是写的不是很好。过段时间再进行整理一下。

附件介绍

  • 以2024年9月的长城杯一个内核pwn的题目做介绍,刚好试试能不能复现一下。

  • 附件如下:https://wwsq.lanzoue.com/iZCI929l3qli 密码:ghfb

  • 题目给的附件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── start.sh # 启动脚本,运行这个脚本来启动QEMU
├── bzImage # 压缩过的内核镜像
└── rootfs.cpio # 作为初始RAM磁盘的文件,这里面的文件如下。注:这里只列出比较重要的文件,具体的看题目附件
|----init # init是系统启动时执行的第一个用户态进程(PID 1)。它是操作系统启动流程的核心部分,负责初始化系统并启动其他进程。这个init是比较重要的
|----linuxrc # linuxrc通常是一个脚本或可执行文件,它在一些早期的Linux版本中被用作默认的启动脚本,类似于init。
|----user # 这个多说
|----sbin # sbin是“system binary”的缩写,通常包含系统管理员使用的二进制文件。
|----lib # lib目录中的文件通常是为了支持基本命令和脚本运行所需的最小化库文件。
|---- .....
|---- test.ko # .ko表示的是内核模块,这个后面会具体介绍
|----dev # 设备文件
|----bin # bin目录通常包含一些基本的用户级二进制文件和命令

image-20240909173723116

内核文件–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,比如用ROPgadgetROPg等工具搜索gadgets。

启动脚本--start.sh

  • start.sh其实就是一些qemu的启动命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh

qemu-system-x86_64 \
-m 256M \ # 参数设置RAM大小为64M
-kernel bzImage \ # 使用当前目录的bzImage作为内核镜像
-initrd rootfs.cpio \ # 指定使用rootfs.cpio作为初始RAM磁盘。可以使用cpio 命令提取这个cpio文件,提取出里面的需要的文件,比如init脚本和babydriver.ko的驱动文件。提取操作的命令放在下面的操作步骤中
-monitor /dev/null \ # 将监视器重定向到字符设备/dev/null
-append "root=/dev/ram console=ttyS0 loglevel=8 ttyS0,115200 kaslr" \
-cpu kvm64,+smep,+smap \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \ # 参数禁用图形输出并将串行I/O重定向到控制台
-no-reboot \ # 发生重启时不要自动重启虚拟机
-no-shutdown # 在虚拟机发出关闭信号(例如通过操作系统的关机命令)时不要自动关闭虚拟机。
  • 给这个脚本附加权限chmod +x start.sh后然后运行这个脚本就可以启动该内核环境

image-20240909180616067

初始RAM磁盘文件–rootfs.cpio

  • 这个文件与内核文件一样重要,这里面也存在几个很重要的文件
  • 之后会介绍如何将一个文件夹打包成这个文件,因为我们需要将静态编译好的用c语言写的exp文件放入这个文件中,然后再打包这个文件,学会打包这个文件是很重要的
  • 下面先介绍几个比较重要的文件(可能不全,还需要待补充)

初始化文件–init

  • init文件是系统启动时执行的第一个用户空间进程(PID 1)。它负责初始化系统,设置环境并启动其他进程。对init文件的分析可以帮助你理解系统的启动流程和配置。

  • init文件中包含的脚本和命令决定了系统如何挂载文件系统、设置网络、启动服务等。这些操作通常涉及到与内核的交互,并可能暴露潜在的漏洞或不安全的配置。

  • 通过分析init文件,你可以获得有关如何启动和配置系统的信息。这些信息有助于你确定如何在内核或内核模块中寻找潜在的漏洞。

  • 接下来我们来看一下init这个文件里面的内容

    • 从init里面的内容就可以比较快速的找出一些漏洞比如
      • test.ko 模块的代码和 /dev/test 设备的权限配置可能包含可利用的安全漏洞。
      • 并且知道了该系统启动时是以uid为1000的用户身份,而不是root身份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh          # 指定该脚本应该由 /bin/sh 解释器执行。

# mkdir是创建目录
mkdir /tmp # 用于临时文件存储。
mkdir /proc # 用于显示内核和系统信息。
mkdir /sys # 用于显示和管理设备信息和系统状态。

# mount是挂载命令
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

insmod /lib/modules/5.10.0-9-generic/kernel/test.ko # 使用 insmod 工具加载内核模块 test.ko。这个模块可能包含漏洞或用于测试的代码。
chmod 666 /dev/test # 修改 /dev/test 设备文件的权限为 666,即所有用户都可以读写。这个设备文件是由前面加载的内核模块创建的。

setsid /bin/cttyhack setuidgid 1000 /bin/sh # 以 setuid 用户(UID 为 1000)身份启动一个新的 shell (/bin/sh)。setsid 命令会创建一个新的会话,cttyhack 确保新 shell 在控制终端上运行。

poweroff -d 0 -f # 强制系统关机。-d 0 表示关机延迟为 0 秒,-f 表示强制关机。

内核模块–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-babydriverbzImage进行解压
1
./extract-vmlinux ./bzImage > vmlinux

image-20240909194322389

安装使用Ropper

  • 安装Ropper
1
pip3 install ropper
  • 下载好后输入命令,看看Ropper是否安装好了
1
ropper --help

image-20240909195418256

  • 使用如下命令查找rop
1
ropper --file vmlinux

image-20240909195715902

  • 过滤和排序 ROP gadgets,过程很慢,因为文件量很大
1
2
3
ropper --file vmlinux --search "pop"
# 要将结果导出到文件,可以使用 -o 选项
ropper --file vmlinux --gadgets "pop" -o gadgets.txt

打包.cpio文件

  • 打包.cpio文件过程如下,现在模拟一下编写好exp如何进行提权。
  • .cpio文件可以直接用zip等解压,也可以在Linux下使用cpio命令解压
image-20240909185804531
  • 在Linux下解压cpio文件,需要先创建一个文件夹用来存放解压后的文件,因为使用cpio指令解压后的文件是分散的。
1
mkdir rootfs
  • 然后进入该文件夹
1
cd rootfs
  • 进入文件夹后使用cpio命令解压cpio文件
1
cpio -id < ../rootfs.cpio

-i:解包模式。

-d:在解包过程中创建目录。

< archive.cpio:从 archive.cpio 文件中读取数据。

  • 先将编写好的exp进行静态编译一下,然后编译成为二进制的文件,放入解压后的.cpio文件中,然后

image-20240909185822096

  • 然后在Linux下输入该命令
1
find ./rootfs1 -print | cpio -ov > rootfs1.cpio

查看.ko文件的保护

  • ko是内核题比较重要的文件,有时候漏洞可能就是由这里面的代码造成的,所以会存在一些保护机制,这就需要查看一下保护机制

  • 就直接使用checksec查看保护就行

1
checksec test.ko

image-20240909201033331

提权

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_cred.
*/
struct cred {
atomic_long_t usage;
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct ucounts *ucounts;
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;

内核的一些函数

  • 关于在内核中有一些函数

本地打内核

  • 用c写exp然后编译成可执行文件,然后再添加到cpio文件夹下,然后启动环境,运行exp,然后提权

远程打内核

  • 远程打内核也是使用Python脚本打。

  • 这里附上一个其他人博客的远程打内核pwn的脚本(只是示例,非本题)

    • 远程打内核最重要的就是将exp上传到远程环境,然后在远程环境执行exp即可提权,提权之后就可以cat flag
    • 该脚本中的upload函数就是将exp发送给远程靶机,然后在靶机中执行该文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import sys
import os
from pwn import *
import string

context.log_level='debug'

sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)

p = remote('127.0.0.1', 1234)

def send_cmd(cmd):
sla('$ ', cmd)

def upload():
lg = log.progress('Upload')
with open('exp', 'rb') as f:
data = f.read()
encoded = base64.b64encode(data)
encoded = str(encoded)[2:-1]
for i in range(0, len(encoded), 300):
lg.status('%d / %d' % (i, len(encoded)))
send_cmd('echo -n "%s" >> benc' % (encoded[i:i+300]))
send_cmd('cat benc | base64 -d > bout')
send_cmd('chmod +x bout')
lg.success()

os.system('musl-gcc -w -s -static -o3 exp.c -o exp')
upload()

p.interactive()

内核保护机制

基础2

下载环境

  • 在下载中可以找到对应的iso镜像文件,将镜像文件下载下来,然后使用VMware创建对应的虚拟机。

image-20240910150116751