frida-stalker-android
Frida Stalker (Android)
Overview
Use this skill when you need to trace native code execution on Android using Frida's Stalker API, with templates geared for ARM/ARM64 and performance-safe defaults.
This skill assumes Frida 17+ JavaScript semantics.
When To Use This Skill
- User explicitly asks for "Frida Stalker" on Android.
- You need to measure native call activity (who got called, how often) with low overhead.
- You need ordered call events (call graph reconstruction) or coarse coverage.
- You need to inject logic on basic-block compilation through
transform(iterator).
Quick Decision Guide
- Want call counts per target and don't care about ordering: use
onCallSummary. - Want ordered call/ret/block/compile events: use
onReceiveand decode withStalker.parse(). - Want instruction-level matching and callouts: use
transform(iterator)(heavy; do it narrowly). - Want to watch a small set of call targets: consider
Stalker.addCallProbe().
Core API Facts (Frida 17+)
Stalker.follow([threadId, options])- Provide exactly one callback:
onReceive(events)oronCallSummary(summary). Stalker.unfollow([threadId])Stalker.parse(events, { annotate, stringify })Stalker.flush()drains buffered events early (otherwise periodic draining is controlled byStalker.queueDrainInterval).Stalker.garbageCollect()should be called afterunfollow()to free accumulated memory at a safe point.Stalker.exclude({ base, size })excludes a memory range from stalking (useful to skip noisy/system modules).- Tuning:
Stalker.trustThresholddefault1(set-1for no trust,0to trust immediately, orNto trust afterNexecutions).Stalker.queueCapacitydefault16384events.Stalker.queueDrainIntervaldefault250ms (set0to disable periodic draining and callStalker.flush()manually).
Workflow
- Define objective.
- Choose thread(s).
- Choose capture mode and filters.
- Pick a template and adapt it.
- Run, tune performance, and clean up.
If you are using the Frida MCP tools, also enable $frida-mcp-workflow and follow its phases (Idea -> Scripting -> Execution -> Notes).
Templates
Start from these and keep scripts file-based.
templates/stalker-call-summary.js: low-overhead call counting viaonCallSummary.templates/stalker-onreceive-parse.js: receive binary events and decode withStalker.parse().templates/stalker-start-stop-around-hook.js: follow/unfollow only during a specific hooked function call.templates/stalker-call-probe.js: observe calls to a single target viaStalker.addCallProbe().templates/stalker-transform-skeleton.js: minimaltransform(iterator)skeleton with ARM/ARM64 safety check.templates/stalker-filter-modules.js: helper to select "app modules" on Android and exclude the rest.
Quick Start
- If you do not know the thread id yet, start with
templates/stalker-start-stop-around-hook.js. - If you already know the thread id and want low overhead, use
templates/stalker-call-summary.js. - If you need ordered events, use
templates/stalker-onreceive-parse.jsand keep event types narrow. - If you only care about calls to one target, start with
templates/stalker-call-probe.js.
MCP Usage Notes (If Available)
When driving this through the Frida MCP tools, prefer this flow:
- Create or attach a session (
mcp__frida__create_interactive_session/mcp__frida__attach_to_process). - Load the selected template with
mcp__frida__load_script. - Start tracing through RPC exports using
mcp__frida__call_rpc_export(templates exposestart()/stop()when appropriate). - Use
mcp__frida__get_session_messagesto consume output.
Keep a script ledger (what is loaded, purpose, and teardown path). This is enforced by $frida-mcp-workflow.
Android-Specific Notes (Practical)
-
Thread choice matters more than you think.
-
If you start stalking the wrong thread, you will see nothing, or only system noise.
-
A safe pattern is to start stalking from inside an
Interceptor.attach()callback, usingProcess.getCurrentThreadId()to capture the thread that is actually executing your target function. -
Module filtering is essential.
-
On Android, app code is usually in modules whose
pathcontains/data/app/,/data/data/, or an extracted APK split path. -
Exclude common noise sources (
libart.so,libc.so,liblog.so, etc.) usingStalker.exclude()when you only care about your app's own native libs. -
32-bit ARM note.
-
If you use raw addresses on 32-bit ARM, Thumb functions require the low bit set. Prefer addresses returned by Frida APIs like
Process.getModuleByName(...).getExportByName(...). -
Avoid
Process.runOnThread()unless you know what you're doing. -
It can interrupt a thread in non-reentrant code and cause deadlocks/crashes.
Performance Rules Of Thumb
- Avoid
events.execunless you truly need instruction-level traces. It produces huge volumes of data. - Prefer
onCallSummaryoveronReceivewhen you can. - Keep your callbacks lean; push heavy work to the host side when possible.
- Use
Stalker.exclude()aggressively to reduce time spent in system libraries. - Prefer manual draining (
Stalker.queueDrainInterval = 0+Stalker.flush()) when you need deterministic windows. - Call
Stalker.garbageCollect()after unfollowing, especially if you repeatedly start/stop.
Cleanup Checklist
Stalker.unfollow(threadId)Stalker.flush()Stalker.garbageCollect()
Troubleshooting
-
No output at all.
-
You are likely stalking the wrong thread, or your callback isn't being invoked (e.g., you followed a thread that never runs).
-
Output is only system noise.
-
Add module filters and exclusions. Start stalking from inside a hook where you know you're in app code.
-
Target slows to a crawl or dies.
-
Reduce enabled events, stop using
exec, and switch toonCallSummary. Exclude large/noisy modules.
For deeper notes, see:
references/stalker-api.mdreferences/android-filtering.md