avatar

目录
Linux Kernel Pwn 入门笔记

[toc]

背景知识

Linux内核

Linux内核是一个免费/开源的模块化,多任务的单内核。它是由Linus Torvalds在1991年为他基于i386的PC构思和创建的,随后被用作GNU操作系统的内核。

内核是操作系统的核心,扮演的是夹在硬件和软件中间的那个角色,说白了就是管理硬件设备,为应用程序提供运行环境。

硬件设备包括CPU、内存、硬盘、I\O设备等等

内核基本上分为两种:单内核和微内核。Windows NT内核和Mach(Mac OS X 的组成部分)是微内核结构,Linux内核是单内核结构。

因为Linus貌似很不喜欢微内核…哈哈哈

基于单内核操作系统和基于微内核操作系统对比

单内核(Monolithic kernel)架构

微内核(Micro kernel)架构

简单讲就是:

  • 单内核所有的事情都自己干,效率高,但是体积大
  • 微内核指挥手下们(servers)去干,需要上传下达,效率低,但是体积小

本文只说linux内核 :p

Ring Model

内核空间运行在Ring 0特权等级,拥有自己的空间,位于内存的高地址。

用户空间则是我们平时应用程序运行的空间,运行在Ring 3特权等级,使用较低地址。

内核拥有自己的栈,和用户空间的栈并不共用。

如何进入kernel 态:

  1. 系统调用 int 0x80 syscall ioctl
  2. 产生异常
  3. 外设产生中断

进入kernel态之前会做什么?

保存用户态的各个寄存器,以及执行到代码的位置

从kernel态返回用户态需要做什么?

执行swapgs(64位)和 iret 指令,当然前提是栈上需要布置好恢复的寄存器的值

内核模块

硬件设备驱动程序可以作为模块添加到内核,他和普通程序的区别如下:

C语言应用程序 内核模块程序
使用函数 libc库 内核函数
例子 printf() printk() #1
memcpy() copy_from_user()/copy_to_user()
malloc() kmalloc() #2
free() kfree()
运行空间 用户空间 内核空间
运行权限 普通用户 超级用户
入口函数 main() module_init()
出口函数 exit() module_exit()
编译 gcc gcc
连接 ld insmod
运行 直接运行 insmod
调试 gdb kdbug, kdb,kgdb等

目前linux kernel pwn题目大多漏洞出现在出题人自写的内核模块(Loadable Kernel Modules) 中

printk

打印信息到内核缓冲区

c
1
printk("<1>Hello World!\n");

<1>是输出的级别,表示立即在终端输出。

kmalloc

和 malloc() 相似,但使用的是 slab/slub 分配器

常用命令

  • 查看模块
    • cat /proc/modules
    • lsmod
  • 插入模块
    • insmod hellomod.ko
  • 卸载模块
    • rmmod hellomod
  • 查看内核模块符号表
    • cat /proc/kallsyms
    • cat /proc/ksyms(老版本内核)
  • 查看内核缓冲区(类似于logs)
    • dmesg
  • 查看开启了哪些保护
    • cat /proc/cpuinfo

攻击流程

  1. 在内核(模块)代码中找出漏洞

  2. 利用漏洞实现任意代码执行

    常规操作:Shellcode, ROP, Pointer Overwrites, Type Confusion

  3. 为进程提权

  4. 安全地返回用户态

    最简单的就是劫持控制流后让内核代码自行返回

    常规操作:篡改函数指针,劫持syscall表,UAF

    注意:内核代码崩了一般会导致重启 :-x

  5. rooted

    返回用户空间后,进程以root权限运行

提权原理

Linux内核使用 cred 结构体记录的,每个进程中都有一个 cred 结构,这个结构保存了该进程的权限等信息(uid,gid 等),如果能修改某个进程的 cred,那么也就修改了这个进程的权限。

可用于修改权限的函数:

  • int commit_creds(struct cred *new)
  • struct cred* prepare_kernel_cred(struct task_struct* daemon)

执行 commit_creds(prepare_kernel_cred(0)) 即可获得 root 权限

函数的地址可以在符号表中查找:

bash
1
2
3
4
5
$ sudo cat /proc/kallsyms | grep prepare_kernel_cred
ffffffffb16cdec0 T prepare_kernel_cred

$ sudo cat /proc/kallsyms | grep commit_creds
ffffffffb16cdb40 T commit_creds

由于开启了kptr_restrict保护,只有root 权限才能查看内核地址

保护机制

canary, PIE, RELRO 等保护与用户态原理和作用相同

KPTI

今年年初的CPU漏洞让KPTI(Kernel PageTable Isolation,内核页表隔离)进入了人们的视野。进程地址空间被分成了内核地址空间和用户地址空间,其中内核地址空间映射到了整个物理地址空间,而用户地址空间只能映射到指定的物理地址空间。内核地址空间和用户地址空间共用一个页全局目录表。为了彻底防止用户程序获取内核数据,可以令内核地址空间和用户地址空间使用两组页表集。linux内核从4.15开始支持KPTI,windows上把这个叫KVA Shadow,原理类似。更多细节请见参考资料。

KASLR

KASLR中的K指kernel,也就是内核地址空间布局随机化。

下面这张图就是几大主流操作系统(windows/linux/ios/os x/android)中ASLR和KASLR的启用情况。不过值得注意的是Android 8.0中为4.4及以后的内核引入了KASLR。从Ubuntu 14.10开始就支持KASLR了,但并不是默认启用的,需要在内核命令行中加入kaslr开启。

SMAP/SMEP

SMAP(Supervisor Mode Access Prevention,管理模式访问保护)和SMEP(Supervisor Mode Execution Prevention,管理模式执行保护)的作用分别是禁止内核访问用户空间的数据和禁止内核执行用户空间的代码。arm里面叫PXN(Privilege Execute Never)和PAN(Privileged Access Never)。SMEP类似于前面说的NX,不过一个是在内核态中,一个是在用户态中。和NX一样SMAP/SMEP需要处理器支持,可以通过cat /proc/cpuinfo查看,在内核命令行中添加nosmap和nosmep禁用。windows系统从win8开始启用SMEP,windows内核枚举哪些处理器的特性可用,当它看到处理器支持SMEP时通过在CR4寄存器中设置适当的位来表示应该强制执行SMEP,可以通过ROP或者jmp到一个RWX的内核地址绕过。linux内核从3.0开始支持SMEP,3.7开始支持SMAP。

在没有SMAP/SMEP的情况下把内核指针重定向到用户空间的漏洞利用方式被称为ret2usr。physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址。另一方面,我们知道用户空间的虚拟内存也会映射到RAM。这就存在两个虚拟内存地址(一个在physmap地址,一个在用户空间地址)映射到同一个RAM地址的情况。也就是说,我们在用户空间里创建的数据,代码很有可能映射到physmap空间。基于这个理论在用户空间用mmap()把提权代码映射到内存,然后再在physmap里找到其对应的副本,修改EIP跳到副本执行就可以了。因为physmap本身就是在内核空间里,所以SMAP/SMEP都不会发挥作用。这种漏洞利用方式叫ret2dir。

Stack Protector

当然在内核中也是有这种防护的,编译内核时设置CONFIG_CC_STACKPROTECTOR选项即可,该补丁是Tejun Heo在09年给主线kernel提交的。

2.6.24:首次出现该编译选项并实现了x64平台的进程上下文栈保护支持

2.6.30:新增对内核中断上下文的栈保护和对x32平台进程上下文栈保护支持

3.14:对该功能进行了一次升级以支持gcc的-fstack-protector-strong参数,提供更大范围的栈保护

关于函数返回地址的问题属于CFI(Control Flow Integrity,控制流完整性保护)中的后向控制流完整性保护。近几年人们提出了safe-stack和shadow-call-stack引入一个专门存储返回地址的栈替代Stack Protector。可以从下图看到shadow-call-stack开销更小一点。这项技术已经应用于android,而linux内核仍然在等待硬件的支持。

dmesg restrictions

在dmesg里可以查看到开机信息。若研究内核代码,在代码中插入printk函数,然后通过dmesg观察是一个很好的方法。从Ubuntu 12.04 LTS开始,可以将/proc/sys/kernel/dmesg_restrict设置为1将dmesg输出的信息当做敏感信息(默认为0)。

Kernel Address Display Restriction

在linux内核漏洞利用中常常使用commit_creds和prepare_kernel_cred来完成提权,它们的地址可以从/proc/kallsyms中读取。从Ubuntu 11.04和RHEL 7开始,/proc/sys/kernel/kptr_restrict被默认设置为1以阻止通过这种方式泄露内核地址。

Address protection

由于内核空间和用户空间共享虚拟内存地址,因此需要防止用户空间mmap的内存从0开始,从而缓解NULL解引用攻击。windows系统从win8开始禁止在零页分配内存。从linux内核2.6.22开始可以使用sysctl设置mmap_min_addr来实现这一保护。从Ubuntu 9.04开始,mmap_min_addr设置被内置到内核中(x86为64k,ARM为32k)。

Kernel Pwn

一般手机的越狱 或是root都是通过内核漏洞完成提权的

附件文件

bash
1
2
3
boot.sh       # 启动脚本
bzImage # 单内核文件
rootfs.cpio # 文件系统映像

文件系统映像

一般要先还原文件系统

bash
1
2
mkdir fs && cd fs
cpio -idmv < ../rootfs.cpio

cpio文件如果是压缩包就先解压一下

启动脚本

bash
1
2
#!/bin/bash
qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep -s
  • -initrd rootfs.cpio,使用 rootfs.cpio 作为内核启动的文件系统
  • -kernel bzImage,使用 bzImage 作为 kernel 映像
  • -cpu kvm64,+smep,设置 CPU 的安全选项,这里开启了 smep
  • -m 64M,设置虚拟 RAM 为 64M,默认为 128M
  • --nographicconsole=ttyS0一起使用,启动的界面就变成当前终端
  • -s相当于-gdb tcp::1234的简写,可以直接通过主机的gdb远程连接

内核文件

可以file一下看看内核版本

bash
1
2
$ file bzImage 
bzImage: Linux kernel x86 boot executable bzImage, version 4.4.72 (atum@ubuntu) #1 SMP Thu Jun 15 19:52:50 PDT 2017, RO-rootFS, swap_dev 0x6, Normal VGA

Exp

编译

一般是c写的,本地编译后,通过 base64 编码把exp放到远程去执行

bash
1
gcc exp.c -static -o exp

需要静态编译,因为一般莫得libc

测试

bash
1
2
3
cp ./exp ./fs && cd fs
find . | cpio -o --format=newc > ../rootfs.cpio # 重新打包文件系统
./boot.sh # 启动&测试exp

远程

通过 base64 编码把exp放到远程去执行

出题人视角

获得内核(bzImage)

要获得一个特定版本的内核,可以编译或是直接下载内核镜像

或者直接用操作系统的自己内核(位于/boot/vmlinuz

编译内核

安装依赖

bash
1
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils

下载内核源码

The Linux Kernel Archives

https://www.kernel.org/theme/images/logos/tux.png

编译内核(生成bzImage)

解压后进入目录,执行下面命令进行配置:

bash
1
make menuconfig

检查保证勾选了这些(一般都是默认有勾选的)

  • Kernel debugging
  • Compile-time checks and compiler options —> Compile the kernel with debug info和Compile the kernel with frame pointers
  • KGDB: kernel debugger

配置的时候基本什么都不需要改动,直接Save,然后Exit

然后运行下面的命令进行内核编译,生成bzImage

bash
1
make bzImage -j4

加上-j4参数可以加快编译

编译内核比较占空间,一定要确保磁盘空闲空间足够

编译好之后,在./arch/x86/boot/拿到bzImage,从源码根目录拿到vmlinux

  • bzImagevmlinuz经过gzip压缩后的文件,适用于大内核
  • vmlinux是未压缩的内核
  • vmlinuzvmlinux的压缩文件
  • vmlinux 是ELF文件,即编译出来的最原始的文件
  • vmlinuz应该是由ELF文件vmlinux经过OBJCOPY后,并经过压缩后的文件
  • zImagevmlinuz经过gzip压缩后的文件,适用于小内核

下载内核镜像

可以直接下载已经编译好的内核镜像,一般不需要自己配置内核,直接下载现成的就行。

根据版本号搜索

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo apt search linux-image- 

Sorting... Done
Full Text Search... Done
linux-image-5.0.0-13-generic/now 5.0.0-13.14 amd64 [residual-config]
(none)

linux-image-5.0.0-31-generic/now 5.0.0-31.33 amd64 [residual-config]
(none)

linux-image-5.0.0-32-generic/now 5.0.0-32.34 amd64 [residual-config]
(none)

# ...

下载镜像

bash
1
sudo apt download linux-image-5.4.0-52-generic

下载下来的是一个deb文件,解压后镜像在data.tar.xz

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
% tar xvf data.tar.xz 
./
./boot/
./boot/vmlinuz-5.4.0-52-generic
./usr/
./usr/share/
./usr/share/doc/
./usr/share/doc/linux-image-5.4.0-52-generic/
./usr/share/doc/linux-image-5.4.0-52-generic/changelog.Debian.gz
./usr/share/doc/linux-image-5.4.0-52-generic/copyright

% file ./boot/vmlinuz-5.4.0-52-generic
./boot/vmlinuz-5.4.0-52-generic: Linux kernel x86 boot executable bzImage, version 5.4.0-52-generic (buildd@lgw01-amd64-060) #57-Ubuntu SMP Thu Oct 15 10:57:00 UTC 2020, RO-rootFS, swap_dev 0xB, Normal VGA

貌似就是bzImage

构建文件系统

busybox中包含了一些常用的命令,使用busybox可以快速构建起文件系统

编译busybox

首先下载BUSYBOX 源码。

下载完成后解压进入源码根目录输入make menuconfig进行配置。

最好在配置时进入Settings,勾上Build static binary (no shared libs),这样就不会依赖libc文件。

bash
1
2
% ldd busybox
not a dynamic executable

如果不勾选的话,需要自行配置libc库,这样步骤会很繁琐。

然后输入make install -j4进行编译,busybox编译要比kernel快很多。

编译完成后会生成一个_install的目录:

bash
1
2
3
4
5
6
7
8
% ls -al _install 
total 20
drwxrwxr-x 5 taqini taqini 4096 Nov 26 21:14 .
drwxr-xr-x 37 taqini taqini 4096 Nov 26 21:14 ..
drwxrwxr-x 2 taqini taqini 4096 Nov 26 21:14 bin
lrwxrwxrwx 1 taqini taqini 11 Nov 26 21:14 linuxrc -> bin/busybox
drwxrwxr-x 2 taqini taqini 4096 Nov 26 21:14 sbin
drwxrwxr-x 4 taqini taqini 4096 Nov 26 21:14 usr

至此busybox就安装完成了。还需要在_install文件夹中增加一些其他文件来自定义我们的文件系统。

配置文件系统

先进行一些简单的初始化:

bash
1
2
3
4
5
cd _install
mkdir proc
mkdir sys
touch init
chmod +x init

init文件是系统启动后的默认入口,详见启动脚本

这里给一个最简单的例子

bash
1
2
#!/bin/sh
/bin/sh

使用上述init文件,系统启动后就会进入/bin/sh进程

一般还需要增加其他文件夹,比如/etc/home,以及一些配置文件:

bash
1
2
3
4
5
6
7
8
9
mkdir etc
mkdir home
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "ctf:x:1000:1000:ctf:/home/ctf:/bin/sh" >> etc/passwd

echo "root:x:0:" > etc/group
echo "ctf:x:1000:" >> etc/group

echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab

分配用户名/组名等等

如果需要动态库,可以添加:

bash
1
2
mkdir lib64
mkdir -p lib/x86_64-linux-gnu/

然后把需要的动态库拷贝进去(比如最基本的libcld

除了init,还可以直接向/etc/init.d中增加启动脚本:

bash
1
2
3
mkdir -p etc/init.d
touch etc/init.d/rcS
chmod +x etc/init.d/rcS

启动脚本

可以是/init也可以是/etc/init.d/rcS

栗子1:

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag

exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod xxx.ko
chmod 777 /dev/xxx

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0 -f

其中setsid cttyhack setuidgid 1000 sh 用于以低权限用户1000执行sh

setsid - run a program in a new session

cttyhack - Give PROG a controlling tty if possible.

setuidgid - set user and group identity

栗子2:

bash
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
#!/bin/sh

# banner
echo "IF9fX19fIF9fX18gIF8gICBfICBfX19fIF9fX19fIF9fX19fIAp8ICBfX198ICBfIFx8IHwgfCB8
LyBfX198XyAgIF98ICBfX198CnwgfF8gIHwgfCB8IHwgfCB8IHwgfCAgICAgfCB8IHwgfF8gICAK
fCAgX3wgfCB8X3wgfCB8X3wgfCB8X19fICB8IHwgfCAgX3wgIAp8X3wgICB8X19fXy8gXF9fXy8g
XF9fX198IHxffCB8X3wgICAgCgpXZWxjb21lIHRvIFpaSidzIHRpbnkgc3lzdGVtISBCdXQgeW91
J3JlIG5vdCByb290LiBFbmpveSA6KQo=" | base64 -d

mount -t proc none /proc
mount -t devtmpfs none /dev
mkdir /dev/pts
mount /dev/pts

insmod /home/pwn/baby.ko
chmod 666 /dev/baby

echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
chown -R 1000:1000 .
setsid cttyhack setuidgid 1000 sh

umount /proc
poweroff -f
  • echo 1 > /proc/sys/kernel/dmesg_restrict

    从Ubuntu 12.04 LTS开始,可以将/proc/sys/kernel/dmesg_restrict设置为1将dmesg输出的信息当做敏感信息(默认为0)。需要root权限才能读取。

  • echo 1 > /proc/sys/kernel/kptr_restrict

    从Ubuntu 11.04和RHEL 7开始,/proc/sys/kernel/kptr_restrict默认设置为1以阻止通过这种方式泄露内核地址。

打包文件系统

在打包之前可以先配置一下文件权限,权限会一起被打包

使用如下命令对文件系统进行打包

bash
1
find . | cpio -o --format=newc > ../rootfs.cpio

运行内核

qemu 是一款由 Fabrice Bellard 等人编写的可以执行硬件虚拟化的开源托管虚拟机,具有运行速度快(配合 kvm),跨平台等优点。

qemu 通过动态的二进制转化模拟 CPU,并且提供一组设备模型,使其能够运行多种未修改的客户机OS。

在 CTF 比赛中,qemu 多用于启动异架构的程序(mips, arm 等)、kernel 及 bootloader 等二进制程序,有时也会作为要 pwn 掉的程序出现。

运行模式

qemu 有多种运行模式,常用的有 User-mode emulationSystem emulation 两种。

User-mode emulation

用户模式,在这个模式下,qemu 可以运行单个其他指令集的 linux 或者 macOS/darwin 程序,允许了为一种架构编译的程序在另外一种架构上面运行

System emulation

系统模式,在这个模式下,qemu 将模拟一个完整的计算机系统,包括外围设备。

安装qemu

bash
1
2
sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev
sudo apt-get install qemu

启动内核

bash
1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram rdinit=/sbin/init console=ttyS0 oops=panic panic=1 loglevel=3 quiet nokalsr" \
-cpu kvm64,+smep \
-smp cores=2,threads=1 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic
  • -m是指定RAM大小

  • -kernel 是指定的内核镜像

    这里是我们编译的镜像路径,也可以是下载好的镜像,如./vmlinuz-4.15.0-22-generic

  • -initrd 设置刚刚利用 busybox 创建的 rootfs.cpio ,作为内核启动的文件系统

  • -append 附加选项,指定nokaslr可以关闭随机偏移,不输出log:quiet&loglevel=3

  • --nographicconsole=ttyS0一起使用,启动的界面就变成当前终端

    不然的话会开一个VNC服务,可以使用VNC Viewer连接

  • -s 相当于-gdb tcp::1234的简写,可以直接通过主机的gdb远程连接

  • -monitor配置用户模式的网络#将监视器重定向到主机设备/dev/null

  • -smp 用于声明所有可能用到的cpus

    i.e. sockets * cores * threads = maxcpus.

  • -cpu 设置CPU的安全选项

这样就在本地完成启动内核啦,远程的话可以用docker,详见docker配置

添加syscall

不同版本的内核可能会有些许差异,我用的版本是 5.9.11

添加源码

内核源码下添加一个目录mysyscall,然后创建Makefilemysyscall.c

bash
1
2
3
4
mkdir mysyscall
cd mysyscall
touch mysyscall.c
touch Makefile

编辑Makefile

makefile
1
obj-y := mysyscall.o

编辑mysyscall.c

c
1
2
3
4
5
6
7
#include <linux/kernel.h>
#include <linux/syscalls.h>

SYSCALL_DEFINE0(mysyscall) {
printk("This is my syscall.\n");
return 0;
}

修改Makefile

接着编辑源码根目录下的Makefile,添加mysyscall/模块:

添加函数声明

然后编辑include/linux/syscalls.h,在末尾#endif之前添加mysyscall函数原型:

c
1
2
/* my syscall */
asmlinkage long sys_mysyscall(void);

分配系统调用号

然后再修改arch/x86/entry/syscalls/syscall_32.tblarch/x86/entry/syscalls/syscall_64.tbl,添加自定义的系统调用号:

c
1
2
3
4
//syscall_32.tbl
666 i386 mysyscall sys_mysyscall
//syscall_64.tbl
666 64 mysyscall sys_mysyscall

至此就添加完syscall,如果调用了666号,就会输出一句This is my syscall!\n

syscall_64.tbl中可以包含32位的系统调用。把第二列设置成common,即可添加一个32和64位通用的系统调用号,但是要注意不要和已有的系统调用号冲突。

重新编译内核

添加完系统调用后,要重新编译一次内核才能生效:

bash
1
make -j4 bzImage

重新得到bzImage

可能出现的问题

上述部分是参考的23R3F师傅的文章,估计是手误写错了,编译时可能遇到如下错误:

bash
1
2
./arch/x86/include/generated/asm/syscalls_64.h:373:18: error: array index in initializer exceeds array bounds
373 | __SYSCALL_COMMON(666, sys_mysyscall)

原因是64位的系统调用号用的是common和32位的冲突了:

bash
1
2
3
4
//syscall_32.tbl
666 i386 mysyscall sys_mysyscall
//syscall_64.tbl
666 common mysyscall sys_mysyscall

common改成64即可:

bash
1
2
3
4
//syscall_32.tbl
666 i386 mysyscall sys_mysyscall
//syscall_64.tbl
666 64 mysyscall sys_mysyscall

或者手动修改编译生成的中间文件./arch/x86/include/generated/asm/syscalls_64.h,把__SYSCALL_COMMON(666, sys_mysyscall)改成__SYSCALL_64(666, sys_mysyscall),然后继续编译

测试系统调用

接下来写个demo.c测试系统调用

c
1
2
3
4
5
#include <unistd.h>
int main(void){
syscall(666);
return 0;
}

静态编译:

bash
1
2
gcc demo.c -static -o demo
gcc demo.c -static -o demo32 -m32

demo放到文件系统中,然后打包文件系统:

bash
1
find . | cpio -o --format=newc > ../rootfs.cpio

用新编译的bzImage启动内核,运行demo

编译内核模块

添加源码

内核源码下添加一个目录myko,然后创建Makefilemyko.c

bash
1
2
3
4
mkdir myko
cd myko
touch myko.c
touch Makefile

编辑myko.c

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cred.h>
MODULE_LICENSE("GPL");
struct cred c1;
static int myko_init(void) {
printk("This is my ko!\n");
printk("size of cred : %ld \n",sizeof(c1));
return 0;
}
static void myko_exit(void) {
printk("<1> Bye, cruel world\n");
}
module_init(myko_init);
module_exit(myko_exit);

编辑Makefile

makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
obj-m := myko.o

KERNELDR := ~/kernel/linux-5.9.11/

PWD := $(shell pwd)

modules:
$(MAKE) -C $(KERNELDR) M=$(PWD) modules

moduels_install:
$(MAKE) -C $(KERNELDR) M=$(PWD) modules_install

clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

需要指定一下内核路径KERNELDR

编译模块

然后输入make命令编译模块:

bash
1
2
3
4
5
6
7
8
9
10
11
% make
make -C ~/kernel/linux-5.9.11/ M=/home/taqini/kernel/linux-5.9.11/myko modules
make[1]: Entering directory '/home/taqini/kernel/linux-5.9.11'
CC [M] /home/taqini/kernel/linux-5.9.11/myko/myko.o
MODPOST /home/taqini/kernel/linux-5.9.11/myko/Module.symvers
CC [M] /home/taqini/kernel/linux-5.9.11/myko/myko.mod.o
LD [M] /home/taqini/kernel/linux-5.9.11/myko/myko.ko
make[1]: Leaving directory '/home/taqini/kernel/linux-5.9.11'

% ls -al myko.ko
-rw-rw-r-- 1 taqini taqini 248832 Nov 27 12:01 myko.ko

测试模块

将编译好的myko.ko放到文件系统中,然后打包文件系统:

bash
1
find . | cpio -o --format=newc > ../rootfs.cpio

启动内核,插入/删除模块:

docker配置

通过docker把题目映射到远程端口。

待续

Reference

Linux kernel - Wikipedia

Linux Kernel Pwn ABC(Ⅰ) - M4x

Linux Kernel Pwn 初探 - 先知社区

linux漏洞缓解机制介绍-二进制漏洞-看雪论坛

kernel pwn初探 - 23R3F’s blog

Linux kernel 初探 – Ex个人博客

Adding A System Call To The Linux Kernel (5.8.1) In Ubuntu (20.04 LTS) - DEV

[kernel pwn 入门] 1. 空指针 - 简书

https://github.com/bsauce/kernel-security-learning

http://www.jx-zhang.xyz/2020/02/07/start2kernelPwn

https://blog.csdn.net/weixin_43889007/article/details/109499534#_linux_kernel_pwn__59

https://github.com/ret2p4nda/kernel-pwn

https://veritas501.space/2018/06/03/kernel%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/

文章作者: TaQini
文章链接: http://taqini.space/2020/11/21/linux-kernel-pwn-learning/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 TaQini
打赏
  • Wechat
    Wechat
  • Alipay
    Alipay

评论