参考
利用qemu+gdb在ubuntu下搭建调试kernel的环境 - EwanHai - 博客园 (cnblogs.com)
在很多情况下,只使用linux内核加文件系统不能得到漏洞依赖的环境
VMware虚拟机调试方便,但是在进行测试时虚拟机会崩溃关机。
用make方式编译安装Ubuntu内核可能会有一些内核选项没有开启,导致Ubuntu黑屏,用官方提供的方式编译成deb包是很稳定的。
用apt安装内核调试信息的方法,调试信息和源码很可能对应不上,自己编译安装是最稳妥的方式。
https://blog.csdn.net/Breeze_CAT/article/details/123787636
举例:linux版本5.15.5
很久之前内核通过版本号中的第二个数字即B的奇偶来表示稳定版和预发布版。但现在已经取消这个规则,现在预发布版用-rcX来表示如5.17-rc3,X为数字,一般不超过rc8
长期维护版本:5.15、5.10、5.4、4.19、4.14、4.9
如
kernel/git/torvalds/linux.git - Linux kernel source tree
查看tree中根目录下的Makefile文件可以看到对应的版本号
cat /etc/issue
Ubuntu 20.04.2 LTS \n \l
或
lsb_release -a 命令
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04 版本
Codename: focal 别名
查看Ubuntu信息
➜ uname -a
Linux tower-virtual-machine 5.15.0-71-generic #78~20.04.1-Ubuntu SMP Wed Apr 19 11:26:48 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
➜ uname -r
5.15.0-71-generic 内核版本 5.15表示内核稳定版本号,一般ubuntu中最后小修订号都是0,因为ubuntu会自己合入补丁。
71表示由ubuntu进行的第35次修订(合入补丁)。
generic:通用版本,除此之外还可能有服务器版server或老式处理器的i386版等。
Ubuntu对应的别名
经过测试,其他版本的内核是可以安装的(如 focal系列的Ubuntu内核代码编译之后可以安装到Ubuntu18主机上)(没有经过更多测试)
下载ubuntu
https://mirrors.aliyun.com/oldubuntu-releases/releases/20.04.0/?spm=a2c6h.25603864.0.0.10467ff3XF6iMS 阿里云镜像
https://old-releases.ubuntu.com/releases/ Ubuntu官方镜像
下载Ubuntu内核
https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.15.10/ 较粗的deb包
下载linux内核源码
https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/ 版本比较粗
kernel/git/torvalds/linux.git - Linux kernel source tree Linux源码
Kernel/Dev/KernelGitGuide - Ubuntu Wiki Ubuntu内核源码
找到对应的源码:
git tag | grep 4.10.0-19
git checkout ubuntu-4.10.0-19........
或使用apt更换内核
sudo apt search 'linux-image-5.4.0-81-generic'
sudo apt install 'linux-image-5.4.0-81-generic'
更新并重启
sudo update-initramfs -u -k all
sudo update-grub
reboot
安装新版本内核头文件,用于开发编译
sudo apt install linux-headers-$(uname -r)
开机按esc选择选高级,找到对应版本内核启动
如果进不去需要修改引导启动信息:
修改文件/etc/default/grub
注释掉GRUB_TIMEOUT_STYLE=hidden
修改GRUB_TIMEOUT=0为GRUB_TIMEOUT=10
执行sudo update-grub启用修改
reboot 重启时会进入引导界面等待选择内核
https://bbs.kanxue.com/thread-249192.htm
在https://wiki.ubuntu.com/Kernel/Dev/KernelGitGuide选择对应的Ubuntu系列
查看主机的Ubuntu系列(这里需要下载被调试主机的源码)
➜ CVE-2021-3493 git:(main) ✗ cat /etc/apt/sources.list
deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
如Ubuntu20可以下载
git clone https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/focal
git tag | grep 4.10.0-19
git checkout ubuntu-4.10.0-19........
gdb中指定源代码路径,或把source文件直接拷贝到gdb检测源码的目录(如/build/linux-kcrkKx/linux-5.4.0)
set substitute-path PATH1 PATH2,PATH1是vmlinux中的路径信息,PATH2是source code存放的真实路径。
apt source linux-image-unsigned-5.11.0-44-generic # 版本号根据需求更改,可以用apt search搜索
ubuntu 内核git:https://kernel.ubuntu.com/git/ubuntu/
可以查看分支名https://kernel.ubuntu.com/git/ubuntu/ubuntu-focal.git/refs/tags
git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git -b Ubuntu-hwe-5.13-5.13.0-35.40_20.04.1 --depth 1
make ARCH=x86_64 x86_64_defconfig
make ARCH=x86_64 menuconfig
配置 Kernel-hacking -> Compile-time checks and compiler options
Compile the kernel with debug info —> Provide GDB scripts for kernel debugging
关闭 Reduce debugging information
make -j8
sudo make modules_install 安装模块
sudo make install 安装
sudo update-initramfs -c -k 5.3.10 启用(数字为版本号)
sudo update-grub 更新grub
reboot
也可以使用docker编译https://registry.hub.docker.com/r/chenaotian/kernelcompile(编译5.x)
docker run -ti --rm -h kc --name kc -v D:/share:/work chenaotian/kernelcompile:latest /bin/bash
docker exec -it kc /bin/bash
# 设置调试符号
CONFIG_DEBUG_INFO=y
# fuse 开启,一些漏洞利用会用到
CONFIG_FUSE_FS=y
# VIPC 开启,可以使用msg系列
CONFIG_SYSVIPC=y
CONFIG_SYSVIPC_SYSCTL=y
CONFIG_SYSVIPC_COMPAT=y
CONFIG_CHECKPOINT_RESTORE=y # 设置这个才能正确调用msg 里的copy 系列函数
cat /usr/src/linux-headers-`uname -r`/.config
或
cat /boot/config-`uname -r`
Kernel/BuildYourOwnKernel - Ubuntu Wiki
安装依赖
sudo apt-get build-dep linux
sudo apt-get install libncurses-dev gawk flex bison openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf llvm
编译
LANG=C fakeroot debian/rules clean
LANG=C fakeroot debian/rules binary-headers binary-generic binary-perarch 快速编译
# if you need linux-tools or lowlatency kernel, run instead: 会多出来更多东西
LANG=C fakeroot debian/rules binary
sudo apt-get install pkg-config-dbgsym
LANG=C fakeroot debian/rules clean
LANG=C fakeroot debian/rules binary-headers binary-generic binary-perarch skipdbg=false
System.map 文件可以查看一些关键函数有没有
cat System.map |grep function_name
查看字符串
strings vmlinux |grep function_name
sudo apt-get install qemu qemu-system
常用的启动脚本
#! /bin/sh
cd ./rootfs
find . | cpio -o --format=newc > ../rootfs.img
cd ../
qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd ./rootfs.img \
-nographic \
-append "console=ttyS0 root=/dev/sda rw nokaslr quiet" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-cpu kvm64,+smep,+smap \
-gdb tcp::10086
解包和打包
cpio -idmv < ../rootfs.img #解包cpio
find . | cpio -o --format=newc > ../rootfs.img #打包cpio
有可能在外层更进行了一次gzip压缩,使用binwalk可以可以解包
binwalk -Me ./rootfs.img
如果一定需要动态链接的exp,偷懒方法就是,ldd 查看exp 需要的动态库,然后将ld-linux-x86-64.so.2 和其他依赖的so全部拷贝到文件系统中,qemu 启动后,用LD_LIBRARY_PATH 来运行
将其依赖的so全部拷贝到一个文件夹内然后放入rootfs中制作成initrd.img,然后启动qemu,按照如下方法运行
cp ...so ./rootfs/exp #将so 拷贝到制作initrd.img的目录中
cd ./rootfs
find . | cpio -o --format=newc > ../rootfs.img #制作initrd.img
cd ../
./boot.sh #启动qemu
# qemu 启动后
cd /expdir
export LD_LIBRARY_PATH=`pwd`
./ld-linux-x86-64.so.2 ./exploit
使用busybox构建文件系统比较复杂,用buildroot相对简单
git clone git://git.buildroot.net/buildroot
cd buildroot
make menuconfig
Target options
Target Architecture (x86_64)
在x86_64
上按空格键,以选择该选项
Target options
同一级的界面选择Filesystem images
在ext2/3/4 root filesystem
按Y,并进入ext2/3/4 variant (ext4)
选择ext4
make -j4
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -jxvf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
make defconfig
make menuconfig
选择settings中选择静态链接
make -j4
sudo make install
make install会把文件拷贝到当前目录下的 _install目录
拷贝文件,创建init
mkdir ramdisk
cd ramdisk
cp -r ../busy-1.25.0/_install/* .
cd ramdisk
ln -s bin/busybox init
设置启动程序,init程序首先会访问etc/inittab文件,以获取开机要启动的程序列表,因此我们得编写一个inittab
cd etc
vim inittab
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
chmod +x etc/inittab
系统初始化寻找etc/init.d/rcS,需要创建,内容添加
#!/bin/sh
mount proc
mount -o remount,rw /
mount -a
clear
echo "My Tiny Linux Start :D ......"
并加权限
chmod +x rcS
rcS中,mount -a 是自动挂载 /etc/fstab 中的内容,因此我们需要一个fstab文件,以设置需要挂载的系统, 创建文件并添加内容
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devtmpfs /dev devtmpfs defaults 0 0
最后把文件压缩成镜像
cd ramdisk
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.img
cpio --null或-0接受新增列控制字符,通常配合find指令的"-print0"参数使用。
-o或--create 执行copy-out模式,建立备份档。
-v或--verbose 详细显示指令的执行过程。
newc 新型 (SVR4) 跨平台格式, 支持大于 65536 i节点的文件系统,一般 制作 ramdisk 就用 这个 格式
-9 表示压缩率
使用buildroot编译的镜像
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
使用busybox构建的镜像启动命令
# 在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
gdb vmlinux
target remote localhost:1234
把自己的文件放入文件系统
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.img
vmlinux文件可能不包含一些符号,如图
原因是ovlerlay文件系统被编译成了内核模块文件,需要手动添加
gef➤ add-symbol-file ./fs/overlayfs/overlay.ko 0xffffffffc04ee000
查看内核模块加载地址(内核模块被加载了之后才能看到,可能需要先执行一次相关的命令或执行相关的poc)
cat /proc/modules
用docker编译的内核会从docker中的路径寻找二进制源码,在调试时会出现找不到源码的情况
gef➤ list
46 in /work/ubuntu-focal/arch/x86/include/asm/irqflags.h
用set命令重定位源码目录即可
set substitute-path /work/ubuntu-focal /home/tower/aiwencode/vul/CVE-2023-0386/ubuntu-focal
Ubuntu内核默认不包含调试信息
安装内核调试信息
https://developer.aliyun.com/article/899339
获取Ubuntu内核调试信息
https://bbs.kanxue.com/thread-249192.htm
cat /boot/config-uname -r| grep -i "GDB"查看当前内核是否支持KGDB
增加符号对应的源文件
codename=$(lsb_release -c | awk '{print $2}')
sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
deb http://ddebs.ubuntu.com/ ${codename} main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
EOF
# 添加访问符号服务器的秘钥文件
wget -O - http://ddebs.ubuntu.com/dbgsym-release-key.asc | sudo apt-key add -
# 更新源文件
sudo apt-get update
或sudo apt-get install aptitude
获取被调试内核信息,并安装对应内核符号
被调试主机:unamr -r
调试主机:sudo apt-get install linux-image-<被调试主机执行uname -r结果>-dbgsym
在调试主机上进行调试
gdb -s /usr/lib/debug/boot/vmlinux-5.15.0-71-generic 直接加载之前下载的包含符号信息的vmlinux
set architecture i386:x86-64:intel
target remote localhost:8864
创建虚拟磁盘
qemu-img create -f qcow2 qemu_disk.img 10G
查看磁盘信息
qemu-img info qemu_disk.img
可以创建的格式如下
Supported formats: blkdebug blklogwrites blkreplay blkverify bochs cloop compress copy-on-read dmg file host_cdrom host_device luks nbd null-aio null-co nvme parallels qcow
qcow2 qed quorum raw replication sheepdog throttle vdi vhdx vmdk vpc vvfat
启动虚拟机
qemu-system-x86_64 -m 4G -smp 4 -boot d -hda ubuntu20.qcow2 -cdrom ubuntu-18.04.5-desktop-amd64.iso
boot order 的选项如下,代表系统的启动顺序,每种方式都有一个对应的字母缩写
-boot [order=drives][,once=drives][,menu=on|off]
'drives': floppy (a), hard disk (c), CD-ROM (d), network (n)
安装好之后再次启动
qemu-system-x86_64 -m 8G -smp 4 -hda ubuntu20.qcow2
https://www.cnblogs.com/sun-ye/p/15750205.html
https://www.cnblogs.com/haiyonghao/p/14440163.html
网桥模式
用ip addr命令查看自己的网络中可以使用的网卡名
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
link/ether 00:0c:29:9e:f6:c2 brd ff:ff:ff:ff:ff:ff
altname enp3s0
主机创建网桥,把主机网卡添加到网桥上(网络会断开重启),启用stp ,
apt-get install bridge-utils 安装依赖
brctl addbr br0 创建
brctl addif br0 ens160绑定
brctl stp br0 on
ifconfig ens160 0
dhclient br0
route 查看创建网桥的信息(br0的信息),对应填到qemu虚拟机里
启动qemu虚拟机,在启动命令后加
-net nic -net tap,ifname=tap1
修改qemu虚拟机内的网络配置编辑/etc/netplan/ *-cloud-init.yaml文件
ip设置为和br0 同一网段,gateway设置为route查到的(我这里是0.0.0.0),dns 8.8.8.8
配置网络 https://blog.csdn.net/allway2/article/details/121949816 (Ubuntu18 以上)
断点断不下来原因为地址不对,如果不关闭kaslr,需要查看/proc/kallsys中对应的符号地址下断点
sudo vim /etc/default/grub
添加
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nokaslr" 添加内核启动选项
更新,重启
sudo update-grub
reboot
在文件xxx.vmx中最后添加(在gdb执行c之后虚拟机崩溃问题没有解决)
debugStub.listen.guest64="TRUE"
debugStub.port.guest64 = "8864"
用gdb连接127.0.0.1:8864
用其他主机进行调试可以加一个端口转发
netsh interface portproxy add v4tov4 listenaddress=192.168.1.68 listenport=8864 connectaddress=127.0.0.1 connectport=8864
使用ipv4 to ipv4模式将源地址是127.0.0.1的8864端口代理到192.168.1.68(本机当前ip)8864端口上,源地址处也可以改为可以内网互通的服务器的内网地址。
netsh interface portproxy show all
关闭转发,这种方式转发可能会失败(不报错)需要删除之后重新设置
netsh interface portproxy delete v4tov4 listenaddress=192.168.1.68 listenport=8864
安装vmwaretools
sudo apt upgrade
sudo apt install open-vm-tools-desktop -y
sudo reboot
下断点continue之后崩溃退出
没有找到解决方案
https://blog.csdn.net/Breeze_CAT/article/details/123787636
https://bbs.kanxue.com/thread-249192.htm
http://blog.nsfocus.net/gdb-kgdb-debug-application/
Makefile替换babydriver为文件名,KERNELDIR 为内核源码的目录
#!/bin/bash
obj-m += babydriver.o
#CROSS_COMPILE ?= /opt/linaro/gcc-linaro-5.3-2016.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- # Compiler path settings
KERNELDIR :=/home/tower/aiwencode/sourcecode/linux-5.15/ # Kernel code directory
PWD ?= $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules # Compile the instructions of the kernel module
clean:
rm -rf *.ko *.mod.c *.o modules.* Module.symvers # delete all generated files
源码举例
/*
* arttnba3_module.ko
* developed by arttnba3
*/
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "babydev"
#define CLASS_NAME "a3module"
static int major_num;
static struct class * module_class = NULL;
static struct device * module_device = NULL;
static spinlock_t spin;
static int __init kernel_module_init(void);
static void __exit kernel_module_exit(void);
static int a3_module_open(struct inode *, struct file *);
static ssize_t a3_module_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t a3_module_write(struct file *, const char __user *, size_t, loff_t *);
static int a3_module_release(struct inode *, struct file *);
static long a3_module_ioctl(struct file *, unsigned int cmd, long unsigned int param);
static struct file_operations a3_module_fo =
{
.owner = THIS_MODULE,
.unlocked_ioctl = a3_module_ioctl,
.open = a3_module_open,
.read = a3_module_read,
.write = a3_module_write,
.release = a3_module_release,
};
static struct
{
void *device_buf;
size_t device_buf_len;
}babydev_struct;
module_init(kernel_module_init);
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("arttnba3");
static int __init kernel_module_init(void)
{
spin_lock_init(&spin);
printk(KERN_INFO "[arttnba3_TestModule:] Module loaded. Start to register device...\n");
major_num = register_chrdev(0, DEVICE_NAME, &a3_module_fo);
if(major_num < 0)
{
printk(KERN_INFO "[arttnba3_TestModule:] Failed to register a major number.\n");
return major_num;
}
printk(KERN_INFO "[arttnba3_TestModule:] Register complete, major number: %d\n", major_num);
module_class = class_create(THIS_MODULE, CLASS_NAME);
if(IS_ERR(module_class))
{
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Failed to register class device!\n");
return PTR_ERR(module_class);
}
printk(KERN_INFO "[arttnba3_TestModule:] Class device register complete.\n");
module_device = device_create(module_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);
if(IS_ERR(module_device))
{
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Failed to create the device!\n");
return PTR_ERR(module_device);
}
printk(KERN_INFO "[arttnba3_TestModule:] Module register complete.\n");
return 0;
}
static void __exit kernel_module_exit(void)
{
printk(KERN_INFO "[arttnba3_TestModule:] Start to clean up the module.\n");
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[arttnba3_TestModule:] Module clean up complete. See you next time.\n");
}
static long a3_module_ioctl(struct file * __file, unsigned int cmd, long unsigned int param)
{
if (cmd == 65537)
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = kmalloc(param, GFP_ATOMIC);
babydev_struct.device_buf_len = param;
printk(KERN_INFO "alloc done\n");
return 0;
}
else
{
printk(KERN_INFO "default arg is %ld\n", param);
return -22;
}
}
static int a3_module_open(struct inode * __inode, struct file * __file)
{
babydev_struct.device_buf = kmalloc(0x40, GFP_ATOMIC);
babydev_struct.device_buf_len = 0x40;
printk(KERN_INFO "device open\n");
return 0;
}
static int a3_module_release(struct inode * __inode, struct file * __file)
{
kfree(babydev_struct.device_buf);
printk(KERN_INFO "device release\n");
return 0;
}
static ssize_t a3_module_read(struct file * __file, char __user * user_buf, size_t size, loff_t * __loff)
{
size_t result;
if (!babydev_struct.device_buf)
return -1LL;
result = -2LL;
if (babydev_struct.device_buf_len > size)
{
copy_to_user(user_buf, babydev_struct.device_buf, size);
result = size;
}
return result;
}
static ssize_t a3_module_write(struct file * __file, const char __user * user_buf, size_t size, loff_t * __loff)
{
size_t result;
if (!babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > size)
{
copy_from_user(babydev_struct.device_buf, user_buf, size);
result = size;
}
return result;
}
查看内核支持的选项
grep CONFIG_PID_NS /boot/config-$(uname -r)
查看内核崩溃日志
/proc/last_kmsg
/sys/fs/pstore/console-ramoops