frida
Use case
Dynamically instrument the target application
Explanation
Hook certain Java or native functions to:
- trace calls and print callstacks
- inspect or modify their arguments or return values
- change their implementation completely
Usage
Note: you need to set up frida first. Simplest case: rooted device/emulator + frida-server.
$ wget https://github.com/frida/frida/releases/download/17.19.0/frida-server-17.19.0-android-arm64.xz
$ xz -d frida-server-17.19.0-android-arm64.xz
$ chmod +x frida-server-17.19.0-android-arm64
$ adb push frida-server-17.19.0-android-arm64 /data/local/tmp/
$ adb shell
$ su
# cd /data/local/tmp
# ./frida-server-17.19.0-android-arm64 &
# exit
$ exit
$ pipx install frida-tools
$ frida-ps -U -a
PID Name Identifier
---- --------------- ---------------------------------------
3177 Camera com.android.camera2
5027 Chrome com.android.chrome
2997 Files com.google.android.documentsui
1783 Google com.google.android.googlequicksearchbox
1783 Google com.google.android.googlequicksearchbox
3791 Messages com.google.android.apps.messaging
3539 Personal Safety com.google.android.apps.safetyhub
2940 Phone com.google.android.dialer
4665 Photos com.google.android.apps.photos
1329 SIM Toolkit com.android.stk
1443 Settings com.android.settings
You can use frida-trace to auto-generate the default onEnter and onLeave handlers. These can be modified after if necessary.
Hook a native function (connect):
$ frida-trace -U -p 5027 -i "connect"
Instrumenting...
connect: Auto-generated handler at "/home/gemesa/git-repos/android-security-handbook/__handlers__/libc.so/connect.js"
Started tracing 1 function. Web UI available at http://localhost:44493/
Tip: Luma, the official Frida app, has frida-trace built in — with a Monaco handler editor and instruction-level tracing you can diff across runs. https://luma.frida.re/
/* TID 0x1405 */
35936 ms connect(sockfd=0x102, addr=0x73a9b303f300, addrlen=0x1c)
35937 ms | connect(sockfd=0x103, addr=0x73acaabdb69e, addrlen=0x6e)
35938 ms | connect(sockfd=0x103, addr=0x73acaabdb69e, addrlen=0x6e)
...
/home/gemesa/git-repos/android-security-handbook/__handlers__/libc.so/connect.js:
/*
* Auto-generated by Frida. Please modify to match the signature of connect.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: https://frida.re/docs/javascript-api/
*/
defineHandler({
onEnter(log, args, state) {
log(`connect(sockfd=${args[0]}, addr=${args[1]}, addrlen=${args[2]})`);
},
onLeave(log, retval, state) {
}
});
Hook a Java function (android.net.Uri!parse):
$ frida-trace -U -p 5027 -j 'android.net.Uri!parse'
Instrumenting...
Uri.parse: Auto-generated handler at "/home/gemesa/git-repos/android-security-handbook/__handlers__/android.net.Uri/parse.js"
Started tracing 1 function. Web UI available at http://localhost:41025/
Tip: Luma, the official Frida app, has frida-trace built in — with a Monaco handler editor and instruction-level tracing you can diff across runs. https://luma.frida.re/
/* TID 0x13a3 */
11338 ms Uri.parse("https://m.youtube.com/")
11339 ms <= "<instance: android.net.Uri, $className: android.net.Uri$StringUri>"
...
/home/gemesa/git-repos/android-security-handbook/__handlers__/android.net.Uri/parse.js:
/*
* Auto-generated by Frida. Please modify to match the signature of Uri.parse.
*
* For full API reference, see: https://frida.re/docs/javascript-api/
*/
defineHandler({
/**
* Called synchronously when about to call Uri.parse.
*
* @this {object} - The Java class or instance.
* @param {function} log - Call this function with a string to be presented to the user.
* @param {array} args - Java method arguments.
* @param {object} state - Object allowing you to keep state across function calls.
*/
onEnter(log, args, state) {
log(`Uri.parse(${args.map(JSON.stringify).join(', ')})`);
},
/**
* Called synchronously when about to return from Uri.parse.
*
* See onEnter for details.
*
* @this {object} - The Java class or instance.
* @param {function} log - Call this function with a string to be presented to the user.
* @param {NativePointer} retval - Return value.
* @param {object} state - Object allowing you to keep state across function calls.
*/
onLeave(log, retval, state) {
if (retval !== undefined) {
log(`<= ${JSON.stringify(retval)}`);
}
}
});
Anti-instrumentation/anti-hooking
Some hardened apps are capable of detecting the presence of frida. A cheap solution might be renaming frida-server and making it listen on a non-default port.
Host:
# mv ./frida-server-17.18.0-android-arm64 ol0
# ./ol0 -l 0.0.0.0:29436
Client:
$ frida-trace -H <host_ip>:29436 -p 5027 -j 'android.net.Uri!parse'
Client isolation
On some networks clients are isolated, meaning they cannot communicate to each other. Some possible solutions are listed below.
Convention:
- client: pc
- host: android
# wifi client isolation is enabled:
#
# client:
# adb forward tcp:29436 tcp:29436
# frida -H 127.0.0.1:29436 -n <app>
# host:
# ./ol0 -l 127.0.0.1:29436 &
# or:
# nohup ./ol0 -l 127.0.0.1:29436 > /dev/null 2>&1 &
#
# https://github.com/jpillora/chisel
# client:
# chisel server -p 8080 --reverse
# host:
# curl -LO https://github.com/jpillora/chisel/releases/download/v1.12.0/chisel_1.12.0_linux_arm64.gz
# gunzip chisel_1.12.0_linux_arm64.gz
# chmod +x chisel_1.12.0_linux_arm64
# chisel client CLIENT_IP:8080 R:29436:127.0.0.1:29436
# ./ol0 -l 127.0.0.1:29436 &
# or:
# nohup ./ol0 -l 127.0.0.1:29436 > /dev/null 2>&1 &
#
# USB tethering
#
# client:
# ip a --> enx... interface
# get gateway (android device) ip:
# ip route | grep enx
# frida -H ENX_GATEWAY_IP:29436 -n <app>
# host:
# Settings --> Network & Internet --> Hotspot & Tethering --> USB Tethering
# ./ol0 -l 0.0.0.0:29436 &
# or:
# nohup ./ol0 -l 0.0.0.0:29436 > /dev/null 2>&1 &
#
# wifi client isolation is disabled:
#
# client:
# frida -H <phone-wifi-ip>:29436 -n <app>
# host:
# ./ol0 -l 0.0.0.0:29436 &
# or:
# nohup ./ol0 -l 0.0.0.0:29436 > /dev/null 2>&1 &
Running frida-server in an improperly configured shell
In some cases (e.g. kernel exploits), we do not spawn a properly configured shell so we need to set up some environment variables manually.
# adb shell
# echo $BOOTCLASSPATH > /data/local/tmp/boot.txt
# echo $DEX2OATBOOTCLASSPATH > /data/local/tmp/dex2oat.txt
BOOTCLASSPATH="$(cat /data/local/tmp/boot.txt)" \
DEX2OATBOOTCLASSPATH="$(cat /data/local/tmp/dex2oat.txt)" \
ANDROID_DATA=/data \
ANDROID_ROOT=/system \
ANDROID_RUNTIME_ROOT=/apex/com.android.runtime \
ANDROID_TZDATA_ROOT=/apex/com.android.tzdata \
ANDROID_I18N_ROOT=/apex/com.android.i18n \
./ol0 -l 0.0.0.0:29436