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.
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;
}
});