When to use / when not to use
- Use: when you need to hook functions (intercept / modify arguments / modify return values), enumerate modules and classes, bypass certificate pinning / root detection, observe runtime call chains
- Use: unified instrumentation needs across mobile (Android / iOS) and desktop (Linux / macOS / Windows)
- Don't use: when you only need static analysis (for Android use [[re-apk]], for formats use the [[re-format-elf]] / [[re-format-macho]] series)
- Don't use: when you need syscall-level tracing (use [[re-tracing]])
- Don't use: when you need a full debugger experience (breakpoints / single-stepping / memory, use [[re-gdb]] / [[re-lldb]] / [[re-x64dbg]])
Tool preparation
Dynamic analysis follows the highest principle of [[re-analyze/platform-tips]]: execute mobile-side within a controlled device / emulator snapshot, and confirm the sandbox environment before desktop-side instrumentation. Verify all tools before use.
frida-tools —— host-side command-line tools
- Cross-platform (Linux / macOS / Windows):
pip install frida-tools(Python 3.8+; venv recommended:python3 -m venv venv && venv/bin/pip install frida-tools) - Verify:
frida --version(outputs the frida version number);frida-ps -U/frida-trace -havailable
frida-server —— mobile-side instrumentation agent
- Download: GitHub release
https://github.com/frida/frida/releases, choosefrida-server-<版本>-android-<架构>(choose-arm64for arm64,-armfor 32-bit,-x86_64) - Client and frida-server must at least share the same major(compare against
frida --version); to avoid feature/API differences, using exactly the same and latest version is recommended. The architecture must match the device, otherwise the connection reports a protocol error (see Pitfall 1) - Android push and launch:
adb push frida-server-xxx /data/local/tmp/frida-server adb shell "chmod 755 /data/local/tmp/frida-server" adb shell "su -c /data/local/tmp/frida-server" & # 或 adb root 后直接运行 - iOS jailbroken device: add the repo
https://build.frida.rein Sileo / Cydia to install the frida deb, orssh root@<设备IP>then install - Verify: host
frida-ps -Ucan list device processes (-U= USB device,-R= remote ip:port)
objection —— quick instrumentation without writing JS
- Requires Python >=3.10 (different from the
>=3.7minimum of frida's own binding, must be satisfied separately):python -m pip install -U objection, recommended to create a separate 3.10+ venv or isolate with pipx - Verify:
objection --version - Usage:
objection -n <应用或 Bundle ID> start, built-inandroid hooking/ios hookingsubcommands (e.g.android hooking list activities、ios sslpinning disable) - As of 1.12.x
-g/exploreis deprecated (hidden in the CLI, only a deprecation warning is emitted, the old syntax still runs); for new scripts use-n+start
Procedure
Execute in order, and record the result of each step.
-
spawn vs attach choice:
frida -U -f com.target.app # spawn:从零启动应用,能抓启动早期逻辑(解密 / 初始化) frida -U com.target.app # attach:附加到已运行进程(不重启) frida-trace -U -f com.target.app -i "Java!*" # 启动即跟踪 Java 方法调用Rule: use spawn to capture startup logic / bypass early detection; use attach to merely observe the current state. attach happens later than startup and may miss early logic that has already executed (see Pitfall 2).
-
Writing JS hooks (intercept / modify arguments / return values):
// hook.js —— 拦截、改参数、改返回值(Android Java 层) Java.perform(function () { var cls = Java.use("com.example.Target"); cls.doLogin.implementation = function (user, pass) { console.log("doLogin(" + user + ", " + pass + ")"); return this.doLogin("hacked", "pass123"); // 改参 }; cls.isLicensed.implementation = function () { return true; // 改返回值 }; });frida -U -f com.target.app -l hook.jsFor native functions use
Interceptor.attach(Process.getModuleByName("libfoo.so").findExportByName("func"), { onEnter: ..., onLeave: ... })to intercept (modify arguments in onEnter, modify the return value in onLeave withretval.replace()). Same example on desktop:frida -p <pid> -l hook.js。 -
Enumeration and invocation (enumerateModules / Java.perform):
Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (c) { if (c.indexOf("target") >= 0) console.log(c); }, onComplete: function () {} }); });// 模块与导出枚举(原生层) Process.enumerateModules().forEach(function (m) { console.log(m.name + " " + m.base); }); Process.getModuleByName("libfoo.so").enumerateExports().forEach(function (e) { console.log(e.name); });Run:
frida -U -f com.target.app -l enum.js. After locating the target, invoke it directly and actively:Java.use("com.x").method(...)/ native exported functions. -
Bypassing certificate pinning / detection (common templates):
// Android SSL 绕过模板(配合抓包工具) Java.perform(function () { var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager"); var TrustAll = Java.registerClass({ name: "com.bypass.TrustAll", implements: [X509TrustManager], methods: { checkClientTrusted: function () {}, checkServerTrusted: function () {}, getAcceptedIssuers: function () { return []; } } }); var SSLContext = Java.use("javax.net.ssl.SSLContext"); SSLContext.init.implementation = function (km, tm, sr) { this.init(km, [TrustAll.$new()], sr); }; });// iOS 证书绕过:hook SecTrustEvaluateWithError 返回值 var SecTrust = Module.findGlobalExportByName("SecTrustEvaluateWithError"); // Frida 17+:全局符号静态查找 Interceptor.attach(SecTrust, { onLeave: function (r) { this.context.x0 = 0; } }); // arm64 返回寄存器 x0;x86_64 用 rdi 场景先验证Ready-made commands:
objection -n com.target.app start→android sslpinning disable/ios sslpinning disable。 -
Anti-detection countermeasures (hide frida-server, rename):
- Common detection points: frida-server default port 27042,
/data/local/tmp/frida-serverpath,gum-js-loop/gmainthread name,/proc/self/mapsfrida signatures in,fridastring - Countermeasures:
# 1) 二进制改名后启动(避开路径检测) cp frida-server-xxx /data/local/tmp/fridad adb shell "su -c 'chmod 755 /data/local/tmp/fridad && /data/local/tmp/fridad' &" # 2) 换端口 + adb 端口转发,主机用 -H 连接 adb shell "su -c '/data/local/tmp/fridad -l 0.0.0.0:27142' &" adb forward tcp:27142 tcp:27142 frida -H 127.0.0.1:27142 -f com.target.app - Root detection countermeasures: first hook the detection function to change the return value, then instrument the target:
Java.use("com.target.rootcheck").isRooted.implementation = function () { return false; }; - Still detected → inject frida-gadget into the App process (gadget listen (interactive) mode), together with [[re-apk]]'s smali patch to load libgadget.so
- Common detection points: frida-server default port 27042,
-
Script templates and countermeasure methodology: for common script skeletons see [[frida-scripts]] (TLS keylog / DEX/SO dump / JNI registration restoration / encryption interception / detection bypass table); for the crash-iteration method and detection-surface comparison table see [[re-analyze/anti-dynamic-workflow]] — run a baseline first to see bare crashes, then hook at specific points, do not preset a full bypass toolkit.
Cross-domain combinations
-
[[re-address-space]]: runtime base address conversion (
Process.getModuleByName(...).basealigned with the link address) -
[[re-mobile]]: step 3 of the workflow, dynamic instrumentation, always calls this skill
-
[[re-apk]] / [[re-ios]]: call this skill when runtime behavior is needed after static analysis (decryption / hooking / bypass / unpacking execution)
-
System call-level tracing complement → [[re-tracing]]; runtime memory extraction → [[re-memdump]]
-
This skill is called by [[re-analyze]]'s triage "mobile app analysis" path (re-mobile → re-frida)
-
Script generation: target characteristics → template selection → rewrite and verify → [[re-frida-script-author]] (template materials [[frida-scripts]])
Common pitfalls and traps
- Version mismatch → protocol error: symptom —
frida-ps -Ureportsunable to communicate with the frida server/ protocol error; cause — the client and frida-server major versions are inconsistent, or the feature used does not exist on the target version; countermeasure — first check whether the major versions match (frida --versionagainst the frida-server version, see tool preparation), then confirm whether that feature is supported by the corresponding version; for the upgrade side usepip install -U frida-tools - Frida 17 removes the static Module lookup/enumeration APIs: symptom — the script reports
TypeError: Module.findExportByName is not a function(or the same kind of getExportByName / enumerateExports / findBaseAddress); cause — since 17.0.0 all static lookup and enumeration APIs have been removed, and statically onlyModule.load/Module.findGlobalExportByName/Module.getGlobalExportByNameremains, while the rest have changed tomodule instance methods; countermeasure — get the instance first, then look up:Process.getModuleByName("libfoo.so").findExportByName("func")(find returns null, get throws an exception), for the base address use.base, for enumeration use.enumerateExports(); for global symbols (formerly thenullmodule parameter) useModule.findGlobalExportByName(...)instead; note thatProcess.getModuleByNamewhen the module is not loadedthrows an exception(the oldModule.findExportByNamereturned null) — when unsure, first useProcess.findModuleByNameto check for null before looking up; before writing,frida --versionconfirm the version before choosing the syntax (for the migration checklist see the version-related group in [[re-frida-script-author]]) - spawn timing too late → miss early logic: symptom — after attach, the hook does not trigger or early decryption has already finished; cause — the app decrypts / verifies right at startup, and by the time of attach it is already past; countermeasure — use
-fspawn mode to instrument from the very start; if it is still missed, hookdlopen/ClassLoader.loadClassor similar earlier execution points - Objection Detection frida (Ports / Signatures): Symptoms—the app crashes / freezes / behaves abnormally after spawn; Cause—the app scans port 27042, the frida-server path,
fridathread names, or maps signatures; Countermeasure—rename in step 5 + change the port; if still detected, use frida-gadget injection (hidden within the process) - root detection blocks instrumentation: Symptoms—frida can connect but the hook doesn't take effect or it exits immediately; Cause—the app performs root / jailbreak detection first and exits directly upon detecting the environment; Countermeasure—first hook the return value of the detection function (step 5 template), and after passing detection, hook the target function
- JS script fails silently: Symptoms—the script loads with no errors but the hook produces no output; Cause—incorrect class name,
Java.performcalling the Java API from outside, or a module name with mismatched case; Countermeasure—first verify the names using the enumeration from step 3, and within the script useconsole.loginstrumentation to locate the issue - Hooking exported APIs is bypassed by packers (direct syscall / API name hashing): Symptoms—successfully hooked
IsDebuggerPresent/NtQueryInformationProcessand other exports, yet anti-debugging still triggers and the process exits; Cause—the packed/hardened target does not go through the import table: it implementsGetProcAddressitself, stores API names as hashes, or makes direct syscalls (inlineNt*direct calls), so the hook point is never traversed; Countermeasure—hook one layer deeper (the syscall wrapper point in ntdll), traceGetProcAddress/hash resolution locations to reverse-engineer the real call sites, and hook anti-debugging and anti-VM (RegOpenKeyExA/GetSystemFirmwareTable) APIs as well (Arkana project practical template); first useProcess.enumerateModules()/enumerateExportsto confirm the actual call target before instrumenting - Detection point upgrades (memfd/JIT pool/memory strings/pipe names): Symptoms—still detected after renaming and changing the port to start frida-server; Cause—newer detection goes beyond ports and paths: it scans
/proc/self/fdmemfd names, JIT cache pools (pool-frida),frida_agent.so/frida_rpcand other in-memory strings and exported symbols, pipe/linjector names, and even non-standard ports are scanned (Promon-style scanning); Countermeasure—use the undetected-frida patch set (full obfuscation of strings/symbols/threads/protocol/memfd/JIT pool, in Magisk/KSU module form), or frida-gadget + a self-compiled hidden version, to reduce the fingerprint surface - Strong hardening (Pairipcore-type) as a whole against Frida: Symptoms—common hook scripts (certificate bypass/anti-detection/decryption) all fail, the app crashes or behaves abnormally on spawn, and community reports say frida-interception-type scripts cannot bypass it; Cause—commercial hardening performs C++/Java dual-layer integrity checks + pseudo-VM instructions + custom dlopen/dlsym/syscall dynamic import obfuscation + prctl/clone/waitpid/ptrace anti-debugging +
/proc/self/maps+/proc/self/statusprocess monitoring + non-standard port scanning, a whole defensive line rather than a single point; Countermeasure—first deal with the integrity check and process monitoring (hook the check function's return, patch the monitoring points), then get past anti-debugging, and make hooks land at the custom import resolution sites rather than exported APIs; if necessary, combine with [[re-apk]] static smali modification + Native-layer instrumentation - Troubleshooting hooks on egress functions with multiple processes/implementations: Symptoms—hooking
SocketOutputStreamyields no result, yet packet capture shows traffic; Cause—the app is multi-process (the traffic-sending logic is in a child process) or uses Netty'sSocketChannelImpl(which does not go through the OutputStream path); Countermeasure—troubleshooting order: hook the outermost egress → if no resultps -echeck child processes and hook each → then trySocketChannelImpland other alternative implementations; after a successful hook, print the stack (Netty'sMessageToByteEncoderchain) locate assembly/encryption code; trace plaintext objects and encrypted artifacts layer by layer upward from the "codec chain" - Symmetric encryption key transmission tracing: Symptom — an RC4/AES ciphertext packet is captured, but the key changes every session and cannot be found by static search; Cause — the symmetric encryption key is dynamically generated and transmitted online (in plaintext/carried after encryption/negotiated); Countermeasure — hook the key input point (the encryption function parameter
bArrclass) to get the current key, then trace upward to the key's source and transmission path, cross-checking with packet captures (a fixed header in the first packet such as89 04 01 01can serve as a protocol anchor) - Multi-layer TLS verification stack: Symptom — some requests still get SSL errors after a single-point hook; Cause — the App simultaneously uses multiple stacks: OkHttp/native HttpsURLConnection/Conscrypt; Countermeasure — layered coverage (hook OkHttp CertificatePinner + TrustManagerImpl.verifyChain + HostnameVerifier at the same time); only full-stack coverage counts as a completed bypass
- Locating target classes after ProGuard obfuscation: Symptom — class names are changed to short names; Cause — obfuscation; Countermeasure — in jadx, use Find Usages to reverse-lookup who instantiates the key Builder, and infer the original class from the instantiation point (Source: reverse-skill field-journal, MIT)
- Wrong test environment chosen → dynamic false negatives / delivery false positives: Symptom — in the same environment, hooks do not trigger, memory patches have no effect, or everything works fine on an emulator but problems appear on a real device; Cause — live Frida is unavailable on x86 translation-layer emulators (
Process.arch==x64and the target native so cannot be found), and the runtime never executes your bytes; some targets have lax verification on emulators, so "it runs on the emulator" may be a degraded illusion; Countermeasure — choose the environment by phase: static patching and rendering regression can use any emulator (including x86 translation layers), dynamic instrumentation must use native ARM, and delivery verification must use a real device matching the target; translation-layer illusions can be falsified: patch the first instruction of a known-required function to a bareret, and if the UI still renders normally, the runtime is not executing your bytes — immediately stop live work in that environment (static patches on disk will be re-translated on the next launch and remain usable); before collecting on a real device,svc power stayon trueand wake it to prevent sleep/screen-off from causing "blank" false screenshots (Source: reverse-skills (inliver233), MIT) - Pitfalls of setting up and using the redroid container: Symptom — the guest hangs silently / servicemanager spins idle, vold crashes, adb repeatedly disconnects, and the container cannot be restarted; Cause — incorrect binder mount method (must bind-mount
/dev/binder, cannot use--deviceor binderfs), failure to addandroidboot.use_memfd=1when the new kernel lacks ashmem, settingro.secure=1 ro.debuggable=0which kills root adb, the binder of a stopped container having disappeared so docker restart is impossible, and adbd crashing under high load; Countermeasure — use bind-mount for binder, force use_memfd on new kernels, retain root adb (to prevent tampering, keep the signature valid rather than hiding root), and re-inject the adb public key after recreating the container (echo $PUBKEY > /data/misc/adb/adb_keys); use docker cp rather than adb push for large files; use spawn mode for dynamic analysis (frida -U -f, attach often gets 0 hits under anti-tampering); Positioning insight — it is a data collection tool, not an iteration environment; after collecting dynamic data, return to a stable environment for static patch regression (Source: reverse-skills (inliver233), MIT) - Hooks silently fail to trigger on AOT targets / caller cannot be found: Symptom — the hook produces no output, the target function has static callers=0, and the guard field is unknown so no probe can be placed; Cause — the target so is loaded later than the engine (not loaded at attach time), virtual methods go through the cid dispatch wall (not called via BL, so there is no static shortcut), and the guard field's location is unknown; Countermeasure — hook after the target so has loaded: a 200ms polling
findModuleByNamewaitForLibapp wrapper, preferring spawn mode at the start; break the cid wall: record the target function's onEnter forlr(returnAddress)+Thread.backtrace(Backtracer.ACCURATE)the first 20 frames, and use one backtrace to locate the dispatch point; the guard-location trio: in the Stalker.follow transform, put a putCallout onldrclass instructions (the first hit PC = guard), MemoryAccessMonitor.enable and other rebuild triggers (arm64 support varies and needs a fallback), and candidate hook sweeping;Flip verification at runtime as a precondition:livewritePointerflip the candidate guard field → trigger a rebuild → if the component disappears, the guard field is confirmed; after confirmation, then apply the static patch, saving repeated repackaging; since frida 17 the Java bridge is no longer built into the underlying GumJS runtime, butfridaCLI/REPL andfrida-tracestill ship with the three bridges, and ordinaryfrida -lscripts need no changes; only when building your own agent / injecting scripts via bindings do you need to explicitly installfrida-java-bridge(use frida-compile to package if necessary) (Source: reverse-skills (inliver233), MIT)