在IoT安全研究过程中,有时候我们出于调试或者增加设备功能的目的,需要将程序交叉编译上传到设备中,这个时候就需要构建交叉编译工具链。我曾经尝试过多种构造交叉编译工具链的方法,例如:
- 寻找对应架构的Debian虚拟机并使用qemu进行系统级仿真,然后像使用一个正常系统一样进行程序编译
- 使用buildroot编译交叉编译工具链
- 使用系统源安装相应的交叉编译工具链
在构造的过程中也踩过很多坑,可以列举一下踩过的比较典型的:
- 以mips架构有I、II、III型,网上的Debian虚拟机往往是高版本类型。高版本类型的好处是可以向下兼容,因此很多系统级的仿真可以直接chroot到设备根目录运行程序;但是对于交叉编译的坏处就是没法编译低版本架构的程序
- 高版本的buildroot中没有对低版本架构的配置
- 同样系统源安装的交叉编译工具链也是没有对低版本架构
- 在不同的设备上安装buildroot可能需要解决各种包依赖问题
这个时候就引出了我的一个需求:如何才能在一个新的工作环境中快速搭建起来一套交叉编译工具链,并且这个交叉编译工具链还能支持较老的低版本架构CPU。
第一个问题:我参考了Google内核fuzz工具syzkaller中的create-image.sh,这个脚本基于命令debootstrap,能够快速搭建指定发行版、架构、软件包的一个Debian文件系统镜像。我稍微修改此脚本,得到了一个能够支持某个指定版本的buildroot的文件系统,以后使用的时候直接chroot到根目录,就能愉快构造编译链了 + 交叉编译了。
第二个问题:我是选择了buildroot-2013.02-rc1作为长期使用的版本,这个版本足够老、支持足够多、足够旧的CPU架构。目前在我的日常使用中来看,已经够用了。
我在Github上创建了一个仓库,里面是创建文件系统和buildroot的脚本,以及静态编译好的适配各个架构的常用程序,后续持续更新。
GitHub - NoneShell/buildrooter
Debian wheezy文件系统搭建
克隆仓库,然后直接运行create-image.sh,会创建一个Debian wheezy的文件系统:
1 | git clone https://github.com/NoneShell/buildrooter.git |
创建完成后,进入chroot目录,这个就是一个Debian wheezy的文件系统,通过这种方式就可以保证所有的Linux都能以后续相同的操作,得到一个100%能成功编译的环境。
chroot到目录后,根据个人习惯可以做一些设置,此时已经和正常使用一个Debian系统没有区别了。我会设置好TERM和locales,因为我平常使用tmux,一些命令例如clear会报错tmux-256 colors;locales则是Busybox编译的时候会报错让至少使用一个UTF-8的locale。
1 | export TERM=xterm |
buildroot编译
1 | wget https://buildroot.org/downloads/buildroot-2013.02-rc1.tar.gz --no-check-certificate |
我使用的是tmux,例如使用命令clear的时候会报错:'tmux-256color': unknown terminal type.
,可以每次手动设置如下,我选择写入到了.bashrc中
1 | export TERM=xterm |
以最近在看的一个目标为例,使用file查看目标的架构如下,mipsel架构,且是version 1,SYSV。目标是编译出来能够在设备上运行的busybox、gdbserver和nc,自己编译的busybox可以使其支持更多的命令,gdbserver是调试必备,nc用来做端口转发、程序运行等。
首先是编译符合目标架构的交叉编译工具链,进入buildroot目录中,执行make menuconfig
,进入配置菜单。
如果仅仅是为了交叉编译工具链,不需要额外设置太多,选择好目标架构、架构版本、ABI是默认O32,然后在Toolchain中选择想要支持的特性即可。有一个地方一定要修改,就是Kernel Header选择Manually specified Linux version,并设置成3.7.1。默认设置是3.7.x,但是在实际make的时候下载不到相应的的Header文件。gdb和gdbserver我没有选择,因为一般是习惯将gdbserver放到设备上运行,然后在本机使用gdb连接进行调试。
编译交叉编译工具链,只需要执行make toolchain
即可,我之前看过很多网上的教程都是直接make,这样会同时编译出来rootfs和kernel,消耗大量时间。
1 | make toolchain |
编译完成的交叉编译工具链路径在/output/host/usr/bin目录中,可以将目录host重命名成host_arch
,例如host_LSB_MIPS_MIPS-I_SYSV
,这样下次编译新的架构的工具链时,make clean
不会删除掉已经编译好的架构的工具链。我的命名习惯是根据file查看的目标程序的信息来重命令文件夹,如下是此次演示的目标架构:
为了使用方便,可以把工具链的路径添加到PATH环境变量中,写入到.bashrc,后续编译程序的时候,指定CC/CXX时就不用输入完整的路径,日常使用的时候也可以在终端tab补全。
1 | export PATH=/root/buildroot-2013.02-rc1/output/host_LSB_MIPS_MIPS-I_SYSV/usr/bin:$PATH |
静态编译实例
当交叉编译工具链制作完成了,交叉编译自己的程序实际上和正常x86/64下的编译没有太大的区别,可能需要踩的坑就是ulibc中的一些系统库缺少需要自己编译并链接、编译版本太新的程序可能会有符号引用报错等,总之就是遇见坑GPT、Google。
busybox
buildroot自带的package中就有busybox,编译前先将目标架构的host_target目录复制一份为host目录,这样buildroot才能正确设置编译相关的参数。
在buildroot根目录使用命令make busybox-menuconfig
进行配置,进入busybox的配置菜单设置静态链接:
- Busybox Settings -> Build Options -> Build Busybox as a static binary,选择静态编译。
- CFLAGS和LDFLAGS都添加上-s,编译后strip掉符号,减少存储占用。
- 在Applets区域选择需要额外增加的功能/命令,例如我会额外勾选上网络相关的nc、ftpd、telnetd,进程相关的pstree,总之根据个人习惯和需求添加。
然后make busybox
即可编译出来静态链接、去除掉符号、增加了额外命令的Busybox,路径:/output/target/busybox
。
gdbserver
没有编译gdb有两个原因,第一个是因为平常调试的时候一般是将gdbserver复制到设备上,然后本机使用gdb remote进行调试;第二个原因是,gdb交叉编译太多坑了,我各种配置都没有成功,最后选择了放弃(shame on me)。
下载源码,我选择的版本是7.12,这个版本不需要额外编译termcap库而是使用了较新的ncurses库,这样可以减少额外的编译工作量。
1 | wget https://ftp.gnu.org/gnu/gdb/gdb-7.12.tar.gz --no-check-certificate |
编译完成如下:
小结
通过创建Debian wheezy文件系统 + chroot的方式,可以忽略宿主机的版本问题;buildroot采用较老版本,这样可以支持更多较旧的CPU型号,尤其是MIPS。
在编译复杂程序的时候,还是可能会踩坑,例如buildroot编译出来的工具链的系统库中没有程序编译依赖的库。总之,只是大概解决了交叉编译问题,实际上最好的解决方案是拿到官方的SDK(我在说什么???)。