Skip to content

Latest commit

 

History

History
163 lines (129 loc) · 10.3 KB

File metadata and controls

163 lines (129 loc) · 10.3 KB

AGENTS

This repository contains ToCode, a Python-only binary exporter. ToCode takes one binary, IDA database, Android APK, or .NET assembly/bundle/package path and writes one source-like project directory for reverse-engineering agents.

Scope

  • Keep the project focused on the exporter CLI and Python library modules.
  • Do not add web UI, server, container wrapper, shortcut, installer, or background service behavior.
  • Do not add chat, report generation, or secondary analysis stages.
  • Export one project tree from one binary or IDA database: raw decompiler output, assembly, summaries, section data, JSON metadata, optional IDA database, and optional scanner-friendly C.
  • Generated export trees must include their own AGENTS.md for agents analyzing that exported binary.

Project Layout

  • src/tocode/cli.py: command-line entry point for tocode.
  • src/tocode/__init__.py: package version and export_from_binaryview(), the in-UI Binary Ninja library entry.
  • src/tocode/analysis.py: backend-neutral binary inventory and call graph normalization.
  • src/tocode/backends/: IDA Domain, radare2, angr, and Binary Ninja (binja.py) session adapters, plus the ASC/droidasc APK backend (asc.py: APK set discovery, DEX inventory, worker-side class decompilation).
  • src/tocode/backends/dotnet.py / dotnet_pe.py: .NET backend (input discovery for assemblies, apphosts, single-file bundles and NuGet packages; dnlib inventory; pure-Python PE/CLR/bundle parsing and IL token scan; worker-side C#/IL decompilation). backends/dotnet_libs.py downloads dnlib and ICSharpCode.Decompiler on demand (ask once, hash-pinned) into a per-user folder.
  • src/tocode/dotnet.py: .NET export pipeline; dotnet_metadata.py: .NET JSON documents and the generated AGENTS.md.
  • src/tocode/apk.py: APK export pipeline (extraction, decompile pool, resource decoding, metadata); apk_metadata.py: manifest parsing and Android JSON documents; apk_native.py: extraction and background native export of every .so.
  • src/tocode/exporter.py: project writer, function rendering, worker-session rendering, generated export AGENTS.md.
  • src/tocode/metadata.py: JSON metadata and triage documents.
  • src/tocode/cluster.py: call-graph clustering.
  • src/tocode/parallel.py: worker-count selection.
  • src/tocode/schema.py: dataclasses shared across the exporter.
  • tests/: unit tests for algorithms, CLI helpers, and export tree generation.

Export Contract

The CLI accepts a regular binary file or IDA database and writes a project directory containing:

  • src/raw/**/*.c
  • src/raw/**/*.asm
  • src/raw/**/*.summary
  • include/*.h
  • data/*.bin
  • data/variables.json
  • data/variables_interesting.json
  • function-index.json
  • functions.json
  • sections.json
  • strings.json
  • imports.json
  • exports.json
  • relocations.json
  • reachable.json
  • cluster-graph.json
  • triage.json
  • project.json
  • export-manifest.json
  • tocode.log
  • generated export AGENTS.md
  • generated export CLAUDE.md

When the IDA backend is used, the export also contains:

  • <binary>.i64 or <binary>.idb

When --tree is passed, the export also contains:

  • src/tree/**/*.c
  • function-index-tree.json

APK input (.apk, .apks, .xapk) uses the ASC backend and writes instead:

  • src/raw/<package>/**/*.java (one file per class, folders are Java packages; no clusters, no summaries)
  • AndroidManifest.xml, manifest.json, classes.json, package-graph.json, native-libs.json
  • functions.json, function-index.json, strings.json, imports.json, exports.json, sections.json, reachable.json, triage.json, project.json, export-manifest.json (same shapes as the native export where applicable)
  • lib/<abi>/*.so (every native library, always extracted) and native/<abi>/<lib>/ (a full nested ToCode export per library, all ABIs, unless --no-native)
  • data/apk/** (all other entries verbatim), data/res/**/*.xml, data/resources.json
  • tocode.log, generated AGENTS.md and CLAUDE.md

base.apk merges sibling split_*.apk files unless --no-splits; bundles are unpacked and merged. --backend selects the native backend for the .so exports (binja is rejected).

.NET input (a managed PE, an apphost launcher with its .dll, a single-file bundle, or a .nupkg; detected by content, --as-native opts out) uses the .NET backend and writes instead:

  • src/raw/<Assembly>/<Namespace>/**/<Type>.cs and <Type>.il (C# and IL with raw bytecode per top-level type; nested types inside), src/raw/<Assembly>/Properties/AssemblyInfo.cs and Manifest.il, src/raw/<Assembly>/<Assembly>.csproj
  • assemblies.json, types.json, namespace-graph.json, resources.json, container.json, native-libs.json
  • functions.json, function-index.json (c = C#, asm = IL), strings.json, imports.json, exports.json, sections.json, reachable.json, triage.json, project.json, export-manifest.json
  • data/resources/<Assembly>/* (embedded resources, .resources decoded to JSON), data/assemblies/* and data/<bundle|nupkg>/* (extracted container entries)
  • lib/<arch>/* and native/<arch>/<lib>/ for mixed-mode code, bundled native libraries, and P/Invoke targets next to the input (unless --no-native)
  • tocode.log, generated AGENTS.md and CLAUDE.md

Framework/runtime assemblies and native runtime libraries inside bundles and packages are inventoried but only decompiled with --include-framework.

Development

  • Prefer uv for local commands.
  • Package entry point: tocode.
  • Keep generated text, environment variables, and user-visible strings branded as ToCode.
  • Keep status output concise but informative. Progress bars are handled by tqdm through Progress.bar(...).
  • Use apply_patch for manual edits.
  • Do not commit generated export directories such as here/, ls/, or *_decompiler/.

Dependencies

  • Runtime dependencies belong in pyproject.toml and uv.lock.
  • IDA Domain is the preferred backend when available.
  • radare2/r2pipe is a fallback backend.
  • angr is the optional pure-Python fallback backend ([angr] extra).
  • ASC (droidasc, PyPI) is the APK/DEX backend and a core runtime dependency. It pulls androguard. backends/asc.py is the only module that imports it; it pre-imports the modules ASC would otherwise stub in sys.modules and silences androguard's loguru logging. Class decompilation is not thread-safe, so it runs in spawned worker processes (recycled in rounds; do not use max_tasks_per_child, it deadlocks spawn pools on some CPython builds). The big per-method/per-class/per-string JSON documents are streamed row by row (apk_metadata.write_json_rows), never built as one object.
  • APK native libraries are exported by apk_native.py on a background thread, each export_binary in its own spawned process so a backend OOM-kill or crash only fails that library (native-libs.json status). The thread waits for TOCODE_NATIVE_MIN_FREE_MB (default 1024) of free memory before starting each library so it does not starve the DEX pool. TOCODE_WORKER_TMP_DIR also places the unpacked .apks bundle.
  • The .NET backend needs pythonnet (core dependency, pinned per Python version) and a .NET 9+ runtime on the host. Never commit or package DLLs: dnlib 4.5.0 and ICSharpCode.Decompiler 11.1 are downloaded from nuget.org by backends/dotnet_libs.py after the user agrees (asked once, remembered; tocode --setup-dotnet for non-interactive use). Their .nupkg and DLL SHA-256 hashes are pinned in LIBRARIES and checked at download time only (normal runs check presence; --setup-dotnet re-verifies). To bump a version, update both hashes. ICSharpCode.Decompiler 11 needs System.Reflection.Metadata 9, hence .NET 9+. Decompilation runs in spawned worker processes (recycled per round; a batch that crashes or hangs a worker is retried type by type so only the culprit fails). Tests must not require .NET: use tests/dotnet_fixtures.py (synthetic PE/bundles) and fake sessions; runtime-backed tests skip without it.
  • Binary Ninja is an opt-in backend (--backend binja, never auto-selected). The binaryninja module is supplied by the Binary Ninja install (in-UI) or the remote VM, so it is not a pip dependency. rpyc, the client used for the headless path via binja-headless, is a core runtime dependency so the backend works out of the box.
  • Pin new dependencies exactly (==) and capture them in uv.lock.
  • Keep dependencies minimal and tied to exporting a project.

Binary Ninja backend

  • Headless (no enterprise license): run binja-headless inside a running Binary Ninja, then tocode --backend binja --binja-host <ip>. It exports an already loaded view (the binary positional is optional). Configure the connection with --binja-host/--binja-port or TOCODE_BINJA_HOST / TOCODE_BINJA_PORT (defaults 127.0.0.1:18812).
  • Choosing the view (headless): default is the focused view (the one binja-headless exposes as conn.root.bv). --list-binja prints every open view with an index; --binja-view N exports that index; --all-views exports every open view, each into its own folder under -o (or the cwd). Active-context lookup is Qt-main- thread bound and unavailable over RPyc, so the focused view comes from conn.root.bv; open views are enumerated through binaryninjaui.UIContext via the service's remote eval. If no view is open, a binary path argument is remote-loaded as a fallback.
  • In the Binary Ninja UI: call tocode.export_from_binaryview(bv, out_dir) from the scripting console (bv is the live view; binaryninja is imported locally). It runs the same export pipeline as the CLI.
  • The backend renders serially against the live view (parallel_safe = False and excluded from exporter.TIMEOUT_WORKER_BACKENDS): a BinaryView / RPyc connection cannot be pickled into a worker process. Do not add it to the spawned-worker path.

Verification

Run focused checks after changes:

uv run --extra dev pytest -q
python3 -m compileall src tests

Run the full local CI/quality gate when changing shared behavior:

./ci-local.sh

On Windows PowerShell:

powershell -ExecutionPolicy Bypass -File .\ci-local.ps1

For backend-sensitive changes, also run a real export when IDA is available:

uv run tocode /bin/true -o /tmp/tocode-check --backend auto -j 2

Confirm the generated project matches the export contract above.