mirror of
https://github.com/LLVMParty/striga
synced 2026-06-21 13:43:42 +00:00
197 lines
6.8 KiB
Python
197 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
import csv
|
|
import json
|
|
import re
|
|
from dataclasses import asdict, dataclass
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
ARTIFACT_DIR = ROOT / "devirt-presentation-artifacts"
|
|
SNIPPET_DIR = ARTIFACT_DIR / "snippets"
|
|
|
|
|
|
@dataclass
|
|
class IrStats:
|
|
path: str
|
|
lines: int
|
|
ram_refs: int
|
|
hook_refs: int
|
|
switches: int
|
|
unresolved_refs: int
|
|
calls: int
|
|
loads: int
|
|
stores: int
|
|
icmp_eq_constants: list[int]
|
|
|
|
|
|
def read(path: Path) -> str:
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def stats_for(path: Path) -> IrStats:
|
|
text = read(path)
|
|
return IrStats(
|
|
path=str(path.relative_to(ROOT)).replace("\\", "/"),
|
|
lines=len(text.splitlines()),
|
|
ram_refs=text.count("@RAM"),
|
|
hook_refs=len(re.findall(r"__striga_(?:jmp|call|ret|syscall)", text)),
|
|
switches=len(re.findall(r"\bswitch\b", text)),
|
|
unresolved_refs=text.count("unresolved"),
|
|
calls=len(re.findall(r"\bcall\b", text)),
|
|
loads=len(re.findall(r"\bload\b", text)),
|
|
stores=len(re.findall(r"\bstore\b", text)),
|
|
icmp_eq_constants=[
|
|
int(x) for x in re.findall(r"icmp eq i32 %[-.$A-Za-z0-9_]+, (\d+)", text)
|
|
],
|
|
)
|
|
|
|
|
|
def window_around(text: str, pattern: str, *, before: int = 8, after: int = 16) -> str:
|
|
lines = text.splitlines()
|
|
rx = re.compile(pattern)
|
|
for index, line in enumerate(lines):
|
|
if rx.search(line):
|
|
start = max(index - before, 0)
|
|
end = min(index + after + 1, len(lines))
|
|
return "\n".join(lines[start:end]) + "\n"
|
|
raise ValueError(f"pattern not found: {pattern}")
|
|
|
|
|
|
def write_snippet(name: str, content: str) -> Path:
|
|
SNIPPET_DIR.mkdir(parents=True, exist_ok=True)
|
|
path = SNIPPET_DIR / name
|
|
path.write_text(content, encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def main() -> None:
|
|
resolver_first_built = ARTIFACT_DIR / "resolvers" / "140016000-01-built.ll"
|
|
resolver_first_inlined = (
|
|
ARTIFACT_DIR / "resolvers" / "140016000-02-inlined-before-hook-rewrite.ll"
|
|
)
|
|
resolver_first_hooked = (
|
|
ARTIFACT_DIR / "resolvers" / "140016000-03-hook-rewritten.ll"
|
|
)
|
|
resolver_first_optimized = ARTIFACT_DIR / "resolvers" / "140016000-04-optimized.ll"
|
|
resolver_jcc_optimized = ARTIFACT_DIR / "resolvers" / "14001676b-04-optimized.ll"
|
|
recovered_skeleton = ARTIFACT_DIR / "recovered" / "01-skeleton-before-inline.ll"
|
|
recovered_round8 = ARTIFACT_DIR / "recovered" / "04-cleanup-round-8.ll"
|
|
recovered_residual = (
|
|
ARTIFACT_DIR / "recovered" / "05-residual-before-final-pattern-cleanup.ll"
|
|
)
|
|
recovered_final = ARTIFACT_DIR / "recovered" / "06-final-clean.ll"
|
|
|
|
files = [
|
|
*sorted((ARTIFACT_DIR / "resolvers").glob("*.ll")),
|
|
*sorted((ARTIFACT_DIR / "recovered").glob("*.ll")),
|
|
]
|
|
stats = [stats_for(path) for path in files]
|
|
|
|
snippets = {
|
|
"resolver-built-call.ll": window_around(
|
|
read(resolver_first_built),
|
|
r"call void @lifted_0x140016000",
|
|
before=10,
|
|
after=4,
|
|
),
|
|
"resolver-inlined-hook-call.ll": window_around(
|
|
read(resolver_first_inlined), r"__striga_jmp", before=14, after=4
|
|
),
|
|
"resolver-hook-rewritten-return.ll": window_around(
|
|
read(resolver_first_hooked),
|
|
r"insertvalue %TraceResult_140016000",
|
|
before=8,
|
|
after=20,
|
|
),
|
|
"resolver-optimized-target.ll": window_around(
|
|
read(resolver_first_optimized),
|
|
r"ret %TraceResult_140016000",
|
|
before=24,
|
|
after=3,
|
|
),
|
|
"jcc-resolver-select.ll": window_around(
|
|
read(resolver_jcc_optimized), r"select i1", before=18, after=20
|
|
),
|
|
"recovered-skeleton-switch.ll": window_around(
|
|
read(recovered_skeleton), r"switch i64", before=4, after=12
|
|
),
|
|
"recovered-round8-first-compare.ll": window_around(
|
|
read(recovered_round8), r"icmp eq i32 %4, 1859", before=12, after=32
|
|
),
|
|
"recovered-residual-membership.ll": window_around(
|
|
read(recovered_residual), r"icmp eq i32 %4, 1859", before=12, after=72
|
|
),
|
|
"recovered-final.ll": read(recovered_final),
|
|
}
|
|
snippet_paths = {
|
|
name: write_snippet(name, content) for name, content in snippets.items()
|
|
}
|
|
|
|
stats_json = ARTIFACT_DIR / "ir-stage-stats.json"
|
|
stats_json.write_text(
|
|
json.dumps([asdict(s) for s in stats], indent=2), encoding="utf-8"
|
|
)
|
|
|
|
stats_csv = ARTIFACT_DIR / "ir-stage-stats.csv"
|
|
with stats_csv.open("w", encoding="utf-8", newline="") as f:
|
|
writer = csv.DictWriter(f, fieldnames=list(asdict(stats[0]).keys()))
|
|
writer.writeheader()
|
|
for item in stats:
|
|
row = asdict(item)
|
|
row["icmp_eq_constants"] = " ".join(map(str, row["icmp_eq_constants"]))
|
|
writer.writerow(row)
|
|
|
|
report_lines = [
|
|
"# BinaryShield devirtualization presentation artifacts",
|
|
"",
|
|
"Generated from `STRIGA_DEVIRT_DUMP_DIR=docs/devirt-presentation-artifacts` and selected resolver dumps for `0x140016000` and `0x14001676b`.",
|
|
"",
|
|
"## Stage statistics",
|
|
"",
|
|
"| File | Lines | @RAM refs | hooks | switches | unresolved | calls | loads | stores | constants |",
|
|
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---|",
|
|
]
|
|
for item in stats:
|
|
constants = ", ".join(map(str, item.icmp_eq_constants[:8]))
|
|
if len(item.icmp_eq_constants) > 8:
|
|
constants += ", ..."
|
|
report_lines.append(
|
|
f"| `{item.path}` | {item.lines} | {item.ram_refs} | {item.hook_refs} | "
|
|
f"{item.switches} | {item.unresolved_refs} | {item.calls} | {item.loads} | "
|
|
f"{item.stores} | {constants} |"
|
|
)
|
|
|
|
report_lines.extend(
|
|
[
|
|
"",
|
|
"## Snippets",
|
|
"",
|
|
]
|
|
)
|
|
for name, path in snippet_paths.items():
|
|
report_lines.append(f"- `{path.relative_to(ROOT).as_posix()}`")
|
|
|
|
snippet_bundle_lines = ["# Intermediate IR snippets", ""]
|
|
for name, content in snippets.items():
|
|
snippet_bundle_lines.extend(
|
|
[f"## {name}", "", "```llvm", content.rstrip(), "```", ""]
|
|
)
|
|
snippet_bundle = ARTIFACT_DIR / "ir-snippets.md"
|
|
snippet_bundle.write_text("\n".join(snippet_bundle_lines), encoding="utf-8")
|
|
|
|
(ARTIFACT_DIR / "summary.md").write_text(
|
|
"\n".join(report_lines) + "\n", encoding="utf-8"
|
|
)
|
|
|
|
print(f"wrote {stats_json.relative_to(ROOT)}")
|
|
print(f"wrote {stats_csv.relative_to(ROOT)}")
|
|
print(f"wrote {snippet_bundle.relative_to(ROOT)}")
|
|
print(f"wrote {(ARTIFACT_DIR / 'summary.md').relative_to(ROOT)}")
|
|
for path in snippet_paths.values():
|
|
print(f"wrote {path.relative_to(ROOT)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|