kernel初探
发表于:2023-01-18 |
字数统计: 1.9k | 阅读时长: 8分钟 | 阅读量:

kernel初探

写在前面

前年(2021.12)封宿舍的时候,闲来无聊,准备入门kernel了,但貌似是因为环境总是出问题,就转去学习异构pwn了。如今,在家荒废了三周,实在是无聊至极,又开始准备kernel的环境,但貌似一下子就成功了,记录一下,为以后查阅准备。

2023.1.24补充:之前配好的环境是在ubuntu16上,在调试驱动的时候出现了问题,gdb版本太低,需要升级gdb,两种方法,一种是更改gdb源码但我在ubuntu16上没有找到可以改源码的地方,第二种是直接编译新的gdb但一直没升级成功()。于是我在ubuntu22.04上重新配置,所以会在配置环境的过程中添加些新的问题。

内核下载和编译

根据wiki给的内核下载网站,将其解压。

解压命令

  • unxz linux-5.4.98.tar.xz
  • tar -xf linux-5.4.98.tar

须提前安装libncurses-dev、flex、bison

如apt-get install libncurses-dev

进入linux-5.4.98目录利用make menuconfig配置内核编译选项,配置时进入 Kernel hacking -> Compile-time checks and compiler options 勾选 Compile the kernel with debug info ,便于后续调试。

安装libelf-devel、dwarves

接着在linux-5.4.98目录下使用make -j3 bzImage命令编译内核镜像。见到下图所示的提示即为编译成功。

image-20230129173119928

构建文件系统

下载并解压busybox

  • wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2

  • tar -jxf busybox-1.32.1.tar.bz2

配置: 在 Setttings 选中 Build static binary (no shared libs),将 busybox 编译为静态链接的文件;在 Linux System Utilities 中取消选中 Support mounting NFS file systems on Linux < 2.6.23 (NEW);在 Networking Utilities 中取消选中 inetd。

  • make menuconfig

编译

  • make -j3

进入解压后的busybox-1.32.1目录,利用make install创建_install目录(作为 rootfs ),进入_install目录利用mkdir -p proc sys dev etc/init.d命令创建文件夹,并编写文件名为init的脚本

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh

给脚本添加执行权限,并将_install目录打包为rootfs.img

  • chmod +x ./init

  • find . | cpio -o --format=newc > ../../rootfs.img打包

  • cpio -idmv < rootfs.img解包(此处命令不需要)

至于busybox的作用,除了构建文件系统外,其他的作用我暂时还不清楚,待后续的深入学习后,再来补充。

文件组成

  • baby.ko是包含漏洞的程序,一般使用ida打开分析,可以根据init文件的路径去rootfs.cpio里面找
  • bzImage
    bzImage是打包的内核代码,一般通过它抽取出vmlinx,寻找gadget也是在这里。
  • initramfs.cpio
    initramfs.cpio是内核采用的文件系统
  • startvm.sh
    startvm.sh是启动QEMU的脚本
  • vmlinux
    静态编译,未压缩的内核文件,可以在里面找ROP
  • init文件
    在rootfs.cpio文件解压可以看到,记录了系统初始化时的操作,一般在文件里insmod一个内核模块.ko文件,通常是有漏洞的文件
    .ko文件:需要拖到IDA里面分析找漏洞的文件,

启动内核

wiki上给的脚本如下:

1
2
3
4
5
6
7
8
9
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel ./bzImage \
-initrd ./rootfs.img \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr" \
-smp cores=2,threads=1 \
-cpu kvm64

参数解释如下:

  • -m, 指定 RAM 大小,默认 384M
  • -kernel,指定内核镜像文件 bzImage 路径
  • -initrd,设置内核启动的内存文件系统
  • -smp [cpus=]n[,cores=cores][,threads=threads][,dies=dies][,sockets=sockets][,maxcpus=maxcpus],指定使用到的核数。
  • -cpu,指定指定要模拟的处理器架构,可以同时开启一些保护,如
    • +smap,开启 smap 保护
    • +smep,开启 smep 保护
  • -nographic,表示不需要图形界面
  • -monitor,对 qemu 提供的控制台进行重定向,如果没有设置的话,可以直接进入控制台。
  • -append,附加选项
    • nokaslr 关闭随机偏移
    • console=ttyS0,和 nographic 一起使用,启动的界面就变成了当前终端。

也可以利用如下脚本:

1
2
3
4
5
6
7
qemu-system-x86_64 \
-kernel ./arch/x86/boot/bzImage \ #bzImage 路径
-initrd ./rootfs.img \ #文件系统路径
-append "console=ttyS0 nokaslr"
-nographic

qemu-system-x86_64 -kernel ../linux-5.4.98/linux-5.4.98/arch/x86/boot/bzImage -initrd ./rootfs.img -append "console=ttyS0 nokaslr" -nographic -gdb tcp::1234

驱动编写调试与加载

驱动代码如下,为.c文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("Dual BSD/GPL");
static int ko_test_init(void)
{
printk("This is a test ko!\n");
return 0;
}
static void ko_test_exit(void)
{
printk("Bye Bye~\n");
}
module_init(ko_test_init);
module_exit(ko_test_exit);

同一目录下编写Makefile:

1
2
3
4
5
6
7
8
9
10
#要与驱动.c文件同名即first_kerneltest.c
obj-m += first_kerneltest.o
#内核目录
KDIR =/home/f1ag/Desktop/kernel/linux-5.4.98/linux-5.4.98/
#将空格换成tab
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
rm -rf *.o *.ko *.mod.* *.symvers *.order

这里简单介绍一下这个 Makefile

  • obj-m 指定要声称哪些模块

  • KDIR 用来标识内核源码目录,提供驱动编译所需环境

  • $(MAKE) -C $(KDIR) M=$(PWD) modules

  • -C 表示进入到指定的内核目录

    • M 指定驱动源码的环境,M 并不是 Makefile 的选项,而是内核根目录下 Makefile 中使用的变量。这会使得该 Makefile 在构造模块之前返回到 M 指定的目录,并在指定的目录中生成驱动模块。

最后make编译驱动即可。

加载驱动

将生成的.ko文件复制到busybox的_install目录下并对init(启动脚本)修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /first_kerneltest.ko #新增
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
#为方便调试,改‘1000’为‘0’,即可获得root权限
setsid /bin/cttyhack setuidgid 0 /bin/sh
poweroff -f

重新打包rootfs.img并启动qemu,加载成功

image-20230119162932352

调试

使用root权限调试(上文提及),使用如下命令调试

1
2
3
4
5
6
7
qemu-system-x86_64 
-kernel ../linux-5.4.98/linux-5.4.98/arch/x86/boot/bzImage
-initrd ./rootfs.img
-append "console=ttyS0 nokaslr"
-nographic
-gdb tcp::1234
-S #qemu启动后立即挂起,方便调试

另起终端,执行命令 gdb -q -ex "target remote localhost:1234" 即可

执行后如下,会发现有些命令无法执行

image-20230130162722386

解决方案:使用gef-remote

关于使用gef-remote可把我坑惨了,[大佬博客]( Kernel pwn CTF 入门 | Kiprey’s Blog )写的是gef-remote --qemu-mode loaclhost:1234,由于gef的更新原因在我这却怎么也执行不了,于是查了gef手册最新的语句为gef-remote --qemu-user --qemu-binary vmlinux_path ip port在使用前一定要先set architecture

为了方便调试,可以添加一些符号文件

  • add-symbol-file vmlinux_path address

    关于address的获取及意义[这里]( [linux - The address where filename has been loaded is missing GDB] - Stack Overflow )写的很清楚,address是gdb想知道的二进制文件的.text段的加载地址,可以使用readelf -WS ./binary获得

  • add-symbol-file .ko address

    grep <target_module_name> /proc/modules可以获得驱动加载的基地址

    grep "0x" /sys/module/ko_test/sections/.*可以获得内核模块各个段的地址,加载ko的符号文件时,添加越多的段地址越好,若只有.text段的地址,可能会下断点不成功

image-20230130170107355

添加完符号文件后,就可以对驱动里的函数下断点,如下

image-20230130170219834

image-20230130170344639

如想要断在ko_test_exit处,在qemu启动的内核shell输入rmmod terget_mode_name即可。

参考链接:

内核下载与编译 - CTF Wiki (ctf-wiki.org)

Kernel pwn CTF 入门 | Kiprey’s Blog

(71条消息) 老表带你学Linux kernel pwn 入门(一)_SiameseJuly的博客-CSDN博客_linux kernel pwn

linux kernel pwn notes - 先知社区 (aliyun.com)

gef-remote - GEF - GDB Enhanced Features documentation (hugsy.github.io)

上一篇:
D-Link-DIR-815栈溢出漏洞复现
下一篇:
llvmpass初探