Android Examples

Frida Android Examples

CTF Tool: Hook Android APK (Java Method Replacement)

Target: com.example.seccon2015.rock_paper_scissors (SECCON Quals CTF 2015 APK1) Environment: Android 4.4 x86 emulator recommended Run: python ctf.py

 1import frida, sys
 2
 3def on_message(message, data):
 4    if message['type'] == 'send':
 5        print("[*] {0}".format(message['payload']))
 6    else:
 7        print(message)
 8
 9jscode = """
10Java.perform(() => {
11  const MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
12
13  const onClick = MainActivity.onClick;
14  onClick.implementation = function (v) {
15    send('onClick');           // notify Python host
16
17    onClick.call(this, v);    // run original handler first
18
19    // Overwrite instance fields after original logic runs
20    this.m.value   = 0;
21    this.n.value   = 1;
22    this.cnt.value = 999;
23
24    console.log('Done:' + JSON.stringify(this.cnt));
25  };
26});
27"""
28
29process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
30script  = process.create_script(jscode)
31script.on('message', on_message)
32print('[*] Running CTF')
33script.load()
34sys.stdin.read()

Field access rules:

SituationSyntax
Set field m (no method named m)this.m.value = 0
Set field m (method named m also exists)this._m.value = 0
General rule for all instance fieldsAlways use .value to read/write

Java Bridge Capabilities

Prerequisites: Script must run inside Java.perform().

 1Java.perform(() => {
 2
 3  // --- 1. Create Java String instance ---
 4  const JavaString    = Java.use('java.lang.String');
 5  const exampleString1 = JavaString.$new('Hello World, this is an example string in Java.');
 6  console.log('[+] exampleString1: '         + exampleString1);
 7  console.log('[+] exampleString1.length(): ' + exampleString1.length());
 8
 9  // --- 2. Create String from byte array + Charset (overloaded constructor) ---
10  const Charset = Java.use('java.nio.charset.Charset');
11  const charset = Charset.defaultCharset();
12
13  const charArray = 'This is a Javascript string converted to a byte array.'
14    .split('').map(c => c.charCodeAt(0));
15
16  // Select the overload: String(byte[], Charset)
17  const exampleString2 = JavaString.$new
18    .overload('[B', 'java.nio.charset.Charset')
19    .call(JavaString, charArray, charset);
20  console.log('[+] exampleString2: '         + exampleString2);
21  console.log('[+] exampleString2.length(): ' + exampleString2.length());
22
23  // --- 3. Hook StringBuilder constructor (String arg overload) ---
24  // Use $init (not $new) to intercept the initializer; $new = alloc + init
25  const StringBuilder = Java.use('java.lang.StringBuilder');
26  const ctor = StringBuilder.$init.overload('java.lang.String');
27  ctor.implementation = function (arg) {
28    const result  = ctor.call(this, arg);
29    const partial = arg !== null ? arg.toString().replace('\n', '').slice(0, 10) : '';
30    console.log('new StringBuilder("' + partial + '");');
31    return result;
32  };
33  console.log('[+] new StringBuilder(java.lang.String) hooked');
34
35  // --- 4. Hook StringBuilder.toString() ---
36  const toString = StringBuilder.toString;
37  toString.implementation = function () {
38    const result  = toString.call(this);
39    const partial = result !== null ? result.toString().replace('\n', '').slice(0, 10) : '';
40    console.log('StringBuilder.toString(); => ' + partial);
41    return result;
42  };
43  console.log('[+] StringBuilder.toString() hooked');
44
45});

Key Java bridge APIs:

APIPurpose
Java.use(className)Get a wrapper for a Java class
$new(args)Call constructor (alloc + init)
$initReference to the initializer only
.overload(sig1, sig2, ...)Select specific overload by JNI type descriptors
.implementation = function(){}Replace method body
.call(thisRef, args)Call original method from replacement
instance.field.valueRead/write instance field
instance._field.valueRead/write field whose name collides with a method

Stack Trace Capture

Use case: Log the Java call stack at the point a sensitive API (e.g., Cipher.init) is called.

 1Java.perform(() => {
 2  const Cipher    = Java.use('javax.crypto.Cipher');
 3  const Exception = Java.use('java.lang.Exception');
 4  const Log       = Java.use('android.util.Log');
 5
 6  // Hook the 2-argument overload: Cipher.init(int opmode, Key key)
 7  const init = Cipher.init.overload('int', 'java.security.Key');
 8  init.implementation = function (opmode, key) {
 9    const result = init.call(this, opmode, key);
10
11    console.log('Cipher.init() opmode:', opmode, 'key:', key);
12    console.log(stackTraceHere());
13
14    return result;
15  };
16
17  // Capture stack trace by constructing a Java Exception and formatting it
18  function stackTraceHere() {
19    return Log.getStackTraceString(Exception.$new());
20  }
21});

How it works: