Linux内核调试方法总结

前言

最近想复现一个关于linux kernel的漏洞,无奈之前没有接触过内核调试,搭建环境还是废了一些功夫的,这里把我自己一路躺过来的坑记录一下分享给大家,如有不正确的,还请大佬不吝赐教^ ^

环境

qemu+gdb

Ubuntu16.04

架构:x86_64

内核:linux5.11.1

qemu:2.5.0

gdb:10.2

busybox:1.33.1

双机调试

debugging:ubuntu18.04 (调试端)

debuggee:ubuntu18.04 (被调试端)

架构:x86_64

内核:linux5.11.1

gdb:8.0

qemu+gdb调试

编译内核

调试目标要清楚,要调试哪些内容,需要具备哪些条件,内核版本变化,然后编译内核,内和编译步骤如下(踩过坑之后的正确步骤)

  1. 确定以下依赖已经安装,可直接安装
#apt-get install make
#apt-get install gcc
#apt-get install flex
#apt-get install bison
#apt-get install libncurses-dev
#apt-get install libssl-dev
#apt-get install libelf-dev
  1. make[2]: *** No rule to make target needed by ‘certs/x509_certificate_list’. Stop

    内核目录下修改.config文件将下面这行注释掉CONFIG_SYSTEM_TRUSTED_KEYS="debian/certs/benh@debian.org.cert.pem"

  2. scripts/Makefile.host:90: recipe for target 'scripts/sign-file' failed

    修改scripts 目录下的 Makefile文件

HOSTLDLIBS_sign-file = -lcrypto
HOSTLDLIBS_extract-cert = -lcrypto

改成

HOSTLDLIBS_sign-file = -L/usr/local/lib -lcrypto
HOSTLDLIBS_extract-cert = -L/usr/local/lib -lcrypto

之后编译应该没问题了,如有问题,按提示安装相应依赖:

  1. cd linux5.11.1
  2. make menuconfig 配置内核,打开或关闭你所需要的配置
  3. make -j4 (单独编译bzImage可执行 make bzImage -j4)
  4. 安装 make modules_install && make install

编译busybox

可以指定makefile中 ARCH=?、CROSS_COMPILE = ?来指定编译架构,默认为本机架构。

  1. cd busybox-1.28.0
  2. make menuconfig

    Settings —>

    [*] Build static binary (no shared libs)

  3. make && make install

    编译完成后在busybox目录下会有_install目录:

-> ls _install 
bin  linuxrc  sbin  usr

制作根文件系统

可以编译好各个架构的文件系统(mips32、64的大小端,arm32、64的大小端,gdb、gdbserver)备用

busybox制作最小根文件系统

以下是脚本:

mkdir initramfs
cd initramfs
cp ../busybox-1.33.1/_install/* -rf ./
ln -s bin/busybox init
mkdir -pv bin sbin etc proc sys usr dev
mkdir usr/bin usr/sbin

cd etc
touch inittab
echo "::sysinit:/etc/init.d/rcS" >> inittab
echo "::askfirst:-/bin/sh" >> inittab
echo "::restart:/sbin/init" >> inittab
echo "::ctrlaltdel:/sbin/reboot" >> inittab
echo "::shutdown:/bin/umount -a -r" >> inittab
echo "::shutdown:/sbin/swapoff -a" >> inittab  
chmod +x inittab


mkdir init.d
cd init.d
touch rcS
echo "#!/bin/sh" >> rcS
echo "mount proc" >> rcS
echo "mount -o remount,rw /" >> rcS
echo "mount -a" >> rcS
echo "clear " >> rcS
echo "echo 'My Tiny Linux Starting, press enter to active'" >> rcS
chmod +x rcS

cd ..
touch fstab
echo "proc            /proc        proc    defaults          0       0" >> fstab
echo "sysfs           /sys         sysfs   defaults          0       0" >> fstab
echo "devtmpfs        /dev         devtmpfs  defaults          0       0" >> fstab


cd ..
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.img

这个是最基础的脚本,如果还需要什么依赖可以添加copy进去,busybox用的是ash,不支持bash,用的话需要把bash和他的依赖拷进去。

buildroot制作根文件系统

获取buildroot源码并设置配置文件

git clone git://git.buildroot.net/buildroot
cd buildroot
make menuconfig

在跳出的UI界面依次选择

Target options

Target Architecture (x86_64)

x86_64上按空格键,以选择该选项

  • 再返回与Target options同一级的界面

    选择Filesystem images

ext2/3/4 root filesystem按Y,并进入ext2/3/4 variant (ext4)选择ext4

退出并保存配置.

编译buildroot

make -j4

编译完成之后,在buildroot/output/images/目录中可以获得文件系统镜像rootfs.ext2rootfs.ext4

参考

如果想要增加一些可执行文件,sudo mount rootfs.ext2 tmp/需要将编译好的文件和相应的依赖放进去,umount tmp/

缺点编译时间太长。

启动调试

我们有2种文件系统,用哪一种都行.

第一种:使用rootfs.ext2作为文件系统运行qemu

# 在A Terminal中运行以下命令
qemu-system-x86_64 -kernel bzImage -boot c -m 1024 -hda rootfs.ext2 -append "root=/dev/sda rw console=ttyS0, 115200 acpi=off nokaslr" -serial stdio -display none -s -S

第二种:使用initramfs.img作为文件系统运行qemu

# 在A Terminal中运行以下命令
qemu-system-x86_64 -kernel bzImage -boot c -m 1024 -initrd initramfs.img -append "root=/dev/sda rw console=ttyS0, 115200 acpi=off nokaslr" -serial stdio -display none -s -S

如果需要调试内核的kvm模块,则向上述命令添加 -enable-kvm

# 在B Terminal中运行以下命令,并在成功启动gdb后输入
# target remote localhost:1234
# 即可开始使用gdb debug我们之前编译的内核
gdb vmlinux

但是gdb调试还是没有成功,没有符号表,调试出问题。

Remote 'g' packet reply is too long: 4092d9b3ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000203e80b4ffffffff203e80b4ffffffff00f72edc02000000e0d1c1c7cf97ffff0000000000000000fa5acc03690000000000000000000000802481b4ffffffff000000000000000000000000000000008695d9b3ffffffff4602000010000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000ffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

我想要的效果是gdb可以看到反编译的源码进行调试。但目前还没有实现。

问题解决:这个错误是当目标程序执行时发生模式切换(real mode 16bit -> protected mode 32bit -> long mode 64bit)的时候,gdb server(此处就是 qemu)发送了不同长度的信息,gdb 无法正确的处理这种情况,所以直接就报错。架构解析的问题,将架构设置成i386-x86_64:intel.问题会消失。参考

如果不是架构问题,就可能是gdb版本低了,编译一个最新版本的gdb。

双机调试

前提机器上已经安装好以上所需环境,编译过内核,具有bzImage、vmlinux。

配置双机

debuggee(被调试端)

双机调试主要是虚拟机通过串口进行调试,在debuggee的虚拟机设置上添加串口硬件,使用命令管道一栏填入//./pipe/com_1(宿主机为win),/tmp/serial(宿主机为linux),选择该端是服务器,另一端是虚拟机。(网上都说该端是client端,我将配置改成客户端出现调试不了,换成服务端就行了)

进入虚拟机,修改/etc/grub.d/40_custom文件,文件内容参考/boot/grub/grub.cfg,将里面你要调试的内核的menuentry复制出来,填入/etc/grub.d/40_custom文件,只需要添加kgdbwait kgdb8250=io,03f8,ttyS0,115200,4 kgdboc=ttyS0,115200 kgdbcon nokaslr

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
menuentry 'Ubuntu, KGDB with nokaslr' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-bf306d0a-28c8-49c6-bffc-446be272ddcf' {
    recordfail
    load_video
    gfxmode $linux_gfx_mode
    insmod gzio
    if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
    insmod part_msdos
    insmod ext2
    set root='hd0,msdos1'
    if [ x$feature_platform_search_hint = xy ]; then
      search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1  bf306d0a-28c8-49c6-bffc-446be272ddcf
    else
      search --no-floppy --fs-uuid --set=root bf306d0a-28c8-49c6-bffc-446be272ddcf
    fi
    echo 'Loading Linux 4.10.0-19 with KGDB built by GEDU lab...'
    linux /boot/vmlinuz-4.10.0-19-generic root=UUID=bf306d0a-28c8-49c6-bffc-446be272ddcf ro find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US quiet kgdbwait kgdb8250=io,03f8,ttyS0,115200,4 kgdboc=ttyS0,115200 kgdbcon nokaslr
    echo 'Loading initial ramdisk ...'
    initrd    /boot/initrd.img-4.10.0-19-generic
}

修改完后,执行命令:sudo update-grub,重启,一直按shift进入高级选项,选择最后一项Ubuntu, KGDB with nokaslr进入kgdb等待另一端gdb的连接

debugging

该虚拟机可由配置前的debuggee虚拟机复制过来,和debuggee一样,设置串口,使用命令管道一栏填入//./pipe/com_1(宿主机为win),/tmp/serial(宿主机为linux),选择该端是客户端,另一端是虚拟机。

然后启动虚拟机,正常使用gdb连接

调试配置

debuggee

双机调试比qemu慢了许多许多,单步调试很可能出现解析出错,我每次调试都只直接下断点,尽量少用单步。

当debuggee进入kgdb等待时,debugging使用gdb连接

vmlinux默认使用绝对路径搜索源码,如果了你在其他虚拟机调试可以使用set substitute-path /home/yrl/linux-5.11.1 /root/linuxkernel/linux-5.11.1 修改源码路径,第一个path是vmlinux中原有路径,第二个是替换的路径

sudo gdb -s vmlinux
#进入gdb,设置架构,设置gef显示尽量不要选线程显示,线程很多显示会很慢,连接ttyS0,进入kgdb断点 断下
set architecture i386:x86-64:intel
gef config context.layout "-legend -regs -stack code -args source -memory -threads trace -extra"
target remote /dev/ttyS0
#至此进入kgdb断点 断下,可以下断点之类的gdb命令操作(可调式内核启动过程),如果你要调试poc或exp需要进入系统,直接c继续执行
c

至此debuggee端已经进入系统,debugging是gdb continuing状态,此时就需要使用sysrq来使debugging的gdb断下,我们就可以在执行exp调试前下断点

#debuggee端执行
su  #切换root
# debugging的gdb不能和正常ctrl+c一样停止后可下断点,他ctrl+c后就断开连接了,所以要用sysrq
echo g > "/proc/sysrq-trigger" #此时debuggee用户态停止,debugging的gdb断下,可以下断点
#debugging下完exp的断点后 c 继续执行
#debuggee端运行exp,触发断点,就可以实现双机调试了
./exp