frida-tutorial-2.md (11025B)
1 --- 2 title: "Frida Tutorial 2" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/android-app-pentesting/frida-tutorial/frida-tutorial-2.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/frida-tutorial/frida-tutorial-2.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Frida Tutorial 2 14 15 **This is a summary of the post**: [https://11x256.github.io/Frida-hooking-android-part-2/](https://11x256.github.io/Frida-hooking-android-part-2/) (Parts 2, 3 and 4)<sup>[[3]](#references)</sup>\ 16 **APKs and Source code**: [https://github.com/11x256/frida-android-examples](https://github.com/11x256/frida-android-examples) 17 18 This part focuses on **overloaded methods**, **calling app code from your Frida script**, **keeping references to live Java objects**, and **using Python as a controller** for interactive instrumentation. 19 20 ## Part 2 21 22 Here you can see an example of how to **hook 2 functions with the same name** but different parameters.\ 23 Also, you are going to learn how to **call a function with your own parameters**.\ 24 And finally, there is an example of how to **find an instance of a class and make it call a function**. 25 26 ```javascript 27 // s2.js 28 console.log("Script loaded successfully"); 29 30 Java.perform(function () { 31 var MyActivity = Java.use("com.example.a11x256.frida_test.my_activity"); 32 var JString = Java.use("java.lang.String"); 33 34 var funInt = MyActivity.fun.overload("int", "int"); 35 funInt.implementation = function (x, y) { 36 console.log("original call: fun(" + x + ", " + y + ")"); 37 return funInt.call(this, 2, 5); 38 }; 39 40 var funString = MyActivity.fun.overload("java.lang.String"); 41 funString.implementation = function (x) { 42 var myString = JString.$new("My TeSt String#####"); 43 console.log("Original arg: " + x); 44 var ret = funString.call(this, myString); 45 console.log("Return value: " + ret); 46 return ret; 47 }; 48 49 Java.choose("com.example.a11x256.frida_test.my_activity", { 50 onMatch: function (instance) { 51 console.log("Found instance: " + instance); 52 console.log("Result of secret func: " + instance.secret()); 53 }, 54 onComplete: function () {}, 55 }); 56 }); 57 ``` 58 59 To create a `String`, this script first references `java.lang.String` and then creates a new object with `$new()`. That is the correct way to instantiate Java objects from Frida. In this specific case, passing a plain JavaScript string like `funString.call(this, "hey there!")` also works because Frida will coerce it to `java.lang.String`. 60 61 ### Python 62 63 ```python 64 # loader.py 65 import frida 66 import time 67 68 with open("s2.js", "r", encoding="utf-8") as f: 69 jscode = f.read() 70 71 device = frida.get_usb_device(timeout=5) 72 pid = device.spawn(["com.example.a11x256.frida_test"]) 73 device.resume(pid) 74 time.sleep(1) # Without it Java.perform may run before ART is ready 75 session = device.attach(pid) 76 script = session.create_script(jscode) 77 script.load() 78 input() 79 ``` 80 81 ```bash 82 python3 loader.py 83 ``` 84 85 ### Keeping instances after `Java.choose` 86 87 `Java.choose()` gives you a live wrapper during the callback. If you want to keep using that object later, retain it explicitly: 88 89 ```javascript 90 var cached = null; 91 92 Java.perform(function () { 93 Java.choose("com.example.a11x256.frida_test.my_activity", { 94 onMatch: function (instance) { 95 cached = Java.retain(instance); 96 console.log("Retained instance: " + cached); 97 return "stop"; 98 }, 99 onComplete: function () {}, 100 }); 101 }); 102 ``` 103 104 This matters when you want to call the same instance later from `rpc.exports`, timers, or another hook without re-scanning the heap each time. 105 106 ### Classes loaded by custom `ClassLoader`s 107 108 On recent Android apps, especially apps using **dynamic feature modules**, **plugin frameworks**, or **packed/encrypted code**, `Java.use()` may fail with `ClassNotFoundException` even though the class exists. In that case enumerate the available class loaders and use the correct one through a dedicated `Java.ClassFactory`:<sup>[[1]](#references)</sup> 109 110 ```javascript 111 Java.perform(function () { 112 var targetLoader = null; 113 114 Java.enumerateClassLoaders({ 115 onMatch: function (loader) { 116 try { 117 if (loader.findClass("com.example.a11x256.frida_test.my_activity")) { 118 targetLoader = loader; 119 console.log("Found loader: " + loader); 120 } 121 } catch (e) {} 122 }, 123 onComplete: function () {}, 124 }); 125 126 if (targetLoader !== null) { 127 var factory = Java.ClassFactory.get(targetLoader); 128 var MyActivity = factory.use("com.example.a11x256.frida_test.my_activity"); 129 130 factory.choose("com.example.a11x256.frida_test.my_activity", { 131 onMatch: function (instance) { 132 console.log("Instance from alternate loader: " + instance); 133 }, 134 onComplete: function () {}, 135 }); 136 } 137 }); 138 ``` 139 140 If you already know the app loads sensitive code late, this pattern is usually more reliable than retrying `Java.use()` in a loop. 141 142 ## Part 3 143 144 ### Python 145 146 Now you are going to see how to send commands to the hooked app via Python and use **Frida RPC exports** to call JavaScript functions: 147 148 ```python 149 # loader.py 150 import time 151 import frida 152 153 154 def on_message(message, payload): 155 print(message) 156 if payload is not None: 157 print(payload) 158 159 160 with open("s3.js", "r", encoding="utf-8") as f: 161 jscode = f.read() 162 163 device = frida.get_usb_device(timeout=5) 164 pid = device.spawn(["com.example.a11x256.frida_test"]) 165 device.resume(pid) 166 time.sleep(1) 167 session = device.attach(pid) 168 script = session.create_script(jscode) 169 script.on("message", on_message) 170 script.load() 171 api = script.exports_sync 172 173 while True: 174 command = input( 175 "Enter command:\n1: Exit\n2: Call secret function\n3: Hook Secret\nchoice: " 176 ).strip() 177 if command == "1": 178 break 179 if command == "2": 180 api.callsecretfunction() 181 elif command == "3": 182 api.hooksecretfunction() 183 ``` 184 185 The command `1` will **exit**, the command `2` will **find an instance of the class and call the private function** `secret()`, and command `3` will **hook** the function `secret()` so it **returns** a **different string**. 186 187 If you call `2` first, you will get the **real secret**. If you call `3` and then `2`, you will get the **fake secret**. 188 189 ### JS 190 191 ```javascript 192 console.log("Script loaded successfully"); 193 var instancesArray = []; 194 195 function callSecretFun() { 196 Java.perform(function () { 197 if (instancesArray.length === 0) { 198 Java.choose("com.example.a11x256.frida_test.my_activity", { 199 onMatch: function (instance) { 200 instancesArray.push(Java.retain(instance)); 201 console.log("Found instance: " + instance); 202 console.log("Result of secret func: " + instance.secret()); 203 return "stop"; 204 }, 205 onComplete: function () {}, 206 }); 207 } else { 208 console.log("Result of secret func: " + instancesArray[0].secret()); 209 } 210 }); 211 } 212 213 function hookSecret() { 214 Java.perform(function () { 215 var MyActivity = Java.use("com.example.a11x256.frida_test.my_activity"); 216 var JString = Java.use("java.lang.String"); 217 var secret = MyActivity.secret.overload(); 218 219 secret.implementation = function () { 220 return JString.$new("TE ENGANNNNEEE"); 221 }; 222 }); 223 } 224 225 rpc.exports = { 226 callsecretfunction: callSecretFun, 227 hooksecretfunction: hookSecret, 228 }; 229 ``` 230 231 In Python, exported JavaScript methods are easier to call through `script.exports_sync`. For example, an export named `enumerateModules` becomes `script.exports_sync.enumerate_modules()`. 232 233 ## Part 4 234 235 Here you will see how to make **Python and JS interact** using JSON objects. JS uses the `send()` function to send data to the Python client, and Python uses `post()` to send a JSON object back to the JS script. The **JS will block the execution** until it receives a response from Python. 236 237 ### Python 238 239 ```python 240 # loader.py 241 import base64 242 import time 243 import frida 244 245 246 def on_message(message, payload): 247 print(message) 248 if message.get("type") != "send": 249 return 250 251 encoded = message["payload"].split(":", 1)[1].strip() 252 user, password = base64.b64decode(encoded).decode().split(":", 1) 253 new_data = base64.b64encode(f"admin:{password}".encode()).decode() 254 script.post({"type": "input", "payload": {"my_data": new_data}}) 255 print(f"Modified data sent for user {user}") 256 257 258 with open("s4.js", "r", encoding="utf-8") as f: 259 jscode = f.read() 260 261 device = frida.get_usb_device(timeout=5) 262 pid = device.spawn(["com.example.a11x256.frida_test"]) 263 device.resume(pid) 264 time.sleep(1) 265 session = device.attach(pid) 266 script = session.create_script(jscode) 267 script.on("message", on_message) 268 script.load() 269 input() 270 ``` 271 272 ### JS 273 274 ```javascript 275 console.log("Script loaded successfully"); 276 277 Java.perform(function () { 278 var TextView = Java.use("android.widget.TextView"); 279 var setText = TextView.setText.overload("java.lang.CharSequence"); 280 281 setText.implementation = function (x) { 282 var outgoing = x.toString(); 283 var incoming = outgoing; 284 285 send("Candidate text: " + outgoing); 286 recv("input", function (message) { 287 incoming = message.payload.my_data; 288 }).wait(); 289 290 console.log("Final string_to_recv: " + incoming); 291 return setText.call(this, incoming); 292 }; 293 }); 294 ``` 295 296 `recv()` handlers receive **one message** and must be registered again for the next one. Using `.wait()` blocks the current hooked thread until Python replies, so keep this pattern for cases where you really need an inline decision before the original method continues. 297 298 ## Modern Frida Notes 299 300 - These examples still work as plain scripts loaded through `frida`, `frida-python`, or `frida-trace`. 301 - If you migrate them to a **Frida 17+ agent project** built with `frida-create`/`frida-compile`, import the Java bridge explicitly with `import Java from "frida-java-bridge"`.<sup>[[2]](#references)</sup> 302 - Frida 17.1.4 bumped `frida-java-bridge` to `7.0.3` in internal Android agents, adding **Android 16** support. If heap scans or Java hooks behave strangely on very recent Android versions, first verify that **frida-tools**, **frida-python**, and **frida-server/gadget** are on matching recent versions. 303 - For **anti-Frida**, **root detection**, and **SSL pinning** bypasses, keep that content in the dedicated page: 304 305 [Android Anti Instrumentation And Ssl Pinning Bypass](/hacktricks/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass) 306 307 There is a part 5 that is not explained here because it doesn't add anything substantially new. If you want to read it, it is here: [https://11x256.github.io/Frida-hooking-android-part-5/](https://11x256.github.io/Frida-hooking-android-part-5/) 308 309 ## References 310 311 - [1] [Frida JavaScript API](https://frida.re/docs/javascript-api/) 312 - [2] [Frida 17.0.0 Released](https://frida.re/news/2025/05/17/frida-17-0-0-released/) 313 - [3] [11x256.github.io - Frida Hooking Android Part 2](https://11x256.github.io/Frida-hooking-android-part-2)