summaryrefslogtreecommitdiffstats
path: root/lib/fuzzing/decode_ndr_X_crash
blob: d90e7efe122331c070d6a1db3de5a5cadde771bb (plain)
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()