ubuntu内核编译与Kasan初探

最近在复现一篇论文的时候发现linux内核的强大之处了,就简单的学习了下关于内核的一些知识,其中主要用到了Kasan这个检测UAF漏洞的工具(严格来说只能是内核编译插桩),本以为很简单,结果搞了好长时间,鉴于目前这方面资料很少,设置过程也缺乏,故将这个设置过程记录下来,以便交流(本文仅限学术交流,严谨转载)

一 内核编译

内核编译过程主要参考了《鸟哥的Linux私房菜》,以及https://www.jianshu.com/p/e1f550ba164d

1  查看自己ubuntu的内核版本 uname -a(-r)

2 查看可用的内核版本:

3 安装与自己内核头文件相应的的内核源码 sudo apt-get install linux-source-4.10.0(内核文件并不大,一般的也就在100M左右),下载完后会直接在/usr/src中看到相应的内核源码,运行 cd /usr/src 转到目录后解压源码:sudo tar -axvf linux-source-4.10.0.tar.bz2,转到相应的源码目录:cd /usr/src/linux-source-4.10.0

源码中一些基本的信息:

arch:与硬件平台有关的选项,大部分指的是CPU的类别,例如 x86,x86_64,Xen虚拟支持等;
block:与区块设备较相关的设置数据,区块数据通常指的是大量存储媒体,还包括ext3等文件系统的支持是否允许等;
crypto:内核所支持的加密技术,如 md5或des等;
Documention:与内核有关的帮助文档;
drivers:一些硬件的驱动程序,如显卡、网卡、PCI相关硬件;
firmware:一些旧式硬件的微指令(固件)数据;
fs:内核所支持的filesystems,如vfat、nfs等;
include:一些可让其他程序调用的头(header)定义数据;
init:一些内核初始化的定义功能,包括挂载与init程序的调用等;
ipc:定义Linux操作系统内各程序的通信;
kernel:定义内核的程序、内核状态、线程、程序的调度(schedule)、程序的信号(signal)等;
lib:一些函数库;
mm:与内存单元有关的各种信息,包括swap与虚拟内存等;
net:与网络有关的各项协议信息,还有防火墙(net/ipv4/netfilter/*)等;
security:包括SELinux等安全性设置;
sound:与音效有关的各项模块;
virt:与虚拟化及其有关的信息,目前内核支持的是KVM(Kernel base Virtual Machine)

4 修改设置:sudo make menuconfig(==make oldconfig ==make xconfig == make gconfig == make config ==sudo vim .config)

报错,其原因是缺少库,解决方法:

   sudo apt-get install libncurses5-dev

之后再次运行 sudo make menuconfig,得到图形化界面,在kernel hacking/memory debugging中,可以使用空格进行选择,前面有M的表示模块

找到kasan,首先查看Help,查看相关的依赖选项:

注意,KASAN[=n]表示的是目前的状态时未打开,selects 则和symbol保持着一致性(symbol是啥,selects就是啥),而相关的依赖需要打开。要是找不到,可以在选好后保存,然后 sudo gedit .config进行修改,不过相关的依赖前面会加上CONFIG_的前缀。同时还要注意:enable,don‘t work with后面的选项。当然你也可以使用别人已经设置好的.config文件。

在Kasan的子目录里,进行模式选择,inline比outline要快一些,所以设置

另外还有SLUB,设置后,保存,退出,在源码的目录下,Ctrl+H,会发现多出了.config文件,这个就是配置文件了。

5 编译内核、模块

make -j 2 clean    #先清除临时文件

make -j 2 bzImage    #先编译内核,生成bzImage文件(中量时间)

sudo make -j 2 modules    #再编译模块,要权限(大量时间)

制作出来的资料是被放置在/usr/src/linux-source-4.10.0/kernel 目录下,还没有被放到系统的相关路径中

##-j [N], --jobs[=N]       同时允许 N 个任务;无参数表明允许无限个任务

6 安装模块

上步编译内核、模块后,还没有将其放入相关的系统路径中,模块需要安装,内核也需要移动位置。模块会被安装在/lib/modules的目录下,这儿一定要注意,当两个版本一模一样时,模块放置的目录也一样,此时就会产生冲突,解决方法:

方法1:先将旧的模块目录更名,然后才安装内核模块到目标目录中去;
方法2:在 make menuconfig 时,将General setup内的Local version修改成新的名称;

我在我的机子上实验并没有遇到这个问题,所以直接运行:sudo make modules_install,安装之后,会在/lib/modules下生成一个新目录

7 移动内核

内核文件通常以 vmlinuz 为开头,后面跟上内核版本的文件格式。复制新内核文件到 /boot/ 下并改名 vmlinuz-3.10.107,保留旧内核文件。(命名时,我参考了我的模块生成的目录)

sudo cp  /usr/src/linux-source-4.10.0/arch/x86/boot/bzImage  /boot/vmlinuz-4.10.17
sudo cp  /usr/src/linux-source-4.10.0/.config  /boot/config-4.10.17
#给新内核文件添加 X 权限
sudo chmod  a+x  /boot/vmlinuz-4.10.17
sudo cp  /usr/src/linux-source-4.10.0/System.map  /boot/System.map-4.10.17
sudo gzip -c  /usr/src/linux-source-4.10.0/Module.symvers  > /boot/symvers-4.10.17(这个要最高权限)
##sudo passwd设置密码(第一次时)
##su
##获取最高权限

8 建立相对应的 Initial Ram Disk(initrd)

首先安装dracut:sudo apt install dracut

其次:sudo dracut  -v  /boot/initramfs-4.10.17.img  4.10.17

9 编辑开机选项(grub)

在/boot/grub下没有发现grub2,只能用grub

grub-mkconfig  -o  /boot/grub/grub.cfg

由此,就可以重新开机并选择新内核来启动系统啦!(启动时进入ubuntu界面按住shift,进入ubuntu高级选项,可以自由选择内核启动)

注意:在虚拟机上编译内核后重启会出现一个错误:

VBoxClient (seamless): failed to start. Stage: Setting guest IRQ filter mas Error: VERR_INTERNAL_ERROR

这个错误是关于VBOX的增强功能的,通过以下命令来解决:

参考:https://askubuntu.com/questions/985815/vboxclient-seamless-failed-to-start-stage-setting-guest-irq-filter-mask-err

sudo apt-get install gcc make perl
cd /media/$USER/VBox_GAs_5.2.22
sudo ./VBoxLinuxAdditions.run
sudo reboot

至此,我们完成了内核编译的整个过程。

二 内核模块的操作

上节内容讲解了内核、模块的编译,安装过程。事实上,有时候我们仅仅只是想安装一个模块,而不想重新编译内核,那么在内核需要的功能开启的情况下,单独编译模块是否可行呢?,回答是正确的,可以实现内核模块的编译过程。本节内容主要参考:http://www.noobyard.com/article/p-uukeezac-e.html

简单的hello world模块

1 hello.c文件

/*  
 *  hello.c - The simplest kernel module.
 */
#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */
int init_module(void)
{
    printk(KERN_INFO "Hello world 1.\n");
    /* 
     * A non 0 return means init_module failed; module can't be loaded. 
     */
    return 0;
}

void cleanup_module(void)
{
    printk(KERN_INFO "Goodbye world 1.\n");
}

2 Makefile文件

obj-m += hello.o

all:
    make -C /lib/modules/4.10.17/build M=$(PWD) modules

clean:
    make -C /lib/modules/4.10.17/build M=$(PWD) clean

注意三点:1 hello.o要和hello.c对应,2 Makefile中的目录是你安装模块时的模块安装目录,3 Makefile中make前面是一个TAB符。运行:make,结果如下

同时在目录里生成一些文件

其中hello.ko文件就是我们说的模块

我们对这个hello模块文件进行操作

1插入一个模块:

sudo insmod hello.ko

要是报错:
insmod: ERROR: could not insert module hello.ko: Invalid module format
可能原因:make时使用的内核版本和本系统的内核版本不一致

需要重新启动,选择内核来实现模块插入等操作

uname -r

运行插入模块的命令

2 查看模块的相关信息

sudo modinfo hello.ko

3 卸载模块

sudo rmmod hello.ko

4 lsmod 命令是查看当前已载入的模块

模块的基本介绍就到此结束,下节介绍对kasan功能的实现

三 对KASAN功能的实现

实际上,linux内核源码提供了关于测试kasan的模块,在内核编译中可以直接选择,但是作者本人太笨,一直不知道怎么搞,另外也想简单的学习下内核模块是怎么搞出来的,就没打开test_kasan模块,而是采用上一小节的方法尝试着将test_kasan.ko做出来。

在内核源码usr/src/linux-source-4.10.0/lib中可以找到test_kasan.c文件,之后,我们选择其中的一个函数进行测试就行,不过依然需要我们编写一个Makefile文件。

首先从test_kasan.c中抽出一个函数作为test.c文件

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/module.h>
int init_module(void)
{
	char *ptr;
	size_t size = 123;

	pr_info("out-of-bounds to right\n");
	ptr = kmalloc(size, GFP_KERNEL);
	if (!ptr) {
		pr_err("Allocation failed\n");
		return 0;
	}
	ptr[size] = 'x';
	kfree(ptr);
}
void cleanup_module(void)
{
    printk(KERN_INFO "GOODBYE\n");
}

其次,制作Makefile文件

obj-m += test.o
all:
	make -C /lib/modules/4.10.17/build M=$(PWD) modules
clean:
	make -C /lib/modules/4.10.17/build M=$(PWD) clean

之后,sudo make和 make 产生了不同的结果(先sudo make,后make)

最终还是生成了相关的文件

运行sudo insmod test.ko后,在运行 dmesg 查看相关信息

会在打印的内容中找到以下的字样:

或者,也可以在/usr/log/kern.log中找到相关的记录

由此来看,kasan是一个自动化的内核插桩工具,他不会给你任何的提示来说明是否打开了kasan,需要在对内核的相关过程中实现自动化的检测,要是不懂dmesg命令,还真不知道这个kasan怎么搞。KASAN藏的真够深的!!

想了解kasan原理的可以参考:https://www.cnblogs.com/alantu2018/p/8457420.html

四 总结

到此,我们实现了整个过程,从中学习到了内核的编译,模块的编译,kasan的相关操作。内核的编译很容易出错,要是在虚拟机中做,最好要学会时刻保存备份。模块的编译,我只看了最基础的那部分内容,其他的并没有深入,有兴趣的可以参考文中的网址。Kasan的操作是一个很有用的工具,能够自动化检测UAF漏洞。总之,经过这次学习,总算理解了为啥搞安全的都喜欢Linux,这个功能真的强大。

再次感谢文中的博客提供的宝贵资料!!