| Tested on | Kali GNU/Linux Rolling 2026.2 (kali-rolling) |
|---|---|
| Package | frida==17.17.0frida-tools==14.10.4 (Python venv)frida-server 17.17.0 (android-x86) |
| Applies to | Kali Linux with adb; rooted Android emulator or owned test device |
| Lab environment | Pentest lab Genymotion Desktop lab Android Studio AVD lab |
| Privilege | Normal user for Frida clients; root on the Android guest for frida-server (adb root or su) |
| Cert prep | CompTIA PenTest+ (mobile specialized systems / dynamic analysis concepts) |
| Scope | Connect with ADB, install a pinned Frida environment, run matching frida-server, enumerate and attach to InsecureShop, and build observe-only then controlled-modify Java hooks. Does not cover SSL pinning bypass (see Android SSL pinning bypass), full Frida Gadget repackaging, Drozer IPC labs, or iOS. |
Frida lets you inspect and modify an Android app while it is running. This tutorial installs a matching frida-server on a rooted Genymotion device, connects from Kali, enumerates InsecureShop, and builds simple Java hooks for observing and modifying runtime values.
Use any rooted Android guest that adb can reach. This lab used Genymotion; the free Android Studio AVD path works the same way once adb devices shows device and you pick the matching frida-server ABI.
ADB provides the device transport and shell access. Android IPC and exported-component testing with Drozer belongs in a separate guide; this page stays on Frida attach, hook, and verify. Use only APKs and devices you own or are authorized to test.
What Frida does on Android
Frida injects a JavaScript engine into a process so you can hook Java and native methods at runtime. On Android the usual lab path is:
| Piece | Role |
|---|---|
| ADB | Device visibility, package install, push frida-server, start activities |
frida / frida-tools on Kali |
Client that talks to the device over USB or TCP |
frida-server on the guest |
Rooted helper process Frida attaches through |
Static scanners such as MobSF and APKHunt find leads in the APK. Frida confirms what happens while the process runs.
APK on disk Running process
┌─────────────────┐ ┌──────────────────────┐
│ MobSF / APKHunt │ leads │ Frida hooks / traces │
│ static findings │ ──────► │ args, returns, logs │
└─────────────────┘ └──────────────────────┘Frida Android lab requirements
You need:
- A rooted emulator or owned rooted device (this lab: Genymotion Android 10
x86— Genymotion Desktop lab; a Google APIs AVD with a matchingfrida-serverABI works the same way) - Working
adbfrom Kali - A training APK you are allowed to instrument
Training target:
- APK: InsecureShop v1.0 (intentionally vulnerable)
- Package identifier:
com.insecureshop
Create a lab folder and download the APK with curl:
mkdir -p "${HOME}/frida-lab" && curl -fL -o "${HOME}/frida-lab/insecureshop.apk" "https://github.com/optiv/InsecureShop/releases/download/v1.0/InsecureShop.apk"curl -fL fails on HTTP errors and follows redirects. Confirm the file size is non-zero before you install it.
Connect to Android with ADB
Pin the Genymotion TCP serial so later commands stay on the right device when more than one ADB target appears:
export ANDROID_SERIAL=192.168.56.103:5555Connect, then list devices:
adb connect "${ANDROID_SERIAL}"already connected to 192.168.56.103:5555Confirm the transport with adb devices. The serial should show as device, not offline:
adb devicesList of devices attached
192.168.56.103:5555 deviceRead the ABI and Android release so the Frida server binary matches the guest:
adb shell getprop ro.product.cpu.abix86Map the property value to the Frida release asset name. The filename is not always a literal copy of ro.product.cpu.abi:
Android ABI (ro.product.cpu.abi) |
Frida frida-server asset |
|---|---|
x86 |
android-x86 |
x86_64 |
android-x86_64 |
arm64-v8a |
android-arm64 |
armeabi-v7a |
android-arm |
Check the Android release next. Genymotion in this lab reports API level 29 as release 10:
adb shell getprop ro.build.version.release10Install the training APK when it is not already present:
adb install -r "${HOME}/frida-lab/insecureshop.apk"Performing Streamed Install
SuccessConfirm the package identifier you will attach to later. A short grep filter keeps the pm list readable:
adb shell pm list packages | grep insecureshoppackage:com.insecureshopInstall Frida on Kali
The Frida core version reported by frida --version should match the frida-server release on Android. The separate frida-tools Python package has its own version number and does not need to equal the server version.
This lab pins the versions that produced the sample output below. Create a venv and install exactly those packages with pip:
python3 -m venv "${HOME}/frida-lab/venv"python3 -m venv is silent on success. Activate the environment and install the pinned packages:
source "${HOME}/frida-lab/venv/bin/activate" && pip install 'frida==17.17.0' 'frida-tools==14.10.4'Confirm the core version you will match on the device:
frida --version17.17.0If you intentionally install a newer Frida release, download the matching frida-server rather than copying the version shown in this example.
Install and start frida-server
Download the matching frida-server for this Genymotion ABI (android-x86), decompress it, and keep the binary named frida-server:
VER=$(frida --version) && curl -fL -o "${HOME}/frida-lab/frida-server-${VER}-android-x86.xz" "https://github.com/frida/frida/releases/download/${VER}/frida-server-${VER}-android-x86.xz"Decompress the archive, rename the binary to frida-server, and mark it executable:
xz -dk "${HOME}/frida-lab/frida-server-${VER}-android-x86.xz" && mv "${HOME}/frida-lab/frida-server-${VER}-android-x86" "${HOME}/frida-lab/frida-server" && chmod 755 "${HOME}/frida-lab/frida-server"Restart adbd as root when the image allows it, then push the server binary before any chmod or start:
adb rootadbd is already running as rootPush first so both the adb root path and the su -c fallback operate on a file that already exists on the device:
adb push "${HOME}/frida-lab/frida-server" /data/local/tmp/frida-server/root/frida-lab/frida-server: 1 file pushed, 0 skipped. 0.4 MB/s (53939692 bytes in 144.678s)Your path and transfer speed will differ. On this eng-style Genymotion image, adb root worked, so mark the binary executable and start it without su:
adb shell "chmod 755 /data/local/tmp/frida-server"chmod is silent on success. Start frida-server as a daemon in the guest:
adb shell "/data/local/tmp/frida-server -D"If adb root instead says adbd cannot run as root in production builds, keep ADB as the normal shell user and run the same steps with su -c after the push:
adb shell "su -c 'chmod 755 /data/local/tmp/frida-server'"chmod through su is silent on success. Start the server the same way:
adb shell "su -c '/data/local/tmp/frida-server -D'"Confirm a live frida-server process with the guest ps listing:
adb shell "ps -A | grep frida-server"root 3747 1 1148076 170356 poll_schedule_timeout f2487bb9 S frida-serverVerify Frida with frida-ps
With frida-server running, list a sample of running processes over the USB/TCP transport (-U):
frida-ps -U | head -n 20 PID Name
---- ---------------------------------------------------------------------------
2810 Files
1701 Phone
2217 Search
2356 WebView Shell
2638 abb
489 adbdInstalled apps, including ones that are not running yet, show up with -Uai. Filter for the training package so the result stays deterministic across devices:
frida-ps -Uai | grep -i insecureshop5086 InsecureShop com.insecureshopLaunch InsecureShop from the emulator UI first if the PID column shows a dash. A dash means the app is installed but not currently running. Use com.insecureshop with -N / -f below, not only the display name.
Attach to an Android app
Frida's attach flags answer different questions:
| Flag | Meaning |
|---|---|
-N / --attach-identifier |
Attach by application identifier (com.insecureshop) |
-n / --attach-name |
Attach by process display name (InsecureShop) |
-f / --file |
Spawn the package, then inject before it fully resumes |
Prefer the package identifier. It stays stable when the launcher label changes.
Clear app data so you land on the login screen, then launch the app:
adb shell pm clear com.insecureshopSuccessLaunch the app so a process exists for attach:
adb shell monkey -p com.insecureshop -c android.intent.category.LAUNCHER 1Allow storage permission if the dialog appears. Create hook-demo.js in the next section, then attach with -N com.insecureshop (or spawn with -f com.insecureshop when the process is not already running).
Hook a Java method
Start with an observe-only method hook before attempting more complex instrumentation. Do not jump straight to SSL pinning bypass. On InsecureShop, login calls android.util.Log.d with the typed username and password before Util.verifyUserNamePassword runs.
Save the method wrapper, replace implementation, then call the original with .call(this, …). That matches Frida's own Android examples and avoids recursive wrappers.
Save this script as hook-demo.js in the lab folder:
Java.perform(function () {
var Log = Java.use('android.util.Log');
var logD = Log.d.overload('java.lang.String', 'java.lang.String');
logD.implementation = function (tag, msg) {
if (tag === 'userName' || tag === 'password') {
console.log('[*] Runtime Log.d tag=' + tag + ' msg=' + msg);
}
return logD.call(this, tag, msg);
};
var LoginActivity = Java.use('com.insecureshop.LoginActivity');
var onLogin = LoginActivity.onLogin.overload('android.view.View');
onLogin.implementation = function (view) {
console.log('[*] LoginActivity.onLogin entered');
var ret = onLogin.call(this, view);
console.log('[*] LoginActivity.onLogin returned');
return ret;
};
console.log('[*] Demo hooks installed');
});With InsecureShop already launched, attach by package identifier:
frida -U -N com.insecureshop -l "${HOME}/frida-lab/hook-demo.js" -q -t 90To spawn the package under Frida instead of attaching to a running process:
frida -U -f com.insecureshop -l "${HOME}/frida-lab/hook-demo.js" -q -t 90On the login screen (after attach or spawn), submit a wrong password first, then the known training credentials shopuser / !ns3csh0p. Sample output from this lab:
[*] Demo hooks installed
[*] LoginActivity.onLogin entered
[*] Runtime Log.d tag=userName msg=shopuser
[*] Runtime Log.d tag=password msg=badpass
[*] LoginActivity.onLogin returned
[*] LoginActivity.onLogin entered
[*] Runtime Log.d tag=userName msg=shopuser
[*] Runtime Log.d tag=password msg=!ns3csh0p
[*] LoginActivity.onLogin returnedRead that cycle as: method call → hook → observe arguments → original behavior continues. The hook observes the login path without changing authentication behavior.
before hook after hook (observe-only)
┌──────────────────────┐ ┌──────────────────────────┐
│ onLogin() │ │ onLogin() │
│ └─ Log.d(user/pass) │ │ └─ Frida prints args │
│ └─ verify(...) │ │ └─ original Log.d runs │
└──────────────────────┘ │ └─ verify(...) unchanged│
└──────────────────────────┘Inspect runtime values and modify controlled UI text
Extend the same script with a filtered TextView.setText hook. AboutUs sets a fixed marketing string when you tap About InsecureShop. Intercepting that string shows controlled modification without touching auth.
Add this block inside Java.perform before the “hooks installed” log line:
var TextView = Java.use('android.widget.TextView');
var setText = TextView.setText.overload('java.lang.CharSequence');
setText.implementation = function (text) {
var s = (text === null) ? null : text.toString();
if (s !== null && s.indexOf('intentionally designed vulnerable') !== -1) {
console.log('[*] AboutUs setText original=' + JSON.stringify(s));
var replacement = Java.use('java.lang.String').$new(
'Frida lab note: AboutUs string intercepted at runtime'
);
console.log('[*] AboutUs setText modified =' + JSON.stringify(replacement.toString()));
return setText.call(this, replacement);
}
return setText.call(this, text);
};Re-attach with the updated script:
frida -U -N com.insecureshop -l "${HOME}/frida-lab/hook-demo.js" -q -t 40In a second terminal, start the exported About activity with adb shell am start -n com.insecureshop/.AboutUsActivity, then tap About InsecureShop so setText runs. The Frida session printed the original string and the replacement:
[*] Demo hooks installed
[*] AboutUs setText original="InsecureShop is an intentionally designed vulnerable android app built in Kotlin."
[*] AboutUs setText modified ="Frida lab note: AboutUs string intercepted at runtime"The on-screen label showed the Frida replacement string. That is enough to prove you can observe and change values at runtime without touching SSL or auth bypass tooling.
Relate static findings to runtime evidence
Static reports already flag InsecureShop logging and exported activities. On this APK, decompiled LoginActivity calls Log.d("userName", …) and Log.d("password", …). Frida just printed those values live.
Manifest evidence for the About screen:
android:name="com.insecureshop.AboutUsActivity"
android:exported="true"MobSF surfaces the exported activity during static triage. In the APKHunt test, manual review of APKHunt's decompiled manifest confirmed the same export even though APKHunt's component summary missed it.
Runtime confirmation here was:
- Frida saw the AboutUs
setTextcall when the UI path ran. - ADB can start
AboutUsActivitywithout walking the normal menu:adb shell am start -n com.insecureshop/.AboutUsActivity.
Treat static output as leads. Use Frida when you need arguments, return values, or proof the code path actually runs.
Frida Gadget versus frida-server
| Approach | When it fits | Trade-off |
|---|---|---|
frida-server on a rooted emulator/device |
Lab intros, attach to many apps | Needs root (or equivalent) on the guest |
| Frida Gadget embedded in a repackaged APK | Non-rooted targets, single-app focus | Rebuild/sign the APK; load config per app |
Official Frida Android docs still steer beginners to the rooted/frida-server path. This article stays on that path. Gadget is the usual next step when root is off the table and the engagement allows repackaging.
Next step: Android IPC with Drozer
Frida answers what a running method sees and returns. When the question is exported activities, services, receivers, and content providers, use Drozer: it focuses on Android IPC and component attack surface rather than general method hooking.
For console and Agent install, port forwarding, attacksurface, and starting exported components, follow the Drozer Android pentesting tutorial. Keep this page on Frida runtime hooks; use ADB am start only for one-off known activities when you are not running Drozer.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Failed to spawn / empty frida-ps -U |
frida-server not running or wrong ABI |
Map ro.product.cpu.abi with the table above; push the matching asset; start as root |
| Version mismatch / protocol errors | Host frida --version ≠ on-device frida-server |
Pin and reinstall the matching release pair; ignore that frida-tools has a different package version |
frida-ps: device not found |
ADB session missing or wrong serial | adb devices; set ANDROID_SERIAL; adb connect host:5555 for Genymotion TCP |
Permission denied starting frida-server |
Not root on guest | adb root on eng/userdebug, or su -c when production builds refuse adb root |
| Process exits as soon as you attach | App anti-tamper or crash in early init | Prefer frida -U -f package …; keep hooks minimal; test on the training APK first |
| Architecture mismatch on push | Wrong frida-server asset |
Genymotion x86 needs android-x86, not arm64 |
| App spawns too quickly to attach | Short-lived process | Use -f so hooks load before resume |
| Emulator quirks / SELinux denials | Guest policy blocks the binary | Confirm frida-server runs as root; read adb logcat / audit messages; try another supported rooted image if policy blocks injection |
Summary
This tutorial uses Frida core 17.17.0 with a matching frida-server, connects through ADB to a rooted Genymotion device, and attaches to InsecureShop by package identifier. Observe-only hooks on Log.d and LoginActivity.onLogin show arguments without changing authentication. A filtered TextView.setText hook then proves you can modify a controlled UI string at runtime.
Match frida --version to the on-device server, and map ro.product.cpu.abi to the correct Frida asset name before you push. Keep frida-tools pin separate from that core version. When you need IPC and exported-component enumeration rather than method hooks, switch to Drozer instead of stretching this page across two search intents.
References
- Frida Android documentation
- Frida releases (frida-server builds)
- OWASP MASTG — Frida (Android)
- InsecureShop (Optiv archived v1.0)
Frequently Asked Questions
Why does Frida fail with a version mismatch?
The Frida core version from frida --version must match the frida-server binary on Android. The frida-tools package version can differ. Pin both Python packages for a reproducible lab, read frida --version, then download that exact frida-server-*-android-<abi> asset.
Do I need a rooted emulator for this tutorial?
For the frida-server path used here, yes. Official Frida Android docs recommend a rooted device or emulator for the simplest introduction. Non-rooted work usually means embedding Frida Gadget in a repackaged APK, which this article only summarizes.
What is a safe first Frida hook on InsecureShop?
Hook android.util.Log.d for the userName and password tags, or wrap LoginActivity.onLogin, save the overload, and call the original with .call(this, …). Avoid SSL pinning bypass scripts until you can attach, enumerate, and read simple method traffic reliably.
Should I attach with -n or -N?
Prefer -N (application identifier) with com.insecureshop. Use -n only when you intentionally match the process display name. Use -f when you need to spawn the package under Frida from the start.
Which frida-server file do I download for arm64-v8a?
Use the android-arm64 asset. Map arm64-v8a to android-arm64, armeabi-v7a to android-arm, x86 to android-x86, and x86_64 to android-x86_64.

