Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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