之前师兄给我看过一个双Ubuntu on VMware Workstation的Linux Kernel调试解决方案,但是在Mac下配起来比Win上困难好多,因此就使用CentOS on VMware Fusion + QEMU/KVM的方案来搭建Linux Kernel(用目标内核替换发行版Linux的内核)调试环境。后来又改进成直接在VM中使用QEMU直接启动Linux Kernel来调试。
使用虚拟机源码调试Linux Kernel
实验环境:
1 | 宿主:macOS Mojave + VMware Fusion 11 Pro |
虚拟机配置
建议分配50G以上的硬盘空间。在设置中需要打开在此虚拟机中启用虚拟化管理程序
选项以让虚拟机提供Intel VT-x/EPT支持。使用CentOS的Live版本的iso进行安装,选择”Test Then Start”的选项启动系统避免发生错误。
能够正常打开系统后,选择安装到硬盘即可。
QEMU/KVM环境配置
在虚拟机中安装QEMU/KVM和VIRT环境:
1 | yum install qemu-kvm qemu-system virt-manager libvirt libvirt-python libvirt-client virt-install virt-viewer |
启动libvirtd
1 | service libvirtd start |
在启动virt-manager
时,如果遇到权限问题,则修改/etc/libvirt/qemu.conf,添加下述内容并重启服务。
1 | user = "root" |
下载所要调试的内核的载体操作系统,并将iso文件放到libvirt的images目录下。通过virt-manager
创建KVM,硬盘空间建议设置为25G以上。在进行到最后一步时勾选启动前进行配置。在设置面板的CPU
选项中,将复制CPU参数选中然后再开始进行安装。
经上述步骤后,KVM启动,使用
1 | virsh list |
来修改配置,将第一行改为
1 | <domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> |
在</device>
后加入
1 | <qemu:commandline> |
当KVM被重启后,将会在localhost
开启1234端口进行gdb的监听。
在被调试机中编译和使用其他版本Linux内核
在QEMU/KVM中安装
1 | yum install gcc ncurses-devel |
从kernel.org上下载3.18.35源码,修改Makefile文件的617行的KBUILD_CFLAGS值为O1,进行编译
1 | make menuconfig |
在make install
后,会报一些could not find module
的错误,忽略之。查看当前已有的内核版本
1 | cat /boot/grub/grub.conf | awk '$1=="title" {print i++ " :" $NF}' |
修改当前默认启动的内核版本后,重启即可。
1 | vi /boot/grub/grub.conf |
调试Linux内核
由于之前已经设置好了参数,在被调试机启动后将会开启1234端口作为gdb的调试端口,将编译好的vmlinux
保存到虚拟机中,启动gdb进行调试。
1 | scp root@kvm_ip:/path/to/vmlinux ./ |
使用QEMU源码调试Linux Kernel
实验环境:
1 | 宿主:macOS Mojave + VMware Fusion 11 Pro + Ubuntu 16.04.6 |
编译Linux内核
由于所需要编译的Linux版本较低,因此需要将gcc切换成gcc 4.8后再编译。
1 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-x x |
编译busybox
一开始在Ubuntu 18.04上静态编译的时候,遇到libc版本的问题,在Linux Kernel启动的时候会提示kernel too old
。在编译前需要使用以下指令检查下当前libc所支持的内核版本,如果所支持的版本过高,需要使用低版本libc。
1 | file /path/to/libc-x.xx.so |
需要将busybox的编译选项设置为静态编译。
1 | make menuconfig |
把生成的_install
文件夹拷贝到linux kernel源代码根目录。
生成文件系统
进入_install
目录,创建文件夹
1 | mkdir etc |
创建etc/init.d/rcS文件
1 | mkdir -p /proc |
1 | chmod +x rcS |
创建etc/fstab文件
1 | proc /proc proc defaults 0 0 |
创建etc/inittab文件
1 | ::sysinit:/etc/init.d/rcS |
在dev/创建设备节点
1 | sudo mknod console c 5 1 |
创建文件系统,在_install
文件夹中执行
1 | find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.img |
QEMU启动Linux Kernel
1 | qemu-system-x86_64 -kernel /path/to/bzImage -initrd /path/to/initramfs.img -m 200M -append "rdinit=/linuxrc" |
使用deboostrap创建文件系统
使用deboostrap需要良好的网络环境支持。
GDB Patch
使用gdb调试的时候,会出现g pack too long
的报错,可通过网上的方法解决(修改gdb源码并重新编译),虽然能够正常调试,但首次attach上无法正常显示pwngdb等插件的界面,退出再重新attach即可(QEMU使用选项-S和-s)。
编译特定内核版本的驱动
在Makefile中指定根目录即可
1 | ROOTDIR := /path/to/kernel/source |
在系统中加载驱动后,会被挂载在sys目录下而不是dev目录。
基本内核栈溢出漏洞调试
简单内核栈溢出示例
使用
1 | qemu-system-x86_64 -kernel /path/to/bzImage -initrd /path/to/initramfs.img -m 200M -append "rdinit=/linuxrc" -S -s |
启动内核并使用gdb进行调试。
示例驱动源代码:
1 |
|
Makefile:
1 | obj-m := sbof.o |
exploit.c:
1 |
|
编译完驱动和exploit后重新打包文件系统并启动Kernel。安装模块后,使用
1 | cat /sys/modules/sbof/section/.text |
查看模块代码段地址,并在gdb中
1 | add-symbols-file sbof.ko <内存位置> |
获取commit_creds
和prepare_kernel_cred
函数的内存地址:
1 | grep commit_creds /sys/kallsyms |
执行exploit之前最好切换到非root用户,执行exploit后获取root权限。
gadget搜索
在性能良好的主机上使用Ropper:
1 | ropper --file ./vmlinux --nocolor > g1 |
Ubuntu 64位下编译32位程序
内核
1 | make ARCH=i386 |
Busybox
修改编译选项
1 | (-m32 -march=i386) Additional CFLAGS |
驱动
1 | export CFLAGS="-m32" |
参考文章和资料
http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2016/06/21/gdb-linux-kernel-by-qemu
https://www.ibm.com/developerworks/cn/linux/1508_zhangdw_gdb/index.html
http://sec-redclub.com/archives/636/
《奔跑吧,Linux内核 入门篇》