Android Native Fuzz Demo

Background

TrapFuzz的思路Fuzzing Android native库,这就是个简单的Demo,只针对黑盒的库。

honggfuzz on Android

  • 设置好NDK路径
  • brew install automake

构建所有的arch(arm64-v8a, armeabi, armeabi-v7a, x86, x86_64)

1
make android-all

坑1: libunwind编译的各种问题:

image-20220824225631169

macos不好使,换linux去编译,然后用ndk r20.

image-20220824225705237

传到手机上试试看:

image-20220824225722034

然后就是写个demo,在手机上跑一下看看情况

hfuzz-cc is missing on android build · Issue #341 · google/honggfuzz

No coverage information on android · Issue #342 · google/honggfuzz

参数 fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls

这个参数的话会有警告信息,应该是clang 参数的问题。

后面参考了谷歌的文档,替换了参数,结果没警告了,但是cov还是0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# muhe @ muhe-Parallels-Virtual-Platform in ~/ndk_fuzzing_demo [14:51:59] 
$ cat Android.mk
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_PATH = .
LOCAL_MODULE := hfuzz
LOCAL_EXPORT_C_INCLUDES := $HOME/honggfuzz/includes
LOCAL_SRC_FILES := /home/muhe/honggfuzz/libs/arm64-v8a/libhfuzz.a
LOCAL_ARM_MODE := arm
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_PATH = .
LOCAL_MODULE := hfuzzcommon
LOCAL_EXPORT_C_INCLUDES := $HOME/honggfuzz/includes
LOCAL_SRC_FILES := /home/muhe/honggfuzz/obj/local/arm64-v8a/libcommon.a
LOCAL_ARM_MODE := arm
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_STATIC_LIBRARIES := hfuzz hfuzzcommon
LOCAL_SRC_FILES := fuzz_test.c
LOCAL_MODULE := fuzz_test
LOCAL_ARM_MODE := arm

include $(BUILD_EXECUTABLE)

# muhe @ muhe-Parallels-Virtual-Platform in ~/ndk_fuzzing_demo [14:52:01]
$ cat Application.mk
APP_BUILD_SCRIPT := ./Android.mk
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address

# muhe @ muhe-Parallels-Virtual-Platform in ~/ndk_fuzzing_demo [14:52:03]
$

image-20220824225744530


退回到honggfuzz 2.2 然后用最开始 #342 那个issue的编译参数是可以的

image-20220824225756287

完整项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# muhe @ muhe-Parallels-Virtual-Platform in ~/ndk_fuzzing_demo [15:53:52] C:130
$ cat Android.mk
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_PATH = .
LOCAL_MODULE := hfuzz
LOCAL_EXPORT_C_INCLUDES := $HOME/honggfuzz/includes
LOCAL_SRC_FILES := /home/muhe/honggfuzz/libs/arm64-v8a/libhfuzz.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_PATH = .
LOCAL_MODULE := hfuzzcommon
LOCAL_EXPORT_C_INCLUDES := $HOME/honggfuzz/includes
LOCAL_SRC_FILES := /home/muhe/honggfuzz/obj/local/arm64-v8a/libcommon.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_STATIC_LIBRARIES := hfuzz hfuzzcommon
LOCAL_SRC_FILES := fuzz_test.c
LOCAL_MODULE := fuzz_test
LOCAL_CFLAGS := -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls
LOCAL_LD_FLAGS := -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls

include $(BUILD_EXECUTABLE)

# muhe @ muhe-Parallels-Virtual-Platform in ~/ndk_fuzzing_demo [15:53:53]
$ cat Application.mk
APP_BUILD_SCRIPT := ./Android.mk
#APP_STL := c++_shared # Or system, or none.
#APP_CFLAGS := -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls
#APP_LDFLAGS := -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls

# muhe @ muhe-Parallels-Virtual-Platform in ~/ndk_fuzzing_demo [15:53:55]
$ cat fuzz_test.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int test_target(char* input_file_path, char* argv_0)
{
char *crash = NULL;
FILE *fp = fopen(input_file_path, "rb");
char c;
if (!fp) {
printf("Error opening file\\n");
return 0;
}
if (fread(&c, 1, 1, fp) != 1) {
printf("Error reading file\\n");
fclose(fp);
return 0;
}
if (c != 't') {
printf("Error 1\\n");
fclose(fp);
return 0;
}
if (fread(&c, 1, 1, fp) != 1) {
printf("Error reading file\\n");
fclose(fp);
return 0;
}
if (c != 'e') {
printf("Error 2\\n");
fclose(fp);
return 0;
}
if (fread(&c, 1, 1, fp) != 1) {
printf("Error reading file\\n");
fclose(fp);
return 0;
}
if (c != 's') {
printf("Error 3\\n");
fclose(fp);
return 0;
}
if (fread(&c, 1, 1, fp) != 1) {
printf("Error reading file\\n");
fclose(fp);
return 0;
}
if (c != 't') {
printf("Error 4\\n");
fclose(fp);
return 0;
}
printf("!!!!!!!!!!OK!!!!!!!!!!\\n");

if (fread(&c, 1, 1, fp) != 1) {
printf("Error reading file\\n");
fclose(fp);
return 0;
}
if (c == '1') {
// cause a crash
crash[0] = 1;
}
else if (c == '2') {
char buffer[5] = { 0 };
// stack-based overflow to trigger the GS cookie corruption
for (int i = 0; i < 5; ++i)
strcat(buffer, argv_0);
printf("buffer: %s\\n", buffer);
}
else {
printf("Error 5\\n");
}
fclose(fp);
return 0;
}

int main(int argc, char** argv)
{
if(argc < 2) {
printf("Usage: %s <input file>\\n", argv[0]);
return 0;
}

//regular single target call
return test_target(argv[1], argv[0]);
}

ndk构建命令:

1
ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk TARGET_ARCH_ABI=arm64-v8a

write harness for .so

使用native-harness-target

参考项目 : https://github.com/CalebFenton/native-harness-target

Android 7.1.1

image-20220824225812164

可以使用这个方式跑起来,不过速度及其的慢。

TODO :

  1. 速度问题,考虑docker Android或者qemu-kvm
  2. port 到 Android 10

Android 10 :

rednaga/native-shim

1
2
3
4
5
6
7
8
9
10
11
12
export LD_LIBRARY_PATH=`pwd`:/apex/com.android.runtime/lib::$LD_LIBRARY_PATH

export LD_LIBRARY_PATH=`pwd`:/apex/com.android.runtime/lib64:$LD_LIBRARY_PATH
255|walleye:/data/local/tmp # ./shim libstr-crypt.so
[*] native-shim - diff
[+] Attempting to load : [ libstr-crypt.so ]
[+] Library Loaded!
[+] Initializing JavaVM Instance
[+] Initialization success (vm=0x74eb6901c0, env=0x74eb6e06c0)
[+] Found JNI_OnLoad, attempting to call
[+] Closing library
walleye:/data/local/tmp #

libpl_droidsonroids_gif.so测试

image-20220824225828132

work with honggfuzz

经典的 patch 跳转指令,实现一个debugger来获取覆盖率的方案

  • 使用之前 patch跳转的方式搞覆盖率,修改honggfuzz即可

    问题 : 创建JVM相关的操作耗时,影响效率

  • 为了解决效率问题,如果可以自己写一个,初始化之后,fork,然后疯狂搞fork出来的子进程即可,这样效率就上去了。

get all JUMP INS

获取patch需要patch的指令地址,直接从p0tools里抄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import idautils
import idaapi
import ida_nalt
import idc

# See <https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml>

from os.path import expanduser
home = expanduser("~")

patchpoints = set()

max_offset = 0
for seg_ea in idautils.Segments():
name = idc.get_segm_name(seg_ea)
if name != ".text":
continue

start = idc.get_segm_start(seg_ea)
end = idc.get_segm_end(seg_ea)
print(hex(start), hex(end))

for func_ea in idautils.Functions(start, end):
f = idaapi.get_func(func_ea)
if not f:
continue
for block in idaapi.FlowChart(f):
if start <= block.start_ea < end:
max_offset = max(max_offset, block.start_ea)
patchpoints.add(block.start_ea)
else:
print("Warning, broken CFG?")

# Round up max_offset to page size
size = max_offset
rem = size % 0x1000
if rem != 0:
size += 0x1000 - rem

with open(home + "/Desktop/patches.txt", "w") as f:
f.write(ida_nalt.get_root_filename() + ':' + hex(size) + '\\n')
f.write('\\n'.join(map(hex, sorted(patchpoints))))
f.write('\\n')

print("Done, found {} patchpoints".format(len(patchpoints)))

image-20220824230049472

Patch or hook INS

问题 : 需要想办法做到 hfuzzcc一样的效果,即 把 libhfuzz.a 链接进目标binary,不然没有桩信息。

  • 看看hfuzzcc是怎么工作的

看起来就是一层wrapper,给clang/gcc编译的时候增加了 CFLAGSLDFLAGS,看起来只需要按照需求把参数放到 Android.mk即可。

这里参考ImageIO例子中的编译的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cc
-I/Users/vuln_test/honggfuzz/includes/
-Wno-unused-command-line-argument
-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls
-mllvm
-sanitizer-coverage-prune-blocks=1
-fno-inline
-fno-builtin
-fno-omit-frame-pointer
-D__NO_STRING_INLINES

-DHFND_FUZZING_ENTRY_FUNCTION_CXX(x,y)=extern const char* LIBHFNETDRIVER_module_netdriver;const char** LIBHFNETDRIVER_tmp1 = &LIBHFNETDRIVER_module_netdriver;extern "C" int HonggfuzzNetDriver_main(x,y);int HonggfuzzNetDriver_main(x,y)

-DHFND_FUZZING_ENTRY_FUNCTION(x,y)=extern const char* LIBHFNETDRIVER_module_netdriver;const char** LIBHFNETDRIVER_tmp1 = &LIBHFNETDRIVER_module_netdriver;int HonggfuzzNetDriver_main(x,y);int HonggfuzzNetDriver_main(x,y)

-Wl,-U,_HonggfuzzNetDriver_main
-Wl,-U,_LIBHFUZZ_module_instrument
-Wl,-U,_LIBHFUZZ_module_memorycmp
**-o
runner**
**runner.m**
-framework
Foundation
-framework
CoreGraphics
-framework
AppKit
/tmp/libhfnetdriver.501.7140081f7cd58e92.a
/tmp/**libhfuzz**.501.2fdc27091cd8b54d.a
/tmp/libhfuzz.501.a5556386f906dc80.a
-pthread
-ldl
include $(CLEAR_VARS)
LOCAL_STATIC_LIBRARIES := hfuzz hfuzzcommon
LOCAL_SRC_FILES := fuzz_test.c
LOCAL_MODULE := fuzz_test
LOCAL_CFLAGS := -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls \\
-fno-omit-frame-pointer -fno-inline -fno-builtin \\
-fno-omit-frame-pointer -Wl,-u,_LIBHFUZZ_module_instrument -Wl,-u,_LIBHFUZZ_module_memorycmp -ldl

LOCAL_LDFLAGS := -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls \\
-fno-inline -fno-builtin \\
-fno-omit-frame-pointer -Wl,-u,_LIBHFUZZ_module_instrument -Wl,-u,_LIBHFUZZ_module_memorycm

image-20220824230115528

harness里需要主动调用 initializeTrapfuzz()

image-20220824230125768

看起来一切都不错!

获取指定so地址也有了:

image-20220824230138101

  • Patch

image-20220824230152707

参考这里修改honggfuzz的代码即可

后续&问题

这个方案主要是效率实在是太差了,性能损耗都在jvm获取那里了,本来也是工作之余的一个小点子,后面也没深入去看了,个人最开始的想法是 winafl模式搬到安卓上 lol…

参考

https://googleprojectzero.blogspot.com/2020/04/fuzzing-imageio.html

https://github.com/googleprojectzero/p0tools/blob/master/TrapFuzz/trapfuzz.patch