Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: CI

on:
push:
branches: ["**"]
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: "3.12"
- run: uv sync
- run: uv run ruff check src/
- run: uv run ruff format --check src/
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,26 @@ Optional (for `analyze`):
| [Il2CppDumper](https://github.com/Perfare/Il2CppDumper) | IL2CPP symbol extraction |
| `strings` | Native `.so` string extraction |

Optional Python extras:

```bash
uv pip install "enma[unity]" # UnityPy for asset extraction
```

---

## Installation

```bash
git clone https://github.com/yourname/enma
git clone https://github.com/ykus4/enma
cd enma
uv sync
```

Install git hooks for development:
Activate the virtual environment (optional — prefix commands with `uv run` instead):

```bash
source .venv/bin/activate
```

For Unity asset extraction, install the optional extra:

```bash
uv run pre-commit install
uv pip install "enma[unity]"
```

---
Expand Down Expand Up @@ -212,6 +212,8 @@ uv run enma repack app.apk -o app-gadget.apk --arch arm64
| `--arch` | `arm64` (default) / `arm` / `x86_64` / `x86` |
| `--keep-workdir` | Keep the intermediate smali directory |

Requires on `PATH`: `apktool`, `zipalign`, and `apksigner` (Android build-tools) or `jarsigner` (JDK).

Pipeline:
1. Download `frida-gadget-{ver}-android-{arch}.so.xz` → cached in `~/.cache/enma/`
2. `apktool d` — decode APK
Expand All @@ -230,11 +232,7 @@ uv run enma unity <dump_dir>
uv run enma unity ./dump -o ./extracted
```

Requires `UnityPy`:

```bash
uv pip install "enma[unity]"
```
Requires the `enma[unity]` extra (see [Installation](#installation)).

Extracts from every `.unity3d` bundle in the dump directory:

Expand Down Expand Up @@ -569,6 +567,7 @@ enma/
├── bypass/ # ssl, crypto, anti_detect, anti_tamper, safetynet
├── network/ # http, websocket, protobuf, binder
├── storage/ # sqlite, fileio, dlopen
├── ue4/ # ue4_sdk, ue4_pak, ue4_blueprint
└── mem/ # memscan, mempatch
```

Expand Down
34 changes: 22 additions & 12 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ enma is a two-process system: a **Python host** running on the analyst's machine
│ Analyst Machine │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ enma CLI (Python) │ │
│ │ enma CLI (Python) │ │
│ │ │ │
│ │ cli.py ──► device.py (frida-server deploy) │ │
│ │ ──► repack.py (APK gadget injection) │ │
│ │ ──► mem.py (memscan / mempatch REPL) │ │
│ │ ──► analyze.py (post-dump analysis) │ │
│ │ ──► report.py (HTML report generation) │ │
│ │ ──► unity.py (AssetBundle extraction) │ │
│ │ ──► ue4.py (UE4 runtime analysis) │ │
│ └────────────────────────┬────────────────────────────────────┘ │
│ │ Frida RPC / message bus │
│ │ (USB / TCP) │
Expand All @@ -37,15 +38,15 @@ enma is a two-process system: a **Python host** running on the analyst's machine
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Target App Process │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Java VM │ │ ART / JIT│ │ Native │ │ libc / │ │ │
│ │ │ (Dalvik) │ │ (libart) │ │ Libs │ │ BoringSSL│ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ └─────────────┴─────────────┴──────────────┘ │ │
│ │ ▲ │ │
│ │ Frida Interceptors │ │
│ │ (JS agents injected) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Java VM │ │ ART / JIT│ │ Native │ │ libc / │ │ │
│ │ │ (Dalvik) │ │ (libart) │ │ Libs │ │ BoringSSL│ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ └─────────────┴─────────────┴──────────────┘ │ │
│ │ ▲ │ │
│ │ Frida Interceptors │ │
│ │ (JS agents injected) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
Expand All @@ -54,7 +55,7 @@ enma is a two-process system: a **Python host** running on the analyst's machine

## Agent Categories

The 20 agents are grouped into 5 functional categories, each in its own subdirectory.
The 23 agents are grouped into 6 functional categories, each in its own subdirectory.

```
agents/
Expand Down Expand Up @@ -89,6 +90,11 @@ agents/
│ ├── fileio_agent.js File open/write/delete monitoring
│ └── dlopen_agent.js Dynamic library load monitoring
├── ue4/ ← Unreal Engine 4 runtime analysis
│ ├── ue4_sdk_agent.js GNames/GUObjectArray dump → SDK JSON (UE4.23–4.27)
│ ├── ue4_pak_agent.js PAK file discovery and dump
│ └── ue4_blueprint_agent.js Blueprint ProcessEvent call tracer
└── mem/ ← Interactive memory manipulation
├── memscan_agent.js CheatEngine-style value scanner
└── mempatch_agent.js Write / NOP / freeze memory
Expand Down Expand Up @@ -209,6 +215,7 @@ enma mempatch com.example.game 0x7ff1a2b4 -t int32 -v 99999
│ "unity" ──► run_unity() ◄── unity.py │
│ "setup" ──► run_setup() ◄── device.py │
│ "repack" ──► run_repack() ◄── repack.py │
│ "ue4" ──► run_ue4() ◄── ue4.py │
│ "list" ──► list_apps() │
└──────────────────────────────────────────────────────────────────┘

Expand Down Expand Up @@ -327,7 +334,10 @@ Each agent operates at one or more of the following hook layers:
│ Layer 2: Native libraries │
│ Interceptor.attach(Module.findExportByName("libssl.so", …)) │
│ Interceptor.attach(Module.findExportByName("libcrypto.so", …)) │
│ → ssl (BoringSSL), crypto (EVP_*), dlopen (libdl) │
│ Interceptor.attach(Module.findExportByName("libUE4.so", …)) │
│ → ssl (BoringSSL), crypto (EVP_*), dlopen (libdl), │
│ ue4_sdk (GNames/GUObjectArray), ue4_pak (FPakFile), │
│ ue4_blueprint (ProcessEvent) │
├─────────────────────────────────────────────────────────────────┤
│ Layer 1: libc / syscall │
│ Interceptor.attach(Module.findExportByName("libc.so", "open")) │
Expand Down
48 changes: 32 additions & 16 deletions src/enma/repack.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,22 @@ def _ensure_debug_keystore(keystore: Path) -> None:
logger.info("Generating debug keystore ...")
keytool_args = [
"-genkeypair",
"-keystore", str(keystore),
"-alias", "androiddebugkey",
"-keyalg", "RSA",
"-keysize", "2048",
"-validity", "10000",
"-storepass", "android",
"-keypass", "android",
"-dname", "CN=Android Debug,O=Android,C=US",
"-keystore",
str(keystore),
"-alias",
"androiddebugkey",
"-keyalg",
"RSA",
"-keysize",
"2048",
"-validity",
"10000",
"-storepass",
"android",
"-keypass",
"android",
"-dname",
"CN=Android Debug,O=Android,C=US",
]
_run("keytool", *keytool_args)

Expand Down Expand Up @@ -137,20 +145,28 @@ def _sign_apk(signer: str, signer_path: str, src: Path, dest: Path, keystore: Pa
_run(
signer_path,
"sign",
"--ks", str(keystore),
"--ks-pass", "pass:android",
"--key-pass", "pass:android",
"--ks-key-alias", "androiddebugkey",
"--out", str(dest),
"--ks",
str(keystore),
"--ks-pass",
"pass:android",
"--key-pass",
"pass:android",
"--ks-key-alias",
"androiddebugkey",
"--out",
str(dest),
str(src),
)
else:
shutil.copy(src, dest)
_run(
signer_path,
"-keystore", str(keystore),
"-storepass", "android",
"-keypass", "android",
"-keystore",
str(keystore),
"-storepass",
"android",
"-keypass",
"android",
str(dest),
"androiddebugkey",
)
Expand Down
Loading