IoT安全基本功系列——内核的编译与qemu启动

 

搞IoT安全绕不开的知识

笔者想要完整的整理linux系统从内核的编译,文件系统的制作,bootloader引导内核启动,最终至一个块设备,字符设备,网卡驱动的编写。做这件事的目的是,笔者发现对于IoT设备的固件模拟,在开机时获取root shell,以及驱动作为攻击面的漏洞挖掘等方面的工作,都绕不开这块内容。

 

为什么要写这个系列文章

本篇文章有可能是一个系列的开篇文章,市面上貌似对这块内容的文章资料总让我感觉是破碎不够系统的,而且实操性或者准确性上都有些问题,笔者虽能力有限,将尽力能够让新手读者看了本文就能够实践出来,本文也是笔者在实践过程中的实录,包括自己踩的坑,以及如何解决的这个的思路历程也记录了下来,所以本系列文章非常适合同学们实践参考。

 

系列文章的最终目的

本系列文章的最终目的是能够编写一个真正的驱动程序,而驱动程序的编写本文选择参考ldd3即 linux设备驱动程序这本书,由于这本书的内核比较老,现在的内核都已经不再支持书上的例程,如果拿着书上的例程去在当前内核下做实验的话,会出现很多莫名的坑,为了降低学习曲线,但又把一些基本linux启动等流程说清楚,尝试就在ldd3指定的内核linux 2.6.10版本上搭建学习开发环境。

 

本文的目标

由于上述的内容一篇文章是很难讲完,本文先讲第一部分,qemu上运行自己编译的linux 2.6.10内核。其具体包括:

1.构建编译ldd linx 2.6.10内核的环境

2.编译内核,构建文件系统

3.qemu运行系统

1. 构建编译ldd linx 2.6.10内核的环境

1.linx 2.6.10内核非常老,我建议使用ubuntu5.0的镜像 镜像地址:http://old-releases.ubuntu.com/releases/5.04/

2.ubuntu5.0也非常老,我直接使用vmware安装有问题,无法检测到cd-rom 使用老版本的支持workstation

选择workstation 5.x是可以安装的

3.后来字ubuntu5.0启动界面发现他使用的就是linux2.6.10

4.安装完毕

5.还需要安装gcc, gcc deb文件http://launchpadlibrarian.net/1299681/gcc-3.3_3.3.5-8ubuntu2_i386.deb , 使用dpkg -i安装这个deb文件,然后还需要在/usr/bin中创建 gcc的软链接,因为这个deb是一个gcc3.3命名的

2. 编译内核,构建文件系统

1.找到linux2.6.10内核代码

链接内核代码:https://mirrors.edge.kernel.org/pub/linux/kernel/v2.6/linux-2.6.10.tar.gz, 其大小为43.7MB, 可以尝试使用ftp从host宿主机上下载代码,因为安装老版本的vmtools比较麻烦。
make的时候要用make ARCH=i386 defconfig,因为menuconfig需要nurses那个库,安装这个库挺麻烦的对于老版本,遂决定放弃好用的menuconfig,毕竟我们的目的是编译成功。

2.initramfs根文件系统

  • 在挂载磁盘上的文件系统之前,需要先挂载一个initramfs文件系统,因为磁盘上的文件系统需要驱动,而驱动的加载也需要文件系统,这是一个鸡蛋问题,所以不能一开始就加载磁盘上的文件系统。 initramfs这个文件系统的加载不需要驱动,可以在这个文件系统里启动基本的挂载磁盘的驱动,initramfs是由bootload加载到内核中的。具体的,initramfs 就是一些文件的 cpio压缩包,由bootloader将其加载后供kernel使用,只要你往initramfs中添加的文件够多,那么你甚至不需要磁盘上的文件系统这就是为什么大家喜欢用busybox去创建initramfs,当加载driver的时候,喜欢把driver写入到initramfs中,所以以后再更新module的时候,不用重新编译内核,只需要编译一下module,然后把他放到内核中就行了。

3.使用busybox 构建initramfs根文件系统

同样的需要下载busybox的源码,我选择的是2004年的busybox-1.00.tar.bz2  ,并且需要在ubuntu5.0上编译,新版本的gcc都是不能用的。  
需要注意的是在由于不能使用menuconfig,只能使用config,要注意在后面可以选择作为一个static 编译
然后正常编译 make
make install得到这个文件目录

3. qemu运行系统

一开始参考的是这篇文章,https://consen.github.io/2018/01/17/debug-linux-kernel-with-qemu-and-gdb/

1.在搞这一块的遇到了很多问题,现在罗列

qemu是否是起到了bootloader的作用?
内核在和initrd 或者initramfs交互的时候是做了什么?
文件系统是如何被初始化的?
initrd和initramfs在本质上的区别?
真实设备的linux启动的流程是什么?
vmlinux与最终zimage的差别?
qemu运行一个vmlinux没有任何反应,不知道是不是我的vmlinux有问题,还是qemu不能启动vmlinux?
上面那个文档可以生成initramfs文件系统,但是却不能生成initrd, 这个busybox生成很少见
我用qemu启动的时候到底需要些什么?
dtb, dts 设备树又是啥?
ttys0是干啥的?

2.解决qemu运行vmlinux没有任何反应的问题

参考: https://freemandealer.github.io/2015/10/04/debug-kernel-with-qemu-2/

尝试用新的编译好的bzimage和vmlinux.bin,就在arch i386目录下的

结果新生成的bzimage和vmlinux 在qemu下运行同样的没有任何反应,运行的命令qemu-system-i386 -kernel ./bzImage -nographic

只有在运行bziamge的时候才会出现这个界面,而且还是要运行下面这命令 qemu-system-i386 -kernel ./bzImage -append "console=ttyS0"

从这个截图可以看出,qemu貌似是使用了seabios这个bootloader去加载内核的

还是卡在启动内核上,从这个图看出内核貌似根本没有任何输出的,我想还是用一个别人编译的例子

后来找到一个帖子说之所出现内核太老不支持ramfs,并不是因为2.6太老,而是因为使用的是vmlinux作为内核启动的,而不是使用的bzimage

直接使用vmlinux作为内核在qemu中启动的结果就是下面这样的

所以问题还是出现在为什么我编译的固件就一直卡在boot kernel上面

找到了一个qemu启动linux 2.6.32的老帖子 https://www.cnblogs.com/senix/archive/2013/02/21/2921221.html

我决定按照他的构建initrd的方法再来一遍,以确定只有可能是内核编译的问题

按照这个文档的方法是可以的没有问题的,但是还是报出错误

显示vfs unable to mount root fs on

这串输出都是内核的代码输出,我在内核的源代码中搜到了这句话

后来发现之前编译的initramfs.cpio.gz也是可以启动内核的,只不过因为我加了一句console=ttyS0,导致看不到输出,我觉着实际上是由输出的,我觉着console=ttyS0的意思就是把显示放到了串口,导致在当前这个console是看不到的,显然在很多串口的地方就是把console给定位到了ttyS0,而由于我的qemu没有这个串口,所以应该就不需要设置这个玩意

目前看来还是这个文档最好用,我是通过搜linux 2.6和qemu关键词搜到的,虽然是很久之前的文章,但是真的还挺有用的,起码这个构建文件系统的是可以的

4.现在的问题是解决为何识别不了这个initrd或者叫做initramfs的东西

目前已经发现内核的启动是很随意的,不要文件系统什么东西的,直接 qemu-system-i386 -kernel bzImage 就可以启动

由上面这个无法识别root device ram可以知道,这个文件系统应该是没有起到效果,或者是dev没启动起来

有可能是编译内核的时候没有启用支持ramdisk,根据这篇链接google 搜索cannot open root device “ram”

The most obvious question is whether or not the kernel you built has
ramdisk support


        $ grep BLK_DEV_RAM .config
        CONFIG_BLK_DEV_RAM=y
        CONFIG_BLK_DEV_RAM_COUNT=16
        CONFIG_BLK_DEV_RAM_SIZE=4096

我去看了一下发现我的linx 2.6.10版本并没有启用这个dev/ram这个设备,我的一开始并没有设置BLK_DEV_RAM,后来我才加上去了

重新编译发现需要的事件很短,看来是之前的已经编译好了,只需要编译新的东西就行了,这就是增量编译吧,重新放入新的bzimage试一下已经发现不再报ram的错误,但是开始报UDF-fs no partition found错误, 而且发现defconfig的UDF-FS也是已经配置的,禁用之后,还是报无法识别block的错误,确定就是没有成功加载提供的initrd文件系统,现在越来越觉着就是cpio这种形式的initrd, 内核是不支持的

initRamFS is a (compressed) CPIO archive, InitRD is a (compressed) ext2/3/4/jfs/xfs/whatever filesystem

参考自https://lists.nongnu.org/archive/html/qemu-devel/2013-10/msg01445.html

5.尝试编译新的内核,完全参照老帖子

参考 https://www.cnblogs.com/senix/archive/2013/02/21/2921221.html

首先下载 linux-2.6.32.60.tar.bz2 链接

用适配性高的内核启动我编译的各种文件系统,观察是否能起来

rootfs.img.gz这个是按照教程生成的gz格式的文件系统,是可以起来的

rootfs.img 这个是没有压缩的文件系统,也是可以起来的

initd.gz 和 initrd都没起来,这个是仿照 https://www.cnblogs.com/shineshqw/articles/2336842.html, 但是新内核的输出貌似是试了ext3 vfat msdos iso9660这几种文件系统,但是都没有成功,而我制作的是ext2的,所以我觉着可以用ext3尝试一下

6.尝试各种创建initrd的办法

参照官方的老帖子,32.60版本的内核可以成功启动,loop device https://web.archive.org/web/20150402090928/https://www.kernel.org/doc/Documentation/initrd.txt

7.最终解决了问题

发现是linux 2.6.10 缺少了对initrd的支持,并不是那个ram_dev设置就支持了,还有一个CONFIG_BLK_DEV_INITRD, 这个在i386defconfig默认配置中是根本没有的,需要自己手动加上
CONFIG_BLK_DEV_INITRD=y

 

结语

本文是笔者在尝试在qemu上运行linux 2.6.10版本内核的实录包括自己在时间时候遇到的各种问题,最终完整编译linux 2.6.10内核,并且通过很多尝试最终发现i386的默认配置是缺少initrd支持的问题的,虽然不是按部就班的给出最优解,但是详细的叙述解决过程,可能对大家更有参考性。

(完)