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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
#!/usr/bin/env python3
#
# Interpret a file that crashes an fuzz_ndr_X binary.
#
# Copyright (C) Catalyst IT Ltd. 2019
import sys
import os
from base64 import b64encode
import struct
import argparse
import re
TYPE_MASK = 3
TYPES = ['struct', 'in', 'out']
FLAGS = [
(4, 'ndr64', '--ndr64'),
]
def print_if_verbose(*args, **kwargs):
if verbose:
print(*args, **kwargs)
def process_one_file(f):
print_if_verbose(f.name)
print_if_verbose('-' * len(f.name))
b = f.read()
flags, function = struct.unpack('<HH', b[:4])
if opnum is not None and opnum != function:
return
t = TYPES[flags & TYPE_MASK]
if ndr_type and ndr_type != t:
return
payload = b[4:]
data64 = b64encode(payload).decode('utf-8')
cmd = ['bin/ndrdump',
pipe,
str(function),
t,
'--base64-input',
'--input', data64,
]
for flag, name, option in FLAGS:
if flags & flag:
print_if_verbose("flag: %s" % name)
cmd.append(option)
print_if_verbose("length: %d\n" % len(payload))
print(' '.join(cmd))
print_if_verbose()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--pipe', default=None,
help=('pipe name (for output command line, '
'default is a guess or "$PIPE")'))
parser.add_argument('-t', '--type', default=None, choices=TYPES,
help='restrict to this type')
parser.add_argument('-o', '--opnum', default=None, type=int,
help='restrict to this function/struct number')
parser.add_argument('FILES', nargs='*', default=(),
help="read from these files")
parser.add_argument('-k', '--ignore-errors', action='store_true',
help='do not stop on errors')
parser.add_argument('-v', '--verbose', action='store_true',
help='say more')
parser.add_argument('-H', '--honggfuzz-file',
help="extract crashes from this honggfuzz report")
parser.add_argument('-f', '--crash-filter',
help="only print crashes matching this rexexp")
args = parser.parse_args()
global pipe, opnum, ndr_type, verbose
pipe = args.pipe
opnum = args.opnum
ndr_type = args.type
verbose = args.verbose
if not args.FILES and not args.honggfuzz_file:
parser.print_usage()
sys.exit(1)
for fn in args.FILES:
if pipe is None:
m = re.search(r'clusterfuzz-testcase.+-fuzz_ndr_([a-z]+)', fn)
if m is None:
pipe = '$PIPE'
else:
pipe = m.group(1)
if args.crash_filter is not None:
if not re.search(args.crash_filter, fn):
print_if_verbose(f"skipping {fn}")
continue
try:
if fn == '-':
process_one_file(sys.stdin)
else:
with open(fn, 'rb') as f:
process_one_file(f)
except Exception:
print_if_verbose("Error processing %s\n" % fn)
if args.ignore_errors:
continue
raise
if args.honggfuzz_file:
print_if_verbose(f"looking at {args.honggfuzz_file}")
with open(args.honggfuzz_file) as f:
pipe = None
crash = None
for line in f:
m = re.match(r'^\s*fuzzTarget\s*:\s*bin/fuzz_ndr_(\w+)\s*$', line)
if m:
pipe = m.group(1).split('_TYPE_', 1)[0]
print_if_verbose(f"found pipe {pipe}")
m = re.match(r'^FUZZ_FNAME: (\S+)$', line)
if m:
crash = m.group(1)
if args.crash_filter is not None:
if not re.search(args.crash_filter, crash):
print_if_verbose(f"skipping {crash}")
pipe = None
crash = None
continue
print_if_verbose(f"found crash {crash}")
if pipe is not None and crash is not None:
with open(crash, 'rb') as f:
process_one_file(f)
pipe = None
crash = None
main()
|