两种方法调试 linux kernel
这篇文章的目的,是构造一个linux内核调试环境的壳,之后只需替换内核,就能完成对不同版本内核的调试。文中涉及编译linux内核、编译busybox生成文件系统、编译内核字符设备ko、以及两种内核调试方法。
本文中用到的环境如下:
- x86_64架构ubuntu16.04虚拟机
- gcc版本5.4.0
编译Linux Kernel
说明
编译内核,简单来说包含两个步骤:
(1)配置内核选项
(2)用配置的选项编内核
配置内核选项的命令 有若干个,我们应当选择哪个呢?
make configs
:基于文本形式的配置,每次弹出一个选项,每个选项都需要手动确认。如果想更改前序配置,只能从头再来。
make menuconfig
:基于图形界面的配置,可以在界面不同选项中来回切换,可以搜索某个选项,可以加载.config
配置文件。它的GUI界面使用了ncurses
库,在Ubuntu中可使用sudo apt install libncurses5-dev
安装。
make defconfig
:根据ARCH
指定的架构,用默认选项生成.config
配置文件。默认的配置存在arch/$(ARCH)/configs
目录下。
make oldconfig
:读取已存在的.config
文件,并提示用户当前kernel的哪些选项在.config
文件中找不到。
make savedefconfig
:在当前目录下生成一个defconfig
文件
例子
安装内核编译过程中可能要用到的包
1
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison
下载linux-4.4.72.tar.gz源码包到本地解压、设置、编译
1
2
3
4
5tar -zxf linux-4.4.72.tar.gz
cd linux-4.4.72
make ARCH=x86 defconfig
make menuconfig # 若无需更改默认配置,这条可省略。
make -j4配置项参考Linux内核调试,设置如下几个关键选项:
1
2
3
4由于我们需要调试内核,注意下面这几项一定要配置好:
KernelHacking --> Compile-time checks and compiler options
选中Compile the kernel with debug info
选中Compile the kernel with frame pointers编译完成后,即可在当前目录下看到
vmlinux
,在arch/x86/boot
目录下看到bzImage
。
编译busybox
通常利用busybox构建文件系统,有两种方式设置启动选项:
- /init文件:[内核pwn] 环境搭建
- /etc/init.d/rcS文件:从零开始的 kernel pwn 入门 - I:Linux kernel 简易食用指南
这里以第一种方式为例,通过以下几个步骤就能构造一个可以使用的文件系统:
下载 busybox-1.30.0.tar.bz2源码包。
以root用户,运行如下命令,解压、设置、编译。
1
2
3
4
5
6
7
8
9
10
11su root
tar -jxf busybox-1.30.0.tar.bz2
make menuconfig
# 进Settings,勾上Build static binary (no shared libs),编译成静态文件
# 关闭下面两个选项:
# Linux System Utilities -> [] Support mounting NFS file system 网络文件系统
# Networking Utilities -> [] inetd (Internet超级服务器)
make install -j 4
# 高版本gcc编译时报错:date.c:(.text.date_main+0x25f): undefined reference to `stime'
# 解决:https://git.busybox.net/busybox/patch/?id=d3539be8f27b8cbfdfee460fe08299158f08bcd9
# 或者下载新版本的busybox,就不会遇到这个问题进入编译后生成的
_install
目录,里面存放了编译生成的文件,在该目录下创建init文件和需要的文件夹。1
2
3
4
5
6cd _install
mkdir proc
mkdir sys
touch flag
touch init
chmod +x initinit中写入如下内容(或者参考其他ctf题目中的init文件)
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
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
echo 1 > /proc/sys/vm/unprivileged_userfaultfd
insmod /xxx.ko
chmod 666 /dev/xxx
chmod 740 /flag
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
chmod 400 /proc/kallsyms
#poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 0 /bin/sh
umount /proc
umount /tmp
poweroff -d 0 -f打包生成roofs.cpio
1
find . | cpio -o --format=newc > ../rootfs.cpio
编译漏洞ko
新建一个存放ko源码和Makefile的文件夹
1 | mkdir testko |
Makefile内容如下:
1 | KDIR := /home/bling/Documents/linux-4.4.72 |
参考 CISCN2017-babydriver 这个题,有漏洞的babydrvier.c源码如下:
1 |
|
编译生成babydriver.ko
1 | make |
测试运行
在_install
目录下,准备好漏洞ko和对应的利用程序,exp参考第一道内核pwn-CISCN2017-babydriver。并在/init
中添加对驱动ko的设置。最后,重打包生成cpio。
1 |
|
启动命令:
1 | qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -monitor /dev/null -m 128M --nographic |
调试方法
经过上面的步骤,我们有了内核镜像bzImage、文件系统rootfs.cpio、漏洞模块babydriver.ko、以及带调试信息的vmlinux。因此,我们可以方便地对内核进行调试,这里汇总两个我目前了解到的调试方法。一种是kernel pwn常用的gdb调试,可以精确调试每一行汇编内容。另一种是源码调试,常用于开发场景,便于c语言级别的流程跟踪。
二进制调试 - gdb
安装好gdb或gdb插件(peda/gef/pwndbg)后,开启两个窗口,一个窗口运行qemu,另一个窗口运行gdb并加载符号文件。
窗口1
1
qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -monitor /dev/null -m 128M --nographic -S -s
窗口2
1
2
3
4
5
6gdb
pwndbg> file ./vmlinux
pwndbg> target remote :1234
pwndbg> add-symbol-file ./xxx.ko 0xffffffffa0000000
pwndbg> b start_kernel
pwndbg> c
源码调试 - vscode
待调试内核文件及编译环境在一台ubuntu中,vscode运行在windows中。为了在windows侧调试ubuntu中的目标,需要先配置好两台电脑的环境。
参考文章推荐:
手把手教你利用VS Code+Qemu+GDB调试Linux内核
准备
linux侧安装好gdb、gdbserver以及openssh-server
1
2
3
4sudo apt update
sudo apt install gdb
sudo apt install gdbserver
sudo apt install openssh-serverwindows侧安装好vscode及插件
1
2
3安装好vscode后安装如下两个插件:
Remote Development插件
C/C++插件配置vscode使用密钥文件连接ubuntu
1
2
3
4
5
6linux侧放入公钥文件至~/.ssh/authorized_keys
windows侧私钥文件在C:\Users\xxx\.ssh\id_rsa
windows的vscode中ssh bling@192.168.133.155,设置好config后,刷新SSH,选中目标ip连接到远程
给远程安装C/C++插件,然后就可以打开远程待调试的文件夹
配置
打开待调试的文件夹后,在文件夹下新建.vscode/launch.json
,并填入如下内容。
1 | { |
然后linux侧让qemu以调试模式运行并处于等待,windows侧vscode中设置断点后F5连接过去,即可开始源码调试。
1 | # linux侧 |
问题
出现错误:ubuntu16.04上的qemu版本太低,导致gdb调试出现Remote 'g' packet reply is too long: ...
的问题。
解决方法:
在ubuntu20.04中编译静态qemu
1
2
3
4
5
6
7
8sudo apt install libpixman-1-dev
wget https://download.qemu.org/qemu-4.2.0.tar.xz
tar xJf qemu-4.2.0.tar.xz
cd qemu-4.2.0/
mkdir build
cd build
./configure --static # 指定静态编译
make -j4 # 默认编译所有架构如果要编译某个特定架构的,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24./configure --help | grep "target-list=LIST" -A20 # 查看支持哪些架构
# --target-list=LIST set target list (default: build everything)
# Available targets: aarch64-softmmu alpha-softmmu
# arm-softmmu cris-softmmu hppa-softmmu i386-softmmu
# lm32-softmmu m68k-softmmu microblaze-softmmu
# microblazeel-softmmu mips-softmmu mips64-softmmu
# mips64el-softmmu mipsel-softmmu moxie-softmmu
# nios2-softmmu or1k-softmmu ppc-softmmu ppc64-softmmu
# riscv32-softmmu riscv64-softmmu s390x-softmmu
# sh4-softmmu sh4eb-softmmu sparc-softmmu
# sparc64-softmmu tricore-softmmu unicore32-softmmu
# x86_64-softmmu xtensa-softmmu xtensaeb-softmmu
# aarch64-linux-user aarch64_be-linux-user
# alpha-linux-user arm-linux-user armeb-linux-user
# cris-linux-user hppa-linux-user i386-linux-user
# m68k-linux-user microblaze-linux-user
# microblazeel-linux-user mips-linux-user
# mips64-linux-user mips64el-linux-user
# mipsel-linux-user mipsn32-linux-user
# mipsn32el-linux-user nios2-linux-user
# or1k-linux-user ppc-linux-user ppc64-linux-user
# ppc64abi32-linux-user ppc64le-linux-user
./configure --target-list=x86_64-softmmu --static # 假如仅编译x86_64架构的
make -j4拷贝对应的文件和文件夹到ubuntu16.04中
1
2qemu-4.2.0/build/x86_64-softmmu/qemu-system-x86_64 # 文件
qemu-4.2.0/pc-bios # 整个文件夹在ubuntu16.04中使用静态编译的4.2.0版本qemu运行kernel
1
2./qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -monitor /dev/null -m 64M --nographic -L ./pc-bios/ -S -s
# 通过-L指定pc-bios目录,使用其中的bios-256k.bin等文件启动系统