Post

Android Hooking in Frida

Intro guide on how to use Frida to hook Android applications at runtime to inject code and override methods. This guide already assumes you have frida installed and have frida-server installed on your Android device.

  1. Basic Android Hooking
  2. Hooking Strings

Basic Android Hooking

Class Methods - WebView URL

The following code will show how to hook a Method in a class and set the implementation to the custom code we define.

We’ll create a new file called webview.js and put the following code:

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function() {
	let Webview = Java.use("android.webkit.WebView");

	console.log('\n\n[+] Using "android.webkit.WebView"\n');

	Webview.loadUrl.overload("java.lang.String").implementation = function(url) {
		console.log('[+] WebView loadUrl =>', url);

		this.loadUrl.overload("java.lang.String").call(this, url);
	}
});

We will then call this use this script with frida on our target application:

1
frida -U -f com.example.app -l webview.js --no-pause
  • -U for USB mode
  • -f to tell frida to start the app
  • -l to specify the script to load
  • --no-pause to tell frida not to pause the app when it first starts so we don’t have to manually resume it (Optional)

Script Breakdown

Let’s breakdown the above script a bit further and explain what each part is doing:

let Webview = Java.use("android.webkit.WebView");

  • Get a reference to the android.webkit.WebView class

Webview.loadUrl.overload("java.lang.String")

  • Get a reference to the loadUrl method in the Webview (android.webkit.WebView) class.
  • Specifically the method that takes a String parameter.
  • Java methods can be overloaded (multiple methods with same name but take different params), so we need to be specific which method.

Webview.loadUrl.overload("java.lang.String").implementation = function(url) { ... }

  • Set the implementation of the loadUrl method to the function that we define.
  • This is applied for all instances of Webview class.

this.loadUrl.overload("java.lang.String").call(this, url);

  • Call the original implementation. We have to specific java.lang.String in the overload method so we call the correct one.

Dealing with Types

For Methods that take string parameters it’s very easy to find that we should put java.lang.String as the type, but what about something more complex? What if the function accepts a Byte array, or Custom class as a parameter? How do we know what type to put?

We could search on Google, or for a quicker method, let the frida errors tell us. If we change java.lang.String to something invalid like abcd, frida will tell us that the signature doesn’t match any of the overloads, and it will tell us what to put to make it valid. Using the previous script as a example:

1
Webview.loadUrl.overload("abced")
1
2
3
4
5
6
[+] Using "android.webkit.WebView"

Error: loadUrl(): specified argument types do not match any of:
        .overload('java.lang.String')
        .overload('java.lang.String', 'java.util.Map')
    at X (frida/node_modules/frida-java-bridge/lib/class-factory.js:563)

From this we can see there are two overloads available:

  • overload('java.lang.String')
  • overload('java.lang.String', 'java.util.Map')

Class Constructors

We can hook the constructor of a class using $init as the method name.

We’ll use the okhttp3 library and Response class within as an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const printObj = (obj) => {
	Object.entries(obj).forEach(([key, value]) => {
		console.log(`   - (${typeof(value)}):`, value)
	});
}

Java.perform(function() {
	let params = ['okhttp3.Request', 'okhttp3.Protocol', 'java.lang.String', 'int', 'okhttp3.Handshake', 'okhttp3.Headers', 'okhttp3.ResponseBody', 'okhttp3.Response', 'okhttp3.Response', 'okhttp3.Response', 'long', 'long', 'okhttp3.internal.connection.Exchange'];

	let okHttp3Response = Java.use('okhttp3.Response');
	console.log(`\n\n[+] Hooking "okhttp3.Response"\n`);

	okHttp3Response.$init.overload(...params).implementation = function() {
		console.log('[+] Response Captured:');
		printObj(arguments);

		this.$init.overload(...params)(...arguments);
	}
});

Calling Static Methods

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function() {
	let AndroidUnicodeUtils = Java.use('com.facebook.hermes.unicode.AndroidUnicodeUtils');

	let result1 = AndroidUnicodeUtils.convertToCase('test string', 0, false);
	let result2 = AndroidUnicodeUtils.convertToCase('TEST STRING', 1, false);
	let result3 = AndroidUnicodeUtils.dateFormat(1636880183370.0, true, true);

	console.log('\n\n[+] Result1:', result1);
	console.log('[+] Result2:', result2);
	console.log('[+] Result3:', result3);
});

OUTPUT

1
2
3
4
5
6
7
8
9
10
C:\>frida -U -f com.facebook.orca -l call-static-method.js --no-pause

...

Spawned `com.facebook.orca`. Resuming main thread!
[Pixel XL::com.facebook.orca]->

[+] Result1: TEST STRING
[+] Result2: test string
[+] Result3: Nov 14, 2021 7:56:23 PM

Creating Objects

TODO: NOT COMPLETED

Already Created Objects

TODO: NOT COMPLETED

WebView DevTools

If the target application is using webview to load content, it’s possible to view the developer tools for the mobile app like you can in any other browser.

You can following the guide on the Google Chrome website about Remote Debugging WebViews found here.

However, it’s very likely that the application won’t have webContentsDebugging enabled. Luckily though, we can do it very easily with Frida. We’ll save the below script in a file called webview-remote-debug.js.

1
2
3
4
5
6
7
Java.perform(function() {
	let Webview = Java.use("android.webkit.WebView");
	console.log('\n\n[+] Hooking "android.webkit.WebView"\n');

	Webview.setWebContentsDebuggingEnabled(true);
	console.log('[+] Enabled WebContentsDebugging\n');
});

We’ll use the above code on the Amazon Shopping app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
C:\>adb shell pm list packages | findstr amazon
package:com.amazon.mShop.android.shopping

C:\>frida -U -f com.amazon.mShop.android.shopping -l webview-remote-debug.js --no-pause

...

Spawned `com.amazon.mShop.android.shopping`. Resuming main thread!
[Pixel XL::com.amazon.mShop.android.shopping]->

[+] Using "android.webkit.WebView"

[+] Enabled WebContentsDebugging

We can now use devtools to run JavaScript, inspect elements, view network traffic, view cookies, view session/local storage, etc.

NOTE: If the application isn’t coming up, try closing Chrome completely and reloading chrome://inspect/#devices as sometimes it can be buggy.


Hooking Strings

There will most likely be A LOT of output from the below scripts:

toString Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java.perform(function() {
	console.log();
	let StringBuilder = Java.use('java.lang.StringBuilder');
	let StringBuffer = Java.use('java.lang.StringBuffer');

	StringBuilder.toString.overload().implementation = function() {
		let StringBuilderResult = this.toString.call(this);
		console.log("[+] StringBuilder:\t", StringBuilderResult);

		return StringBuilderResult;
	}

	StringBuffer.toString.overload().implementation = function() {
		let StringBufferResult = this.toString.call(this);
		console.log("[+] StringBuffer:\t", StringBufferResult);

		return StringBufferResult;
	}
});

String Equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Java.perform(function() {
	console.log();
	let str = Java.use('java.lang.String');

	str.equals.overload('java.lang.Object').implementation = function(obj) {
		let StringEqualsResult = this.equals.overload('java.lang.Object').call(this, obj);

		console.log(`[+] StringEquals (${StringEqualsResult}):`);
		console.log("      [String1]:", this.toString());
		console.log("      [String2]:", obj.toString());

		return StringEqualsResult;
	}
});


Resources & References

This post is licensed under CC BY 4.0 by the author.