Skip to content

Commit cad7536

Browse files
Mutation Chain Tool (#2281)
* Mutation Chain Tool * Address comments domenukk * Address comments domenukk 2
1 parent 1ddfb1f commit cad7536

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

utils/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ Here's a quick overview of the stuff you can find in this directory:
5656

5757
- libpng_no_checksum - a sample patch for removing CRC checks in libpng.
5858

59+
- mutation_chain - a tool that backtraces the mutation chain of AFL
60+
crash files based on the crash/queue file naming
61+
standards and outputs this in a json format.
62+
5963
- persistent_mode - an example of how to use the LLVM persistent process
6064
mode to speed up certain fuzzing jobs.
6165

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python3
2+
#
3+
# MUTATION CHAIN COMPUTATION TOOL
4+
#
5+
# Ever wondered what the complete history of your AFL crash file looks like?
6+
# Now you can!
7+
#
8+
# This tool is developed to support file structures for parallel fuzzing runs using the
9+
# naming of main/secondary nodes as stated in the AFL docs (fuzzer01, fuzzer02 etc...)
10+
# In case you want to use it for single node runs just recreate the directory structure
11+
# which is used when parallel fuzzing is used (dump your results in a dir called fuzzer01).
12+
#
13+
# author: Maarten Dekker
14+
15+
# import required modules
16+
import os, re, json
17+
import argparse
18+
19+
crashes = {}
20+
queues = {}
21+
22+
def fillDictWithFilenameKeys(dir):
23+
dict = {}
24+
for filename in os.listdir(dir):
25+
if re.match("^id:\\d+", filename):
26+
dict[filename] = None
27+
return dict
28+
29+
# recusively compute the chain of queue items that led to the AFL crash file
30+
def compute_mutation_chain(filename, current_fuzzer, n):
31+
32+
if re.match(".*src:(\\d+),", filename):
33+
34+
source_id = re.match(".*src:(\\d+),", filename).group(1)
35+
file_we_look_for_rex = "^id:" + source_id + ","
36+
37+
fuzzer_queue = None
38+
39+
# determine if we need to look in the queue of another fuzzer instance
40+
if re.match(".*sync:(fuzzer\\d+),", filename):
41+
fuzzer_queue = re.match(".*sync:(fuzzer\\d+),", filename).group(1)
42+
else:
43+
fuzzer_queue = current_fuzzer
44+
45+
for k,v in queues[fuzzer_queue].items():
46+
47+
if re.match(file_we_look_for_rex, k):
48+
49+
retval = {}
50+
retval[k] = compute_mutation_chain(k, fuzzer_queue, n+1)
51+
return retval
52+
53+
# if the mutation result is a splice it thas 2 sources
54+
elif re.match(".*src:(\\d+)\\+(\\d+)", filename):
55+
56+
sources = re.match(".*src:(\\d+)\\+(\\d+)", filename)
57+
58+
source_id_1 = sources.group(1)
59+
source_id_2 = sources.group(2)
60+
61+
file_we_look_for_1_rex = "^id:" + source_id_1 + ","
62+
file_we_look_for_2_rex = "^id:" + source_id_2 + ","
63+
64+
# for mutation with two sources, the sources are never synced form other queues
65+
retval = {}
66+
67+
for k,v in queues[current_fuzzer].items():
68+
69+
if re.match(file_we_look_for_1_rex, k):
70+
retval[k] = compute_mutation_chain(k, current_fuzzer, n+1)
71+
72+
elif re.match(file_we_look_for_2_rex, k):
73+
retval[k] = compute_mutation_chain(k, current_fuzzer, n+1)
74+
75+
return retval
76+
77+
else:
78+
return "seed"
79+
80+
81+
def main():
82+
83+
parser = argparse.ArgumentParser(
84+
prog='mutation_chain.py',
85+
description='Compute the mutation chain of AFL crash files to visulise the mutation history from seed files to crash' +
86+
'This tool just dump json data to the CLI, it is advised to echo them into a file for further analysis (i.e. [command] >> your_file.json)',
87+
epilog='Greetings from old zealand'
88+
)
89+
90+
parser.add_argument(
91+
"-m", "--mode",
92+
choices = ['single', 'all'],
93+
help = 'compute chain for one file or all crash files in supplied directory. In single mode the -f argument is required',
94+
required = True
95+
)
96+
97+
parser.add_argument(
98+
"-i", "--input",
99+
action = 'store',
100+
help = 'Input directory for the mutation chain tool (the fuzzer\'s output directory)',
101+
required = True
102+
)
103+
104+
parser.add_argument(
105+
"-n", "--node",
106+
action = 'store',
107+
help = '[Only used in single mode; optinal] name of the fuzzer node that contains the crash file supplied in the --file argument (e.g. \'fuzzer03\'). Defaults to \'fuzzer01\' if not supplied',
108+
required = False
109+
)
110+
111+
parser.add_argument(
112+
"-f", "--file",
113+
action = 'store',
114+
help = '[Only used in single mode; required] filename of specific crash file (e.g. \'id:000008,sig:06,src:000005,op:havoc,rep:8\')',
115+
required = False
116+
)
117+
118+
args = parser.parse_args()
119+
120+
if args.mode == "single":
121+
122+
if args.node == None:
123+
args.node = "fuzzer01"
124+
125+
if args.file == None:
126+
parser.error("'--mode single' requires the '--file' argument.")
127+
128+
crash_file_path = args.input + '/' + args.node + '/crashes/' + args.file
129+
if not os.path.isfile(crash_file_path):
130+
print("Error: \'" + crash_file_path + "\' does not exist.\nPlease verify whether the node and filename are correct.")
131+
return
132+
133+
# Create the interal representation of the various queues of parallel fuzzing nodes
134+
for dir in os.listdir(args.input):
135+
if re.match("^fuzzer\\d+", dir):
136+
queues[dir] = fillDictWithFilenameKeys(args.input + '/' + dir + '/queue')
137+
138+
if args.mode == "all":
139+
140+
for dir in os.listdir(args.input):
141+
if re.match("^fuzzer\\d+", dir):
142+
for filename in os.listdir(args.input + '/' + dir + "/crashes"):
143+
if re.match("^id:\\d+", filename):
144+
print(filename)
145+
crashes[filename] = compute_mutation_chain(filename, dir, 0)
146+
147+
elif args.mode == "single":
148+
149+
crashes[args.file] = compute_mutation_chain(args.file, args.node, 0)
150+
151+
print(json.dumps(crashes, sort_keys=True, indent=4))
152+
153+
if __name__ == '__main__':
154+
main()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy