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:
| Situation | Syntax |
|---|---|
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 fields | Always 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:
| API | Purpose |
|---|---|
Java.use(className) | Get a wrapper for a Java class |
$new(args) | Call constructor (alloc + init) |
$init | Reference 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.value | Read/write instance field |
instance._field.value | Read/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:
Exception.$new()constructs a JavaExceptionat the current call point, capturing a stack trace.Log.getStackTraceString(throwable)converts it to a human-readable string.- No actual exception is thrown; this is a pure introspection technique.