Skip to content

Commit 97b9917

Browse files
authored
Merge pull request #13 from buzzer-re/dev
Add support to APK files using DroidASC
2 parents b6d7f1c + 3350b0a commit 97b9917

15 files changed

Lines changed: 5989 additions & 90 deletions

‎AGENTS.md‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AGENTS
22

3-
This repository contains ToCode, a Python-only binary exporter. ToCode takes one binary or IDA database path and writes one source-like project directory for reverse-engineering agents.
3+
This repository contains ToCode, a Python-only binary exporter. ToCode takes one binary, IDA database, or Android APK path and writes one source-like project directory for reverse-engineering agents.
44

55
## Scope
66

@@ -15,7 +15,8 @@ This repository contains ToCode, a Python-only binary exporter. ToCode takes one
1515
- `src/tocode/cli.py`: command-line entry point for `tocode`.
1616
- `src/tocode/__init__.py`: package version and `export_from_binaryview()`, the in-UI Binary Ninja library entry.
1717
- `src/tocode/analysis.py`: backend-neutral binary inventory and call graph normalization.
18-
- `src/tocode/backends/`: IDA Domain, radare2, angr, and Binary Ninja (`binja.py`) session adapters.
18+
- `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).
19+
- `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`.
1920
- `src/tocode/exporter.py`: project writer, function rendering, worker-session rendering, generated export `AGENTS.md`.
2021
- `src/tocode/metadata.py`: JSON metadata and triage documents.
2122
- `src/tocode/cluster.py`: call-graph clustering.
@@ -59,6 +60,17 @@ When `--tree` is passed, the export also contains:
5960
- `src/tree/**/*.c`
6061
- `function-index-tree.json`
6162

63+
APK input (`.apk`, `.apks`, `.xapk`) uses the ASC backend and writes instead:
64+
65+
- `src/raw/<package>/**/*.java` (one file per class, folders are Java packages; no clusters, no summaries)
66+
- `AndroidManifest.xml`, `manifest.json`, `classes.json`, `package-graph.json`, `native-libs.json`
67+
- `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)
68+
- `lib/<abi>/*.so` (every native library, always extracted) and `native/<abi>/<lib>/` (a full nested ToCode export per library, all ABIs, unless `--no-native`)
69+
- `data/apk/**` (all other entries verbatim), `data/res/**/*.xml`, `data/resources.json`
70+
- `tocode.log`, generated `AGENTS.md` and `CLAUDE.md`
71+
72+
`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).
73+
6274
## Development
6375

6476
- Prefer `uv` for local commands.
@@ -74,6 +86,8 @@ When `--tree` is passed, the export also contains:
7486
- IDA Domain is the preferred backend when available.
7587
- radare2/r2pipe is a fallback backend.
7688
- angr is the optional pure-Python fallback backend (`[angr]` extra).
89+
- 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.
90+
- 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_APK_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.
7791
- Binary Ninja is an opt-in backend (`--backend binja`, never auto-selected). The
7892
`binaryninja` module is supplied by the Binary Ninja install (in-UI) or the
7993
remote VM, so it is not a pip dependency. `rpyc`, the client used for the

‎README.md‎

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ToCode
22

3-
ToCode exports a binary or IDA database into a source-like project tree: raw recovered C, matching assembly, function summaries, section data, optional IDA database, and metadata that coding agents can read directly.
3+
ToCode exports a binary, IDA database, or Android APK into a source-like project tree: raw recovered C (or Java for APKs), matching assembly, function summaries, section data, optional IDA database, and metadata that coding agents can read directly.
44

55
## Why
66

@@ -23,11 +23,22 @@ sample_decompiler/
2323
src/raw/**/*.c
2424
src/raw/**/*.asm
2525
src/raw/**/*.summary
26+
src/raw/<package>/**/*.java # Only for APK files (replaces the .c/.asm/.summary tree)
2627
include/*.h
2728
include/*.types.h
2829
data/*.bin
2930
data/variables.json
3031
data/variables_interesting.json
32+
data/apk/<apk>/** # Only for APK files: every non-code APK entry, verbatim
33+
data/res/<apk>/**/*.xml # Only for APK files: binary XML resources decoded to text
34+
data/resources.json # Only for APK files: decoded resources.arsc
35+
lib/<abi>/*.so # Only for APK files: extracted native libraries
36+
native/<abi>/<lib>/ # Only for APK files: full nested ToCode export per .so
37+
AndroidManifest.xml # Only for APK files
38+
manifest.json # Only for APK files
39+
classes.json # Only for APK files
40+
package-graph.json # Only for APK files (replaces cluster-graph.json)
41+
native-libs.json # Only for APK files
3142
function-index.json
3243
functions.json
3344
types.json
@@ -47,11 +58,19 @@ sample_decompiler/
4758
| Path | Description |
4859
| --- | --- |
4960
| `src/raw` | Decompiled C-like output, assembly, and summaries. Grouped by call-graph cluster, or by the original source file/directory when the binary has debug info (DWARF). |
50-
| `include` | Generated headers, including `*.types.h` with the structs/enums/typedefs recovered from the binary. |
61+
| `src/raw/<package>/**/*.java` | **Only for APK files.** Decompiled Java (ASC + androguard DAD), one file per class, folders follow Java packages; inner classes are `Outer$Inner.java`. No clustering, no `.summary` files. |
62+
| `include` | Generated headers, including `*.types.h` with the structs/enums/typedefs recovered from the binary. Not written for APK files. |
5163
| `data` | Raw section dumps and variable metadata. |
64+
| `data/apk`, `data/res`, `data/resources.json` | **Only for APK files.** Every non-code entry of each APK in the set verbatim, `res/**/*.xml` decoded from binary XML, and the decoded `resources.arsc` tables. |
65+
| `lib/<abi>/*.so` | **Only for APK files.** Every native library found in the APK set (all ABIs), always extracted. |
66+
| `native/<abi>/<lib>/` | **Only for APK files.** A complete nested ToCode export for each native library (own `AGENTS.md` with an Origin section naming the APK, `src/raw/*.c`, `functions.json`, `exports.json`, ...). Skipped with `--no-native`. |
67+
| `AndroidManifest.xml` / `manifest.json` | **Only for APK files.** Decoded manifest and its parsed form: package, versions, SDKs, permissions, components with intent filters and exported state, application attributes, split manifests. |
68+
| `classes.json` | **Only for APK files.** Every class with superclass, interfaces, access flags, fields, methods, source file, and Java file/line ranges. |
69+
| `package-graph.json` | **Only for APK files.** Inter-package call graph (the APK counterpart of `cluster-graph.json`). |
70+
| `native-libs.json` | **Only for APK files.** ABI, hash, source APK, export directory, and decompilation status of every native library. |
5271
| `types.json` | Catalog of types recovered from the binary's debug info or type library, with C declarations. |
53-
| `*.json` | Functions (with recovered types and original source decl file/line), sections, strings, imports, exports, relocations, reachability, clusters, triage, project metadata, and export manifest. |
54-
| `tocode.log` | Export log with checkpoint, resume, and per-function render history. |
72+
| `*.json` | Functions (with recovered types and original source decl file/line), sections, strings, imports, exports, relocations, reachability, clusters, triage, project metadata, and export manifest. For APK files the same documents describe DEX methods, strings, framework imports, exported components/JNI methods, and reachability from manifest components; `relocations.json`, `cluster-graph.json`, and `types.json` are not written. |
73+
| `tocode.log` | Export log with checkpoint, resume, and per-function render history. For APK files it also carries the native library export status. |
5574
| `AGENTS.md` / `CLAUDE.md` | Instructions for agents analyzing the exported binary. |
5675
| `src/tree` | Optional scanner-friendly C output when `--tree` is used. |
5776

@@ -76,6 +95,23 @@ Three backends are supported, selected with `--backend` (default `auto`, which p
7695

7796
Other disassemblers may be added in the future.
7897

98+
### Android APKs
99+
100+
`tocode app.apk` (also `.apks`/`.xapk` bundles) uses [ASC](https://github.com/MG1937/ASC) (`droidasc`, a core dependency) for the DEX side and the regular native backends for every shared object in the package:
101+
102+
- `src/raw/<package>/<Class>.java`: one decompiled Java file per class, folders follow Java packages (no clustering, no summaries).
103+
- `AndroidManifest.xml` + `manifest.json`: decoded and parsed manifest (permissions, components with intent filters and exported state, application attributes, split manifests).
104+
- `classes.json`, `functions.json`, `function-index.json`, `strings.json`, `imports.json`, `exports.json` (exported components + JNI methods), `reachable.json` (from manifest components), `package-graph.json`, `sections.json`, `triage.json`.
105+
- `lib/<abi>/*.so`: every native library extracted; `native/<abi>/<lib>/`: a complete nested ToCode export per library (all ABIs), produced on a background thread while the DEX side decompiles. `native-libs.json` records the status of each (each library runs in its own process; one failing or being OOM-killed never fails the APK export, and the native thread waits for `TOCODE_APK_NATIVE_MIN_FREE_MB`, default 1024 MB, of free memory before each library). Pass `--no-native` to skip the native decompilation (libraries are still extracted). `--backend` picks the native backend (`auto`/`ida`/`r2`/`angr`; `binja` is not supported for APKs).
106+
- `data/apk/**`: every other APK entry verbatim; `data/res/**/*.xml` and `data/resources.json`: decoded binary XML and `resources.arsc`.
107+
108+
`base.apk` automatically merges sibling `split_*.apk` files (config and ABI splits) into the same project; `--no-splits` exports it alone. The default output directory is `<manifest package>_decompiler`.
109+
110+
```bash
111+
tocode base.apk # DEX + all splits + native libs (IDA/r2/angr)
112+
tocode app.apks --no-native -j 4 # bundle, DEX/Android side only
113+
```
114+
79115

80116
### Using
81117

‎pyproject.toml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ description = "Export compiled binaries as source-like projects for reverse engi
99
readme = "README.md"
1010
requires-python = ">=3.10"
1111
dependencies = [
12+
"droidasc==0.1.1.post1",
1213
"ida-domain==0.5.0",
1314
"idapro==0.0.9",
1415
"r2pipe==1.9.8",
@@ -59,6 +60,11 @@ ignore_missing_imports = true
5960
module = ["binaryninja.*"]
6061
ignore_missing_imports = true
6162

63+
# droidasc (ASC, the APK backend) and androguard ship no type stubs.
64+
[[tool.mypy.overrides]]
65+
module = ["droidasc.*", "androguard.*"]
66+
ignore_missing_imports = true
67+
6268
[tool.setuptools]
6369
package-dir = { "" = "src" }
6470

0 commit comments

Comments
 (0)