Skip to content

Commit 153eed9

Browse files
author
Samuel Groß
committed
Add TrapFuzz
1 parent 917d4fd commit 153eed9

4 files changed

Lines changed: 371 additions & 0 deletions

File tree

TrapFuzz/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# TrapFuzz
2+
3+
Hacky support for (basic-block) coverage guided fuzzing of closed source libraries for honggfuzz.
4+
5+
See https://googleprojectzero.blogspot.com/2020/04/fuzzing-imageio.html for some more information.
6+
7+
## Usage
8+
9+
1. Enumerate all basic blocks of the target library with the findPatchPoints.py IDAPython script
10+
11+
2. Apply trapfuzz.patch to [hoggfuzz](https://github.com/google/honggfuzz) and build honggfuzz
12+
13+
3. Implement a runner to call the API with fuzz input, see runner.m for an example
14+
15+
4. Compile the runner with honggfuzz's clang wrapper:
16+
17+
$honggfuzz/hfuzz_cc/hfuzz-clang -o runner runner.m -framework Foundation -framework CoreGraphics -framework AppKit
18+
19+
5. Start fuzzing!
20+
21+
$honggfuzz/honggfuzz --input input --output output --threads 12 --env TRAPFUZZ_FILE=trapfuzz.patches --env OS_ACTIVITY_MODE=disable --env DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib --rlimit_rss 4096 -- ./runner
22+

TrapFuzz/findPatchPoints.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import idautils
2+
import idaapi
3+
import ida_nalt
4+
import idc
5+
6+
# See https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
7+
8+
from os.path import expanduser
9+
home = expanduser("~")
10+
11+
patchpoints = set()
12+
13+
max_offset = 0
14+
for seg_ea in idautils.Segments():
15+
name = idc.get_segm_name(seg_ea)
16+
if name != "__text":
17+
continue
18+
19+
start = idc.get_segm_start(seg_ea)
20+
end = idc.get_segm_end(seg_ea)
21+
for func_ea in idautils.Functions(start, end):
22+
f = idaapi.get_func(func_ea)
23+
if not f:
24+
continue
25+
for block in idaapi.FlowChart(f):
26+
if start <= block.start_ea < end:
27+
max_offset = max(max_offset, block.start_ea)
28+
patchpoints.add(block.start_ea)
29+
else:
30+
print("Warning, broken CFG?")
31+
32+
# Round up max_offset to page size
33+
size = max_offset
34+
rem = size % 0x1000
35+
if rem != 0:
36+
size += 0x1000 - rem
37+
38+
with open(home + "/Desktop/patches.txt", "w") as f:
39+
f.write(ida_nalt.get_root_filename() + ':' + hex(size) + '\n')
40+
f.write('\n'.join(map(hex, sorted(patchpoints))))
41+
f.write('\n')
42+
43+
print("Done, found {} patchpoints".format(len(patchpoints)))

TrapFuzz/runner.m

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// Example runner implementation for fuzzing ImageIO on macOS
3+
//
4+
5+
#include <Foundation/Foundation.h>
6+
#include <Foundation/NSURL.h>
7+
#include <dlfcn.h>
8+
#include <stdint.h>
9+
#include <sys/shm.h>
10+
#include <dirent.h>
11+
12+
#import <ImageIO/ImageIO.h>
13+
#import <AppKit/AppKit.h>
14+
#import <CoreGraphics/CoreGraphics.h>
15+
16+
#include <libhfuzz/instrument.h>
17+
18+
extern bool CGRenderingStateGetAllowsAcceleration(void*);
19+
extern bool CGRenderingStateSetAllowsAcceleration(void*, bool);
20+
extern void* CGContextGetRenderingState(CGContextRef);
21+
22+
void dummyLogProc() { }
23+
24+
extern void HF_ITER(uint8_t** buf, size_t* len);
25+
extern void ImageIOSetLoggingProc(void*);
26+
27+
int main(int argc, const char* argv[]) {
28+
NSError* err = 0;
29+
30+
// Must manually load libOpenEXR
31+
dlopen("/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libOpenEXR.dylib", RTLD_LAZY);
32+
//dlopen("/System/Library/PrivateFrameworks/AppleVPA.framework/AppleVPA", RTLD_LAZY);
33+
34+
// Replace with dummy log procedure to save a few CPU cylces
35+
// (logging should additionally be disabled via environment variables).
36+
ImageIOSetLoggingProc(&dummyLogProc);
37+
38+
initializeTrapfuzz();
39+
40+
size_t len;
41+
uint8_t* buf;
42+
43+
for (int i = 0; i < 10000; i++) {
44+
HF_ITER(&buf, &len);
45+
46+
NSData* content = [NSData dataWithBytes:buf length:len];
47+
NSImage* img = [[NSImage alloc] initWithData:content];
48+
49+
CGImageRef cgImg = [img CGImageForProposedRect:nil context:nil hints:nil];
50+
if (cgImg) {
51+
size_t width = CGImageGetWidth(cgImg);
52+
size_t height = CGImageGetHeight(cgImg);
53+
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
54+
CGContextRef ctx = CGBitmapContextCreate(0, width, height, 8, 0, colorspace, 1);
55+
void* renderingState = CGContextGetRenderingState(ctx);
56+
CGRenderingStateSetAllowsAcceleration(renderingState, false);
57+
CGRect rect = CGRectMake(0, 0, width, height);
58+
CGContextDrawImage(ctx, rect, cgImg);
59+
60+
CGColorSpaceRelease(colorspace);
61+
CGContextRelease(ctx);
62+
CGImageRelease(cgImg);
63+
}
64+
65+
[img release];
66+
[content release];
67+
}
68+
69+
return 0;
70+
}

TrapFuzz/trapfuzz.patch

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
commit 30e6e00a9ab5c85b0a5a18306c390cf46684522e
2+
Author: Samuel Groß <saelo@google.com>
3+
Date: Mon Oct 14 10:36:30 2019 +0200
4+
5+
Initial trapfuzz implementation
6+
7+
diff --git a/Makefile b/Makefile
8+
index c014ad25..47fd6c8a 100644
9+
--- a/Makefile
10+
+++ b/Makefile
11+
@@ -69,7 +69,9 @@ else ifeq ($(OS),Darwin)
12+
# Figure out which crash reporter to use.
13+
CRASHWRANGLER := third_party/mac
14+
OS_VERSION := $(shell sw_vers -productVersion)
15+
- ifneq (,$(findstring 10.14,$(OS_VERSION)))
16+
+ ifneq (,$(findstring 10.15,$(OS_VERSION)))
17+
+ CRASH_REPORT := $(CRASHWRANGLER)/CrashReport_Sierra.o
18+
+ else ifneq (,$(findstring 10.14,$(OS_VERSION)))
19+
CRASH_REPORT := $(CRASHWRANGLER)/CrashReport_Sierra.o
20+
else ifneq (,$(findstring 10.13,$(OS_VERSION)))
21+
CRASH_REPORT := $(CRASHWRANGLER)/CrashReport_Sierra.o
22+
diff --git a/hfuzz_cc/hfuzz-cc.c b/hfuzz_cc/hfuzz-cc.c
23+
index df985482..ed8ba0be 100644
24+
--- a/hfuzz_cc/hfuzz-cc.c
25+
+++ b/hfuzz_cc/hfuzz-cc.c
26+
@@ -310,10 +310,10 @@ static void commonOpts(int* j, char** args) {
27+
args[(*j)++] = "-fsanitize-coverage=trace-pc,trace-cmp";
28+
}
29+
} else {
30+
- args[(*j)++] = "-Wno-unused-command-line-argument";
31+
- args[(*j)++] = "-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls";
32+
- args[(*j)++] = "-mllvm";
33+
- args[(*j)++] = "-sanitizer-coverage-prune-blocks=1";
34+
+ //args[(*j)++] = "-Wno-unused-command-line-argument";
35+
+ //args[(*j)++] = "-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls";
36+
+ //args[(*j)++] = "-mllvm";
37+
+ //args[(*j)++] = "-sanitizer-coverage-prune-blocks=1";
38+
}
39+
40+
/*
41+
diff --git a/libhfuzz/instrument.c b/libhfuzz/instrument.c
42+
index fa0a39b0..9f4224a6 100644
43+
--- a/libhfuzz/instrument.c
44+
+++ b/libhfuzz/instrument.c
45+
@@ -4,6 +4,7 @@
46+
#include <errno.h>
47+
#include <fcntl.h>
48+
#include <inttypes.h>
49+
+#include <signal.h>
50+
#include <stdbool.h>
51+
#include <stdint.h>
52+
#include <stdio.h>
53+
@@ -395,3 +396,172 @@ void instrumentUpdateCmpMap(uintptr_t addr, uint32_t v) {
54+
ATOMIC_POST_ADD(feedback->pidFeedbackCmp[my_thread_no], v - prev);
55+
}
56+
}
57+
+
58+
+
59+
+// Trapfuzz
60+
+
61+
+// Thanks ianbeer@
62+
+#include <mach-o/dyld_images.h>
63+
+static void* find_library_load_address(const char* library_name) {
64+
+ kern_return_t err;
65+
+
66+
+ // get the list of all loaded modules from dyld
67+
+ // the task_info mach API will get the address of the dyld all_image_info struct for the given task
68+
+ // from which we can get the names and load addresses of all modules
69+
+ task_dyld_info_data_t task_dyld_info;
70+
+ mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
71+
+ err = task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count);
72+
+
73+
+ const struct dyld_all_image_infos* all_image_infos = (const struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr;
74+
+ const struct dyld_image_info* image_infos = all_image_infos->infoArray;
75+
+
76+
+ for(size_t i = 0; i < all_image_infos->infoArrayCount; i++){
77+
+ const char* image_name = image_infos[i].imageFilePath;
78+
+ mach_vm_address_t image_load_address = (mach_vm_address_t)image_infos[i].imageLoadAddress;
79+
+ if (strstr(image_name, library_name)){
80+
+ return (void*)image_load_address;
81+
+ }
82+
+ }
83+
+ return NULL;
84+
+}
85+
+
86+
+// Translates the address of a trap instruction to the corresponding address in the shadow memory.
87+
+// As a basic block can be less than four bytes in size, but we need 4 bytes of storage for every patch
88+
+// (3 byte bitmap index, 1 byte original value), we create 4 shadow mappings and select the correct one
89+
+// from the last 2 bits of the address.
90+
+#define SHADOW(addr) ((uint32_t*)(((uintptr_t)addr & 0xfffffffffffffffc) - 0x200000000000 - ((uintptr_t)addr & 0x3)*0x10000000000))
91+
+
92+
+static void sigtrap_handler(int signum, siginfo_t* si, void* context) {
93+
+ // Must re-execute the instruction, so decrement PC by one instruction.
94+
+#if defined(__APPLE__) && defined(__LP64__)
95+
+ ucontext_t* ctx = (ucontext_t*)context;
96+
+ ctx->uc_mcontext->__ss.__rip -= 1;
97+
+#else
98+
+#error "Unsupported platform"
99+
+#endif
100+
+
101+
+ uint8_t* faultaddr = (uint8_t*)si->si_addr - 1;
102+
+ // If the trap didn't come from our instrumentation, then we probably will just segfault here
103+
+ uint32_t shadow = *SHADOW(faultaddr);
104+
+
105+
+ uint8_t orig_byte = shadow & 0xff;
106+
+ uint32_t index = shadow >> 8;
107+
+
108+
+ // Index zero is invalid so that it is still possible to catch actual trap instructions in instrumented libraries.
109+
+ if (index == 0) {
110+
+ abort();
111+
+ }
112+
+
113+
+ // Restore original instruction
114+
+ *faultaddr = orig_byte;
115+
+
116+
+ // Update coverage information.
117+
+ bool prev = ATOMIC_XCHG(feedback->pcGuardMap[index], true);
118+
+ if (prev == false) {
119+
+ ATOMIC_PRE_INC_RELAXED(feedback->pidFeedbackEdge[my_thread_no]);
120+
+ }
121+
+}
122+
+
123+
+void initializeTrapfuzz() {
124+
+ char* filename = getenv("TRAPFUZZ_FILE"); // TODO rename maybe?
125+
+ if (!filename) {
126+
+ LOG_F("TRAPFUZZ_FILE environment variable not set");
127+
+ }
128+
+
129+
+ FILE* patches = fopen(filename, "r");
130+
+ if (!patches) {
131+
+ LOG_F("Couldn't open patchfile %s", filename);
132+
+ }
133+
+
134+
+ // Index into the coverage bitmap for the current trap instruction.
135+
+ int bitmap_index = -1;
136+
+
137+
+ // Base address of the library currently being instrumented.
138+
+ uint8_t* lib_base = NULL;
139+
+ // Size of the library, or rather it's .text section which will be modified and thus has to be mprotect'ed.
140+
+ size_t lib_size = 0;
141+
+
142+
+ char* line = NULL;
143+
+ size_t nread, len = 0;
144+
+ while ((nread = getline(&line, &len, patches)) != -1) {
145+
+ char* end = line + len;
146+
+
147+
+ char* col = strchr(line, ':');
148+
+ if (col) {
149+
+ // It's a library:size pair
150+
+ *col = 0;
151+
+
152+
+ lib_base = find_library_load_address(line);
153+
+ if (!lib_base) {
154+
+ LOG_F("Library %s does not appear to be loaded", line);
155+
+ }
156+
+
157+
+ lib_size = strtoul(col + 1, &end, 16);
158+
+ if (lib_size % 0x1000 != 0) {
159+
+ LOG_F("Invalid library size 0x%zx. Must be multiple of 0x1000", lib_size);
160+
+ }
161+
+
162+
+ // Make library code writable.
163+
+ if (mprotect(lib_base, lib_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
164+
+ LOG_F("Failed to mprotect library %s writable", line);
165+
+ }
166+
+
167+
+ // Create shadow memory.
168+
+ for (int i = 0; i < 4; i++) {
169+
+ void* shadow_addr = SHADOW(lib_base + i);
170+
+ void* shadow = mmap(shadow_addr, lib_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, 0, 0);
171+
+ if (shadow == MAP_FAILED) {
172+
+ LOG_F("Failed to mmap shadow memory");
173+
+ }
174+
+ }
175+
+
176+
+ // Done, continue with next line.
177+
+ continue;
178+
+ }
179+
+
180+
+ // It's an offset, parse it and do the patching.
181+
+ unsigned long offset = strtoul(line, &end, 16);
182+
+ if (offset > lib_size) {
183+
+ LOG_F("Invalid offset: 0x%lx. Current library is 0x%zx bytes large", offset, lib_size);
184+
+ }
185+
+
186+
+ bitmap_index++;
187+
+
188+
+ if (bitmap_index >= _HF_PC_GUARD_MAX) {
189+
+ LOG_F("Too many basic blocks to instrument");
190+
+ }
191+
+
192+
+ uint32_t* shadow = SHADOW(lib_base + offset);
193+
+ if (*shadow != 0) {
194+
+ LOG_F("Potentially duplicate patch entry: 0x%lx", offset);
195+
+ }
196+
+
197+
+ if (ATOMIC_GET(feedback->pcGuardMap[bitmap_index])) {
198+
+ // This instrumentation trap has already been found.
199+
+ continue;
200+
+ }
201+
+
202+
+ // Make lookup entry in shadow memory.
203+
+ uint8_t orig_byte = lib_base[offset];
204+
+ *shadow = (bitmap_index << 8) | orig_byte;
205+
+
206+
+ // Replace instruction with int3, an instrumentation trap.
207+
+ lib_base[offset] = 0xcc;
208+
+ }
209+
+
210+
+ // Store number of basic blocks for statistical purposes.
211+
+ if (ATOMIC_GET(feedback->guardNb) < bitmap_index + 1) {
212+
+ ATOMIC_SET(feedback->guardNb, bitmap_index + 1);
213+
+ }
214+
+
215+
+ free(line);
216+
+ fclose(patches);
217+
+
218+
+ // Install signal handler for SIGTRAP.
219+
+ struct sigaction s;
220+
+ s.sa_flags = SA_SIGINFO; // TODO add SA_NODEFER?
221+
+ s.sa_sigaction = sigtrap_handler;
222+
+ sigemptyset(&s.sa_mask);
223+
+ sigaction(SIGTRAP, &s, 0);
224+
+}
225+
+
226+
diff --git a/libhfuzz/instrument.h b/libhfuzz/instrument.h
227+
index 87647fce..e073222f 100644
228+
--- a/libhfuzz/instrument.h
229+
+++ b/libhfuzz/instrument.h
230+
@@ -29,4 +29,6 @@
231+
void instrumentUpdateCmpMap(uintptr_t addr, uint32_t v);
232+
void instrumentClearNewCov();
233+
234+
+void initializeTrapfuzz();
235+
+
236+
#endif /* ifdef _HF_LIBHFUZZ_INSTRUMENT_H_ */

0 commit comments

Comments
 (0)