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
89 changes: 15 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,25 @@
# jdwp-wire

CLI (`jdt`) that turns an Android APK/package into a JDWP session usable from Android Studio / jdb.
Uma coisa que eu gosto de fazer na busca por falhas em aplicativo Android é análise dinâmica com JDWP e o Android Studio. Dali eu mapeio a chamada com o objeto já montado na mão e escrevo o script de Frida com bem menos chute. Chegar nesse ponto era um ritual de apktool, patch, assinatura, install e `adb forward`. O `jdt` faz esse ritual.

This is not MobSF. It is not Frida-first. The main path is **smali/Java + JVM debugger**.
O caminho é smali, Java e o debugger da JVM. Frida entra depois do forward, quando o breakpoint já mostrou o que interceptar. O relato mais longo está em [notes.c1r5.dev/posts/jdwp-wire](https://notes.c1r5.dev/posts/jdwp-wire/).

Status: **MVP wired**. `jdt devices`, `jdt apps`, `jdt pull`, `jdt patch`, `jdt install`, `jdt attach`, `jdt reset`, `jdt targets`. `jdt attach` always pulls and decodes into `.jdt/<pkg>`, patches whatever is still missing, re-signs, and reinstalls, then forwards JDWP. An apktool tree or a patch that is already applied is skipped on its own; the later steps still run. When the official Android CLI is on PATH, install and launch use `android run --debug`; otherwise `adb` + `am`. `jdt attach --studio` writes `.jdt/<pkg>/idea` after the forward and does not launch Android Studio. `--bypass` and `--script` load a Frida companion after the forward and follow its filtered log; `-d` only writes `.jdt/<pkg>/frida/YYYY-MM-DD.log`. SIGINT/SIGTERM, once launch has started, force-stops the package, clears the debug app, and removes the forward; the Frida CLI dies with the command and `frida-server` stays up. An attach that returns without a signal leaves the app waiting for the debugger. `--crypto` on targets is v1. `jdt apps` lists third-party packages (PID when running, running apps first); `--system` includes system packages. `jdt pull <index>` uses that list, and `jdt pull --system <index>` uses the `--system` list.
```bash
go build -o jdt .
go install . # binário no GOBIN: jdwp-wire (module path)
jdt attach br.com.example --port 8700 --studio
```

Happy path (`com.alvo` is the package). With more than one device, pass `-s <serial>` on `pull`, `install`, and `attach`.
O attach puxa o APK pra `.jdt/<package>/`, decodifica, põe `android:debuggable="true"` e a CA de usuário no network security config quando o decode ainda não tem isso, reassina e reinstala. Etapa já feita é skip só dela. O install corre na mesma. Se a Android CLI 1.0 estiver no PATH, o launch sai de `android run --debug`. Sem ela, `adb install` e `am set-debug-app -w`. No fim o forward escuta em IPv4:

```text
┌─ 1. devices ──────────────────────────────────────────┐
│ jdt devices │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 2. pull ─────────────────────────────────────────────┐
│ jdt pull --decode com.alvo │
│ → .jdt/com.alvo/apk/base.apk │
│ → .jdt/com.alvo/decode │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 3. patch ────────────────────────────────────────────┐
│ jdt patch .jdt/com.alvo/decode │
│ debuggable=true + network security config (user CA) │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 4. install ──────────────────────────────────────────┐
│ jdt install .jdt/com.alvo/decode │
│ apktool rebuild, debug sign, adb install -r -d │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 5. attach ───────────────────────────────────────────┐
│ jdt attach com.alvo --port 8700 --studio │
│ repack if not debuggable, then forward + probe │
│ → .jdt/com.alvo/idea │
│ → 15:04:05 [ok] [attach] 127.0.0.1:8700 │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 6. breakpoint ───────────────────────────────────────┐
│ Android Studio → Remote JVM Debug │
│ host 127.0.0.1, port 8700 │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 7. reset ────────────────────────────────────────────┐
│ jdt reset com.alvo │
│ clear-debug-app and remove the JDWP forward │
└───────────────────────────────────────────────────────┘
15:04:40 [ok] [attach] 127.0.0.1:8700
```

```
go build -o jdt .
go install . # binário GOBIN: jdwp-wire (module path)
./jdt --help
./jdt devices
./jdt devices --json
./jdt apps
./jdt apps --system
./jdt apps --json
./jdt pull com.alvo
./jdt pull 2
./jdt pull --decode com.alvo
./jdt pull ./app.apk --package com.alvo
./jdt patch .jdt/com.alvo/decode
./jdt patch --apk ./app.apk --package com.alvo
./jdt install com.alvo
./jdt install .jdt/com.alvo/decode
./jdt install .jdt/com.alvo/apk/base.apk
./jdt attach com.alvo --port 8700
./jdt attach com.alvo --port 8700 --studio
./jdt attach com.alvo --bypass
./jdt attach com.alvo --script sslpinning-bypass,./custom.js -d
./jdt reset com.alvo
./jdt targets com.alvo
./jdt targets .jdt/com.alvo/decode --http
./jdt targets com.alvo --json
```
Eu abro `.jdt/br.com.example/idea` no Android Studio. A run configuration `Remote_Debug` aponta pra `127.0.0.1` na porta impressa. A primeira conexão TCP nessa porta é o handshake JDWP, então o attach deixa o socket quieto até o Studio ligar.

O desenho dos comandos está em [docs/diagram.md](docs/diagram.md). O uso, com o attach no centro, está em [docs/uso.md](docs/uso.md). Análise dinâmica e o hook de Frida estão em [docs/analise.md](docs/analise.md).

Workspace local: `./.jdt/<package>/`. Nada disso vai pro git.

Product contract: [`AGENTS.md`](AGENTS.md).
Contrato do produto: [AGENTS.md](AGENTS.md).
23 changes: 23 additions & 0 deletions docs/analise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Análise dinâmica

No JDWP eu paro num método e olho o objeto que o app já montou. URL, header e body estão nesse objeto, e dali eu escrevo o script de Frida com bem menos chute.

O `jdt targets` lê o smali em `.jdt/<pkg>/decode` e imprime `classe#metodo` pra OkHttp, Retrofit e `HttpURLConnection`. Eu abro `.jdt/<pkg>/idea` no Android Studio, ligo a `Remote_Debug` em `127.0.0.1` na porta do attach e quebro nesse método. "Attach Debugger to Android Process" e o Debug de um projeto Android normal falam com outra sessão. O uso do attach está em [uso.md](uso.md).

O processo sobe em wait-for-debugger. Enquanto eu não ligo o Studio, o app fica parado.

## Hook com Frida

Frida entra quando o breakpoint já mostrou o que interceptar, ou quando o app recusa o depurador, o root ou o proxy. No `jdt` ele é companheiro do attach, depois do forward.

`--bypass` carrega três scripts embutidos, nesta ordem:

- `antiroot-bypass` esconde checagem comum de root e Magisk.
- `antidebug-bypass` esconde debugger conectado e `TracerPid`.
- `sslpinning-bypass` solta pinning TLS comum em Java, pro app falar com a CA de usuário que o patch colocou no network security config.

`--script` carrega um desses nomes ou um `.js` meu, no mesmo ponto.

O `jdt` escreve um `load.js` e o Frida carrega só esse arquivo. O `load.js` avalia o script quando o class loader da `Application` existe. Se `ActivityThread.currentApplication()` já devolve a instância, o script roda na hora, com o class loader dela. Enquanto o processo espera o debugger, `handleBindApplication` já está na stack e `currentApplication()` ainda vem nulo. O script entra em `Application.attach`, com o class loader do contexto, e o `attach` original segue. Isso é antes de `onCreate`. O `Java.perform` do script roda nesse momento, depois que o Studio continua.

A linha que o script manda pro `console.log` (por exemplo `sslpinning-bypass armed`) cai filtrada em `.jdt/<pkg>/frida/AAAA-MM-DD.log`. Sem `-d`, o attach acompanha esse fluxo no stderr. Com `-d`, grava o arquivo e volta. `SIGINT` ou `SIGTERM` mata o CLI do Frida (ou o `frida-session`, no `-d`) e fecha o app. O `frida-server` em `/data/local/tmp/frida-server` continua. Se ele estava parado, o attach sobe ele com `su` antes de carregar o script.
50 changes: 50 additions & 0 deletions docs/diagram.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Diagrama

O `jdt attach` corre esta sequência sozinho, do pull ao forward, e pula a etapa que já está feita. `devices`, `pull`, `patch`, `install` e `reset` são as mesmas etapas, soltas.

`com.alvo` é o package. Com mais de um aparelho, `-s <serial>` no pull, no install e no attach.

```text
┌─ 1. devices ──────────────────────────────────────────┐
│ jdt devices │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 2. pull ─────────────────────────────────────────────┐
│ jdt pull --decode com.alvo │
│ → .jdt/com.alvo/apk/base.apk │
│ → .jdt/com.alvo/decode │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 3. patch ────────────────────────────────────────────┐
│ jdt patch .jdt/com.alvo/decode │
│ debuggable=true + network security config (user CA) │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 4. install ──────────────────────────────────────────┐
│ jdt install .jdt/com.alvo/decode │
│ apktool rebuild, debug sign, adb install -r -d │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 5. attach ───────────────────────────────────────────┐
│ jdt attach com.alvo --port 8700 --studio │
│ pull, decode, patch, sign, install, forward │
│ → .jdt/com.alvo/idea │
│ → 15:04:05 [ok] [attach] 127.0.0.1:8700 │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 6. breakpoint ───────────────────────────────────────┐
│ Android Studio → Remote JVM Debug │
│ host 127.0.0.1, port 8700 │
└────────────────────────────┬──────────────────────────┘
│
v
┌─ 7. reset ────────────────────────────────────────────┐
│ jdt reset com.alvo │
│ clear-debug-app and remove the JDWP forward │
└───────────────────────────────────────────────────────┘
```
Loading
Loading