I’ve always wanted to make my frida scripts permanent in the applications I’ve hacked but always thought it was too difficult, well I recently revisited this idea and realized how easy it actually is. So here is a quick guide on how to do just that.
Initial hack/modification
For this demonstration I will be using “Kittycorn diary” (https://play.google.com/store/apps/details?id=pl.netigen.kittycorn) as I wanted an app that would be easy to demonstrate a hack on, it also has an odd developer choice of putting the stored password in logcat when you first open the app, which will be useful for the final demonstration.
Once you have the apk simply open it with jadx-gui and find the code that compares the stored pin to the pin that the user has supplied. Right click on that and “copy as frida snippet”
Paste that into a text editor and make it so it always returns true (no matter what pin we put in it will be accepted as the correct pin). It should look as follows:
setImmediate(function() { //prevent timeout Java.perform(function() { let LoginActivityPresenter = Java.use("pl.netigen.diaryunicorn.loginactivity.LoginActivityPresenter"); LoginActivityPresenter["equalsPin"].implementation = function () { console.log('equalsPin is called'); let ret = this.equalsPin(); console.log('equalsPin ret value is ' + ret); return true; } }); });
That is it, we have successfully hacked/modified the app in the way we intended. We can test this by storing it as “1.js” and running the following command:
╰» frida -U --no-pause -l 1.js -f pl.netigen.kittycorn
Now whatever pin you enter will let you in to the app.
Frida gadgets
Here’s where the magic happens allowing you to pack that .js file with the apk. Typically you inject the frida gadget library into an apk so you can use Frida without root, however it turns out you can also insert the script inside the apk and make it load automatically, without requiring the frida client.
There are 2 methods of doing this (stolen from https://fadeevab.com/frida-gadget-injection-on-android-no-root-2-methods/):
- Method 1: If targeted APK contains any native library (<apk>/lib/arm64-v8a/libfromapk.so), then you can inject libfrida-gadget.so as a dependency into the native library.
- Method 2: If APK doesn’t contain a native library, then you can inject System.loadLibrary bytecode.
I suggest you read the above blog to understand how to inject the frida gadget in both instances. When injecting the frida gadget you also supply a config file, this file can contain the location to your script i.e.
{ "interaction": { "type": "script", "path": "/data/local/tmp/myscript.js", "on_change": "reload" } }
Using the above you would then store your script in “/data/local/tmp/myscript.js”. This is useful for quick and constant changes as you can then modify the script on the device and re-open the app which will then load it along with the changes. this is how https://github.com/darvincisec/InjectFridaGadget works.
When I was looking for all the different ways of doing this I found what is possibly the simplest solution.
Objection
It turns out this has already been built into objection all you have to run is the “patchapk” function along with the config file and the script to inject. This will inject the script directly into the apk, however if you want to make changes in the future you will have to re-compile again. This method has the added benefit that it also automatically signs and zip-aligns the APK after it has recompiled it!
For our example firstly create “config.json” containing:
{ "interaction": { "type": "script", "path": "libfrida-gadget.script.so" } }
Then run the following command:
╰» objection patchapk -s pl.netigen.kittycorn.apk -c config.json -l 1.js No architecture specified. Determining it using `adb`... Detected target device architecture as: x86_64 Using latest Github gadget version: 15.2.2 Patcher will be using Gadget version: 15.2.2 Detected apktool version as: 2.6.1 Running apktool empty-framework-dir... I: Removing 1.apk framework file... Unpacking pl.netigen.kittycorn.apk App already has android.permission.INTERNET Target class not specified, searching for launchable activity instead... Reading smali from: /tmp/tmpknaa5qlw.apktemp/smali/pl/netigen/diaryunicorn/splash/SplashActivity.smali Injecting loadLibrary call at line: 22 Attempting to fix the constructors .locals count Current locals value is 0, updating to 1: Writing patched smali back to: /tmp/tmpknaa5qlw.apktemp/smali/pl/netigen/diaryunicorn/splash/SplashActivity.smali Copying Frida gadget to libs path... Adding a gadget configuration file... Copying over a custom script to use with the gadget config. Rebuilding the APK with the frida-gadget loaded... Built new APK with injected loadLibrary and frida-gadget Performing zipalign Zipalign completed Signing new APK. Signed the new APK Copying final apk from /tmp/tmpknaa5qlw.apktemp.aligned.objection.apk to pl.netigen.kittycorn.objection.apk in current directory... Cleaning up temp files...
Finally install the generated apk *note: the generated one ends with .objection.apk*
╰» adb install pl.netigen.kittycorn.objection.apk Performing Push Install pl.netigen.kittycorn.objection.apk: 1 file pu...skipped. 15.1 MB/s (72033951 bytes in 4.554s) pkg: /data/local/tmp/pl.netigen.kittycorn.objection.apk Success
Demo
The logcat on the left shows the actual pin code stored on the device, this is from code the developer has left in the app. The android VM on the right shows access being granted no matter what pin is entered.
I hope this has helped someone, and possibly you have learned a something from this quick blog.