MAC下编译arm架构的tcpdump
最近小伙伴遇到一个问题,就是在阅读《Android软件安全》这本书的时候,学习使用Android平台下的tcpdump抓包时出现了问题。因为原文编写时Android尚处于4.x时代,那个时候Android对于可执行程序没有进行PIE编译选项的强制要求,而现在Android最低都是6.0的版本,因此在使用书中的tcpdump可执行程序的时候报错了:
error: only position independent executables (PIE) are supported.
小伙伴作为Android软件安全新手,对这种错误来说可以说是一脸懵逼的。后来我帮他解决了这个问题,在这里做个记录。
PIE即位置无关的可执行程序,在两次执行过程中其映射到内存中的位置是不固定的,这是C、C++编译器对防止内存攻击提供的一个安全编译选项。Android 5.0之前是支持非PIE编译的可执行程序执行的,但是5.0之后(包括5.0)强制用户执行的程序中在编译时必须加入PIE选项。具体地,如果使用gcc作为编译工具的话,需要加入-fPIE -pie这两个参数即可。
在进行NDK开发时,我们常常将Android SDK提供的NDK开发套件目录加入到环境变量中。当具体编译Native程序时,都是事先编辑好一个Android.mk和Application.mk文件,然后使用NDK套件提供的ndk-build脚本工具进行编译。例如,编译一个helloword程序的时候,我们事先建立好一个名称为jni的文件夹,然后把helloworld.c文件,Android.mk和Application.mk文件放在里面,再在命令行里面输入ndk-build,此时在外层的libs目录中就能生成对应的可执行程序。一般而言,一个简单的Android.mk文件中的内容大致是这样的:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := helloworldLOCAL_CFLAGS += -fPIE -pieLOCAL_SRC_FILES := helloworld.cinclude $(BUILD_EXECUTABLE)
这是一个简易的makefile文件,ndk-build程序通过封装好的接口,类似于Java的继承和接口一样,对其进行引用,设置编译选项,最后进行编译。其实归根到底它还是调用的NDK中的交叉编译工具对代码进行编译。
所谓交叉编译,通俗地理解就是在宿主操作系统上(本机)编译其他操作系统环境上能够执行的程序
然而我在具体编译的时候并不喜欢这种方式,一来是封装后东西虽然美好,但是往往让人忽略了底层的细节。二是因为这种方式十分不灵活,对于本例来说,编译一个具有configure脚本或者本身就带有makefile文件的的源码程序来说,就显得十分另类了。因此一般情况下我喜欢使用工具链(toolchains)的形式进行交叉编译。
工具链是一系列用于制作软件的工具。这些工具一般一个接一个地运用,一件工具的输出输入至下一件工具,因此得名。交叉编译的工具链就是指可以编译目标系统中的编译工具、调试工具、分析工具、可执行库和头文件等一系列工具的集合。
Android NDK Bundle中提供了构建我们自己的工具链的工具。在ndk-bundle/build/tools
目录中,名称为make-standalone-toolchain.sh
,使用它可以很轻松地构建一个针对特定目标Android系统版本的交叉编译工具链。例如,我想在本机/Users/hellokitty/ndk_toolchains/android_6.0
目录中构建一个Android 6.0交叉编译的工具链,使用如下命令即可:
./make-standalone-toolchain.sh --arch=arm --platform=android-23 --verbose --install-dir=/Users/hellokitty/ndk_toolchains/android_6.0
如上图,工具链中最重要的目录有两个,一个是sysroot
目录,它模拟了目标文件系统的编译环境。如头文件、库文件等。还有一个是bin
目录,这里面就是各类工具的集合了,如C编译工具gcc、C++编译工具g++,还有一些连接器、符号查看器、调试工具等等。下面我们来在我本机Mac系统上编译Android 6.0支持的tcpdump可执行程序。
根据官方文档,tcpdump需要pcap的支持,因此需要先下载编译pcap。两个程序的下载地址:
libpcap
tcpdump
解压libpcap后就可以开始编译了。
export TOOLCHAIN_ROOT=/Users/hellokitty/ndk_toolchains/android_6.0CFLAGS="-fPIE -pie" CC=$TOOLCHAIN_ROOT/bin/arm-linux-androideabi-gcc ./configure --host=arm-linux --with-pcap=linux --prefix=$TOOLCHAIN_ROOT/sysroot/usr --includedir=$TOOLCHAIN_ROOT/sysroot/usr/includemake && make install
第一条命令设置了一个环境变量TOOLCHAIN_ROOT,它的值是我们刚才新建的工具链的路径。
第二条命令分为两个部分,./configure
前面的部分是通过命令行传递给confgure脚本的环境变量,CFLAGS
表示的是编译器的编译选项,CC
表示的是使用哪个编译工具。后面的就是具体的配置参数,prefix
参数告诉configure最后编译好的结果放在工具链目录的sysroot/usr下面,includedir
参数告诉配置工具如果编译时需要用到一些头文件可以在后面的路径中查找。
第三条命令就是具体的编译和安装选项。由于pcap是一个库文件,因此最后生成的是pcap的静态库(.a文件)和动态库(.so文件),以及其对应的软连接。它们放置在编译目标路径的lib文件夹中。
然后使用类似的命令编译tcpdump:
CC=$TOOLCHAIN_ROOT/bin/arm-linux-androideabi-gcc CFALGS="-fPIE -pie" LDFLAGS=-L$TOOLCHAIN_ROOT/sysroot/usr/lib ./configure --host=arm-linux-androideabi --includedir=$TOOLCHAIN_ROOT/sysroot/usr/include --prefix=$TOOLCHAIN_ROOT/sysroot/usr/local/tcpdumpmake CFLAGS="-fPIE -pie -I$TOOLCHAIN_ROOT/usr/include"make install
需要注意的是这里configure脚本貌似出了点问题,CFLAGS环境变量传递不进去,所以只能在make的时候再次设置CFLAGS=-fPIE -pie -I$TOOLCHAIN_ROOT/usr/include
。
编译完成后,得到下面的文件:
把得到的tcpdump文件push到Android 6.0的设备中,发现是动态链接的可执行文件,需要把前面编译好的libpcap.so.1.8.1放到设备的/system/lib目录下,然后在同目录下创建两个软连接文件libpcap.so和libpcap.so.1,这样就可以使用了。
静态编译
上面编译后的文件是动态链接的文件,使用时需要把那需要动态链接的so放到系统的lib目录下,相当麻烦。如果使用静态链接的编译选项,那么就可以避免这个问题。然而,这里碰到了不少坑,继续做个记录。
首先使用工具链中的ranlib工具对libpcap.a
静态库的符号索引表进行更新。
$TOOLCHAIN_ROOT/bin/arm-linux-androideabi-gcc-ranlib libpcap.a
然后在编译tcpdump时第二步变为
make CFLAGS="-fPIE -pie -I$TOOLCHAIN_ROOT/usr/include -static"
也就是加入了一个-static参数,这样编译器在编译时会在-L指定的目录中默认寻找.a后缀的静态库文件进行静态链接。然而貌似是Android SDK的NDK Bundle的问题,或者是API>=23时libc提供的几个符号有所变化,虽然理论上讲这样做应该是没啥问题的,但是实际编译的时候却报错:
tcpdump.o:tcpdump.c:function error: error: undefined reference to 'stderr'……collect2: error: ld returned 1 exit status
时间太晚了,这个问题留待以后解决。如果有大牛能够帮忙指出问题所在,小弟不胜感激。
更多相关文章
- Myeclipse添加android开发插件
- android代码混淆
- 在Ubuntu 9.10下编译Android源码
- android webview自定义标签!(实现打电话的功能);
- 通过修改hosts文件成功更新Android(安卓)sdk .
- Android(安卓)-- 多线程下载
- Android(安卓)Gradle编译学习日记之二(使用 Gradle 编译 Eclipse,
- Android(安卓)make脚本简记
- NPM 和webpack 的基础使用