This article serves as a review of the use of Frida and provides some example questions, cover image via AI.
Common Frida Command Quick Reference#
frida-ls-devices # List all devices
frida-ps -U # List processes on USB devices
frida-ps -Ua # List running applications
frida-ps -Uai # List all installed applications
frida-ps -D xxxxx # Connect to a specified device, device ID can be obtained from the first command
# Start the application and inject the script; the difference between these two will be mentioned later
frida -U -l exploit.js -f com.xxx.yyy
# The application is already running, inject the script
frida -U -l exploit.js "XXXX"
Frida Basics#
frida
is a hook framework based on Python and JavaScript, which can run on various platforms such as Android, iOS, Linux, and Windows, primarily using dynamic binary instrumentation technology.
Install frida
and frida-tools
using pip:
pip install frida
pip install frida-tools
Check the version of Frida installed with Python:
frida --version
Then install Frida and Frida-server on both PC and mobile, ensuring the versions match.
Next, run Frida-server on the mobile device and forward the mobile port to the PC so that the Python script on the PC can communicate with the Frida-server on the mobile. You can use the adb command to achieve port forwarding; this step does not seem to be mandatory, as it appears to work without forwarding as well. For example:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
You can use the frida-ps -U
command to view process information on the mobile device to select the target process to hook.
In summary:
- Download the corresponding Frida server from the official GitHub release page. Note that if using an emulator (like Nox), the corresponding architecture is x86 instead of ARM.
- Connect to the emulator via adb, push the downloaded server to the directory /data/local/tmp (usually this directory), and start it after running
chmod a+x
. - Run
pip install frida frida-tools
. - Enter
frida-ps -U
to see the list of processes on the mobile device, indicating successful configuration.
Writing and Running Hook Scripts#
Start and inject a hook script:
frida -U -l exploit.js -f com.xxx.yyy
Some simple writing usage, which will be easy to understand if you are familiar with Java reflection.
If you want to use a string, since java.lang.String
is a class, you need to instantiate it (the same applies to other classes if you want to use class methods; if it's a static method, you don't need to instantiate):
var x = Java.use("java.lang.String").$new("xxxxxxxx")
If you want to use a static method of a certain class (class methods require an object to be instantiated first):
Java.use("com.xxx.yyy").method(param1, param2)
If you want to hook a certain method:
Java.perform(function() {
var class = Java.use("com.xxx.yyy")
class.methodName.implementation = function(param1, param2){
send(); // Print log
return this.xxxx // Hook return value
}
})
That's about it for the basic usage.
Pitfalls & Tips#
Jadx supports directly copying Frida code snippets, which can save a lot of effort.
Hook methods need to specify parameters:
// JS code
Java.perform(function () {
var MainActivity = Java.use("com.example.MainActivity"); // Get class reference
var a = MainActivity.a.overload("int", "int", "long"); // Specify parameter types of the overloaded method to hook
a.implementation = function (x, y, z) { // Replace the implementation of the overloaded method
console.log("x: " + x + ", y: " + y + ", z: " + z); // Print parameters
var result = this.a(x, y, z); // Call the original overloaded method
console.log("result: " + result); // Print return value
return result; // Return the original value
};
});
If some functions have no parameters, then overload
does not need to be specified:
// JS code
Java.perform(function () {
var MainActivity = Java.use("com.example.MainActivity"); // Get class reference
var onCreate = MainActivity.onCreate.overload(); // Specify the overloaded type of the function to hook, without passing any parameters
onCreate.implementation = function () { // Replace the implementation of the function
console.log("onCreate() is called"); // Print log
this.onCreate(); // Call the original function
};
});
Example#
Hook MainActivity Method Example#
Refer to the official documentation example; the APK can also be downloaded: Android | Frida • A world-class dynamic instrumentation toolkit
Analyze the logic with jadx, and you can see that it requires this.n
and this.m
to differ by 1, and this.cnt+1
must equal 1000.
So when will these variables be modified? Looking further down, we see the onClick
method.
Then we can write a JS script to hook the onClick
method:
Java.perform (() => {
// Select class
const MainActivity = Java.use ('com.example.seccon2015.rock_paper_scissors.MainActivity');
// Select onClick method
const onClick = MainActivity.onClick;
// Hook onClick
onClick.implementation = function (v) {
// Print a message when the function is called
send ('onClick');
// Call the original onClick method
onClick.call (this, v);
// After calling, modify m, n, cnt values
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
// Print result in Frida console
// Hook ends
console.log ('Done:' JSON.stringify (this.cnt));
};
});
Run the command:
frida -U -l .\seccon2015.js rock_paper_scissors
After execution, it will enter the Frida shell, and log messages will be printed.
Frida also provides a way to call it using Python, but there is a pitfall in the code comments; I personally prefer using the JS method.
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
...copy the above jscode
"""
# Note that there is a pitfall here; when attaching, do not enter the full package name
process = frida.get_usb_device().attach('rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
Found the reason:
import frida
process = frida.get_usb_device().enumerate_processes() # Enumerate all processes
At this point, the actual process name is found, so the code directly writes rock_paper_scissors
instead of the full package name.
Process(pid=2982, name="rock_paper_scissors", parameters={})
Running effect:
At this point, the application has printed the flag.
Love Breaking Red Packet Third Question#
Analyze the logic with jadx; as long as the result returned by the check
function equals 999, the flag can be obtained.
Therefore, directly hook the check
method under MainActivity
to return 999.
function main(){
Java.perform(function(){
var activity = Java.use("com.zj.wuaipojie2023_3.MainActivity");
var check_method = activity.check.overload(); // No overload here, so it is not needed
check_method.implementation = function(arg){
// send('check');
check_method.call(this);
return 999
}
})
}
setImmediate(main)
Click a few times to obtain the flag.
Hook RSA Encryption#
In a competition, there was a level to crack an APK (source files lost). Using jadx to obtain the source code revealed that it was an RSA encryption, and there was a pitfall that took a long time to solve.
Android's RSA implementation is different from that in the Java package; you need to manually download a BouncyCastleProvider.jar package, import it into the project, and modify the code to run.
Actually, the simplest solution is to use Frida to hook.
The implemented code is as follows:
function main(){
Java.perform(function(){
var enc = Java.use("java.lang.String").$new("F3RSCi59ewpMm+dIGtiAn752tBQ0TPx7tCayrCXrrwyG2gtlUJ3C82rcrfgg2A4X7XfhXWGidsJgGDzbD9JwvzGbKMHTK5kxJUf1EJ605R/Rv2N+6Jtr54wQAKbqGyU8g/rUhLUf/EgNqPPYTQ6fa4LqFEMTrPm/UZMvL/4yTNhFoM5vQbceQeD9QNATh6kdYEzLfRdWbv/ESllURlgZbQsc7+pHC8Sg9wVh/2MGZHKzZI8a67Usqhk8ojmfgN5ABodQ2z1DOCICg7AH+l+3fx6eOn1dup/Q1hpCHVCgtojMOPfoLcxICY5LkSonrFjAKzv4JHO/f5ihFXX8Dr4MFw=="); // Instantiate a string
var PrivateKey = Java.use("java.lang.String").$new("MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcscawF6Fy4Smk\nqwOy2W/IcewNTYzHe9ZFV6w1dqrZ+sRBRD6Sapv7k4deLF9ziYsWXfcDNuvG2HN6\nAgk6CeN5QHUj7OiJmTnitHAQFIw7Ed6O3uyE64i3dhZIKh7oAies6EMpfKusXIZ6\nt1lFwVVpPjlrW6435j/nW5X0OeaRRk89zOxIbzAhS0yxArkMSecy9lr+UwzWQhFO\nW4+O/ywesds9VdCuycx2S/1zbG7BwJP507nTxstLkUnet0BHqXH3tH8cRXXDyFo1\n13gBgrBZ7Y79Z7T39B+21qq7RxfwvPDqu6Rq3p9cOUQCwfJSWlHYBVtYOME04vZP\nkZj/PqLhAgMBAAECggEBAL9QLRkBoydfISZqSW6FJ23vs91kWKGlLH44HkTKdhk4\nVWDus/9hA472uOZeOPCDT1s4YaUbuxBJGvGtSipR4CuAQQu/l2WQuqlYGb0W0ulx\n+CiJ/ybKt12ytGYifWBKXfJ59Z7FQFzOaJWA05awnkX5dvUmKMS0iLkan8dzWfic\nCn379Jn/bLmRXSTKgWNMi9toT4kNhnr2/cf1ah1i2MHHZb4ZMdC9rqnSvyzFRAFM\nuess0Qg8ckyoNzx2daP9vXapn8rg6B/MFBZqjorwyv0KJms/S6W/YKw88OixM/J3\nxlBsk8QHJqaL5IZW6/QeIHJk4K6o9+3fTxtHEjcWoAECgYEA8ASApzgATrHUFVdT\nZ6ZohLFMyQQZzaKLtZhBRfenASrWQSpHHcGdF15C45fik/vohZvF/byrHLkwvosp\nvC6BHyF7toJDrTEWBo/xymGNYmovr0keMUc3T4aRomaERz0gY8CLhKpHMKJrVtgj\nkHlAGqkRZB6AAlcEQUSOyvYziyECgYEA62Pg4xMw1BfJKSfLhCoaWHeWBMAbuZ8K\ncEOLIO8CtGKwXRLBe6MUKTDnHt7aXawVd/j6jk27uhD8p1noYqbsAexzMIKAAzic\nI6fsCVgUfvRZzhaQ98xyBO34AmAtzhJQEg65TnybyEf8tC5xPSegCzzYA5w8QfaM\necRq8V8R38ECgYEAkRNXpDuATBDGzaN8AXGfSV1VuUcmRZpTcg82nREFO/pliPwC\nAmVuC5rpOWIsDSC9ukyezzEECJeTdAjv0BQX7fYyLe3s9DlwQ8Ur9BUk/XCLpBrB\nCi4uq33+dzgaCmlTM5hFfInj/+ycjuCUFpaDfdnlbivVWhS4uK601M8d9qECgYEA\nuxel1ZaZRtqo/tcsgZ3dVtemG5x97OmmKFjnKoQOiEWwnCmeM6EJEzxVV0oWEkIG\nxlbU/2p1fYym5HUnqdG24EiJvdLb4LOMFyMPDtY9ZDLfdlilXO/Y6GYkq+66OYKA\ntfmR+/o679OX3bbUNqFaKdRwdV4m3t8SBG7D+Zlw4YECgYAp2B4F1WOu5H/P0yC6\n6Hn/FGcQec8ty6MCPlH1km9dQBS3jMvBAFpmnMv+hWiR4W93tOHRqRndR6VFC/dX\n+io76NklgjQGteUJ/Nawhg0sDpWCUGECRypwbNTgmhcrmu/RF1B22rO8sckPXXeB\nDzVmjKFXgpDDIJ0eDUTQbqEEMw=="); // Instantiate a string
var encB64Decode = Java.use("com.chaos.view.example.Base64Utils").decode(enc); // Call Base64Utils's decode method
var loadPrivateKey = Java.use("com.chaos.view.example.RSAUtils").loadPrivateKey(PrivateKey); // Call RSAUtils's loadPrivateKey method
var res = Java.use("java.lang.String").$new(Java.use("com.chaos.view.example.RSAUtils").decryptDataPrivate(encB64Decode, loadPrivateKey)); // Call RSAUtils's decryptDataPrivate decryption method
var qaq = Java.use("com.chaos.view.example.Qaq").decode(res, 127);
console.log(qaq);
})
}
setImmediate(main)
Call frida -U -l xxx.js
.
OWASP MSTG Uncrackable 1 Bypass Root Detection#
The APK has root detection; using jadx to view the logic reveals that the judgment is written in the a.a
function.
Following the a.a
method, simply hook the return result to be true.
First, bypass the root detection and find a script shared by an expert:
Then try it, and successfully bypass the root detection.
frida -U --codeshare dzonerzy/fridantiroot -f "owasp.mstg.uncrackable1"
Next, hook it.
Java.perform(() => {
// Find class a
const a = Java.use('sg.vantagepoint.uncrackable1.a');
// Find method a; although there is no overload, still specify the parameter type
const a_method = a.a.overload("java.lang.String");
a_method.implementation = function (v) {
// Print a message indicating it has been hooked
send('a method call');
// Call the original method
a_method.call(this, v);
return true; // Hook the return value of the original function
};
});
Then run your script with -l
, and --codeshare
is used to run the script that bypasses root detection.
frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable1.js -f "owasp.mstg.uncrackable1"
OWASP MSTG Uncrackable 2#
The logic here is similar to the first one, but the method is located in the native lib, so you need to hook the function in libfoo.so
.
The template for hooking native methods is as follows:
Java.perform(() => {
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'xxxx'),{
onEnter: function(args){
// Modify the parameters when entering the function
},
onLeave: function(retval){
// Modify the return value of the function
}
});
},2000);
});
Referencing the template code, you can write the following hook script:
Java.perform(() => {
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'Java_sg_vantagepoint_uncrackable2_CodeCheck_bar'),{
onEnter: function(args){
},
onLeave: function(retval){
console.log(retval);
retval.replace(ptr(1));
console.log(retval);
}
});
},2000);
});
Note the pitfall here:
There are two ways to hook with Frida:
- Inject
frida -U -l .\uncrackable2.js "Uncrackable Level 2" # Inject into the corresponding process
- Start
frida -U -l .\uncrackable2.js -f owasp.mstg.uncrackable2 # Start the corresponding process
I initially used -f
, which caused the program not to load libfoo.so
at the beginning, leading to confusion for a long time, even thinking that the Nox emulator was unsupported.
The final code that works for both methods just needs to add setTimeout
.
Note that the first run may fail, but the second will succeed.
frida -U --codeshare dzonerzy/fridantiroot -l .\uncrackable2.js -f owasp.mstg.uncrackable2
Refer to https://1337.dcodx.com/mobile-security/owasp-mstg-crackme-2-writeup-android for code to hook the strncmp
method in native.
Java.perform(() => {
console.log("[*] Hijacking the onClick button")
var clazz_main = Java.use('sg.vantagepoint.uncrackable2.MainActivity$1')
clazz_main.onClick.implementation = function () {
console.log('onCLick() is replaced ');
};
console.log()
console.log('[*] ACTION NEEDED: Insert the string "I want your secret asap" as input')
console.log()
setTimeout(function(){
Interceptor.attach(Module.findExportByName('libfoo.so', 'strncmp'),{
onEnter: function(args){
if( Memory.readUtf8String(args[1]).length == 23 && Memory.readUtf8String(args[0]).includes("I want your secret asap")){
console.log()
console.log()
console.log("*******SECRET********")
console.log(Memory.readUtf8String(args[1]))
console.log("*******SECRET********")
console.log()
console.log()
}
},
onLeave: function(retval){
}
});
},2000);
});
Run it:
frida -U -l .\uncrackable2.js "Uncrackable Level 2"
OWASP MSTG Uncrackable 3#
To be continued...