GwendalTsang/kimi / reverse /verify_artifacts.py
GwendalTsang's picture
download
raw
4.97 kB
#!/usr/bin/env python3
"""Verify archive extraction, DEX fallback, decoded resources and saved native analysis."""
import csv
import hashlib
import json
from pathlib import Path
import re
import zipfile
import xml.etree.ElementTree as ET
ROOT = Path(__file__).resolve().parent
checks = []
def check(name, condition):
checks.append({"check": name, "passed": bool(condition)})
inventory = json.loads((ROOT / "reports/apk-inventory.json").read_text())
for record in inventory:
apk = ROOT.parent / record["apk"]
check(f"{apk.name}: SHA256 unchanged", hashlib.sha256(apk.read_bytes()).hexdigest() == record["sha256"])
with zipfile.ZipFile(apk) as z:
check(f"{apk.name}: ZIP integrity", z.testzip() is None)
destination = ROOT / "extracted" / apk.stem
check(f"{apk.name}: every entry extracted byte-for-byte", all(
entry.is_dir() or (destination / entry.filename).read_bytes() == z.read(entry)
for entry in z.infolist()))
manifest = ET.parse(ROOT / "jadx/resources/AndroidManifest.xml").getroot()
check("Decoded application manifest", manifest.get("package") == "com.moonshot.kimichat")
check("Decoded XML resources", any((ROOT / "jadx/resources/res").rglob("*.xml")))
for split in ["arm64", "xxhdpi"]:
check(f"{split}: split resources decoded", (ROOT / f"logs/jadx-split-{split}.exit").read_text().strip() == "0")
split_manifest = ET.parse(ROOT / f"jadx-splits/{split}/resources/AndroidManifest.xml").getroot()
check(f"{split}: split manifest", bool(split_manifest.get("split")))
check("Primary JADX exit recorded (partial decompilation permitted)", (ROOT / "logs/jadx.exit").read_text().strip() in {"0", "3"})
check("DEX fallback completed without errors", (ROOT / "logs/jadx-fallback.exit").read_text().strip() == "0")
check("All three original DEX files preserved", len(list((ROOT / "extracted").rglob("*.dex"))) == 3)
missing_fallback = []
fallback_errors = []
for path in (ROOT / "jadx/sources").rglob("*.java"):
if "Method not decompiled:" in path.read_text():
fallback = ROOT / "jadx-fallback/sources" / path.relative_to(ROOT / "jadx/sources")
if not fallback.exists():
missing_fallback.append(str(path))
for path in (ROOT / "jadx-fallback/sources").rglob("*.java"):
if "JADX ERROR:" in path.read_text() or "Method not decompiled:" in path.read_text():
fallback_errors.append(str(path))
check("Every incomplete structured source has a DEX fallback", not missing_fallback)
check("DEX fallback has no error/stub markers", not fallback_errors)
check("Ghidra analysis process completed", (ROOT / "logs/ghidra.exit").read_text().strip() == "0")
check("Saved Ghidra project reopened successfully", (ROOT / "logs/ghidra-verify.exit").read_text().strip() == "0")
libraries = sorted((ROOT / "extracted").rglob("*.so"))
check("Expected 22 native libraries", len(libraries) == 22)
native_totals = {"functions": 0, "decompiled": 0, "failed": 0, "thunks": 0}
for library in libraries:
directory = ROOT / "ghidra/exports" / library.name
with (directory / "summary.tsv").open() as stream:
summary = next(csv.DictReader(stream, delimiter="\t"))
with (directory / "functions.tsv").open() as stream:
functions = list(csv.DictReader(stream, delimiter="\t"))
check(f"{library.name}: function index covers analysis", len(functions) == int(summary["functions"]))
c = (directory / "decompiled.c").read_text()
for key, status in [("decompiled", "ok"), ("failed", "failed"), ("thunks", "thunk")]:
count = sum(row["status"] == status for row in functions)
check(f"{library.name}: {status} count", count == int(summary[key]))
check(f"{library.name}: exported C bodies", len(re.findall(r"^/\* [0-9a-f]+ .+ \*/$", c, re.MULTILINE)) == int(summary["decompiled"]))
with (directory / "project-verification.tsv").open() as stream:
saved = next(csv.DictReader(stream, delimiter="\t"))
check(f"{library.name}: saved binary verified", saved["verified"] == "true" and
saved["sha256"] == hashlib.sha256(library.read_bytes()).hexdigest() and
saved["functions"] == summary["functions"] and saved["language"] == summary["language"])
check(f"{library.name}: ARM64", summary["language"] == "AARCH64:LE:64:v8A")
if int(summary["failed"]):
check(f"{library.name}: failed functions have assembly", (directory / "failed-functions.asm").stat().st_size > 0)
for key in native_totals:
native_totals[key] += int(summary[key])
result = {"passed": all(row["passed"] for row in checks), "checks": checks,
"native_totals": native_totals, "missing_fallback": missing_fallback,
"fallback_errors": fallback_errors}
(ROOT / "reports/verification.json").write_text(json.dumps(result, indent=2) + "\n")
print(json.dumps({"passed": result["passed"], "checks": len(checks), "native_totals": native_totals}, indent=2))
raise SystemExit(0 if result["passed"] else 1)

Xet Storage Details

Size:
4.97 kB
·
Xet hash:
35e124ee50ca04dfd0f07426daab99891f6a5d728849d38f796f27ad3dc8afe1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.