Skip to content

Commit 28d653d

Browse files
h0tak88rclaude
andcommitted
feat(apk-auditor): apk-mitm patching — trust user CAs + disable NSC pinning, re-sign
Adds a "Patch for MITM" action to the APK Auditor: the loaded APK is uploaded to a new server endpoint, patched the way niklashigi/apk-mitm does, re-signed, and downloaded back. - internal/tools/apkmitm: faithful Go reimplementation of apk-mitm's default patch — apktool decode → network security config trusting system+user CAs in both base-config and debug-overrides (cleartext permitted) → overwrite the app's existing config file (dropping its pin-set) or create + reference a new one → set manifest debuggable + networkSecurityConfig → apktool build → uber-apk-signer re-sign. Same scope as apk-mitm (NSC-based pinning only; programmatic pinning still needs Frida). Unit-tested (NSC contents, existing vs new config, pin-set removal, manifest edits). - POST /api/apk/mitm: authed multipart upload (600 MiB cap), temp-file handling, 503 with guidance when the toolchain is absent, streams the patched APK back. - APK Auditor UI: "Patch for MITM" button + upload/progress/download (cookie→Bearer). - README MITM section updated to the new upload→patch→direct-download flow. Tooling (apktool + uber-apk-signer + JRE) already ships in the Docker image, so end-to-end patching runs there; the in-browser static analysis is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1188453 commit 28d653d

7 files changed

Lines changed: 500 additions & 13 deletions

File tree

README.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Results are automatically uploaded to **Cloudflare R2 storage** and linked direc
3434
| **JavaScript** | Extract secrets, API endpoints, auth tokens from JS files |
3535
| **GitHub Recon** | Org-level and repo-level scanning for secrets, dependency confusion |
3636
| **APK Auditor** | Browser-based Android analysis: DEX decompiler, manifest + cert parsing, tracker detection, MASVS mapping, and regex-driven findings with APX secret patterns. (Based on [apkauditor](https://github.com/thecybersandeep/apkauditor) by @thecybersandeep) |
37-
| **MITM Patch** | Fetch any Android app by Package ID → auto-patch `network_security_config.xml` → re-sign → R2 download link in one click |
37+
| **MITM Patch** | One-click **Patch for MITM** in the APK Auditor → server runs `apktool` + `uber-apk-signer` to trust user CAs, disable cert pinning, and re-sign → direct download of the patched APK |
3838
| **IPA Auditor** | Browser-based iOS IPA analysis: plist + Mach-O inspection, binary strings extraction, and findings tab powered by 200+ regex signatures plus MASVS-style rules. (Based on [ipaauditor](https://github.com/thecybersandeep/ipaauditor) by @thecybersandeep) |
3939
| **ADB Auditor** | Browser-based ADB security tool: USB device inspection, app enumeration, logcat tailing, file pull, activity launching. (Based on [adbauditor](https://github.com/thecybersandeep/adbauditor) by @thecybersandeep) |
4040
| **Misconfigs** | 100+ service misconfiguration checks |
@@ -215,23 +215,21 @@ The **APK Auditor** is a fully browser-based static analysis tool available at `
215215
- Regex presets and bulk pattern scans for secrets/tokens across code and resources
216216
- OWASP MASVS aligned reporting — one-click export
217217

218-
**Remote Fetch by Package ID (server-side, with MITM patch):**
218+
**MITM Patch (server-side, `apk-mitm` style):**
219219

220220
```bash
221-
# Via the dashboard UI — click "Fetch Package ID" in the APK Auditor page
222-
# Enter the package ID, optionally enable MITM patch, click Start
221+
# In the APK Auditor page, load a .apk, then click "Patch for MITM"
223222
```
224223

225-
What happens:
226-
1. Downloads the APK from APKPure (supports `.xapk` / split APKs automatically)
227-
2. *(Optional)* Patches `network_security_config.xml` to trust user-installed CAs + disables certificate pinning
228-
3. Re-signs with `uber-apk-signer` and uploads the patched APK to R2
229-
4. Shows a **download panel** in the Auditor UI with direct R2 links for:
230-
- Original APK
231-
- MITM Patched APK (if requested)
232-
5. Automatically loads the APK into the browser auditor for analysis
224+
What happens (runs `apktool` + `uber-apk-signer` on the server):
225+
1. Decodes the APK with `apktool`
226+
2. Injects a network security config that trusts user-installed CAs and disables certificate pinning, and sets `android:networkSecurityConfig` + `android:debuggable` on the manifest
227+
3. Rebuilds and re-signs the APK with a debug key (`uber-apk-signer`)
228+
4. Streams the patched, re-signed APK straight back as a **direct download** — uninstall the original, install this one on your test device, and you can intercept its HTTPS traffic with Burp/mitmproxy
233229

234-
> **Scan records from APK Auditor are hidden from the main Scans dashboard** — they exist only within the Auditor context.
230+
> Requires the Docker image (it bundles `apktool` + `uber-apk-signer` + a JRE). The in-browser analysis above still runs entirely in the tab; only the MITM patch uploads the APK to the server.
231+
232+
> **The APK Auditor never creates records in the main Scans dashboard** — it runs in its own context.
235233
236234
### Mobile Application Analysis (IPA Auditor)
237235

internal/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,8 @@ func SetupAPI() *gin.Engine {
594594
apiGroup.GET("/nuclei/templates", apiListNucleiTemplates)
595595
// Security Lab — JWT HMAC secret brute-force (client-side analyzer calls this)
596596
apiGroup.POST("/jwt/brute", apiJWTBrute)
597+
// APK Auditor — patch an uploaded APK for MITM (trust user CAs + disable pinning)
598+
apiGroup.POST("/apk/mitm", apiAPKMitm)
597599
// Report Templates
598600
apiGroup.GET("/report-templates", apiListReportTemplates)
599601
apiGroup.GET("/report-templates/export", apiExportReportTemplates)

internal/api/apk_mitm.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package api
2+
3+
import (
4+
"net/http"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/gin-gonic/gin"
10+
"github.com/h0tak88r/AutoAR/internal/tools/apkmitm"
11+
)
12+
13+
// maxAPKUpload caps the uploaded APK size for the MITM patch endpoint.
14+
const maxAPKUpload = 600 << 20 // 600 MiB
15+
16+
// apiAPKMitm patches an uploaded APK for HTTPS interception — it makes the app
17+
// trust user-installed CAs and disables certificate pinning (apk-mitm style),
18+
// re-signs it with a debug key, and streams the patched APK back as a download.
19+
// The patching runs apktool + uber-apk-signer server-side and can take a few
20+
// minutes for large apps.
21+
func apiAPKMitm(c *gin.Context) {
22+
// Fail fast (and clearly) if the server toolchain is missing — e.g. when not
23+
// running inside the Docker image that bundles apktool + uber-apk-signer.
24+
if err := apkmitm.CheckTools(); err != nil {
25+
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "APK MITM tooling unavailable: " + err.Error()})
26+
return
27+
}
28+
29+
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxAPKUpload)
30+
file, err := c.FormFile("apk")
31+
if err != nil {
32+
c.JSON(http.StatusBadRequest, gin.H{"error": "no APK uploaded (expected multipart field 'apk')"})
33+
return
34+
}
35+
if !strings.HasSuffix(strings.ToLower(file.Filename), ".apk") {
36+
c.JSON(http.StatusBadRequest, gin.H{"error": "file must be a .apk"})
37+
return
38+
}
39+
40+
work, err := os.MkdirTemp("", "apkmitm-*")
41+
if err != nil {
42+
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create work directory"})
43+
return
44+
}
45+
defer os.RemoveAll(work)
46+
47+
in := filepath.Join(work, "input.apk")
48+
if err := c.SaveUploadedFile(file, in); err != nil {
49+
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save upload: " + err.Error()})
50+
return
51+
}
52+
53+
out, err := apkmitm.Patch(in, work)
54+
if err != nil {
55+
c.JSON(http.StatusInternalServerError, gin.H{"error": "patch failed: " + err.Error()})
56+
return
57+
}
58+
59+
// Stream the patched APK back (written synchronously before the deferred cleanup).
60+
base := strings.TrimSuffix(filepath.Base(file.Filename), filepath.Ext(file.Filename))
61+
c.FileAttachment(out, base+"-patched.apk")
62+
}

internal/api/ui/apkauditor/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ <h2 id="appName">App Name</h2>
150150
<button class="export-item" data-export="sarif" role="menuitem" type="button"><span class="export-item-name">SARIF 2.1</span><span class="export-item-desc">GitHub Code Scanning</span></button>
151151
</div>
152152
</div>
153+
<button class="btn btn-ghost" id="mitmPatchBtn" type="button" title="Patch this APK to trust user CAs &amp; disable certificate pinning, then download the re-signed APK">
154+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 2 4 5v6c0 5 3.5 8 8 11 4.5-3 8-6 8-11V5l-8-3z"/><path d="m9 12 2 2 4-4"/></svg>
155+
<span id="mitmPatchBtnLabel">Patch for MITM</span>
156+
</button>
153157
<button class="btn btn-primary" id="newScanBtn" type="button">
154158
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
155159
New Scan

internal/api/ui/apkauditor/src/main.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,65 @@ function setupPreviewRotator() {
14621462
}, 3600);
14631463
}
14641464

1465+
// The auditor is served in an iframe/tab where the dashboard sets a JS-readable
1466+
// autoar_token cookie; the /api auth middleware wants it as a Bearer header.
1467+
function getAuthTokenFromCookie() {
1468+
const m = document.cookie.match(/(?:^|;\s*)autoar_token=([^;]+)/);
1469+
return m ? decodeURIComponent(m[1]) : '';
1470+
}
1471+
1472+
// patchApkForMitm uploads the currently-loaded APK to the server, which patches
1473+
// it (trust user CAs + disable cert pinning) and re-signs it, then downloads the
1474+
// patched APK. The patching is server-side (apktool + uber-apk-signer) and can
1475+
// take a few minutes — this is the one action where the APK leaves the tab.
1476+
async function patchApkForMitm() {
1477+
const btn = $('#mitmPatchBtn');
1478+
const label = $('#mitmPatchBtnLabel');
1479+
if (!State.currentFile) {
1480+
toast('Load an .apk first, then patch it for MITM', 'error');
1481+
return;
1482+
}
1483+
if (!/\.apk$/i.test(State.currentFile.name)) {
1484+
toast('MITM patching only works on .apk files', 'error');
1485+
return;
1486+
}
1487+
const origLabel = label ? label.textContent : 'Patch for MITM';
1488+
if (btn) btn.disabled = true;
1489+
if (label) label.textContent = 'Patching…';
1490+
toast('Uploading & patching APK — apktool + re-signing runs on the server and can take a few minutes…', 'info');
1491+
try {
1492+
const fd = new FormData();
1493+
fd.append('apk', State.currentFile, State.currentFile.name);
1494+
const tok = getAuthTokenFromCookie();
1495+
const resp = await fetch('/api/apk/mitm', {
1496+
method: 'POST',
1497+
headers: tok ? { Authorization: 'Bearer ' + tok } : {},
1498+
body: fd,
1499+
});
1500+
if (!resp.ok) {
1501+
let msg = 'HTTP ' + resp.status;
1502+
try { const j = await resp.json(); if (j && j.error) msg = j.error; } catch (_) { /* non-JSON */ }
1503+
throw new Error(msg);
1504+
}
1505+
const blob = await resp.blob();
1506+
const base = State.currentFile.name.replace(/\.apk$/i, '');
1507+
const url = URL.createObjectURL(blob);
1508+
const a = document.createElement('a');
1509+
a.href = url;
1510+
a.download = base + '-patched.apk';
1511+
document.body.appendChild(a);
1512+
a.click();
1513+
a.remove();
1514+
setTimeout(() => URL.revokeObjectURL(url), 4000);
1515+
toast('Patched APK downloaded — uninstall the original, then install this one on your test device.', 'success');
1516+
} catch (e) {
1517+
toast('MITM patch failed: ' + e.message, 'error');
1518+
} finally {
1519+
if (btn) btn.disabled = false;
1520+
if (label) label.textContent = origLabel;
1521+
}
1522+
}
1523+
14651524
function init() {
14661525
setupGlobalErrors();
14671526
if ('serviceWorker' in navigator) {
@@ -1476,6 +1535,7 @@ function init() {
14761535
setupCursorSpot();
14771536
setupPreviewRotator();
14781537
$('#newScanBtn') && $('#newScanBtn').addEventListener('click', () => $('#fileInput').click());
1538+
$('#mitmPatchBtn') && $('#mitmPatchBtn').addEventListener('click', patchApkForMitm);
14791539
$('#heroCta') && $('#heroCta').addEventListener('click', () => $('#fileInput').click());
14801540
$('#downloadFileBtn') && $('#downloadFileBtn').addEventListener('click', downloadCurrentFile);
14811541
document.querySelectorAll('a.logo, .logo[href]').forEach(el => {

0 commit comments

Comments
 (0)