| Tested on | Kali GNU/Linux Rolling 2026.2 |
|---|---|
| Package | golang-go 2:1.26~1jadx 1.5.6-0kali1dex2jar 2.1~nightly-28-0kali2apktool 2.7.0 (independent manifest check)APKHunt commit 386a0d5 (Cyber-Buddy/APKHunt) |
| Applies to | Linux; tested here on Kali Linux. Upstream states the tool was tested on Linux only. |
| Privilege | Normal user for clone and scans; sudo for apt installs |
| Input | InsecureShop v1.0 training APK (archived Optiv release) |
| Scope | Static APK scan with APKHunt plus manual validation of findings. No emulator required. Does not cover MobSF dynamic analysis, Frida, or runtime instrumentation. |
| Related guides | Frida Android tutorial Android Studio AVD lab Genymotion Desktop lab |
APKHunt is a command-line static analyzer for Android APK files. This tutorial installs it on Kali, scans the intentionally vulnerable InsecureShop APK, and then manually checks which findings are real, noisy, or missed by the scanner.
Static analysis means the tool reads the APK without running it on a phone or emulator. APKHunt is focused on CLI-based static analysis, while MobSF provides a broader web-based static and dynamic workflow. Use APKHunt for a fast first pass; open MobSF when you want a UI or runtime testing. When you move to dynamic work, use either the free Android Studio AVD lab or Genymotion on the host before Frida or MobSF Dynamic Analyzer.
What APKHunt does and its limitations
APKHunt is a terminal tool for Android APK files. Upstream documents Linux as the tested platform; this article runs it on Kali. You give it an APK path. Behind the scenes it:
- Converts the app bytecode with
dex2jar - Decompiles readable Java-like sources with
jadx - Searches those files and the app manifest for known risky patterns
Those patterns are labeled with older OWASP mobile checklist IDs (MASVS v1.5 / MSTG names such as MSTG-STORAGE-2). Treat those IDs as scanner references, not as proof that the app currently fails MASVS.
Keep these limits in mind before you trust a hit:
- APKHunt mainly performs pattern-based checks over decompiled files; it does not perform complete interprocedural data-flow analysis.
- A matching line means "this pattern appeared here," not "I proved an exploit."
- The tool does not grade findings as Critical / High / Medium. You decide severity after you read the code.
- There is no web dashboard. Output stays in the terminal (and optional log files next to the APK).
- APKHunt still runs on current Kali as a lightweight CLI triage tool, but its public rule taxonomy remains based on MASVS v1.5.0 / MSTG-era identifiers. Use it for static leads rather than current MASVS compliance or runtime validation.
Later sections show those limits in practice: noisy library hits, generic advice text, and a wrong "zero exported activities" count.
APKHunt vs MobSF
Choose the tool that matches the job. For the broader static-plus-dynamic workflow, use the MobSF Android pentesting guide.
| Dimension | APKHunt | MobSF |
|---|---|---|
| Role | Focused CLI static scanning and reporting | Broader static and dynamic platform |
| Interface | CLI only | Web UI (+ REST API) |
| Analysis model | Decompile + pattern checks on disk | Static modules plus Dynamic Analyzer on a device/emulator |
| Output | Terminal + TXT (and a browser-viewable HTML log with -l) |
Interactive reports, scores, screenshots, runtime data |
| Best when | Fast MASVS v1.5-style SAST, batch folders, file-path evidence | Interactive review, Frida, traffic, runtime proof |
| Not a substitute for | Emulator dynamic tests | Lightweight one-shot CLI SAST in a script |
Start with APKHunt when you only need static leads. Switch to MobSF when you need runtime proof or a richer dashboard.
Install APKHunt on Kali Linux
APKHunt needs three helpers: Go (to run the tool), jadx (to decompile), and dex2jar (to convert DEX to JAR). Install them with apt:
sudo apt-get update && sudo apt-get install -y jadx dex2jar golang-go gitConfirm Go is available:
go versionSample output:
go version go1.26.7 linux/amd64This lab used Go 1.26.7. If APKHunt fails to build on a different Go release, check the current upstream repository before troubleshooting the scanner itself. Confirm jadx next:
jadx --versionSample output:
1.5.6jadx is installed and on PATH. Download APKHunt from GitHub into your home directory:
git clone --depth 1 https://github.com/Cyber-Buddy/APKHunt.git "${HOME}/APKHunt"The clone creates ${HOME}/APKHunt with the scanner source inside. Record the short commit hash so findings stay tied to a known revision:
git -C "${HOME}/APKHunt" rev-parse --short HEADSample output:
386a0d5This article's implementation notes refer to that commit. Show the built-in help. The first run compiles briefly; the entrypoint file is lowercase apkhunt.go:
cd "${HOME}/APKHunt" && go run apkhunt.go -hSample output (banner trimmed):
OWASP MASVS Static Analyzer
[+] APKHunt - a comprehensive static code analysis tool for Android apps
[+] Based on: OWASP MASVS - https://mobile-security.gitbook.io/masvs/
APKHunt Usage:
go run APKHunt.go [options] {.apk file}
Options:
-h For help
-p Provide a single apk file-path
-m Provide the folder-path for multiple apk scanning
-l For logging (.txt file)
Note:
- Tested on linux only!
- Keep tools such as jadx, dex2jar, go, grep, etc.! installedRead three flags from that help: -p for one APK, -m for a folder of APKs, and -l for log files. The built-in help still shows APKHunt.go, but the current repository entrypoint is lowercase apkhunt.go. On a case-sensitive Linux filesystem, use the lowercase filename. Upstream documents Linux as the tested platform.
Scan an Android APK with APKHunt
Use a practice (intentionally vulnerable) APK so findings are expected and safe. This walkthrough uses the archived InsecureShop v1.0 release because that file stays the same every time. Current development lives at hax0rgb/InsecureShop.
Create a lab folder and download the release APK with curl. -fL fails on HTTP errors and follows redirects. The official v1.0 release asset is InsecureShop.apk (capital I and S); save it locally under a simple lowercase path:
mkdir -p "${HOME}/apk-lab" && curl -fL -o "${HOME}/apk-lab/insecureshop.apk" "https://github.com/optiv/InsecureShop/releases/download/v1.0/InsecureShop.apk"Confirm you have the same training APK this lab used by checking the SHA-256 digest:
sha256sum "${HOME}/apk-lab/insecureshop.apk"Sample output:
a83298ae4a37fcab8101e8b41e513dd2199af71a94ea537d556a318e07d4d1bd /home/kali/apk-lab/insecureshop.apkThat hash matches the APKHunt scan output later in this article. Start the scan with -p (path) and -l (write logs). Expect several minutes while decompilation runs:
cd "${HOME}/APKHunt" && go run apkhunt.go -p "${HOME}/apk-lab/insecureshop.apk" -lTrimmed transcript (jadx progress lines omitted):
[+] Scan has been started at: 2026-09-09 12:15:57.565815168 -0400 EDT m=+0.000502779
[+] APK Base: insecureshop.apk
[+] APK Size: 4.5341797 MB
[+] APK Directory: /home/kali/apk-lab/
[+] APK Static Analysis Path: /home/kali/apk-lab/insecureshop_SAST/
[+] APK Hash: MD5: c5d872355e43322f1692288e2c4e6f00
[+] APK Hash: SHA256: a83298ae4a37fcab8101e8b41e513dd2199af71a94ea537d556a318e07d4d1bd
[+] d2j-dex2jar has started converting APK to Java JAR file
dex2jar /home/kali/apk-lab/insecureshop.apk -> /home/kali/apk-lab/insecureshop.jar
[+] Jadx has started decompiling the application
exit status 3
INFO - loading ...
INFO - processing ...
ERROR - finished with errors, count: 11
[+] Capturing the data from the AndroidManifest file
...
[+] Scan has been finished at: 2026-09-09 12:24:45.925463564 -0400 EDT m=+528.360151157
[+] Total time taken for hunting: 8m48.359665809sOn this Kali VM the full run took about 8m48s (convert + decompile + rules). Your time will differ with CPU, disk, and APK size.
Notice exit status 3 and "finished with errors, count: 11" from jadx. That means some classes failed to decompile cleanly, but APKHunt still continued. Treat missing findings as inconclusive until you confirm the file you care about exists under insecureshop_SAST.
-l writes log files next to the APK. Upstream help still says TXT only, but this tested run also wrote HTML. The HTML is a simple browser view of the log, not a MobSF-style dashboard. Prefer the TXT file when you search. List what appeared:
ls -lh "${HOME}/apk-lab"/APKHunt_insecureshop_*.txt "${HOME}/apk-lab"/APKHunt_insecureshop_*.html "${HOME}/apk-lab"/insecureshop_SASTSample output:
-rw-r--r-- 1 kali kali 178K Sep 9 12:24 /home/kali/apk-lab/APKHunt_insecureshop_2026-9-9_12-15-57.html
-rw-r--r-- 1 kali kali 178K Sep 9 12:24 /home/kali/apk-lab/APKHunt_insecureshop_2026-9-9_12-15-57.txt
drwxr-xr-x 4 kali kali 4.0K Sep 9 12:16 /home/kali/apk-lab/insecureshop_SASTYou should see a .txt log, an .html log, and the insecureshop_SAST folder. That folder holds the decompiled sources you will open by hand next.
How to read APKHunt findings
Every finding block in the log looks roughly the same. Learn the pieces once:
| Field | What it means (plain English) |
|---|---|
Category header (==>> …) |
Which old MASVS v1.5 topic APKHunt is checking |
| File path + line | Where to look in the decompiled tree |
| Matched snippet | The exact text that matched |
| QuickNote | Generic fix advice from the tool (not always a perfect fit) |
| Reference | Old MSTG-style ID plus a CWE number |
APKHunt does not prove severity. Copy category, path, rule ID, and one evidence line into your notes. Decide severity only after you open the file.
Save the newest TXT log path in a shell variable so later greps stay short:
REPORT=$(ls -1t "${HOME}/apk-lab"/APKHunt_insecureshop_*.txt | head -1)Search the log for the cleartext-traffic finding:
grep -A8 'allowing cleartext traffic' "${REPORT}"Sample output (ANSI color codes omitted):
==>> The app is allowing cleartext traffic...
/home/kali/apk-lab/insecureshop_SAST/resources/AndroidManifest.xml
27: android:usesCleartextTraffic="true"
[!] QuickNote:
- It is recommended to set android:usesCleartextTraffic or cleartextTrafficPermitted to false. Please note that, Sensitive information should be sent over secure channels only.
[*] Reference:
- OWASP MASVS: MSTG-NETWORK-2 | CWE-319: Cleartext Transmission of Sensitive InformationThe important evidence line is android:usesCleartextTraffic="true" in the manifest. The QuickNote is generic advice; the Reference ID is the scanner's label.
The same report also flags backup and debug settings (android:allowBackup="true", android:debuggable="true"). Next, find SharedPreferences hits and keep only the app's own package path:
grep -A12 'Shared Preferences related instances' "${REPORT}" | grep -E '==>>|insecureshop/'Sample output:
==>> The Shared Preferences related instances...
/home/kali/apk-lab/insecureshop_SAST/sources/com/insecureshop/util/Prefs.javaPrefs.java under com/insecureshop/ is the useful hit. Ignore AndroidX library paths when you triage. WebView JavaScript findings appear later under platform checks and point into the shop WebView activities.
Manually validate APKHunt findings
Scanner output is a lead list. Open the cited files and decide: real issue, needs a phone test, or noise.
Confirm cleartext, debug, and backup manifest flags
The Android manifest is the app's configuration file. Grep the three flags from the report inside the decompiled copy:
grep -E 'usesCleartextTraffic|allowBackup|debuggable' "${HOME}/apk-lab/insecureshop_SAST/resources/AndroidManifest.xml"Sample output:
android:debuggable="true"
android:allowBackup="true"
android:usesCleartextTraffic="true"All three flags are really present. Record them as confirmed configuration findings, then judge impact separately:
usesCleartextTraffic="true"permits the app to use cleartext network traffic where the platform and network-security configuration allow it; it does not prove that the app actually sends sensitive data over HTTP. On Android 7.0 / API 24 and later, if the app definesandroid:networkSecurityConfig, inspect that configuration because it determines the effective cleartext policy.debuggable="true"is bad for a store release, but normal on many training builds.allowBackup="true"is only a signal. Whether data can be copied off the device depends on Android version, backup rules, and device policy. Do not infer practical data extraction from the flag alone; validate the app's backup rules and behavior on the target Android version.
Validate password storage and logging separately
Open the SharedPreferences helper APKHunt named:
sed -n '40,105p' "${HOME}/apk-lab/insecureshop_SAST/sources/com/insecureshop/util/Prefs.java"Sample output (trimmed):
SharedPreferences sharedPreferences = context.getSharedPreferences("Prefs", 0);
...
public final String getPassword() {
...
return sharedPreferences.getString("password", "");
...
public final void setPassword(String value) {
...
sharedPreferences.edit().putString("password", value).apply();Finding A: password stored as a plaintext value in SharedPreferences. The key line is putString("password", value). Mode 0 means MODE_PRIVATE (not world-readable). So the scanner's generic "world-readable preferences" QuickNote does not describe this call. MODE_PRIVATE prevents ordinary cross-app reads, but it does not make the stored credential encrypted or safe against backup, rooted-device access, or local forensic access.
Finding B: password written to device logs. That is a different problem. Check the login screen code:
grep -n 'Log.d' "${HOME}/apk-lab/insecureshop_SAST/sources/com/insecureshop/LoginActivity.java"Sample output:
87: Log.d("userName", username);
88: Log.d("password", password);Lines 87–88 write username and password to Logcat. Keep that as a separate finding from SharedPreferences storage. Modern Android restricts access to other apps' Logcat output, so the practical exposure depends on device privileges, debugging context, Android version, and who has access to the device or logging interface.
Trace WebView input to loadUrl
Do not stop at "JavaScript is enabled." Open the WebView activity and see where the URL comes from:
sed -n '55,115p' "${HOME}/apk-lab/insecureshop_SAST/sources/com/insecureshop/WebViewActivity.java"Sample output (trimmed):
settings.setJavaScriptEnabled(true);
...
Uri uri = intent.getData();
if (uri != null) {
...
String queryParameter = data2.getQueryParameter("url");
...
webview.loadUrl(data);In plain steps, the code does this:
Deep link / Intent arrives (path /web or /webview)
│
▼
Read query parameter "url" into variable data
│
▼
webview.loadUrl(data)WebViewActivity also has a BROWSABLE deep-link filter (insecureshop://com.insecureshop/...), so the activity is externally invokable through that intent filter. Call this a confirmed externally reachable path from a deep link to WebView.loadUrl(). That proves attacker-controlled navigation may reach the WebView code path; it does not by itself prove useful JavaScript execution, file access, or sensitive-session impact. WebView2Activity can load URLs from even more Intent fields. JavaScript-on alone is not enough for a vulnerability claim.
APKHunt false negative: exported activities reported as 0
This is the most important scanner-validation result in this test. Ask the TXT log what APKHunt counted for exported components:
grep -A5 'APK Component Summary' "${REPORT}"Sample output:
==>> APK Component Summary
[+] --------------------------------
Exported Activities: 0
Exported Content Providers: 0
Exported Broadcast Receivers: 0
Exported Services: 0APKHunt claims Exported Activities: 0. Check the JADX-produced manifest yourself:
grep -n 'android:exported\|AboutUsActivity\|InsecureShopProvider' "${HOME}/apk-lab/insecureshop_SAST/resources/AndroidManifest.xml"Sample output:
51: android:name="com.insecureshop.AboutUsActivity"
52: android:exported="true"/>
86: android:name="com.insecureshop.contentProvider.InsecureShopProvider"
88: android:exported="true"Confirm the same fact with a second parser that does not reuse APKHunt's JADX tree. Install apktool if it is not already on the host:
sudo apt-get install -y apktoolDecode the original APK into a fresh directory:
apktool d -f -o "${HOME}/apk-lab/insecureshop-apktool" "${HOME}/apk-lab/insecureshop.apk"Search the decoded manifest for AboutUsActivity:
grep -n 'AboutUsActivity' "${HOME}/apk-lab/insecureshop-apktool/AndroidManifest.xml"Sample output:
25: <activity android:exported="true" android:name="com.insecureshop.AboutUsActivity"/>apktool also reports android:exported="true" for AboutUsActivity, so the miss is not a JADX-only artifact.
| Source | AboutUsActivity export |
|---|---|
| APKHunt component summary | Exported Activities: 0 |
| JADX manifest (APKHunt tree) | android:exported="true" on a following line |
apktool decoded manifest |
android:exported="true" on the same <activity> line |
| Conclusion | Two independent parsers show the exported activity; APKHunt's summary under-reported it |
AboutUsActivity alone proves the activity count is wrong. Other android:exported="true" rows may be providers or different components, so classify each one. Do not treat every true as another missed activity.
Why did APKHunt miss it? In the APKHunt revision tested here (386a0d5), the component-count logic uses a line-oriented grep pipeline: it greps lines containing <activity, then pipes those lines into a second grep for android:exported="true". In the JADX-formatted manifest used by the scanner, the activity name and android:exported attribute often sit on separate lines, so the second grep never sees the attribute on the same line as <activity. That under-count matches this lab. Treat it as an implementation miss on multiline declarations in this revision, not as a claim that every future APKHunt release will fail the same way.
You can reproduce the empty pipeline against the JADX tree:
bash -c "grep -ne '<activity' \"${HOME}/apk-lab/insecureshop_SAST/resources/AndroidManifest.xml\" | grep -e 'android:exported=\"true\"'" || trueNo matching lines print. That is the same style of check APKHunt uses before it prints Exported Activities: 0.
After each check, tag the finding in your notes: confirmed, needs runtime, or noise / library.
Common APKHunt false positives, false negatives, and blind spots
Static tools miss context. Watch for these patterns:
- Library noise. Matches inside AndroidX or Glide are often less interesting than
com/insecureshop/code. - Generic QuickNotes. The advice text is a template. Always read the matched line.
- Under-reporting. Exported components can exist even when the summary says zero.
- Pattern matching without data flow. "JavaScript enabled" is not proof of WebView injection until you see where the URL comes from.
- Jadx errors. Partial decompile can hide issues. Missing finding ≠ safe app.
- Obfuscation. Hardened release builds give weaker static results.
- Old checklist IDs. Remap MSTG-era labels to current MASVS language in the client report.
For every APKHunt hit you might put in a report, open the class and nearby code in *_SAST. If you cannot name the class and the user impact, it is not ready.
Troubleshooting APKHunt
| Symptom | Likely cause | Fix |
|---|---|---|
go: command not found / jadx: not found |
Missing packages | Install golang-go, jadx, and dex2jar; confirm with go version and jadx --version |
APKHunt.go: no such file |
Filename case | Current repository entrypoint is lowercase apkhunt.go |
Jadx exit status 3 / finished with errors |
Partial decompile | Confirm *_SAST/sources and resources/AndroidManifest.xml exist; inspect JADX errors before trusting empty categories |
| Empty or tiny report | Wrong -p path, non-APK file, or scan aborted early |
Confirm ls on the APK; watch for dex2jar/jadx failures at the start of the log |
| Almost only library hits | Broad greps | Filter to the app package directory under *_SAST/sources/ |
| "Exported Activities: 0" but you expect exports | Tool under-count | Grep android:exported yourself; confirm with a second manifest parser on client work |
| Heavy obfuscation / empty useful sources | Release hardening | Expect weaker SAST; rely more on MobSF dynamic and manual reverse engineering |
| Multi-APK folder scan skips files | Non-.apk names or nested folders |
Keep APKs directly in the -m directory |
| HTML log looks incomplete or messy in a browser | Current upstream HTML logging closes the HTML wrapper before the scan body is written | Use the TXT log as the primary scanner output for searching and note-taking; validate important findings against the manifest and decompiled source |
Summary
APKHunt gives you a Linux command-line static pass over Android APKs. It decompiles with dex2jar and jadx, then pattern-matches older MASVS v1.5 / MSTG-style checks. On this Kali host the lab installed the packages, cloned Cyber-Buddy/APKHunt at commit 386a0d5, and scanned InsecureShop v1.0 with -p and -l in about 8m48s.
The scanner found useful leads: cleartext, backup, and debug manifest flags; plaintext password storage in SharedPreferences; password logging to Logcat; and WebView-related code worth investigating. Manual tracing then confirmed an externally reachable deep-link path into loadUrl. These are confirmed static code and configuration observations; severity and exploit impact still require context and, where relevant, runtime validation.
Manual review also showed the tool can be wrong. The exported-component summary said zero exports, but both the JADX tree and an independent apktool decode show AboutUsActivity as exported. That miss is why every APKHunt engagement still needs a pass through the manifest and sources before anything goes into a client report.
In practice:
- Use APKHunt for quick CLI static leads
- Use manual review to validate impact and catch misses
- Use MobSF when you need dynamic or runtime work
Remap scanner IDs to current MASVS language yourself, and always read the code behind the hit.
References
- Cyber-Buddy/APKHunt — upstream project, usage, and MASVS positioning
- OWASP MASVS — current Mobile Application Security Verification Standard
- OWASP MASTG — mobile application security testing guide
- Android Developers —
<application>element —allowBackup,debuggable,usesCleartextTraffic, and related attributes - Android Developers — App backup — backup behavior across platform versions
- optiv/InsecureShop — archived v1.0 training APK used in this lab
- hax0rgb/InsecureShop — current InsecureShop development repository
- jadx — Dex to Java decompiler used by the pipeline
- apktool — independent APK decode used to confirm exported components
Frequently Asked Questions
Is APKHunt a replacement for MobSF?
No. APKHunt is a focused CLI static scanner with MASVS v1.5-era reporting. MobSF is a broader platform with static and dynamic modules, a web UI, and runtime analysis. Use APKHunt for lightweight SAST; use MobSF when you need interactive or dynamic coverage.
Does a finding with an MSTG ID mean the app fails current MASVS?
Not by itself. APKHunt's taxonomy is based on OWASP MASVS v1.5.0 and older MSTG identifiers. Treat those IDs as scanner references, confirm the issue in source or at runtime, then map the risk to your engagement's current MASVS language.
Why did jadx print errors but APKHunt still finished?
Jadx can exit with a non-zero status and still write a partial *_SAST tree. APKHunt continues grepping whatever decompiled files exist. Confirm that resources/AndroidManifest.xml and the app package under sources/ are present, and treat missing findings as inconclusive when decompilation was incomplete.
Does APKHunt assign severity to findings?
No. It prints categories, evidence paths, QuickNotes, and MSTG-era references. Assign severity only after you validate the hit and understand impact for the target app and Android version.
Why does APKHunt report 0 exported activities when the APK contains exported components?
In the APKHunt revision tested for this article (386a0d5), the grep-based component detection can miss multiline manifest declarations where <activity and android:exported="true" appear on separate lines. In the InsecureShop test it printed Exported Activities: 0, while both the JADX tree and an independent apktool decode showed AboutUsActivity with android:exported="true". Treat scanner summaries as leads and verify the manifest independently.
Should every SharedPreferences match go into the report?
No. Prefer matches under the application package that touch credentials or PII, then read the mode and call sites. Library noise and generic QuickNotes are common; confirm sensitivity before you assign severity. Treat Logcat leaks of the same secret as a separate finding.

