android-afl模糊测试android环境下的so文件
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、p7zip源代码下载以及ndk交叉编译
- 二、编写c程序以wrapper上述so
- 三、利用android-afl对目标so文件进行模糊测试
- 四、利用android-afl对可执行文件进行模糊测试
- 总结
前言
一切起源于想要实现对android so的模糊测试。两个备选方案,一个是frida,另一个是AFL。考虑到frida需要应用执行并出发到目标so接口,有点间接。因此,先尝试用AFL对android so文件进行模糊测试。
而AFL实现对android so的模糊测试有两种思路:一是在android环境下,进行afl模糊测试;二是在pc环境下通过模拟android环境实现afl对so的模糊测试。
本篇主要是为了实现思路一。具体描述了,在ubuntu环境下,通过ndk交叉编译生成适用于android环境下的so文件和c可执行程序,然后利用android-afl实现对so文件的fuzz测试。
一、p7zip源代码下载以及ndk交叉编译
参考论文black-box-fuzzing-android-native-libraries。在apk文件中存在用于压缩的so包libp7zip.so。
本篇以此为目标(因为参考论文就是以此为目标),从github上下载了相关apk文件和代码(https://github.com/hzy3774/AndroidP7zip)。下载解压缩,进入libp7zip/src/main/cpp/目录,通过分析p7zip.cpp文件可知,apk文件中java层调用的executeCommand方法是经过封装的。真正的源码只在libp7zip/src/main/cpp/p7zip目录中。
(需要说明,之所以不能直接将apk文件中的libp7zip.so包提取出来,是因为对于executeCommand方法而言,还存在JNIEnv
,不知道用C程序wrapper这个so时应该怎么处理,所以我这边选择直接编译源码来生成libp7zip.so,但事实是后面函数能调用起来,但是解压缩功能不可用)
接下来,对其源代码进行编译。
1.在libp7zip/src/main/cpp/p7zip目录中编写Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)SRC_SUFFIX := *.cpp *.c
ALL_FILES := $(shell find $(LOCAL_PATH) -type f)
SRC_FILES := $(filter $(subst *,%,$(SRC_SUFFIX)),$(ALL_FILES))
LOCAL_SRC_FILES := $(SRC_FILES:$(LOCAL_PATH)/%=%)#fPIC指地址无关代码LOCAL_MODULE := p7zip
LOCAL_CFLAGS := -fexceptions -fPIC
LOCAL_CPPFLAGS := -DANDROID_NDK -fexceptions \-DNDEBUG -D_REENTRANT -DENV_UNIX \-DEXTERNAL_CODECS \-DBREAK_HANDLER \-DUNICODE -D_UNICODE -DUNIX_USE_WIN_FILE -fPIC
#LOCAL_CFLAGS += -Wl,-Bsymbolic
LOCAL_LDFLAGS += -fPIE -pie -Wl,-BsymbolicALL_FILES_H := $(shell find $(LOCAL_PATH) -type d)
LOCAL_C_INCLUDES := $(ALL_FILES_H)#普通的so库
include $(BUILD_SHARED_LIBRARY)
#生成独立的可执行文件
#include $(BUILD_EXECUTABLE)
2.在编译过程中,曾出现错误如下:
1)error: cannot use ‘throw’ with exceptions disabled
解决办法是在LOCAL_CPPFLAGS 和LOCAL_CFLAGS 中都添加-fexceptions
2)error:requires unsupported dynamic reloc R_ARM_REL32; recompile with -fPIC;
解决办法是在LOCAL_CPPFLAGS 和LOCAL_CFLAGS 中都添加-fPIC,但是不能解决,最后通过设置LOCAL_LDFLAGS := -Wl,-Bsymbolic,实现成功编译,但是据说这个方案有隐患,目前未测试(参考https://android.googlesource.com/platform/frameworks/av/+/3b909164de79904137bb6661514d5ca6236a49c9/media/libstagefright/codecs/mp3dec/Android.mk)
3)error: case value evaluates to -2 , which cannot be narrowed
解决办法:更换ndk版本为16b,参考https://blog.csdn.net/shulianghan/article/details/116244700。
二、编写c程序以wrapper上述so
编写c语言程序来调用目标so中的方法,并将其编译为可执行程序。
harness.c代码如下(示例):
#include
#include
#include
#include typedef int (*FunctionPointerOne)(int, const char**);
typedef char* (*FunctionPointerTwo)(void);#define ARGV_LEN_MAX 512
#define ARGC_MAX 256
typedef enum
{true=1, false=0
}bool;
/*** get args from string*/
bool str2args(const char *s, char argv[][ARGV_LEN_MAX], int* argc) {bool in_token, in_container, escaped;bool ret;char container_start, c;int len, i;int index = 0;int arg_count = 0;ret = true;container_start = 0;in_token = false;in_container = false;escaped = false;len = strlen(s);for (i = 0; i < len; i++) {c = s[i];switch (c) {case ' ':case '\t':case '\n':if (!in_token)continue;if (in_container) {argv[arg_count][index++] = c;continue;}if (escaped) {escaped = false;argv[arg_count][index++] = c;continue;}_Z5Main2iPPc/* if reached here, we're at end of token */in_token = false;argv[arg_count++][index] = '\0';index = 0;break;/* handle quotes */case '\'':case '\"':if (escaped) {argv[arg_count][index++] = c;escaped = false;continue;}if (!in_token) {in_token = true;in_container = true;container_start = c;continue;}if (in_container) {if (c == container_start) { //container endin_container = false;in_token = false;argv[arg_count++][index] = '\0';index = 0;continue;} else { //not the same as contain start charargv[arg_count][index++] = c;continue;}}printf("Parse Error! Bad quotes\n");ret = false;break;case '\\':if (in_container && s[i + 1] != container_start) {argv[arg_count][index++] = c;continue;}if (escaped) {argv[arg_count][index++] = c;continue;}escaped = true;break;default: //normal charif (!in_token) {in_token = true;}argv[arg_count][index++] = c;if (i == len - 1) { //reach the endargv[arg_count++][index++] = '\0';}break;}}*argc = arg_count;if (in_container) {printf("Parse Error! Still in container\n");ret = false;}if (escaped) {printf("Parse Error! Unused escape (\\)\n");ret = false;}return ret;
}//模拟apk中executeCommand方法实现
int main(int argc, char *argv[])
{void *handle;//1.解析参数char* cmd = argv[1];int argc1 = 0;char temp[ARGC_MAX][ARGV_LEN_MAX];char *argv1[ARGC_MAX];if (!str2args(cmd, temp, &argc1)) {return 7;}printf("argc:%d\n", argc);printf("argc1:%d\n", argc1);for (int i = 0; i < argc1; i++) {argv1[i] = temp[i];printf("arg[%d]:[%s]\n", i, argv1[i]);}//2.获取可执行函数地址并进行调用handle = dlopen("/data/local/tmp/libp7zip.so", RTLD_NOW|RTLD_LOCAL);if(!handle){printf("errors:%s\n", dlerror());printf("error1!");return -1;}elseprintf("library is loaded!");int(*pfun)(int, const char**);printf("handle:%p\n", handle);pfun = dlsym(handle,"_Z5Main2iPPc");if(!pfun){printf("errors:%s\n", dlerror());printf("error2!");return -1;}elseprintf("function is found!\n");printf("pfun:%p\n", pfun);int result = pfun(argc1, argv1);printf("exec success!\n");//printf("result is: %s\n", result);dlclose(handle);return 0;
}
上述代码是以_Z5Main2iPPc方法为例,该方法名是反汇编上一步得到的libp7zip.so文件得到的。在github上下载的apk源码,在executeCommand方法中调用的main方法定义在CPP/p7zip/UI/Console/MainAr.cpp文件中。整个harness.c文件就是模拟executeCommand方法实现的。接下来编写对应的Android.mk文件即可。
Android.mk文件如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := harness
LOCAL_SRC_FILES :=harness.c
LOCAL_ARM_MODE :=arm
LOCAL_C_INCLUDES:=include
LOCAL_CFLAGS+= -pie -fPIE
LOCAL_LDFLAGS+= -pie -fPIE
include $(BUILD_EXECUTABLE)
接下来进入harness.c所在目录,通过命令编译:
ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_PLATFORM=android-19
最终即可得到可执行文件harness,然后将harness和libp7zip.so文件,一起push到手机的data/local/tmp目录下即可。
在android环境下执行命令:
/harness "7z x '/data/local/tmp/test.7z' '-o/storage/emulated/0/test.7z-ext' -aoa"
结果如下:
argc:2
argc1:5
arg[0]:[7z]
arg[1]:[x]
arg[2]:[/data/local/tmp/test.7z]
arg[3]:[-o/storage/emulated/0/test.7z-ext]
arg[4]:[-aoa]
library is loaded!handle:0xf460b44da7b790f9
function is found!
pfun:0x7793a1342c
Can't load './7z.dll' (dlopen failed gqdadd: library "./7z.so" not found)
exec success!
通过上述结果可知,通过wrapper so的方式能实现方法调用,但是可能由于自编译的libp7zip.so存在问题,导致方法执行没有成功,至于原因还没有分析。
三、利用android-afl对目标so文件进行模糊测试
android-afl下载和编译参考https://github.com/ele7enxxh/android-afl。
因为这个过程中涉及到android源码的下载和编译,在此不进行赘述,网上资源丰富。
编译完成之后,共生成了5个可执行文件:afl-analyze, afl-fuzz, afl-gotcpu, afl-tmin和afl-showmap。
将afl-fuzz push到data/local/tmp目录下,然后进行对之前放入的harness进行模糊测试即可。
由于前面harness执行时调用方法没有成功,所以这一步无法在自己编译成功的libp7zip.so中实现。
那么此时,又回到能否利用现有apk中的libp7zip.so,避开executeCommand方法(也就是避开JNIEnv对象),重新选择一个新的方法进行调用,而这个方法就是上述harness.c中的Main2方法。但事实证明此时Main2方法只剩下地址,在so中没有相应符号。
接下来,回到p7zip源码,在其p7zip_16.02/CPP/ANDROID目录中,有多个目录,可以编译多个工具,如7z,7zr等等。一开始选择7z目录,编译出android环境下的arm64架构so,但是编译出来通过wrapper之后调用会出错,出错内容为
Can't load './7z.dll' (dlopen failed gqdadd: library "./7z.so" not found)
接下来,重新选择7zr目录,编译出的arm64架构下的lib7zr.so。通过查看7z和7zr目录下Android.mk文件,发现其实两者编译的源代码文件差不多。在lib7zr.so中同样包含有Main2函数。
构造wrapper代码如第二部分所示,只需修改dlopen中参数为/data/local/tmp/lib7zr.so即可,编译生成harness可执行文件,push到/data/local/tmp目录即可。
在samples_in目录中包含有压缩文件7zrtest.7z,解压之后输出目录即为当前目录
执行fuzz命令如下:
./afl-fuzz -d -n -i samples_in/ -o afl_out/ ./harness "7zr x @@ -aoa"
四、利用android-afl对可执行文件进行模糊测试
在上述测试过程中,编译出来的so,通过wrapper之后并不能执行。因此退而求其次,按照源码中原有的Android.mk文件(p7zip_16.02/CPP/ANDROID目录下),编译出可执行文件。尝试选择子目录7z,以编译出7z可执行文件,但是编译之后放入手机环境,执行失败,与编译so并wrapper之后的可执行程序错误相同。
接下来,选择7zr子目录编译可执行文件,其中ndk-build版本为16
cd p7zip_16.02/CPP/ANDROID/7zr/jni
ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_PLATFORM=android-21
将可执行文件7zr(在当前目录的libs目录下)push进/data/local/tmp,将测试文件p7zip_16.02/check/test目录下的后缀为7z的文件也导入到/data/local/tmp/sample_in目录,然后执行模糊测试命令。包括的命令
./afl-fuzz -m 2G -d -n -i samples_in/ -o afl_out/ ./7zr x @@ -o/data/local/tmp -aoa
其中sample_in目录下包含有两个后缀为7z的压缩包,上述命令需要再其后添加-aoa,意思是设置为覆写模式。
总结
通过android-afl实现了在android环境中,对so进行fuzz的测试。不足之处:
1.一个可执行程序可能只能fuzz一个方法
2.执行环境在android环境下,不知道是否会受硬件资源限制
3.android-afl已经多年没有更新,目前流行的是afl++(https://github.com/AFLplusplus/AFLplusplus),而这个也是支持android环境运行的,后续应该是编译出afl++在android环境下的可执行文件。
在android下的fuzz效果还没有运行出来,不好评价。下一步,应该是考虑往pc环境迁移。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!