def cli(context, snapshot_str, coverage, max_time, max_iterations, display_delay, stop_on_crash, resume, input, input_size, workdir): whvp.init_log() if ":" in snapshot_str: hostname, port = snapshot_str.split(":") snapshot = RpycSnapshot(hostname, int(port)) else: path = snapshot_str snapshot = DumpSnapshot(path) context = snapshot.get_initial_context() tracer = whvp.Tracer(snapshot.memory_access_callback) if resume is None: os.makedirs(workdir, exist_ok=True) guid = str(uuid.uuid4()) fuzzer_workdir = os.path.join(workdir, guid) os.makedirs(fuzzer_workdir) input = int(input, 16) input_size = int(input_size, 16) else: fuzzer_workdir = resume whvp.log(F"fuzzer workdir is {fuzzer_workdir}") crashes_path = os.path.join(fuzzer_workdir, "crashes") os.makedirs(crashes_path, exist_ok=True) corpus_path = os.path.join(fuzzer_workdir, "corpus") os.makedirs(corpus_path, exist_ok=True) fuzzer = whvp.Fuzzer(fuzzer_workdir) trace_params = snapshot.get_params() trace_params["limit"] = 0 trace_params["coverage"] = coverage trace_params["save_context"] = False trace_params["save_instructions"] = False fuzz_params = { "max_iterations": max_iterations, "max_time": max_time, "input": input, "input_size": input_size, "stop_on_crash": stop_on_crash, "display_delay": display_delay, "snapshot": snapshot_str } path = os.path.join(fuzzer_workdir, "params.json") with open(path, "w") as fp: json.dump(fuzz_params, fp, indent=2) fuzzer.run(tracer, context, trace_params, fuzz_params)
def test_complex_trace(): with open(os.path.join("tests", "sdb", "context.json"), "r") as fp: context = json.load(fp) for r in ["cs", "ss", "ds", "es", "fs", "gs"]: context[r] = { "selector": context[r], "base": 0, "limit": 0, "flags": 0, } def callback(gpa, gva): path = os.path.join("tests", "sdb", "mem", F"{gpa:016x}.bin") if os.path.exists(path): with open(path, "rb") as fp: data = fp.read() if data: return data else: print("no data") else: print(F"missing file {path}") tracer = whvp.Tracer(callback) # context["rflags"] |= 0x100 tracer.set_initial_context(context) params = {"coverage": "no", "limit": 0, "save_context": False, "save_instructions": False, "excluded_addresses": { }, "return_address": context["return_address"]} result = tracer.run(params) print(result) coverage = result.get_coverage() assert len(coverage) == 1 tracer.set_initial_context(context) tracer.restore_snapshot() params["coverage"] = "instrs" result = tracer.run(params) print(result) coverage = result.get_coverage() assert len(coverage) == 59120
def test_complex_trace2(): with open(os.path.join("tests", "sdb", "context.json"), "r") as fp: context = json.load(fp) for r in ["cs", "ss", "ds", "es", "fs", "gs"]: context[r] = { "selector": context[r], "base": 0, "limit": 0, "flags": 0, } def callback(gpa, gva): path = os.path.join("tests", "sdb", "mem", F"{gpa:016x}.bin") if os.path.exists(path): with open(path, "rb") as fp: data = fp.read() if data: return data else: print("no data") else: print(F"missing file {path}") tracer = whvp.Tracer(callback) tracer.set_initial_context(context) params = {"coverage": "hit", "limit": 0, "excluded_addresses": { }, "save_context": False, "save_instructions": False, "return_address": context["return_address"]} result = tracer.run(params) print(result) coverage = result.get_coverage() seen = result.get_unique_addresses() status = result.get_status() # FIXME: check with seen assert status == "Success" assert len(coverage) == len(seen)
def cli(crashes, output, snapshot, limit): whvp.init_log() if ":" in snapshot: hostname, port = snapshot.split(":") snapshot = RpycSnapshot(hostname, int(port)) else: path = snapshot snapshot = DumpSnapshot(path) context = snapshot.get_initial_context() params = snapshot.get_params() tracer = whvp.Tracer(snapshot.memory_access_callback) os.makedirs(output, exist_ok=True) traces_dir = os.path.join(output, "traces") os.makedirs(traces_dir, exist_ok=True) buckets_dir = os.path.join(output, "buckets") os.makedirs(buckets_dir, exist_ok=True) coverages = {} files = [crash for crash in os.listdir(crashes) if crash.endswith(".bin")] whvp.log(F"loaded {len(files)} crash(es)") whvp.log(F"gathering coverage") for index, f in enumerate(files): coverage_path = os.path.join(traces_dir, f + ".trace.json") if os.path.exists(coverage_path): whvp.log(F"coverage exists for {f}, loading from file") with open(coverage_path, "r") as fp: coverage = json.load(fp) else: whvp.log(F"doing coverage for {f}") whvp.log("replaying input") replay = os.path.join(crashes, f) with open(replay, "rb") as fp: replay_data = fp.read() path = os.path.splitext(replay)[0] + ".json" with open(path, "r") as fp: replay_params = json.load(fp) tracer.set_initial_context(context) params["limit"] = 0 params["coverage"] = "no" params["save_context"] = False params["save_instructions"] = False whvp.log("first run to map memory") trace = tracer.run(params) tracer.restore_snapshot() tracer.write_virtual_memory(replay_params["input"], replay_data) params["limit"] = 0 params["coverage"] = "instrs" params["save_context"] = False params["save_instructions"] = False whvp.log("second run to replay crash") tracer.set_initial_context(context) trace = tracer.run(params) tracer.restore_snapshot() coverage = trace.get_coverage() seen = trace.get_unique_addresses() status = trace.get_status() time = trace.get_elapsed_time() whvp.log(F"executed {len(coverage)} instruction(s), {len(seen)} were unique in {time} ({status})") whvp.log(F"saving trace to {coverage_path}") trace.save(coverage_path) with open(coverage_path, "r") as fp: coverage = json.load(fp) coverages[f] = coverage buckets = collections.defaultdict(list) for f in files: m = hashlib.sha1() for (address, context) in coverages[f]["coverage"][-limit:]: data = F"{address:016x}" m.update(bytes(data, encoding="utf-8")) bucket = m.hexdigest() buckets[bucket].append(f) whvp.log(F"triaged {len(files)} crash(es)") whvp.log(F"found {len(buckets)} unique crash(es)") for bucket, duplicates in buckets.items(): whvp.log(F"bucket {bucket} contains {len(duplicates)} file(s)") bucket_path = os.path.join(buckets_dir, bucket) os.makedirs(bucket_path, exist_ok=True) for d in duplicates: src = os.path.join(crashes, d) shutil.copy(src, bucket_path) src = os.path.join(crashes, os.path.splitext(d)[0] + ".json") shutil.copy(src, bucket_path)
def cli(snapshot, coverage, save_context, save_instructions, save_trace, replay, max_time): whvp.init_log() if ":" in snapshot: hostname, port = snapshot.split(":") snapshot = RpycSnapshot(hostname, int(port)) else: path = snapshot snapshot = DumpSnapshot(path) context = snapshot.get_initial_context() params = snapshot.get_params() tracer = whvp.Tracer(snapshot.memory_access_callback) # FIXME: rename to set_context tracer.set_initial_context(context) params["limit"] = 0 params["coverage"] = coverage params["save_context"] = save_context params["save_instructions"] = save_instructions if max_time != 0: params["max_time"] = max_time whvp.log("running tracer") trace = tracer.run(params) coverage = trace.get_coverage() seen = trace.get_unique_addresses() status = trace.get_status() time = trace.get_elapsed_time() whvp.log( F"executed {len(coverage)} instruction(s), {len(seen)} were unique in {time} ({status})" ) pages = tracer.restore_snapshot() whvp.log(F"{pages} page(s) were modified") # FIXME: get dirty pages, code pages, data pages if replay: whvp.log("replaying input") with open(replay, "r") as fp: replay_params = json.load(fp) path = os.path.splitext(replay)[0] + ".bin" with open(path, "rb") as fp: replay_data = fp.read() tracer.set_initial_context(context) orig_data = tracer.read_virtual_memory(replay_params["input"], replay_params["input_size"]) input = helpers.Input(replay_params["input"], orig_data) whvp.log("input bytes:") print(input.diff(replay_data)) whvp.log("running tracer with modified input") tracer.write_virtual_memory(replay_params["input"], replay_data) trace = tracer.run(params) coverage = trace.get_coverage() seen = trace.get_unique_addresses() status = trace.get_status() time = trace.get_elapsed_time() whvp.log( F"executed {len(coverage)} instruction(s), {len(seen)} were unique in {time} ({status})" ) if save_trace: whvp.log(F"saving trace to {save_trace}") trace.save(save_trace)
def test_basic_trace(): with open(os.path.join("tests", "RtlInitUnicodeString.json"), "r") as fp: context = json.load(fp) regs = context["regs"] regs["rflags"] = 0x40246 regs["efer"] = context["efer"] for r in ["cs", "ss", "ds", "es", "fs", "gs"]: regs[r] = { "selector": regs[r], "base": 0, "limit": 0, "flags": 0, } def callback(gpa, gva): pfn = gpa >> 12 print(F"writing pfn {pfn:x}") data = context["pfn"].get(str(pfn)) if data: data = base64.b64decode(data) return data else: print("unknown pfn") tracer = whvp.Tracer(callback) tracer.set_initial_context(regs) params = {"limit": 25, "coverage": "instrs", "save_context": False, "save_instructions": False, "excluded_addresses": { }, "return_address": context["return_address"]} result = tracer.run(params) print(result) coverage = result.get_coverage() print(coverage) assert len(coverage) == 5 assert len(coverage[0]) == 1 instructions = result.get_instructions() assert len(instructions) == 0 tracer.set_initial_context(regs) params["save_context"] = True result = tracer.run(params) print(result) coverage = result.get_coverage() print(coverage) assert len(coverage) == 5 assert len(coverage[0]) == 18 instructions = result.get_instructions() assert len(instructions) == 0 tracer.set_initial_context(regs) params["save_instructions"] = True result = tracer.run(params) print(result) instructions = result.get_instructions() print(instructions) assert len(instructions) == 5