用忽略configure的方式 交叉编译 静态链接 的 tcpdump

在开源软件的编译过程中,一般都是先使用configure检查编译环境并生成makefile,然后使用make进行编译。但是对于IoT安全研究来说,经常要遇到两个坎,一个是交叉编译,一个是静态链接。如果是单个c代码文件,类似shellcode或者后门,使用相应的交叉编译工具,在加上 -static 参数直接编译就好了,可是对于一个有configure以及makefile的软件,我们该怎么跨过这两个坎呢?一般的交叉编译都是在configure处做一系列的配置,这个配置虽然方便,但却令人困惑,我们配置的那些变量到底在哪生效的呢?如:交叉编译+静态编译。不过,最终编译还是makefile的事,所以其实可以在某些比较简单的情景下,直接忽略configure。由于make的命令行参数优先于makefile文件中的变量,所以可直接在make命令后加相应的参数,进而完成交叉编译和静态链接。本篇采用这种奇怪的方法,编译出5种架构(x86_64,arm,aarch64,mips,mipsel)下的静态链接的tcpdump程序。

环境准备

纯净的ubuntu20.04,使用tuna换源,并安装好一些基础的工具,编译tcpdump需要的依赖软件,以及交叉编译工具链:

$ sudo apt install -y vim 
$ sudo vim /etc/apt/sources.list
$ sudo apt update
$ sudo apt install -y  gcc flex bison make
$ sudo apt install -y  gcc-aarch64-linux-gnu gcc-arm-linux-gnueabi gcc-mipsel-linux-gnu gcc-mips-linux-gnu

交叉编译工具链可以按照如下方法搜索:

$ sudo apt search arm | grep gcc 
$ sudo apt search mip | grep gcc 

下载源码

tcpdump官网:https://www.tcpdump.org/

$ wget https://www.tcpdump.org/release/tcpdump-4.99.1.tar.gz
$ wget https://www.tcpdump.org/release/libpcap-1.10.1.tar.gz
$ tar -xvzf ./tcpdump-4.99.1.tar.gz
$ tar -xvzf ./libpcap-1.10.1.tar.gz

本地编译

首先本地编译出x86_64版本的,一个是为了生成正常的makefile,另外也是最开始的排错,如果本地编译都错了,就也先别交叉编译了。

编译libpcap

$ cd libpcap-1.10.1/
$ ./configure 
$ make
$ sudo make install
$ ls /usr/local/lib
libpcap.a  libpcap.so  libpcap.so.1  libpcap.so.1.10.1  pkgconfig  python3.8

最后的路径/usr/local/lib可以在makefile中看到:

prefix = /usr/local

如果我们在./configure时指明参数--prefix,最终就会落实到makefile这个变量中,所以也可以在make时直接使用参数prefix=/XXX完成最终install路径的更换:

$ make prefix=./test install
$ ls -al ./test/
total 32
drwxrwxr-x  6 xuanxuan xuanxuan  4096 Aug 15 12:03 .
drwxr-xr-x 14 xuanxuan xuanxuan 12288 Aug 15 12:03 ..
drwxr-xr-x  2 xuanxuan xuanxuan  4096 Aug 15 12:03 bin
drwxr-xr-x  3 xuanxuan xuanxuan  4096 Aug 15 12:03 include
drwxr-xr-x  3 xuanxuan xuanxuan  4096 Aug 15 12:03 lib
drwxrwxr-x  3 xuanxuan xuanxuan  4096 Aug 15 12:03 share

编译tcpdump

正常install完libpcap/usr/local后,tcpdump的./configure即可顺利生成makefile,然后直接make完成编译:

$ cd tcpdump-4.99.1/
$ ./configure 
$ make
$ file ./tcpdump
./tcpdump: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked

如果想编译一个静态链接的tcpdump可以在make时直接加入LDFLAGS='-static'参数:

$ make clean
$ make LDFLAGS='-static'
$ file ./tcpdump
./tcpdump: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked,

测试运行成功:

$ cp ./tcpdump ../tcpdump-x86_64
$ sudo ../tcpdump-x86_64
tcpdump-x86_64: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

这个LDFLAGS='-static'背后的道理必定在makefile中:

PROG = tcpdump

all: $(PROG)

$(PROG): $(OBJ) ../libpcap-1.10.1/libpcap.a $(LIBNETDISSECT)
        @rm -f $@
        $(CC) $(FULL_CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LIBNETDISSECT) $(LIBS)

所以其实不和其他编译代码冲突的话,改FULL_CFLAGS甚至CC也对可以,反正就是拼接:

$ make CC='gcc -static'
$ file ./tcpdump
./tcpdump: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked

交叉编译

因为我们之前本地编译以及成功了,所以makefile都已经生成好了,所以直接复用并修改参数即可

编译libpcap

在编译libpcap时要注意,这里make install的背后,并不是直接复制拷贝到目标目录,而是也存在着编译:

DYEXT = so

install: install-shared install-archive libpcap.pc pcap-config
        [ -d $(DESTDIR)$(libdir) ] || \
            (mkdir -p $(DESTDIR)$(libdir); chmod 755 $(DESTDIR)$(libdir))
        [ -d $(DESTDIR)$(includedir) ] || \
            (mkdir -p $(DESTDIR)$(includedir); chmod 755 $(DESTDIR)$(includedir))
        [ -d $(DESTDIR)$(includedir)/pcap ] || \


install-shared: install-shared-$(DYEXT)

install-shared-so: libpcap.so

libpcap.so: $(OBJ)
        @rm -f $@
        VER=`cat $(srcdir)/VERSION`; \
        MAJOR_VER=`sed 's/\([0-9][0-9]*\)\..*/\1/' $(srcdir)/VERSION`; \
        $(CC) $(LDFLAGS) -shared -Wl,-soname,$@.$$MAJOR_VER \
            -o $@.$$VER $(OBJ) $(ADDLOBJS) $(LIBS)

所以在make install时一样要替换CC变量:

这里我将编译出的libpcap库放在了:/home/xuanxuan/Desktop/tcpdump/libpcap/

$ cd libpcap-1.10.1/
$ make clean

$ make CC=arm-linux-gnueabi-gcc
$ make CC=arm-linux-gnueabi-gcc prefix=/home/xuanxuan/Desktop/tcpdump/libpcap/arm install
$ make clean

$ make CC=aarch64-linux-gnu-gcc
$ make CC=aarch64-linux-gnu-gcc prefix=/home/xuanxuan/Desktop/tcpdump/libpcap/aarch64 install
$ make clean

$ make CC=mips-linux-gnu-gcc
$ make CC=mips-linux-gnu-gcc prefix=/home/xuanxuan/Desktop/tcpdump/libpcap/mips install
$ make clean

$ make CC=mipsel-linux-gnu-gcc
$ make CC=mipsel-linux-gnu-gcc prefix=/home/xuanxuan/Desktop/tcpdump/libpcap/mipsel install
$ make clean

编译好后我们可以挨个看看指令集,均成功编译出对应架构的库:

$ cd ../libpcap

$ file ./arm/lib/libpcap.so.1.10.1 
./arm/lib/libpcap.so.1.10.1: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked,

$ file ./aarch64/lib/libpcap.so.1.10.1
./aarch64/lib/libpcap.so.1.10.1: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked

$ file ./mips/lib/libpcap.so.1.10.1
./mips/lib/libpcap.so.1.10.1: ELF 32-bit MSB shared object, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked

$ file ./mipsel/lib/libpcap.so.1.10.1
./mipsel/lib/libpcap.so.1.10.1: ELF 32-bit LSB shared object, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked

编译tcpdump

编译tcpdump时,需要libpcap库,具体来说就是:

  • 原理:编译时,静态链接需要.a的库,动态链接需要.so的库,二者都需要.h头文件。
  • 编译:在gcc中,额外的头文件使用-I参数,库代码使用-L参数。
  • 变量:在makefile的变量中,编译参数一般是CFLAGS,链接参数一般是LDFLAGS,编译器本身是CC

以arm为例,为完成交叉编译,make需要以下三个参数:

  • CC=arm-linux-gnueabi-gcc
  • CFLAGS='-I/home/xuanxuan/Desktop/tcpdump/libpcap/arm/include'
  • LDFLAGS='-L/home/xuanxuan/Desktop/tcpdump/libpcap/arm/lib/ -static'

最后编译:

$ cd tcpdump-4.99.1/
$ make clean

$ make CC=arm-linux-gnueabi-gcc CFLAGS='-I/home/xuanxuan/Desktop/tcpdump/libpcap/arm/include' LDFLAGS='-L/home/xuanxuan/Desktop/tcpdump/libpcap/arm/lib/ -static' 
$ file ./tcpdump
./tcpdump: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=2f4af2d4b19f3abff33868d363b4f51530245239, for GNU/Linux 3.2.0, with debug_info, not stripped
$ cp ./tcpdump ../tcpdump-arm
$ make clean

$ make CC=aarch64-linux-gnu-gcc CFLAGS='-I/home/xuanxuan/Desktop/tcpdump/libpcap/aarch64/include' LDFLAGS='-L/home/xuanxuan/Desktop/tcpdump/libpcap/aarch64/lib/ -static' 
$ file ./tcpdump
./tcpdump: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=5b833dabb3175ab4cc0578d0d1ee534924f1201b, for GNU/Linux 3.7.0, with debug_info, not stripped
$ cp ./tcpdump ../tcpdump-aarch64
$ make clean

$ make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/xuanxuan/Desktop/tcpdump/libpcap/mipsel/include' LDFLAGS='-L/home/xuanxuan/Desktop/tcpdump/libpcap/mipsel/lib/ -static' 
$ file ./tcpdump
./tcpdump: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, BuildID[sha1]=4f5688b096e43e24ef45f8f84590278ebc421c1d, for GNU/Linux 3.2.0, with debug_info, not stripped
$ cp ./tcpdump ../tcpdump-mipsel
$ make clean

$ make CC=mips-linux-gnu-gcc CFLAGS='-I/home/xuanxuan/Desktop/tcpdump/libpcap/mips/include' LDFLAGS='-L/home/xuanxuan/Desktop/tcpdump/libpcap/mips/lib/ -static' 
$ file ./tcpdump
./tcpdump: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, BuildID[sha1]=4f5688b096e43e24ef45f8f84590278ebc421c1d, for GNU/Linux 3.2.0, with debug_info, not stripped
$ cp ./tcpdump ../tcpdump-mips
$ make clean

使用qemu测试,均可运行:

$ cd ..
$ sudo apt install qemu-user
$ qemu-aarch64 ./tcpdump-aarch64 
Unknown host QEMU_IFLA type: 54
Unknown host QEMU_IFLA type: 32820
Unsupported ioctl: cmd=0x8946
tcpdump-aarch64: ens33: SIOCETHTOOL(ETHTOOL_GLINK) ioctl failed: Function not implemented
$ qemu-arm ./tcpdump-arm 
Unknown host QEMU_IFLA type: 54
Unknown host QEMU_IFLA type: 32820
Unsupported ioctl: cmd=0x8946
tcpdump-arm: ens33: SIOCETHTOOL(ETHTOOL_GLINK) ioctl failed: Function not implemented
$ qemu-mips ./tcpdump-mips
Unknown host QEMU_IFLA type: 54
Unknown host QEMU_IFLA type: 32820
Unsupported ioctl: cmd=0x8946
tcpdump-mips: ens33: SIOCETHTOOL(ETHTOOL_GLINK) ioctl failed: Function not implemented
$ qemu-mipsel  ./tcpdump-mipsel 
Unknown host QEMU_IFLA type: 54
Unknown host QEMU_IFLA type: 32820
Unsupported ioctl: cmd=0x8946
tcpdump-mipsel: ens33: SIOCETHTOOL(ETHTOOL_GLINK) ioctl failed: Function not implemented