81 lines
3.2 KiB
Python
Executable File
81 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate an angr symbolic-execution harness (and report angr availability).
|
|
|
|
`check` reports whether the angr package is importable. `harness` writes a ready-to-edit angr
|
|
script that loads a binary, explores to a target address while avoiding another, and prints the
|
|
solving input. This tool only generates text — it does not load the binary or run the sample.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import angr # type: ignore # noqa: F401
|
|
HAVE_ANGR = True
|
|
except ImportError:
|
|
HAVE_ANGR = False
|
|
|
|
|
|
def check() -> dict:
|
|
return {"angr_available": HAVE_ANGR,
|
|
"note": "pip install angr to run generated harnesses" if not HAVE_ANGR else "ready"}
|
|
|
|
|
|
def harness(binary: str, find: str, avoid: str | None, out: str) -> dict:
|
|
avoid_line = (f" avoid_addr = {avoid}\n" if avoid else " avoid_addr = []\n")
|
|
explore = ("simgr.explore(find=find_addr, avoid=avoid_addr)"
|
|
if avoid else "simgr.explore(find=find_addr)")
|
|
text = (
|
|
"#!/usr/bin/env python3\n"
|
|
"# angr symbolic-execution harness (generated skeleton). Run in an isolated VM.\n"
|
|
"import angr\n"
|
|
"import claripy\n\n"
|
|
"def solve():\n"
|
|
f" proj = angr.Project({binary!r}, auto_load_libs=False)\n"
|
|
f" find_addr = {find}\n"
|
|
f"{avoid_line}"
|
|
" # Symbolic stdin of bounded length; tighten the charset/length below.\n"
|
|
" flag_len = 32\n"
|
|
" flag = claripy.BVS('flag', 8 * flag_len)\n"
|
|
" state = proj.factory.full_init_state(stdin=flag)\n"
|
|
" for i in range(flag_len):\n"
|
|
" b = flag.get_byte(i)\n"
|
|
" state.solver.add(b >= 0x20, b <= 0x7e) # printable\n"
|
|
" simgr = proj.factory.simulation_manager(state)\n"
|
|
" simgr.use_technique(angr.exploration_techniques.Veritesting())\n"
|
|
f" {explore}\n"
|
|
" if simgr.found:\n"
|
|
" found = simgr.found[0]\n"
|
|
" print(found.posix.dumps(0)) # the solving input\n"
|
|
" else:\n"
|
|
" print('no solution reached the target')\n\n"
|
|
"if __name__ == '__main__':\n"
|
|
" solve()\n"
|
|
)
|
|
Path(out).write_text(text, encoding="utf-8")
|
|
return {"output": out, "find": find, "avoid": avoid, "angr_available": HAVE_ANGR}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Generate an angr harness")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
sub.add_parser("check")
|
|
ph = sub.add_parser("harness")
|
|
ph.add_argument("--binary", required=True)
|
|
ph.add_argument("--find", required=True, help="target address, e.g. 0x401234")
|
|
ph.add_argument("--avoid", help="address to avoid, e.g. 0x401260")
|
|
ph.add_argument("--out", required=True)
|
|
args = parser.parse_args(argv)
|
|
if args.cmd == "check":
|
|
print(json.dumps(check(), indent=2))
|
|
else:
|
|
print(json.dumps(harness(args.binary, args.find, args.avoid, args.out), indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|