├── README.md ├── gif ├── README.md └── intercept_open_chrome_android.gif └── scripts ├── FridaCodeGenerator.py ├── WIP_android_ipc.js ├── WIP_dump_dynamically_created_files.js ├── WIP_ios_app_info.js ├── WIP_unpack_64.js ├── android_proxy.js ├── check_for_native_calls.py ├── dump_dynamically_created_files.py ├── enable_remote_debugging.js ├── enumerateNativeMethods.js ├── exec_shell_cmd.py ├── extact_ipa.sh ├── how_to_access_inner_class_static_field.md ├── install_frida_server.sh ├── ios.md ├── ios_ssl_unpin.js ├── log_string_builders_and_string_compare.js ├── objc_ssl_unppining_helper.js ├── print_native_method_arguments.py ├── stalker.js ├── trace_class.js └── unity.js /README.md: -------------------------------------------------------------------------------- 1 | ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square) & also output examples 2 | 3 | ## Table of Contents 4 | 5 |
6 | Native 7 | 8 | * [`Load C/C++ module`](#load-cpp-module) 9 | * [`One time watchpoint`](#one-time-watchpoint) 10 | * [`Socket activity`](#socket-activity) 11 | * [`Intercept open`](#intercept-open) 12 | * [`Execute shell command`](#execute-shell-command) 13 | * [`List modules`](#list-modules) 14 | * [`Log SQLite query`](#log-sqlite-query) 15 | * [`Log method arguments`](#log-method-arguments) 16 | * [`Intercept entire module`](#intercept-entire-module) 17 | * [`Dump memory segments`](#dump-memory-segments) 18 | * [`Memory scan`](#memory-scan) 19 | * [`Stalker`](#stalker) 20 | * [`Cpp Demangler`](#cpp-demangler) 21 | * [`Early hook`](#early-hook) 22 | 23 |
24 | 25 |
26 | Android 27 | 28 | * [`Binder transactions`](#binder-transactions) 29 | * [`Get system property`](#system-property-get) 30 | * [`Reveal manually registered native symbols`](#reveal-native-methods) 31 | * [`Enumerate loaded classes`](#enumerate-loaded-classes) 32 | * [`Class description`](#class-description) 33 | * [`Turn WiFi off`](#turn-wifi-off) 34 | * [`Set proxy`](#set-proxy) 35 | * [`Get IMEI`](#get-imei) 36 | * [`Hook io InputStream`](#hook-io-inputstream) 37 | * [`Android make Toast`](#android-make-toast) 38 | * [`Await for specific module to load`](#await-for-condition) 39 | * [`Webview URLS`](#webview-urls) 40 | * [`Print all runtime strings & stacktrace`](#print-runtime-strings) 41 | * [`Print shared preferences updates`](#Print-shared-preferences-updates) 42 | * [`String comparison`](#string-comparison) 43 | * [`Hook JNI by address`](#hook-jni-by-address) 44 | * [`Hook constructor`](#hook-constructor) 45 | * [`Hook Java reflection`](#hook-refelaction) 46 | * [`Trace class`](#trace-class) 47 | * [`Hooking Unity3d`](https://github.com/iddoeldor/mplus) 48 | * [`Get Android ID`](#get-android-id) 49 | * [`Change location`](#change-location) 50 | * [`Bypass FLAG_SECURE`](#bypass-flag_secure) 51 | * [`Shared Preferences update`](#shared-preferences-update) 52 | * [`Hook all method overloads`](#hook-overloads) 53 | * [`Register broadcast receiver`](#register-broadcast-receiver) 54 | * [`Increase step count`](#increase-step-count) 55 | * [`list classes implements interface with class loaders`](#list-classes-implements-interface) 56 | * File system access hook `$ frida --codeshare FrenchYeti/android-file-system-access-hook -f com.example.app --no-pause` 57 | * How to remove/disable java hooks ? Assign `null` to the `implementation` property. 58 | 59 |
60 | 61 |
62 | iOS 63 | 64 | * [`OS Log`](#os-log) 65 | * [`iOS alert box`](#ios-alert-box) 66 | * [`File access`](#file-access) 67 | * [`Observe class`](#observe-class) 68 | * [`Find application UUID`](#find-ios-application-uuid) 69 | * [`Extract cookies`](#extract-cookies) 70 | * [`Describe class members`](#describe-class-members) 71 | * [`Class hierarchy`](#class-hierarchy) 72 | * [`Hook refelaction`](#hook-refelaction) 73 | * [`Device properties`](#device-properties) 74 | * [`Take screenshot`](#take-screenshot) 75 | * [`Log SSH commands`](#log-ssh-commands) 76 | 77 |
78 | 79 |
80 | Windows 81 | 82 | ![HAHAHA. No.](https://i.kym-cdn.com/photos/images/original/000/551/854/06f.jpg) 83 | 84 |
85 |
86 | Sublime snippets 87 | 88 | { 89 | "scope": "source.js", 90 | "completions": [ 91 | {"trigger": "fridainterceptor", "contents": "Interceptor.attach(\n ptr,\n {\n onEnter:function(args) {\n\n },\n onLeave: function(retval) {\n\n }\n }\n)"}, 92 | {"trigger": "fridaperform", "contents": "function main(){\n console.log('main()');\n}\n\nconsole.log('script loaded');\nJava.perform(main);"}, 93 | {"trigger": "fridause", "contents": "var kls = Java.use('kls');"}, 94 | {"trigger": "fridahex", "contents": "hexdump(\n ptr,\n {\n offset: 0,\n length: ptr_size\n }\n);" }, 95 | {"trigger": "fridabacktrace", "contents": "console.log('called from:\\n' +\n Thread.backtrace(this.context, Backtracer.ACCURATE)\n .map(DebugSymbol.fromAddress).join('\\n') + '\\n'\n);"}, 96 | {"trigger": "fridamods", "contents": "var mods = Process.enumerateModules().filter(function(mod){\n return mod.name.includes(\"\");\n});"}, 97 | {"trigger": "fridaexport", "contents": "Module.findExportByName(null, \"\");"}, 98 | {"trigger": "fridabase", "contents": "Module.findBaseAddress(name);"}, 99 | {"trigger": "fridaoverload", "contents": "kls.method_name.overload().implementation=function(){}"} 100 | ] 101 | } 102 |
103 | 104 | 105 |
106 | Vim snippets 107 | 108 | To list abbreviations `:ab` 109 | 110 | Expand by writing `key` and `` 111 | 112 | * Add to `~/.vimrc` 113 | 114 | ``` 115 | ab fridaintercept Interceptor.attach(ptr, {onEnter: function(args) {},onLeave: function(retval) {}}) 116 | ab fridabacktrace console.warn(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));F(3; 117 | ab fridadescribe console.log(Object.getOwnPropertyNames(Java.use('$').__proto__).join('\n\t'))F$ 118 | ``` 119 | 120 |
121 | 122 | 123 |
124 | JEB 125 | 126 | Java method hook generator using keyboard shortcut 127 | 128 | 1. `curl -o ~/$JEB$/scripts/FridaCodeGenerator.py https://raw.githubusercontent.com/iddoeldor/frida-snippets/master/scripts/FridaCodeGenerator.py` 129 | 2. Place cursor at Java method's signature 130 | 3. Press `Ctrl+Shift+Z` 131 | 4. Code is copied to system clipboard (using `xclip`) 132 | 133 | 134 |
135 |
136 | 137 | 138 | #### Fetch SSL keys 139 | 140 | ```js 141 | var keylog_callback = new NativeCallback((ssl, line) => { 142 | send(Memory.readCString(line)); 143 | }, 'void', ['pointer', 'pointer']); 144 | 145 | if (ObjC.available) { 146 | var CALLBACK_OFFSET = 0x2A8 147 | if (Memory.readDouble(Module.findExportByName('CoreFoundation', 'kCFCoreFoundationVersionNumber')) >= 1751.108) { 148 | CALLBACK_OFFSET = 0x2B8 149 | } 150 | Interceptor.attach(Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_info_callback'), { 151 | onEnter(args) { 152 | ptr(args[0]).add(CALLBACK_OFFSET).writePointer(keylog_callback) 153 | } 154 | }) 155 | } else if (Java.available) { 156 | var set_keylog_callback = new NativeFunction(Module.findExportByName('libssl.so', 'SSL_CTX_set_keylog_callback'), 'void', ['pointer', 'pointer']); 157 | Interceptor.attach(Module.findExportByName('libssl.so', 'SSL_CTX_new'), { 158 | onLeave(retval) { 159 | set_keylog_callback(retval, keylog_callback) 160 | } 161 | }) 162 | } 163 | ``` 164 | 165 | 166 |
[⬆ Back to top](#table-of-contents) 167 | 168 | 169 | #### Load CPP module 170 | 171 | ```cpp 172 | #include 173 | #include 174 | 175 | extern "C" { 176 | void* create_stdstr(char *data, int size) { 177 | std::string* s = new std::string(); 178 | (*s).assign(data, size); 179 | return s; 180 | } 181 | } 182 | ``` 183 | 184 | ```sh 185 | $ ./android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang++ a.cpp -o a -shared -static-libstdc++ && adb push a /data/local/tmp/a 186 | ``` 187 | 188 | ```js 189 | [device]-> 190 | function readStdString(str) { 191 | if ((str.readU8() & 1) === 1) { // size LSB (=1) indicates if it's a long string 192 | return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); 193 | } 194 | return str.add(1).readUtf8String(); 195 | } 196 | [device]-> Module.load('/data/local/tmp/a'); 197 | [device]-> var fp_create_stdstr = Module.findExportByName('a', 'create_stdstr'); 198 | [device]-> var createStdString = new NativeFunction(fp_create_stdstr, 'pointer', ['pointer', 'int']); 199 | [device]-> var stdstr1 = createStdString(Memory.allocUtf8String("abcd"), 3); 200 | "0x07691234567" 201 | [device]-> readStdString(stdstr1); 202 | "abc" 203 | ``` 204 | 205 | #### Load C module 206 | 207 | * https://frida.re/docs/javascript-api/#cmodule 208 | * https://frida.re/news/2019/09/18/frida-12-7-released/ 209 | 210 | 211 | ```sh 212 | $ ./aarch64-linux-android21-clang /tmp/b.c -o /tmp/a -shared ../sysroot/usr/lib/aarch64-linux-android/21/liblog.so && adb push /tmp/a /data/local/tmp/a 213 | ``` 214 | 215 | ```c 216 | #include 217 | #include 218 | #include 219 | 220 | #define TAG "TEST1" 221 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) 222 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) 223 | 224 | void test(void) { 225 | FILE* fp = popen("ls -l /sdcard 2>&1", "r"); 226 | if (fp == NULL) 227 | LOGE("executing cmd failed"); 228 | char b[256]; 229 | while (fgets(b, sizeof(b), fp) != NULL) { 230 | LOGI("%s", b); 231 | } 232 | pclose(fp); 233 | } 234 | 235 | ``` 236 | 237 | ```sh 238 | $ frida -Uf com.app --no-pause --enable-jit -e "Module.load('/data/local/tmp/a')" 239 | [ ] -> new NativeFunction(Module.findExportByName('a', 'test'), 'void', [])() 240 | ``` 241 | 242 | 243 |
[⬆ Back to top](#table-of-contents) 244 | 245 | 246 | 247 | #### One time watchpoint 248 | 249 | Intercept `funcPtr` & log who read/write to `x2` via removing permissions w/ `mprotect`. 250 | 251 | ```js 252 | Process.setExceptionHandler(function(exp) { 253 | console.warn(JSON.stringify(Object.assign(exp, { _lr: DebugSymbol.fromAddress(exp.context.lr), _pc: DebugSymbol.fromAddress(exp.context.pc) }), null, 2)); 254 | Memory.protect(exp.memory.address, Process.pointerSize, 'rw-'); 255 | // can also use `new NativeFunction(Module.findExportByName(null, 'mprotect'), 'int', ['pointer', 'uint', 'int'])(parseInt(this.context.x2), 2, 0)` 256 | return true; // goto PC 257 | }); 258 | 259 | Interceptor.attach(funcPtr, { 260 | onEnter: function (args) { 261 | console.log('onEnter', JSON.stringify({ 262 | x2: this.context.x2, 263 | mprotect_ret: Memory.protect(this.context.x2, 2, '---'), 264 | errno: this.errno 265 | }, null, 2)); 266 | }, 267 | onLeave: function (retval) { 268 | console.log('onLeave'); 269 | } 270 | }); 271 | ``` 272 | 273 |
274 | Output example 275 | 276 | ``` 277 | [iOS Device::com.app]-> onEnter { 278 | "x2": "0x1c145c6e0", 279 | "mprotect_ret": true, 280 | "errno": 2 281 | } 282 | { 283 | "type": "access-violation", 284 | "address": "0x1853b0198", 285 | "memory": { 286 | "operation": "read", 287 | "address": "0x1c145c6e0" 288 | }, 289 | "context": { 290 | "lr": "0x100453358", 291 | "fp": "0x16fb2e860", 292 | "x28": "0x0", 293 | "x27": "0x0", 294 | "x26": "0x104312600", 295 | "x25": "0x0", 296 | "x24": "0x0", 297 | "x23": "0x0", 298 | "x22": "0x0", 299 | "x21": "0xb000000422bbda03", 300 | "x20": "0x1c4a22560", 301 | "x19": "0xb000000422bbda03", 302 | "x18": "0x0", 303 | "x17": "0x100d25290", 304 | "x16": "0x1853b0190", 305 | "x15": "0x0", 306 | "x14": "0x5", 307 | "x13": "0xe5a1c4119597", 308 | "x12": "0x10e80ca30", 309 | "x11": "0x180000003f", 310 | "x10": "0x10e80ca00", 311 | "x9": "0x1020ad7c3", 312 | "x8": "0x0", 313 | "x7": "0x0", 314 | "x6": "0x0", 315 | "x5": "0x0", 316 | "x4": "0xb000000422bbda03", 317 | "x3": "0x1c4a22560", 318 | "x2": "0x1c145c6e0", 319 | "x1": "0x1020ad7c3", 320 | "x0": "0x1c145c6e0", 321 | "sp": "0x16fb2e790", 322 | "pc": "0x1853b0198" 323 | }, 324 | "nativeContext": "0x16fc42b24" 325 | } 326 | onLeave 327 | ``` 328 | 329 | 330 |
331 | 332 |
[⬆ Back to top](#table-of-contents) 333 | 334 | 335 | 336 | #### Socket activity 337 | 338 | ```js 339 | Process 340 | .getModuleByName({ linux: 'libc.so', darwin: 'libSystem.B.dylib', windows: 'ws2_32.dll' }[Process.platform]) 341 | .enumerateExports().filter(ex => ex.type === 'function' && ['connect', 'recv', 'send', 'read', 'write'].some(prefix => ex.name.indexOf(prefix) === 0)) 342 | .forEach(ex => { 343 | Interceptor.attach(ex.address, { 344 | onEnter: function (args) { 345 | var fd = args[0].toInt32(); 346 | var socktype = Socket.type(fd); 347 | if (socktype !== 'tcp' && socktype !== 'tcp6') 348 | return; 349 | var address = Socket.peerAddress(fd); 350 | if (address === null) 351 | return; 352 | console.log(fd, ex.name, address.ip + ':' + address.port); 353 | } 354 | }) 355 | }) 356 | ``` 357 | 358 |
359 | Output example 360 | 361 | Android example 362 | ```sh 363 | # wrap the script above inside Java.perform 364 | $ frida -Uf com.example.app -l script.js --no-pause 365 | [Android Model-X::com.example.app]-> 117 write 5.0.2.1:5242 366 | 117 read 5.0.2.1:5242 367 | 135 write 5.0.2.1:4244 368 | 135 read 5.0.2.1:4244 369 | 135 read 5.0.2.1:4244 370 | ``` 371 | 372 |
373 | 374 |
[⬆ Back to top](#table-of-contents) 375 | 376 | #### Intercept Open 377 | 378 | An example for intercepting `libc#open` & logging backtrace if specific file was opened. 379 | 380 | ```js 381 | Interceptor.attach(Module.findExportByName("/system/lib/libc.so", "open"), { 382 | onEnter: function(args) { 383 | this.flag = false; 384 | var filename = Memory.readCString(ptr(args[0])); 385 | console.log('filename =', filename) 386 | if (filename.endsWith(".xml")) { 387 | this.flag = true; 388 | var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n\t"); 389 | console.log("file name [ " + Memory.readCString(ptr(args[0])) + " ]\nBacktrace:" + backtrace); 390 | } 391 | }, 392 | onLeave: function(retval) { 393 | if (this.flag) // passed from onEnter 394 | console.warn("\nretval: " + retval); 395 | } 396 | }); 397 | ``` 398 | 399 | 400 | ```js 401 | var fds = {}; // for f in /proc/`pidof $APP`/fd/*; do echo $f': 'readlink $f; done 402 | Interceptor.attach(Module.findExportByName(null, 'open'), { 403 | onEnter: function (args) { 404 | var fname = args[0].readCString(); 405 | if (fname.endsWith('.jar')) { 406 | this.flag = true; 407 | this.fname = fname; 408 | } 409 | }, 410 | onLeave: function (retval) { 411 | if (this.flag) { 412 | fds[retval] = this.fname; 413 | } 414 | } 415 | }); 416 | ['read', 'pread', 'readv'].forEach(fnc => { 417 | Interceptor.attach(Module.findExportByName(null, fnc), { 418 | onEnter: function (args) { 419 | var fd = args[0]; 420 | if (fd in fds) 421 | console.log(`${fnc}: ${fds[fd]} 422 | \t${Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')}`); 423 | } 424 | }); 425 | }); 426 | ``` 427 | 428 |
429 | Output example 430 | Intecepting `com.android.chrome` 431 | 432 | ![](https://github.com/iddoeldor/frida-snippets/blob/master/gif/intercept_open_chrome_android.gif) 433 | 434 | 435 |
436 | 437 |
[⬆ Back to top](#table-of-contents) 438 | 439 | #### Execute shell command 440 | 441 | 442 | ```python 443 | import frida 444 | from frida_tools.application import Reactor 445 | import threading 446 | import click 447 | 448 | 449 | class Shell(object): 450 | def __init__(self, argv, env): 451 | self._stop_requested = threading.Event() 452 | self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait()) 453 | 454 | self._device = frida.get_usb_device() 455 | self._sessions = set() 456 | 457 | self._device.on("child-added", lambda child: self._reactor.schedule(lambda: self._on_child_added(child))) 458 | self._device.on("child-removed", lambda child: self._reactor.schedule(lambda: self._on_child_removed(child))) 459 | self._device.on("output", lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))) 460 | 461 | self.argv = argv 462 | self.env = env 463 | self.output = [] # stdout will pushed into array 464 | 465 | def exec(self): 466 | self._reactor.schedule(lambda: self._start()) 467 | self._reactor.run() 468 | 469 | def _start(self): 470 | click.secho("✔ spawn(argv={})".format(self.argv), fg='green', dim=True) 471 | pid = self._device.spawn(self.argv, env=self.env, stdio='pipe') 472 | self._instrument(pid) 473 | 474 | def _stop_if_idle(self): 475 | if len(self._sessions) == 0: 476 | self._stop_requested.set() 477 | 478 | def _instrument(self, pid): 479 | click.secho("✔ attach(pid={})".format(pid), fg='green', dim=True) 480 | session = self._device.attach(pid) 481 | session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason))) 482 | click.secho("✔ enable_child_gating()", fg='green', dim=True) 483 | session.enable_child_gating() 484 | # print("✔ resume(pid={})".format(pid)) 485 | self._device.resume(pid) 486 | self._sessions.add(session) 487 | 488 | def _on_child_added(self, child): 489 | click.secho("⚡ child_added: {}".format(child), fg='green', dim=True) 490 | self._instrument(child.pid) 491 | 492 | @staticmethod 493 | def _on_child_removed(child): 494 | click.secho("⚡ child_removed: {}".format(child), fg='green', dim=True) 495 | 496 | def _on_output(self, pid, fd, data): 497 | # print("⚡ output: pid={}, fd={}, data={}".format(pid, fd, repr(data))) 498 | # fd=0 (input) fd=1(stdout) fd=2(stderr) 499 | if fd != 2: 500 | self.output.append(data) 501 | 502 | def _on_detached(self, pid, session, reason): 503 | click.secho("⚡ detached: pid={}, reason='{}'".format(pid, reason), fg='green', dim=True) 504 | self._sessions.remove(session) 505 | self._reactor.schedule(self._stop_if_idle, delay=0.5) 506 | 507 | @staticmethod 508 | def _on_message(pid, message): 509 | click.secho("⚡ message: pid={}, payload={}".format(pid, message), fg='green', dim=True) 510 | ``` 511 | 512 |
513 | Usage example 514 | 515 | List directory contents: 516 | 517 | ```python 518 | def ls(folder): 519 | cmd = Shell(['/bin/sh', '-c', 'ls -la ' + folder], None) 520 | cmd.exec() 521 | for chunk in cmd.output: 522 | print(chunk.strip().decode()) 523 | ``` 524 | 525 | Pull binary from iOS 526 | 527 | ```python 528 | cmd = Shell(['/bin/sh', '-c', 'cat /System/Library/PrivateFrameworks/Example.framework/example'], None) 529 | cmd.exec() 530 | with open('/tmp/example', 'wb+') as f: 531 | f.writelines(cmd.output) 532 | # $ file /tmp/example 533 | # /tmp/example: Mach-O 64-bit 64-bit architecture=12 executable 534 | ``` 535 |
536 | 537 |
[⬆ Back to top](#table-of-contents) 538 | 539 | 540 | #### List modules 541 | 542 | ```js 543 | Process.enumerateModulesSync() 544 | .filter(function(m){ return m['path'].toLowerCase().indexOf('app') !=-1 ; }) 545 | .forEach(function(m) { 546 | console.log(JSON.stringify(m, null, ' ')); 547 | // to list exports use Module.enumerateExportsSync(m.name) 548 | }); 549 | ``` 550 | 551 | List modules & exports 552 | 553 | ```js 554 | sudo frida Process --no-pause --eval 'var x={};Process.enumerateModulesSync().forEach(function(m){x[m.name] = Module.enumerateExportsSync(m.name)});x' -q | less +F 555 | ``` 556 |
557 | Output example 558 | 559 | ```js 560 | { 561 | "name": "app_process64", 562 | "base": "0x6313a1c000", 563 | "size": 40960, 564 | "path": "/system/bin/app_process64" 565 | } 566 | { 567 | "name": "libappfuse.so", 568 | "base": "0x749ab96000", 569 | "size": 53248, 570 | "path": "/system/lib64/libappfuse.so" 571 | } 572 | { 573 | "name": "android.hardware.graphics.mapper@2.0.so", 574 | "base": "0x749b448000", 575 | "size": 90112, 576 | "path": "/system/lib64/android.hardware.graphics.mapper@2.0.so" 577 | } 578 | { 579 | "name": "android.hardware.graphics.mapper@2.1.so", 580 | "base": "0x749ac9e000", 581 | "size": 94208, 582 | "path": "/system/lib64/android.hardware.graphics.mapper@2.1.so" 583 | } 584 | { 585 | "name": "android.hardware.graphics.mapper@3.0.so", 586 | "base": "0x74981e0000", 587 | "size": 98304, 588 | "path": "/system/lib64/android.hardware.graphics.mapper@3.0.so" 589 | } 590 | { 591 | "name": "android.hardware.graphics.mapper@2.0-impl-2.1.so", 592 | "base": "0x73fb4cc000", 593 | "size": 40960, 594 | "path": "/vendor/lib64/hw/android.hardware.graphics.mapper@2.0-impl-2.1.so" 595 | } 596 | { 597 | "name": "android.hardware.graphics.mapper@2.0.so", 598 | "base": "0x73fb51f000", 599 | "size": 90112, 600 | "path": "/system/lib64/vndk-sp-29/android.hardware.graphics.mapper@2.0.so" 601 | } 602 | { 603 | "name": "android.hardware.graphics.mapper@2.1.so", 604 | "base": "0x73fb542000", 605 | "size": 94208, 606 | "path": "/system/lib64/vndk-sp-29/android.hardware.graphics.mapper@2.1.so" 607 | } 608 | { 609 | "name": "base.odex", 610 | "base": "0x73ab4cd000", 611 | "size": 16965632, 612 | "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/oat/arm64/base.odex" 613 | } 614 | { 615 | "name": "libfrida-gadget.so", 616 | "base": "0x73a2c05000", 617 | "size": 22876160, 618 | "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libfrida-gadget.so" 619 | } 620 | { 621 | "name": "libmain.so", 622 | "base": "0x73fb894000", 623 | "size": 73728, 624 | "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libmain.so" 625 | } 626 | { 627 | "name": "libunity.so", 628 | "base": "0x739bf88000", 629 | "size": 24461312, 630 | "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libunity.so" 631 | } 632 | { 633 | "name": "libil2cpp.so", 634 | "base": "0x7396fe6000", 635 | "size": 25272320, 636 | "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libil2cpp.so" 637 | } 638 | { 639 | "name": "DynamiteLoader.odex", 640 | "base": "0x73b7ee4000", 641 | "size": 376832, 642 | "path": "/data/user_de/0/com.google.android.gms/app_chimera/m/00000278/oat/arm64/DynamiteLoader.odex" 643 | } 644 | { 645 | "name": "base.odex", 646 | "base": "0x72f2ee3000", 647 | "size": 166838272, 648 | "path": "/data/app/com.google.android.gms-j7RpxBsNAd3ttAYEdp2ahg==/oat/arm64/base.odex" 649 | } 650 | { 651 | "name": "base.odex", 652 | "base": "0x7396e6d000", 653 | "size": 28672, 654 | "path": "/data/app/com.google.android.trichromelibrary_432418133-X7Kc2Mqi-VXkY12N59kGug==/oat/arm64/base.odex" 655 | } 656 | { 657 | "name": "base.odex", 658 | "base": "0x724f15e000", 659 | "size": 13225984, 660 | "path": "/data/app/com.google.android.webview-w6i6OBFZ7T_wK4W4TpDAiQ==/oat/arm64/base.odex" 661 | } 662 | { 663 | "name": "libmonochrome.so", 664 | "base": "0x73b8592000", 665 | "size": 76673024, 666 | "path": "/data/app/com.google.android.webview-w6i6OBFZ7T_wK4W4TpDAiQ==/base.apk!/lib/arm64-v8a/libmonochrome.so" 667 | } 668 | { 669 | "name": "libnativeNoodleNews.so", 670 | "base": "0x723add3000", 671 | "size": 962560, 672 | "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libnativeNoodleNews.so" 673 | } 674 | { 675 | "name": "libconscrypt_gmscore_jni.so", 676 | "base": "0x7206629000", 677 | "size": 1130496, 678 | "path": "/data/app/com.google.android.gms-j7RpxBsNAd3ttAYEdp2ahg==/base.apk!/lib/arm64-v8a/libconscrypt_gmscore_jni.so" 679 | } 680 | 681 | ``` 682 |
683 | 684 |
[⬆ Back to top](#table-of-contents) 685 | 686 | #### Log SQLite query 687 | ```js 688 | Interceptor.attach(Module.findExportByName('libsqlite.so', 'sqlite3_prepare16_v2'), { 689 | onEnter: function(args) { 690 | console.log('DB: ' + Memory.readUtf16String(args[0]) + '\tSQL: ' + Memory.readUtf16String(args[1])); 691 | } 692 | }); 693 | ``` 694 | 695 |
696 | Output example 697 | TODO 698 |
699 | 700 |
[⬆ Back to top](#table-of-contents) 701 | 702 | #### system property get 703 | 704 | ```js 705 | Interceptor.attach(Module.findExportByName(null, '__system_property_get'), { 706 | onEnter: function (args) { 707 | this._name = args[0].readCString(); 708 | this._value = args[1]; 709 | }, 710 | onLeave: function (retval) { 711 | console.log(JSON.stringify({ 712 | result_length: retval, 713 | name: this._name, 714 | val: this._value.readCString() 715 | })); 716 | } 717 | }); 718 | ``` 719 | 720 |
721 | Output example 722 | 723 | ```sh 724 | {"result_length":"0x0","name":"ro.kernel.android.tracing","val":""} 725 | {"result_length":"0x0","name":"ro.config.hw_log","val":""} 726 | {"result_length":"0x0","name":"ro.config.hw_module_log","val":""} 727 | {"result_length":"0x1","name":"ro.debuggable","val":"0"} 728 | {"result_length":"0x1","name":"persist.sys.huawei.debug.on","val":"0"} 729 | {"result_length":"0x1","name":"ro.logsystem.usertype","val":"6"} 730 | {"result_length":"0x6","name":"ro.board.platform","val":"hi6250"} 731 | {"result_length":"0x4","name":"persist.sys.enable_iaware","val":"true"} 732 | {"result_length":"0x1","name":"persist.sys.cpuset.enable","val":"1"} 733 | {"result_length":"0x4","name":"persist.sys.cpuset.subswitch","val":"1272"} 734 | {"result_length":"0x4","name":"persist.sys.boost.durationms","val":"1000"} 735 | {"result_length":"0x4","name":"persist.sys.boost.isbigcore","val":"true"} 736 | {"result_length":"0x7","name":"persist.sys.boost.freqmin.b","val":"1805000"} 737 | {"result_length":"0x4","name":"persist.sys.boost.ipapower","val":"3500"} 738 | {"result_length":"0x0","name":"persist.sys.boost.skipframe","val":""} 739 | {"result_length":"0x0","name":"persist.sys.boost.byeachfling","val":""} 740 | {"result_length":"0x1","name":"debug.force_rtl","val":"0"} 741 | {"result_length":"0x0","name":"ro.hardware.gralloc","val":""} 742 | {"result_length":"0x6","name":"ro.hardware","val":"hi6250"} 743 | {"result_length":"0x0","name":"ro.kernel.qemu","val":""} 744 | {"result_length":"0x0","name":"ro.config.hw_force_rotation","val":""} 745 | {"result_length":"0x0","name":"persist.fb_auto_alloc","val":""} 746 | {"result_length":"0x0","name":"ro.config.hw_lock_res_whitelist","val":""} 747 | {"result_length":"0x3","name":"ro.sf.lcd_density","val":"480"} 748 | {"result_length":"0x0","name":"persist.sys.dpi","val":""} 749 | {"result_length":"0x0","name":"persist.sys.rog.width","val":""} 750 | {"result_length":"0x4","name":"dalvik.vm.usejitprofiles","val":"true"} 751 | {"result_length":"0x1","name":"debug.atrace.tags.enableflags","val":"0"} 752 | {"result_length":"0x1","name":"ro.debuggable","val":"0"} 753 | {"result_length":"0x1","name":"debug.force_rtl","val":"0"} 754 | {"result_length":"0x0","name":"ro.config.hw_lock_res_whitelist","val":""} 755 | .... 756 | ``` 757 | 758 |
759 | 760 |
[⬆ Back to top](#table-of-contents) 761 | 762 | 763 | #### Binder transactions 764 | 765 | ```js 766 | var LAST_MSG = ''; 767 | Java.perform(() => { 768 | Interceptor.attach(Module.findExportByName('libbinder.so', 'ioctl'), { 769 | onEnter: function(args) { 770 | var binder_write_read_ptr = args[2]; 771 | if (args[1] == 0xC0306201) { // BINDER_WRITE_READ 772 | var binder_write_read = { 773 | // 'fd': args[0].toInt32(), 774 | 'write_size': binder_write_read_ptr.readU64(), 775 | 'write_consumed': binder_write_read_ptr.add(Process.pointerSize).readU64(), 776 | 'write_buffer': binder_write_read_ptr.add(Process.pointerSize * 2).readPointer(), 777 | } 778 | if (binder_write_read.write_size > 0) { 779 | var ptr = binder_write_read.write_buffer.add(binder_write_read.write_consumed + 4); 780 | switch (binder_write_read.write_buffer.readU32() & 0xff) { 781 | case 0: // BC_TRANSACTION 782 | case 1: // BC_REPLY 783 | var binder_transaction_data = { 784 | 'target': { 785 | 'handle': ptr.readU32(), 786 | 'ptr': ptr.readPointer() 787 | }, 788 | 'cookie': ptr.add(8).readPointer(), 789 | 'code': ptr.add(16).readU32(), 790 | 'flags': ptr.add(20).readU32(), 791 | 'sender_pid': ptr.add(24).readS32(), 792 | 'sender_euid': ptr.add(28).readU32(), 793 | 'data_size': ptr.add(32).readU64(), 794 | 'offsets_size': ptr.add(40).readU64(), 795 | 'data': { 796 | 'ptr': { 797 | 'buffer': ptr.add(48).readPointer(), 798 | 'offsets': ptr.add(56).readPointer() 799 | }, 800 | 'buf': ptr.add(48).readByteArray(8) 801 | } 802 | } 803 | var _log = hexdump(binder_transaction_data.data.ptr.buffer, { length: binder_transaction_data.data_size, ansi: true }); 804 | if (LAST_MSG.toString() != _log.toString()) { 805 | console.log(JSON.stringify(binder_transaction_data, null, 2)); 806 | console.log(_log); 807 | } 808 | break; 809 | } 810 | } 811 | } 812 | } 813 | }); 814 | }); 815 | ``` 816 | 817 |
818 | Output example 819 | 820 | ```sh 821 | { 822 | "target": { 823 | "handle": 16, 824 | "ptr": "0x10" 825 | }, 826 | "cookie": "0x0", 827 | "code": 22, 828 | "flags": 16, 829 | "sender_pid": 0, 830 | "sender_euid": 0, 831 | "data_size": "68", 832 | "offsets_size": "0", 833 | "data": { 834 | "ptr": { 835 | "buffer": "0x78dce3dcf0", 836 | "offsets": "0x0" 837 | }, 838 | "buf": {} 839 | } 840 | } 841 | 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 842 | 78dce3dcf0 04 00 40 01 1d 00 00 00 61 00 6e 00 64 00 72 00 ..@.....a.n.d.r. 843 | 78dce3dd00 6f 00 69 00 64 00 2e 00 6e 00 65 00 74 00 2e 00 o.i.d...n.e.t... 844 | 78dce3dd10 77 00 69 00 66 00 69 00 2e 00 49 00 57 00 69 00 w.i.f.i...I.W.i. 845 | 78dce3dd20 66 00 69 00 4d 00 61 00 6e 00 61 00 67 00 65 00 f.i.M.a.n.a.g.e. 846 | 78dce3dd30 72 00 00 00 r... 847 | ``` 848 | 849 |
850 | 851 |
[⬆ Back to top](#table-of-contents) 852 | 853 | 854 | #### Reveal native methods 855 | 856 | `registerNativeMethods` can be used as anti reversing technique to the native .so libraries, e.g. hiding the symbols as much as possible, obfuscating the exported symbols and eventually adding some protection over the JNI bridge. 857 | [source](https://stackoverflow.com/questions/51811348/find-manually-registered-obfuscated-native-function-address) 858 | 859 | ```js 860 | var RevealNativeMethods = function() { 861 | var pSize = Process.pointerSize; 862 | var env = Java.vm.getEnv(); 863 | var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html 864 | var jclassAddress2NameMap = {}; 865 | function getNativeAddress(idx) { 866 | return env.handle.readPointer().add(idx * pSize).readPointer(); 867 | } 868 | // intercepting FindClass to populate Map 869 | Interceptor.attach(getNativeAddress(FindClassIndex), { 870 | onEnter: function(args) { 871 | jclassAddress2NameMap[args[0]] = args[1].readCString(); 872 | } 873 | }); 874 | // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977 875 | Interceptor.attach(getNativeAddress(RegisterNatives), { 876 | onEnter: function(args) { 877 | for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) { 878 | /* 879 | https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 880 | typedef struct { 881 | const char* name; 882 | const char* signature; 883 | void* fnPtr; 884 | } JNINativeMethod; 885 | */ 886 | var structSize = pSize * 3; // = sizeof(JNINativeMethod) 887 | var methodsPtr = ptr(args[2]); 888 | var signature = methodsPtr.add(i * structSize + pSize).readPointer(); 889 | var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr 890 | var jClass = jclassAddress2NameMap[args[0]].split('/'); 891 | var methodName = methodsPtr.add(i * structSize).readPointer().readCString(); 892 | console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({ 893 | module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol 894 | package: jClass.slice(0, -1).join('.'), 895 | class: jClass[jClass.length - 1], 896 | method: methodName, // methodsPtr.readPointer().readCString(), // char* name 897 | signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java 898 | address: fnPtr 899 | }), '\x1b[39;49;00m'); 900 | } 901 | } 902 | }); 903 | } 904 | 905 | Java.perform(RevealNativeMethods); 906 | ``` 907 | 908 | @OldVersion 909 | ```js 910 | var fIntercepted = false; 911 | 912 | function revealNativeMethods() { 913 | if (fIntercepted === true) { 914 | return; 915 | } 916 | var jclassAddress2NameMap = {}; 917 | var androidRunTimeSharedLibrary = "libart.so"; // may change between devices 918 | Module.enumerateSymbolsSync(androidRunTimeSharedLibrary).forEach(function(symbol){ 919 | switch (symbol.name) { 920 | case "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib": 921 | /* 922 | $ c++filt "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib" 923 | art::JNI::RegisterNativeMethods(_JNIEnv*, _jclass*, JNINativeMethod const*, int, bool) 924 | */ 925 | var RegisterNativeMethodsPtr = symbol.address; 926 | console.log("RegisterNativeMethods is at " + RegisterNativeMethodsPtr); 927 | Interceptor.attach(RegisterNativeMethodsPtr, { 928 | onEnter: function(args) { 929 | var methodsPtr = ptr(args[2]); 930 | var methodCount = parseInt(args[3]); 931 | for (var i = 0; i < methodCount; i++) { 932 | var pSize = Process.pointerSize; 933 | /* 934 | https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 935 | typedef struct { 936 | const char* name; 937 | const char* signature; 938 | void* fnPtr; 939 | } JNINativeMethod; 940 | */ 941 | var structSize = pSize * 3; // JNINativeMethod contains 3 pointers 942 | var namePtr = Memory.readPointer(methodsPtr.add(i * structSize)); 943 | var sigPtr = Memory.readPointer(methodsPtr.add(i * structSize + pSize)); 944 | var fnPtrPtr = Memory.readPointer(methodsPtr.add(i * structSize + (pSize * 2))); 945 | // output schema: className#methodName(arguments)returnVal@address 946 | console.log( 947 | // package & class, replacing forward slash with dot for convenience 948 | jclassAddress2NameMap[args[0]].replace(/\//g, '.') + 949 | '#' + Memory.readCString(namePtr) + // method 950 | Memory.readCString(sigPtr) + // signature (arguments & return type) 951 | '@' + fnPtrPtr // C side address 952 | ); 953 | } 954 | }, 955 | onLeave: function (ignoredReturnValue) {} 956 | }); 957 | break; 958 | case "_ZN3art3JNI9FindClassEP7_JNIEnvPKc": // art::JNI::FindClass 959 | Interceptor.attach(symbol.address, { 960 | onEnter: function(args) { 961 | if (args[1] != null) { 962 | jclassAddress2NameMap[args[0]] = Memory.readCString(args[1]); 963 | } 964 | }, 965 | onLeave: function (ignoredReturnValue) {} 966 | }); 967 | break; 968 | } 969 | }); 970 | fIntercepted = true; 971 | } 972 | 973 | Java.perform(revealNativeMethods); 974 | ``` 975 | 976 |
977 | Output example 978 | 979 | ```sh 980 | $ frida -Uf com.google.android.apps.photos --no-pause -l script.js 981 | ``` 982 | 983 | ```sh 984 | {"class":"org/chromium/net/GURLUtils","method":"nativeGetOrigin","signature":"(Ljava/lang/String;)Ljava/lang/String;","address":"0x..da910"} 985 | .. 986 | ``` 987 | 988 |
989 | 990 |
[⬆ Back to top](#table-of-contents) 991 | 992 | #### Log method arguments 993 | 994 | 995 | ```python 996 | def on_message(m, _data): 997 | if m['type'] == 'send': 998 | print(m['payload']) 999 | elif m['type'] == 'error': 1000 | print(m) 1001 | 1002 | 1003 | def switch(argument_key, idx): 1004 | """ 1005 | c/c++ variable type to javascript reader switch implementation 1006 | # TODO handle other arguments, [long, longlong..] 1007 | :param argument_key: variable type 1008 | :param idx: index in symbols array 1009 | :return: javascript to read the type of variable 1010 | """ 1011 | argument_key = argument_key.replace(' ', '') 1012 | return '%d: %s' % (idx, { 1013 | 'int': 'args[%d].toInt32(),', 1014 | 'unsignedint': 'args[%d].toInt32(),', 1015 | 'std::string': 'Memory.readUtf8String(Memory.readPointer(args[%d])),', 1016 | 'bool': 'Boolean(args[%d]),' 1017 | }[argument_key] % idx) 1018 | 1019 | 1020 | def list_symbols_from_object_files(module_id): 1021 | import subprocess 1022 | return subprocess.getoutput('nm --demangle --dynamic %s' % module_id) 1023 | 1024 | 1025 | def parse_nm_output(nm_stdout, symbols): 1026 | for line in nm_stdout.splitlines(): 1027 | split = line.split() 1028 | open_parenthesis_idx = line.find('(') 1029 | raw_arguments = [] if open_parenthesis_idx == -1 else line[open_parenthesis_idx + 1:-1] 1030 | if len(raw_arguments) > 0: # ignore methods without arguments 1031 | raw_argument_list = raw_arguments.split(',') 1032 | symbols.append({ 1033 | 'address': split[0], 1034 | 'type': split[1], # @see Symbol Type Table 1035 | 'name': split[2][:split[2].find('(')], # method name 1036 | 'args': raw_argument_list 1037 | }) 1038 | 1039 | 1040 | def get_js_script(method, module_id): 1041 | js_script = """ 1042 | var moduleName = "{{moduleName}}", nativeFuncAddr = {{methodAddress}}; 1043 | Interceptor.attach(Module.findExportByName(null, "dlopen"), { 1044 | onEnter: function(args) { 1045 | this.lib = Memory.readUtf8String(args[0]); 1046 | console.log("[*] dlopen called with: " + this.lib); 1047 | }, 1048 | onLeave: function(retval) { 1049 | if (this.lib.endsWith(moduleName)) { 1050 | Interceptor.attach(Module.findBaseAddress(moduleName).add(nativeFuncAddr), { 1051 | onEnter: function(args) { 1052 | console.log("[*] hook invoked", JSON.stringify({{arguments}}, null, '\t')); 1053 | } 1054 | }); 1055 | } 1056 | } 1057 | }); 1058 | """ 1059 | replace_map = { 1060 | '{{moduleName}}': module_id, 1061 | '{{methodAddress}}': '0x' + method['address'], 1062 | '{{arguments}}': '{' + ''.join([switch(method['args'][i], i + 1) for i in range(len(method['args']))]) + '}' 1063 | } 1064 | for k, v in replace_map.items(): 1065 | js_script = js_script.replace(k, v) 1066 | print('[+] JS Script:\n', js_script) 1067 | return js_script 1068 | 1069 | 1070 | def main(app_id, module_id, method): 1071 | """ 1072 | $ python3.x+ script.py --method SomeClass::someMethod --app com.company.app --module libfoo.so 1073 | :param app_id: application identifier / bundle id 1074 | :param module_id: shared object identifier / known suffix, will iterate loaded modules (@see dlopen) 1075 | :param method: method/symbol name 1076 | :return: hook native method and print arguments when invoked 1077 | """ 1078 | # TODO extract all app's modules via `adb shell -c 'ls -lR /data/app/' + app_if + '*' | grep "\.so"` 1079 | 1080 | nm_stdout = list_symbols_from_object_files(module_id) 1081 | 1082 | symbols = [] 1083 | parse_nm_output(nm_stdout, symbols) 1084 | 1085 | selection_idx = None 1086 | for idx, symbol in enumerate(symbols): 1087 | if method is None: # if --method flag is not passed 1088 | print("%4d) %s (%d)" % (idx, symbol['name'], len(symbol['args']))) 1089 | elif method == symbol['name']: 1090 | selection_idx = idx 1091 | break 1092 | if selection_idx is None: 1093 | if method is None: 1094 | selection_idx = input("Enter symbol number: ") 1095 | else: 1096 | print('[+] Method not found, remove method flag to get list of methods to select from, `nm` stdout:') 1097 | print(nm_stdout) 1098 | exit(2) 1099 | 1100 | method = symbols[int(selection_idx)] 1101 | print('[+] Selected method: %s' % method['name']) 1102 | print('[+] Method arguments: %s' % method['args']) 1103 | 1104 | from frida import get_usb_device 1105 | device = get_usb_device() 1106 | pid = device.spawn([app_id]) 1107 | session = device.attach(pid) 1108 | script = session.create_script(get_js_script(method, module_id)) 1109 | script.on('message', on_message) 1110 | script.load() 1111 | device.resume(app_id) 1112 | # keep hook alive 1113 | from sys import stdin 1114 | stdin.read() 1115 | 1116 | 1117 | if __name__ == '__main__': 1118 | from argparse import ArgumentParser 1119 | parser = ArgumentParser() 1120 | parser.add_argument('--app', help='app identifier "com.company.app"') 1121 | parser.add_argument('--module', help='loaded module name "libfoo.2.so"') 1122 | parser.add_argument('--method', help='method name "SomeClass::someMethod", if empty it will print select-list') 1123 | args = parser.parse_args() 1124 | main(args.app, args.module, args.method) 1125 | 1126 | ``` 1127 | 1128 |
1129 | Symbol Type Table 1130 |
1131 |     "A" The symbol's value is absolute, and will not be changed by further linking.
1132 |     "B" The symbol is in the uninitialized data section (known as BSS).
1133 |     "C" The symbol is common.  Common symbols are uninitialized data.
1134 |        When linking, multiple common symbols may appear	with the same name.  
1135 |        If the symbol is defined anywhere, the common symbols are treated as undefined	references.
1136 |     "D" The symbol is in the initialized data section.
1137 |     "G" The symbol is in an initialized data section for small objects.
1138 |        Some object file formats permit more efficient access to small data objects, such as a global int variable as 
1139 |        opposed to a large global array.
1140 |     "I" The symbol is an indirect reference to another symbol.
1141 |        This is a GNU extension to the a.out object file format which is rarely used.
1142 |     "N" The symbol is a debugging symbol.
1143 |     "R" The symbol is in a read only data section.
1144 |     "S" The symbol is in an uninitialized data section for small objects.
1145 |     "T" The symbol is in the text (code) section.
1146 |     "U" The symbol is undefined.
1147 |     "V" The symbol is a weak object. When a weak defined symbol is linked with a normal defined symbol, 
1148 |         the normal defined symbol is used with no error.  
1149 |         When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes 
1150 |         zero with no error.
1151 |     "W" The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. 
1152 |         When a weak defined symbol is linked with a normal defined symbol, 
1153 |         the normal defined symbol is used with no error.  
1154 |         When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined 
1155 |         in a system-specific manner without error.  
1156 |         On some systems, uppercase indicates that a default value has been specified.
1157 |     "-" The symbol is a stabs symbol in an a.out object file.  
1158 |         In this case, the next values printed are the stabs other field, the stabs desc field, and the stab type.  
1159 |         Stabs symbols are used to hold debugging information.
1160 |     "?" The symbol type is unknown, or object file format specific.
1161 | 
1162 |
1163 | 1164 |
[⬆ Back to top](#table-of-contents) 1165 | 1166 | #### Enumerate loaded classes 1167 | 1168 | And save to a file named `pkg.classes` 1169 | 1170 | ```bash 1171 | $ frida -U com.pkg -qe 'Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(c){console.log(c);}});});' -o pkg.classes 1172 | ``` 1173 | 1174 |
1175 | Output example 1176 | TODO 1177 |
1178 | 1179 |
[⬆ Back to top](#table-of-contents) 1180 | 1181 | 1182 | 1183 | #### Class description 1184 | 1185 | Get class methods & members. 1186 | 1187 | ```js 1188 | Object.getOwnPropertyNames(Java.use('com.company.CustomClass').__proto__).join('\n\t') 1189 | ``` 1190 | 1191 | If there is a name collision, method & member has the same name, an underscore will be added to member. [source](https://github.com/frida/frida-java/pull/21) 1192 | ```js 1193 | let fieldJsName = env.stringFromJni(fieldName); 1194 | while (jsMethods.hasOwnProperty(fieldJsName)) { 1195 | fieldJsName = '_' + fieldJsName; 1196 | } 1197 | ``` 1198 | 1199 |
1200 | Output example 1201 | TODO 1202 |
1203 | 1204 |
[⬆ Back to top](#table-of-contents) 1205 | 1206 | #### Turn Wifi OFF 1207 | 1208 | It will turn WiFi off on the creation of the first Acivity. 1209 | 1210 | ```js 1211 | var WifiManager = Java.use("android.net.wifi.WifiManager"); 1212 | Java.use("android.app.Activity").onCreate.overload("android.os.Bundle").implementation = function(bundle) { 1213 | var wManager = Java.cast(this.getSystemService("wifi"), WifiManager); 1214 | console.log('isWifiEnabled ?', wManager.isWifiEnabled()); 1215 | wManager.setWifiEnabled(false); 1216 | this.$init(bundle); 1217 | } 1218 | ``` 1219 | 1220 |
1221 | Output example 1222 | TODO 1223 |
1224 | 1225 |
[⬆ Back to top](#table-of-contents) 1226 | 1227 | #### Set proxy 1228 | 1229 | It will set a system-wide proxy using the supplied IP address and port. 1230 | 1231 | ```js 1232 | var ActivityThread = Java.use('android.app.ActivityThread'); 1233 | var ConnectivityManager = Java.use('android.net.ConnectivityManager'); 1234 | var ProxyInfo = Java.use('android.net.ProxyInfo'); 1235 | 1236 | var proxyInfo = ProxyInfo.$new('192.168.1.10', 8080, ''); // change to null in order to disable the proxy. 1237 | var context = ActivityThread.currentApplication().getApplicationContext(); 1238 | var connectivityManager = Java.cast(context.getSystemService('connectivity'), ConnectivityManager); 1239 | connectivityManager.setGlobalProxy(proxyInfo); 1240 | ``` 1241 | 1242 |
1243 | Output example 1244 | TODO 1245 |
1246 | 1247 |
[⬆ Back to top](#table-of-contents) 1248 | 1249 | #### Get IMEI 1250 | 1251 | Can also hook & change IMEI. 1252 | 1253 | ```js 1254 | function getIMEI(){ 1255 | console.log('IMEI =', Java.use("android.telephony.TelephonyManager").$new().getDeviceId()); 1256 | } 1257 | Java.perform(getIMEI) 1258 | ``` 1259 | 1260 |
1261 | Output example 1262 | TODO 1263 |
1264 | 1265 |
[⬆ Back to top](#table-of-contents) 1266 | 1267 | #### Hook io InputStream 1268 | 1269 | Hook `InputputStream` & print buffer as `ascii` with char limit & exclude list. 1270 | 1271 | ```js 1272 | function binaryToHexToAscii(array, readLimit) { 1273 | var result = []; 1274 | // read 100 bytes #performance 1275 | readLimit = readLimit || 100; 1276 | for (var i = 0; i < readLimit; ++i) { 1277 | result.push(String.fromCharCode( // hex2ascii part 1278 | parseInt( 1279 | ('0' + (array[i] & 0xFF).toString(16)).slice(-2), // binary2hex part 1280 | 16 1281 | ) 1282 | )); 1283 | } 1284 | return result.join(''); 1285 | } 1286 | 1287 | function hookInputStream() { 1288 | Java.use('java.io.InputStream')['read'].overload('[B').implementation = function(b) { 1289 | // execute original and save return value 1290 | var retval = this.read(b); 1291 | var resp = binaryToHexToAscii(b); 1292 | // conditions to not print garbage packets 1293 | var reExcludeList = new RegExp(['Mmm'/*, 'Ping' /*, ' Yo'*/].join('|')); 1294 | if ( ! reExcludeList.test(resp) ) { 1295 | console.log(resp); 1296 | } 1297 | var reIncludeList = new RegExp(['AAA', 'BBB', 'CCC'].join('|')); 1298 | if ( reIncludeList.test(resp) ) { 1299 | send( binaryToHexToAscii(b, 1200) ); 1300 | } 1301 | return retval; 1302 | }; 1303 | } 1304 | 1305 | Java.perform(hookInputStream); 1306 | ``` 1307 | 1308 |
1309 | Output example 1310 | TODO 1311 |
1312 | 1313 |
[⬆ Back to top](#table-of-contents) 1314 | 1315 | 1316 | #### Android make Toast 1317 | 1318 | ```js 1319 | // 0 = // https://developer.android.com/reference/android/widget/Toast#LENGTH_LONG 1320 | Java.scheduleOnMainThread(() => { 1321 | Java.use("android.widget.Toast") 1322 | .makeText(Java.use("android.app.ActivityThread").currentApplication().getApplicationContext(), Java.use("java.lang.StringBuilder").$new("Text to Toast here"), 0).show(); 1323 | }); 1324 | ``` 1325 | 1326 |
1327 | Output example 1328 | TODO 1329 |
1330 |
[⬆ Back to top](#table-of-contents) 1331 | 1332 | #### Await for condition 1333 | Await until specific DLL will load in Unity app, can implement hot swap. 1334 | ```javascript 1335 | var awaitForCondition = function(callback) { 1336 | var int = setInterval(function() { 1337 | if (Module.findExportByName(null, "mono_get_root_domain")) { 1338 | clearInterval(int); 1339 | callback(); 1340 | return; 1341 | } 1342 | }, 0); 1343 | } 1344 | 1345 | function hook() { 1346 | Interceptor.attach(Module.findExportByName(null, "mono_assembly_load_from_full"), { 1347 | onEnter: function(args) { 1348 | this._dll = Memory.readUtf8String(ptr(args[1])); 1349 | console.log('[*]', this._dll); 1350 | }, 1351 | onLeave: function(retval) { 1352 | if (this._dll.endsWith("Assembly-CSharp.dll")) { 1353 | console.log(JSON.stringify({ 1354 | retval: retval, 1355 | name: this._dll 1356 | }, null, 2)); 1357 | } 1358 | } 1359 | }); 1360 | } 1361 | Java.perform(awaitForCondition(hook)); 1362 | ``` 1363 | 1364 |
1365 | Output example 1366 | TODO 1367 |
1368 | 1369 |
[⬆ Back to top](#table-of-contents) 1370 | 1371 | 1372 | #### Webview URLS 1373 | 1374 | Log whenever WebView switch URL. 1375 | 1376 | ```js 1377 | Java.use("android.webkit.WebView").loadUrl.overload("java.lang.String").implementation = function (s) { 1378 | send(s.toString()); 1379 | this.loadUrl.overload("java.lang.String").call(this, s); 1380 | }; 1381 | ``` 1382 | 1383 |
1384 | Output example 1385 | TODO 1386 |
1387 | 1388 |
[⬆ Back to top](#table-of-contents) 1389 | 1390 | #### Print runtime strings 1391 | 1392 | Hoooking `toString` of StringBuilder/Buffer & printing stacktrace. 1393 | 1394 | ```js 1395 | Java.perform(function() { 1396 | ['java.lang.StringBuilder', 'java.lang.StringBuffer'].forEach(function(clazz, i) { 1397 | console.log('[?] ' + i + ' = ' + clazz); 1398 | var func = 'toString'; 1399 | Java.use(clazz)[func].implementation = function() { 1400 | var ret = this[func](); 1401 | if (ret.indexOf('') != -1) { 1402 | // print stacktrace if return value contains specific string 1403 | Java.perform(function() { 1404 | var jAndroidLog = Java.use("android.util.Log"), jException = Java.use("java.lang.Exception"); 1405 | console.log( jAndroidLog.getStackTraceString( jException.$new() ) ); 1406 | }); 1407 | } 1408 | send('[' + i + '] ' + ret); 1409 | return ret; 1410 | } 1411 | }); 1412 | }); 1413 | ``` 1414 | 1415 |
1416 | Output example 1417 | TODO 1418 |
1419 | 1420 |
[⬆ Back to top](#table-of-contents) 1421 | 1422 | #### Print shared preferences updates 1423 | 1424 | 1425 | ```js 1426 | Java.perform(function() { 1427 | var shared_pref_class = Java.use('android.app.SharedPreferencesImpl$EditorImpl'); 1428 | 1429 | shared_pref_class.putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) { 1430 | console.log('Shared preference updated: ', k, '=', v); 1431 | return this.putString(k, v); 1432 | } 1433 | 1434 | shared_pref_class.putInt.overload('java.lang.String', 'int').implementation = function(k, v) { 1435 | console.log('Shared preference updated: ', k, '=', v); 1436 | return this.putInt(k, v); 1437 | } 1438 | 1439 | 1440 | shared_pref_class.putFloat.overload('java.lang.String', 'float').implementation = function(k, v) { 1441 | console.log('Shared preference updated: ', k, '=', v); 1442 | return this.putFloat(k, v); 1443 | } 1444 | 1445 | shared_pref_class.putBoolean.overload('java.lang.String', 'boolean').implementation = function(k, v) { 1446 | console.log('Shared preference updated: ', k, '=', v); 1447 | return this.putBoolean(k, v); 1448 | } 1449 | 1450 | shared_pref_class.putLong.overload('java.lang.String', 'long').implementation = function(k, v) { 1451 | console.log('Shared preference updated: ', k, '=', v); 1452 | return this.putLong(k, v); 1453 | } 1454 | 1455 | shared_pref_class.putStringSet.overload('java.lang.String', java.util.Set).implementation = function(k, v) { 1456 | console.log('Shared preference updated: ', k, '=', v); 1457 | return this.putStringSet(k, v); 1458 | } 1459 | }); 1460 | ``` 1461 | 1462 |
1463 | Output example 1464 | TODO 1465 |
1466 | 1467 |
[⬆ Back to top](#table-of-contents) 1468 | 1469 | 1470 | 1471 | 1472 | #### String comparison 1473 | 1474 | 1475 | ```js 1476 | Java.perform(function() { 1477 | var str = Java.use('java.lang.String'), objectClass = 'java.lang.Object'; 1478 | str.equals.overload(objectClass).implementation = function(obj) { 1479 | var response = str.equals.overload(objectClass).call(this, obj); 1480 | if (obj) { 1481 | if (obj.toString().length > 5) { 1482 | send(str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response); 1483 | } 1484 | } 1485 | return response; 1486 | } 1487 | }); 1488 | ``` 1489 | 1490 |
1491 | Output example 1492 | TODO 1493 |
1494 | 1495 |
[⬆ Back to top](#table-of-contents) 1496 | 1497 | #### Hook JNI by address 1498 | 1499 | Hook native method by module name and method address and print arguments. 1500 | 1501 | ```js 1502 | var moduleName = "libfoo.so"; 1503 | var nativeFuncAddr = 0x1234; // $ nm --demangle --dynamic libfoo.so | grep "Class::method(" 1504 | 1505 | Interceptor.attach(Module.findExportByName(null, "dlopen"), { 1506 | onEnter: function(args) { 1507 | this.lib = Memory.readUtf8String(args[0]); 1508 | console.log("dlopen called with: " + this.lib); 1509 | }, 1510 | onLeave: function(retval) { 1511 | if (this.lib.endsWith(moduleName)) { 1512 | console.log("ret: " + retval); 1513 | var baseAddr = Module.findBaseAddress(moduleName); 1514 | Interceptor.attach(baseAddr.add(nativeFuncAddr), { 1515 | onEnter: function(args) { 1516 | console.log("[-] hook invoked"); 1517 | console.log(JSON.stringify({ 1518 | a1: args[1].toInt32(), 1519 | a2: Memory.readUtf8String(Memory.readPointer(args[2])), 1520 | a3: Boolean(args[3]) 1521 | }, null, '\t')); 1522 | } 1523 | }); 1524 | } 1525 | } 1526 | }); 1527 | ``` 1528 | 1529 |
1530 | Output example 1531 | TODO 1532 |
1533 | 1534 |
[⬆ Back to top](#table-of-contents) 1535 | 1536 | #### Hook constructor 1537 | ```js 1538 | Java.use('java.lang.StringBuilder').$init.overload('java.lang.String').implementation = function(stringArgument) { 1539 | console.log("c'tor"); 1540 | return this.$init(stringArgument); 1541 | }; 1542 | ``` 1543 | 1544 |
1545 | Output example 1546 | TODO 1547 |
1548 | 1549 |
[⬆ Back to top](#table-of-contents) 1550 | 1551 | #### Hook reflection 1552 | 1553 | `java.lang.reflect.Method#invoke(Object obj, Object... args, boolean bool)` 1554 | 1555 | ```javascript 1556 | Java.use('java.lang.reflect.Method').invoke.overload('java.lang.Object', '[Ljava.lang.Object;', 'boolean').implementation = function(a,b,c) { 1557 | console.log('hooked!', a, b, c); 1558 | return this.invoke(a,b,c); 1559 | }; 1560 | ``` 1561 | 1562 |
1563 | Output example 1564 | TODO 1565 |
1566 | 1567 |
[⬆ Back to top](#table-of-contents) 1568 | 1569 | #### Trace class 1570 | 1571 | Tracing class method, with pretty colors and options to print as JSON & stacktrace. 1572 | 1573 | TODO add trace for c'tor. 1574 | 1575 | ```js 1576 | 1577 | var Color = { 1578 | RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", Green: "2;01", Purple: "5;01", Red: "1;01", Yellow: "3;01", 1579 | Light: { 1580 | Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", Green: "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11" 1581 | } 1582 | }; 1583 | 1584 | /** 1585 | * 1586 | * @param input. 1587 | * If an object is passed it will print as json 1588 | * @param kwargs options map { 1589 | * -l level: string; log/warn/error 1590 | * -i indent: boolean; print JSON prettify 1591 | * -c color: @see ColorMap 1592 | * } 1593 | */ 1594 | var LOG = function (input, kwargs) { 1595 | kwargs = kwargs || {}; 1596 | var logLevel = kwargs['l'] || 'log', colorPrefix = '\x1b[3', colorSuffix = 'm'; 1597 | if (typeof input === 'object') 1598 | input = JSON.stringify(input, null, kwargs['i'] ? 2 : null); 1599 | if (kwargs['c']) 1600 | input = colorPrefix + kwargs['c'] + colorSuffix + input + Color.RESET; 1601 | console[logLevel](input); 1602 | }; 1603 | 1604 | var printBacktrace = function () { 1605 | Java.perform(function() { 1606 | var android_util_Log = Java.use('android.util.Log'), java_lang_Exception = Java.use('java.lang.Exception'); 1607 | // getting stacktrace by throwing an exception 1608 | LOG(android_util_Log.getStackTraceString(java_lang_Exception.$new()), { c: Color.Gray }); 1609 | }); 1610 | }; 1611 | 1612 | function traceClass(targetClass) { 1613 | var hook; 1614 | try { 1615 | hook = Java.use(targetClass); 1616 | } catch (e) { 1617 | console.error("trace class failed", e); 1618 | return; 1619 | } 1620 | 1621 | var methods = hook.class.getDeclaredMethods(); 1622 | hook.$dispose(); 1623 | 1624 | var parsedMethods = []; 1625 | methods.forEach(function (method) { 1626 | var methodStr = method.toString(); 1627 | var methodReplace = methodStr.replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]; 1628 | parsedMethods.push(methodReplace); 1629 | }); 1630 | 1631 | uniqBy(parsedMethods, JSON.stringify).forEach(function (targetMethod) { 1632 | traceMethod(targetClass + '.' + targetMethod); 1633 | }); 1634 | } 1635 | 1636 | 1637 | function traceMethod(targetClassMethod) { 1638 | try { 1639 | var delim = targetClassMethod.lastIndexOf('.'); 1640 | if (delim === -1) 1641 | return; 1642 | 1643 | var targetClass = targetClassMethod.slice(0, delim); 1644 | var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length); 1645 | 1646 | var hook = Java.use(targetClass); 1647 | var overloadCount = hook[targetMethod].overloads.length; 1648 | 1649 | LOG({ tracing: targetClassMethod, overloaded: overloadCount }, { c: Color.Green }); 1650 | 1651 | for (var i = 0; i < overloadCount; i++) { 1652 | hook[targetMethod].overloads[i].implementation = function () { 1653 | var log = { '#': targetClassMethod, args: [] }; 1654 | 1655 | for (var j = 0; j < arguments.length; j++) { 1656 | var arg = arguments[j]; 1657 | // quick&dirty fix for java.io.StringWriter char[].toString() impl because frida prints [object Object] 1658 | if (j === 0 && arguments[j]) { 1659 | if (arguments[j].toString() === '[object Object]') { 1660 | var s = []; 1661 | for (var k = 0, l = arguments[j].length; k < l; k++) { 1662 | s.push(arguments[j][k]); 1663 | } 1664 | arg = s.join(''); 1665 | } 1666 | } 1667 | log.args.push({ i: j, o: arg, s: arg ? arg.toString(): 'null'}); 1668 | } 1669 | 1670 | var retval; 1671 | try { 1672 | retval = this[targetMethod].apply(this, arguments); // might crash (Frida bug?) 1673 | log.returns = { val: retval, str: retval ? retval.toString() : null }; 1674 | } catch (e) { 1675 | console.error(e); 1676 | } 1677 | LOG(log, { c: Color.Blue }); 1678 | return retval; 1679 | } 1680 | } 1681 | } catch(error) { 1682 | LOG({ tracing: targetClassMethod, "overloaded": 0}, { c: Color.Red }); 1683 | } 1684 | } 1685 | // remove duplicates from array 1686 | function uniqBy(array, key) { 1687 | var seen = {}; 1688 | return array.filter(function (item) { 1689 | var k = key(item); 1690 | return seen.hasOwnProperty(k) ? false : (seen[k] = true); 1691 | }); 1692 | } 1693 | 1694 | 1695 | var Main = function() { 1696 | Java.perform(function () { // avoid java.lang.ClassNotFoundException 1697 | [ 1698 | // "java.io.File", 1699 | 'java.net.Socket' 1700 | ].forEach(traceClass); 1701 | 1702 | Java.use('java.net.Socket').isConnected.overload().implementation = function () { 1703 | LOG('Socket.isConnected.overload', { c: Color.Light.Cyan }); 1704 | printBacktrace(); 1705 | return true; 1706 | } 1707 | }); 1708 | }; 1709 | 1710 | Java.perform(Main); 1711 | 1712 | 1713 | ``` 1714 | 1715 |
1716 | Output example 1717 | TODO 1718 |
1719 | 1720 |
[⬆ Back to top](#table-of-contents) 1721 | 1722 | #### Get Android ID 1723 | The [ANDROID_ID](https://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID) is unique in each application in Android. 1724 | 1725 | 1726 | ```javascript 1727 | function getContext() { 1728 | return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext().getContentResolver(); 1729 | } 1730 | 1731 | function logAndroidId() { 1732 | console.log('[-]', Java.use('android.provider.Settings$Secure').getString(getContext(), 'android_id')); 1733 | } 1734 | ``` 1735 | 1736 |
1737 | Output example 1738 | https://stackoverflow.com/a/54818023/2655092 1739 |
1740 | 1741 |
[⬆ Back to top](#table-of-contents) 1742 | 1743 | 1744 | #### Change location 1745 | 1746 | 1747 | ```js 1748 | Java.perform(() => { 1749 | var Location = Java.use('android.location.Location'); 1750 | Location.getLatitude.implementation = function() { 1751 | return LATITUDE; 1752 | } 1753 | Location.getLongitude.implementation = function() { 1754 | return LONGITUDE; 1755 | } 1756 | }) 1757 | ``` 1758 | 1759 |
1760 | Output example 1761 | TODO 1762 |
1763 | 1764 |
[⬆ Back to top](#table-of-contents) 1765 | 1766 | 1767 | 1768 | #### Bypass FLAG_SECURE 1769 | Bypass screenshot prevention [stackoverflow question](https://stackoverflow.com/questions/9822076/how-do-i-prevent-android-taking-a-screenshot-when-my-app-goes-to-the-background) 1770 | 1771 | ```javascript 1772 | Java.perform(function() { 1773 | Java.use('android.view.SurfaceView').setSecure.overload('boolean').implementation = function(flag){ 1774 | console.log('[1] flag:', flag); 1775 | this.call(false); 1776 | }; 1777 | var LayoutParams = Java.use('android.view.WindowManager$LayoutParams'); 1778 | Java.use('android.view.ViewWindow').setFlags.overload('int', 'int').implementation = function(flags, mask){ 1779 | console.log('flag secure: ', LayoutParams.FLAG_SECURE.value); 1780 | console.log('before:', flags); 1781 | flags = (flags.value & ~LayoutParams.FLAG_SECURE.value); 1782 | console.log('after:', flags); 1783 | this.call(this, flags, mask); 1784 | }; 1785 | }); 1786 | ``` 1787 | 1788 |
1789 | Output example 1790 | https://stackoverflow.com/a/54818023/2655092 1791 |
1792 | 1793 |
[⬆ Back to top](#table-of-contents) 1794 | 1795 | #### Shared Preferences update 1796 | 1797 | ```javascript 1798 | function notifyNewSharedPreference() { 1799 | Java.use('android.app.SharedPreferencesImpl$EditorImpl').putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) { 1800 | console.log('[SharedPreferencesImpl]', k, '=', v); 1801 | return this.putString(k, v); 1802 | } 1803 | } 1804 | ``` 1805 | 1806 |
1807 | Output example 1808 | TODO 1809 |
1810 | 1811 |
[⬆ Back to top](#table-of-contents) 1812 | 1813 | #### Hook overloads 1814 | 1815 | ```javascript 1816 | function hookOverloads(className, func) { 1817 | var clazz = Java.use(className); 1818 | var overloads = clazz[func].overloads; 1819 | for (var i in overloads) { 1820 | if (overloads[i].hasOwnProperty('argumentTypes') || overloads[i]['argumentTypes'] != undefined) { 1821 | var parameters = []; 1822 | 1823 | var curArgumentTypes = overloads[i].argumentTypes, args = [], argLog = '['; 1824 | for (var j in curArgumentTypes) { 1825 | var cName = curArgumentTypes[j].className; 1826 | parameters.push(cName); 1827 | argLog += "'(" + cName + ") ' + v" + j + ","; 1828 | args.push('v' + j); 1829 | } 1830 | argLog += ']'; 1831 | 1832 | var script = "var ret = this." + func + '(' + args.join(',') + ") || '';\n" 1833 | + "console.log(JSON.stringify(" + argLog + "));\n" 1834 | + "return ret;" 1835 | 1836 | args.push(script); 1837 | clazz[func].overload.apply(this, parameters).implementation = Function.apply(null, args); 1838 | } 1839 | } 1840 | } 1841 | 1842 | Java.perform(function() { 1843 | hookOverloads('java.lang.StringBuilder', '$init'); 1844 | }) 1845 | ``` 1846 | 1847 |
1848 | Output example 1849 | TODO 1850 |
1851 | 1852 |
[⬆ Back to top](#table-of-contents) 1853 | 1854 | 1855 | #### Register broadcast receiver 1856 | 1857 | ```javascript 1858 | Java.perform(() => { 1859 | const MyBroadcastReceiver = Java.registerClass({ 1860 | name: 'MyBroadcastReceiver', 1861 | superClass: Java.use('android.content.BroadcastReceiver'), 1862 | methods: { 1863 | onReceive: [{ 1864 | returnType: 'void', 1865 | argumentTypes: ['android.content.Context', 'android.content.Intent'], 1866 | implementation: function(context, intent) { 1867 | // .. 1868 | } 1869 | }] 1870 | }, 1871 | }); 1872 | let ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); 1873 | ctx.registerReceiver(MyBroadcastReceiver.$new(), Java.use('android.content.IntentFilter').$new('com.example.JAVA_TO_AGENT')); 1874 | }); 1875 | ``` 1876 | 1877 |
1878 | Output example 1879 | TODO 1880 |
1881 | 1882 |
[⬆ Back to top](#table-of-contents) 1883 | 1884 | 1885 | #### list classes implements interface 1886 | 1887 | ```js 1888 | function listClassesImplementsInterface(aInterface) { 1889 | let classLoaders = Java.enumerateClassLoadersSync() 1890 | Java.enumerateLoadedClassesSync().forEach(className => { 1891 | for (let i = 0; i < classLoaders.length; i++) { 1892 | let classLoader = classLoaders[i] 1893 | Java.classFactory.loader = classLoader 1894 | try { 1895 | let jclass = Java.use(className).class 1896 | let ifaces = jclass.getInterfaces().toString() 1897 | jclass = null 1898 | if (ifaces.indexOf(aInterface) != -1) { 1899 | console.log(JSON.stringify({ 1900 | name: className, 1901 | loader: classLoader.toString(), 1902 | interfaces: ifaces 1903 | })) 1904 | break // we found one ClassLoader, that's enough 1905 | } 1906 | } catch (e) { 1907 | // continue to next ClassLoader 1908 | } 1909 | } 1910 | }) 1911 | } 1912 | ``` 1913 | 1914 |
[⬆ Back to top](#table-of-contents) 1915 | 1916 | 1917 | #### Increase step count 1918 | 1919 | 1920 | ```js 1921 | Java.perform(() => { 1922 | var customSensorEventListener = null; 1923 | var curSteps = 0; 1924 | var totalNumberOfRequiredSteps = 10000; 1925 | 1926 | function incSteps() { 1927 | Java.perform(() => { 1928 | var sEvent = Java.use('android.hardware.SensorEvent').$new(1); 1929 | sEvent.values.values = Java.array('float', [curSteps]); // https://developer.android.com/reference/android/hardware/SensorEvent#values 1930 | sEvent.timestamp = Java.use('java.lang.Long').$new(Java.use('java.lang.System').nanoTime()); 1931 | sEvent.accuracy = Java.use('java.lang.Integer').$new(3); // https://developer.android.com/reference/android/hardware/SensorManager#SENSOR_STATUS_ACCURACY_HIGH 1932 | customSensorEventListener.onSensorChanged(sEvent); 1933 | 1934 | if (curSteps < totalNumberOfRequiredSteps) { 1935 | setTimeout(() => { 1936 | curSteps += 50; 1937 | incSteps(); 1938 | }, 1500) 1939 | } 1940 | }); 1941 | } 1942 | 1943 | Java.choose('.CustomSensorEventListener', { // class that implements SensorEventListener 1944 | onMatch: function (instance) { 1945 | customSensorEventListener = instance; 1946 | }, 1947 | onComplete: function () { 1948 | incSteps(); 1949 | } 1950 | }); 1951 | }); 1952 | ``` 1953 | 1954 | 1955 |
1956 | Output example 1957 | TODO 1958 |
1959 | 1960 |
[⬆ Back to top](#table-of-contents) 1961 | 1962 | #### OS Log 1963 | 1964 | ```js 1965 | var m = 'libsystem_trace.dylib'; 1966 | // bool os_log_type_enabled(os_log_t oslog, os_log_type_t type); 1967 | var isEnabledFunc = Module.findExportByName(m, 'os_log_type_enabled'); 1968 | // _os_log_impl(void *dso, os_log_t log, os_log_type_t type, const char *format, uint8_t *buf, unsigned int size); 1969 | var logFunc = Module.findExportByName(m, '_os_log_impl'); 1970 | 1971 | // Enable all logs 1972 | Interceptor.attach(isEnabledFunc, { 1973 | onLeave: function (ret) { 1974 | ret.replace(0x1); 1975 | } 1976 | }); 1977 | 1978 | Interceptor.attach(logFunc, { 1979 | onEnter: function (a) { 1980 | /* 1981 | OS_ENUM(os_log_type, uint8_t, 1982 | OS_LOG_TYPE_DEFAULT = 0x00, 1983 | OS_LOG_TYPE_INFO = 0x01, 1984 | OS_LOG_TYPE_DEBUG = 0x02, 1985 | OS_LOG_TYPE_ERROR = 0x10, 1986 | OS_LOG_TYPE_FAULT = 0x11); 1987 | */ 1988 | var type = a[2]; 1989 | var format = a[3]; 1990 | if (type !== 0x2) { 1991 | console.log(JSON.stringify({ 1992 | type: type, 1993 | format: format.readCString(), 1994 | //buf: a[4].readPointer().readCString() // TODO 1995 | }, null, 2)); 1996 | } 1997 | } 1998 | }) 1999 | ``` 2000 | 2001 |
2002 | Output example 2003 | TODO 2004 |
2005 | 2006 |
[⬆ Back to top](#table-of-contents) 2007 | 2008 | 2009 | 2010 | #### iOS alert box 2011 | 2012 | ```js 2013 | var UIAlertController = ObjC.classes.UIAlertController; 2014 | var UIAlertAction = ObjC.classes.UIAlertAction; 2015 | var UIApplication = ObjC.classes.UIApplication; 2016 | var handler = new ObjC.Block({ retType: 'void', argTypes: ['object'], implementation: function () {} }); 2017 | 2018 | ObjC.schedule(ObjC.mainQueue, function () { 2019 | var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_('Frida', 'Hello from Frida', 1); 2020 | var defaultAction = UIAlertAction.actionWithTitle_style_handler_('OK', 0, handler); 2021 | alert.addAction_(defaultAction); 2022 | // Instead of using `ObjC.choose()` and looking for UIViewController instances on the heap, we have direct access through UIApplication: 2023 | UIApplication.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(alert, true, NULL); 2024 | }) 2025 | ``` 2026 | 2027 |
2028 | Output example 2029 | TODO 2030 |
2031 | 2032 |
[⬆ Back to top](#table-of-contents) 2033 | 2034 | 2035 | #### File Access 2036 | 2037 | Log each file open 2038 | 2039 | ```js 2040 | Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, { 2041 | onEnter: function (args) { 2042 | console.log('open' , ObjC.Object(args[2]).toString()); 2043 | } 2044 | }); 2045 | ``` 2046 | 2047 |
2048 | Output example 2049 | TODO 2050 |
2051 | 2052 |
[⬆ Back to top](#table-of-contents) 2053 | 2054 | #### Observe class 2055 | ```js 2056 | function observeClass(name) { 2057 | var k = ObjC.classes[name]; 2058 | k.$ownMethods.forEach(function(m) { 2059 | var impl = k[m].implementation; 2060 | console.log('Observing ' + name + ' ' + m); 2061 | Interceptor.attach(impl, { 2062 | onEnter: function(a) { 2063 | this.log = []; 2064 | this.log.push('(' + a[0] + ',' + Memory.readUtf8String(a[1]) + ') ' + name + ' ' + m); 2065 | if (m.indexOf(':') !== -1) { 2066 | var params = m.split(':'); 2067 | params[0] = params[0].split(' ')[1]; 2068 | for (var i = 0; i < params.length - 1; i++) { 2069 | try { 2070 | this.log.push(params[i] + ': ' + new ObjC.Object(a[2 + i]).toString()); 2071 | } catch (e) { 2072 | this.log.push(params[i] + ': ' + a[2 + i].toString()); 2073 | } 2074 | } 2075 | } 2076 | 2077 | this.log.push( 2078 | Thread.backtrace(this.context, Backtracer.ACCURATE) 2079 | .map(DebugSymbol.fromAddress) 2080 | .join('\n') 2081 | ); 2082 | }, 2083 | 2084 | onLeave: function(r) { 2085 | try { 2086 | this.log.push('RET: ' + new ObjC.Object(r).toString()); 2087 | } catch (e) { 2088 | this.log.push('RET: ' + r.toString()); 2089 | } 2090 | 2091 | console.log(this.log.join('\n') + '\n'); 2092 | } 2093 | }); 2094 | }); 2095 | } 2096 | ``` 2097 | 2098 |
2099 | Output example 2100 | 2101 | `observeClass('Someclass$innerClass');` 2102 | 2103 | ``` 2104 | Observing Someclass$innerClass - func 2105 | Observing Someclass$innerClass - empty 2106 | (0x174670040,parameterName) Someclass$innerClass - func 2107 | 0x10048dd6c libfoo!0x3bdd6c 2108 | 0x1005a5dd0 libfoo!0x4d5dd0 2109 | 0x1832151c0 libdispatch.dylib!_dispatch_client_callout 2110 | 0x183215fb4 libdispatch.dylib!dispatch_once_f 2111 | RET: 0xabcdef 2112 | ``` 2113 | 2114 |
2115 | 2116 |
[⬆ Back to top](#table-of-contents) 2117 | 2118 | 2119 | #### Find iOS application UUID 2120 | 2121 | Get UUID for specific path when attached to an app by reading plist file under each app container. 2122 | 2123 | ```js 2124 | var PLACEHOLDER = '{UUID}'; 2125 | function extractUUIDfromPath(path) { 2126 | var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier')); 2127 | var path_prefix = path.substr(0, path.indexOf(PLACEHOLDER)); 2128 | var plist_metadata = '/.com.apple.mobile_container_manager.metadata.plist'; 2129 | var errorPtr = Memory.alloc(Process.pointerSize); 2130 | Memory.writePointer(errorPtr, NULL); 2131 | var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, errorPtr); 2132 | var error = Memory.readPointer(errorPtr); 2133 | if (errorPtr) 2134 | console.error( new ObjC.Object( error ) ); 2135 | for (var i = 0, l = folders.count(); i < l; i++) { 2136 | var uuid = folders.objectAtIndex_(i); 2137 | var metadata = path_prefix + uuid + plist_metadata; 2138 | var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata); 2139 | var enumerator = dict.keyEnumerator(); 2140 | var key; 2141 | while ((key = enumerator.nextObject()) !== null) { 2142 | if (key == 'MCMMetadataIdentifier') { 2143 | var appId = String(dict.objectForKey_(key)); 2144 | if (appId.indexOf(bundleIdentifier) != -1) { 2145 | return path.replace(PLACEHOLDER, uuid); 2146 | } 2147 | } 2148 | } 2149 | } 2150 | } 2151 | console.log( extractUUIDfromPath('/var/mobile/Containers/Data/Application/' + PLACEHOLDER + '/Documents') ); 2152 | ``` 2153 | 2154 |
2155 | Output example 2156 | TODO 2157 |
2158 | 2159 |
[⬆ Back to top](#table-of-contents) 2160 | 2161 | 2162 | #### Extract cookies 2163 | 2164 | ```js 2165 | var cookieJar = {}; 2166 | var cookies = ObjC.classes.NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies(); 2167 | for (var i = 0, l = cookies.count(); i < l; i++) { 2168 | var cookie = cookies['- objectAtIndex:'](i); 2169 | cookieJar[cookie.Name()] = cookie.Value().toString(); // ["- expiresDate"]().toString() 2170 | } 2171 | console.log(JSON.stringify(cookieJar, null, 2)); 2172 | ``` 2173 | 2174 |
2175 | Output example 2176 | ```js 2177 | { 2178 | "key1": "value 1", 2179 | "key2": "value 2" 2180 | } 2181 | ``` 2182 |
2183 | 2184 |
[⬆ Back to top](#table-of-contents) 2185 | 2186 | 2187 | #### Describe class members 2188 | 2189 | Print map of members (with values) for each class instance 2190 | 2191 | ```js 2192 | ObjC.choose(ObjC.classes[clazz], { 2193 | onMatch: function (obj) { 2194 | console.log('onMatch: ', obj); 2195 | Object.keys(obj.$ivars).forEach(function(v) { 2196 | console.log('\t', v, '=', obj.$ivars[v]); 2197 | }); 2198 | }, 2199 | onComplete: function () { 2200 | console.log('onComplete', arguments.length); 2201 | } 2202 | }); 2203 | ``` 2204 | 2205 |
2206 | Output example 2207 | TODO 2208 |
2209 | 2210 |
[⬆ Back to top](#table-of-contents) 2211 | 2212 | #### Class hierarchy 2213 | 2214 | Object.keys(ObjC.classes) will list all available Objective C classes, 2215 | but actually this will return all classes loaded in current process, including system frameworks. 2216 | If we want something like weak_classdump, to list classes from executable it self only, Objective C runtime already provides such function [objc_copyClassNamesForImage](#https://developer.apple.com/documentation/objectivec/1418485-objc_copyclassnamesforimage?language=objc) 2217 | 2218 | ```js 2219 | var objc_copyClassNamesForImage = new NativeFunction( 2220 | Module.findExportByName(null, 'objc_copyClassNamesForImage'), 2221 | 'pointer', 2222 | ['pointer', 'pointer'] 2223 | ); 2224 | var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']); 2225 | var classes = new Array(count); 2226 | var p = Memory.alloc(Process.pointerSize); 2227 | 2228 | Memory.writeUInt(p, 0); 2229 | 2230 | var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String(); 2231 | var pPath = Memory.allocUtf8String(path); 2232 | var pClasses = objc_copyClassNamesForImage(pPath, p); 2233 | var count = Memory.readUInt(p); 2234 | for (var i = 0; i < count; i++) { 2235 | var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)); 2236 | classes[i] = Memory.readUtf8String(pClassName); 2237 | } 2238 | 2239 | free(pClasses); 2240 | 2241 | var tree = {}; 2242 | classes.forEach(function(name) { 2243 | var clazz = ObjC.classes[name]; 2244 | var chain = [name]; 2245 | while (clazz = clazz.$superClass) { 2246 | chain.unshift(clazz.$className); 2247 | } 2248 | 2249 | var node = tree; 2250 | chain.forEach(function(clazz) { 2251 | node[clazz] = node[clazz] || {}; 2252 | node = node[clazz]; 2253 | }); 2254 | }); 2255 | 2256 | send(tree); 2257 | ``` 2258 | 2259 | 2260 |
2261 | Output example 2262 | TODO 2263 |
2264 | 2265 |
[⬆ Back to top](#table-of-contents) 2266 | 2267 | #### Hook refelaction 2268 | Hooking `objc_msgSend` 2269 | 2270 | ```py 2271 | import frida, sys 2272 | 2273 | f = open('/tmp/log', 'w') 2274 | 2275 | def on_message(msg, _data): 2276 | f.write(msg['payload']+'\n') 2277 | 2278 | frida_script = """ 2279 | Interceptor.attach(Module.findExportByName('/usr/lib/libobjc.A.dylib', 'objc_msgSend'), { 2280 | onEnter: function(args) { 2281 | var m = Memory.readCString(args[1]); 2282 | if (m != 'length' && !m.startsWith('_fastC')) 2283 | send(m); 2284 | } 2285 | }); 2286 | """ 2287 | device = frida.get_usb_device() 2288 | pid = device.spawn(["com.example"]) # or .get_frontmost_application() 2289 | session = device.attach(pid) 2290 | script = session.create_script(frida_script) 2291 | script.on('message', on_message) 2292 | script.load() 2293 | device.resume(pid) 2294 | sys.stdin.read() 2295 | ``` 2296 | ```sh 2297 | $ sort /tmp/log | uniq -c | sort -n 2298 | ``` 2299 | 2300 |
2301 | Output example 2302 | TODO 2303 |
2304 | 2305 |
[⬆ Back to top](#table-of-contents) 2306 | 2307 | #### Intercept Entire Module 2308 | 2309 | To reduce UI related functions I ues the following steps: 2310 | 2311 | 1. Output log to a file using `-o /tmp/log1` 2312 | 2. Copy MRU to excludesList using `$ sort /tmp/log1 | uniq -c | sort -rn | head -n20 | cut -d# -f2 | paste -sd "," -` 2313 | 2314 | ```js 2315 | var mName = 'MyModule', excludeList = ['Alot', 'Of', 'UI', 'Related', 'Functions']; 2316 | Module.enumerateExportsSync(mName) 2317 | .filter(function(e) { 2318 | var fromTypeFunction = e.type == 'function';· 2319 | var notInExcludes = excludeList.indexOf(e.name) == -1; 2320 | return fromTypeFunction && notInExcludes; 2321 | }) 2322 | .forEach(function(e) { 2323 | Interceptor.attach(Module.findExportByName(mName, e.name), { 2324 | onEnter: function(args) { 2325 | console.log(mName + "#'" + e.name + "'"); 2326 | } 2327 | }) 2328 | }) 2329 | ``` 2330 | 2331 |
2332 | Output example 2333 | TODO 2334 |
2335 | 2336 |
[⬆ Back to top](#table-of-contents) 2337 | 2338 | 2339 | 2340 | #### Dump memory segments 2341 | 2342 | ```js 2343 | Process.enumerateRanges('rw-', { 2344 | onMatch: function (range) { 2345 | var fname = `/sdcard/${range.base}_dump`; 2346 | var f = new File(fname, 'wb'); 2347 | f.write(instance.base.readByteArray(instance.size)); 2348 | f.flush(); 2349 | f.close(); 2350 | console.log(`base=${range.base} size=${range.size} prot=${range.protection} fname=${fname}`); 2351 | }, 2352 | onComplete: function () {} 2353 | }); 2354 | ``` 2355 | 2356 |
2357 | Output example 2358 | TODO 2359 |
2360 | 2361 |
[⬆ Back to top](#table-of-contents) 2362 | 2363 | 2364 | 2365 | #### Memory scan 2366 | 2367 | ```js 2368 | function memscan(str) { 2369 | Process.enumerateModulesSync().filter(m => m.path.startsWith('/data')).forEach(m => { 2370 | var pattern = str.split('').map(letter => letter.charCodeAt(0).toString(16)).join(' '); 2371 | try { 2372 | var res = Memory.scanSync(m.base, m.size, pattern); 2373 | if (res.length > 0) 2374 | console.log(JSON.stringify({m, res})); 2375 | } catch (e) { 2376 | console.warn(e); 2377 | } 2378 | }); 2379 | } 2380 | ``` 2381 | 2382 | 2383 | ```js 2384 | var memscn = function (str) { 2385 | Process.enumerateModulesSync().forEach(function (m) { 2386 | var pattern = str.split('').map(function (l) { return l.charCodeAt(0).toString(16) }).join(' '); 2387 | try { 2388 | var res = Memory.scanSync(m.base, m.size, pattern); 2389 | if (res.length > 0) 2390 | console.log(JSON.stringify({m, res}, null , 2)); 2391 | } catch (e) { 2392 | console.warn(e); 2393 | } 2394 | }); 2395 | } 2396 | ``` 2397 | 2398 |
2399 | Output example 2400 | pattern [ 52 41 4e 44 4f 4d ] { 2401 | "name": "Test", 2402 | "base": "0x1048fc000", 2403 | "size": 147000, 2404 | "path": "/var/containers/Bundle/Application/CD74EB00-9D90-4600-BF5D-F6E5E0CDF878/Test.app/Test" 2405 | } 2406 | [{"address":"0x10491f211","size":6}] 2407 | 2408 |
2409 | 2410 |
[⬆ Back to top](#table-of-contents) 2411 | 2412 | 2413 | 2414 | 2415 | 2416 | 2417 | 2418 | #### Stalker 2419 | 2420 | ```js 2421 | var _module = Process.findModuleByName('myModule'); 2422 | var base = ptr(_module.base); 2423 | var startTraceOffset = 0xabcd1234, numInstructionsToTrace = 50; 2424 | var startTrace = base.add(startTraceOffset), endTrace = startTrace.add(4 * (numInstructionsToTrace - 1)); 2425 | 2426 | Interceptor.attach(ObjC.classes.CustomClass['- func'].implementation, { 2427 | onEnter: function (args) { 2428 | var tid = Process.getCurrentThreadId(); 2429 | this.tid = tid; 2430 | console.warn(`onEnter [ ${tid} ]`); 2431 | Stalker.follow(tid, { 2432 | transform: function (iterator) { 2433 | var instruction; 2434 | while ((instruction = iterator.next()) !== null) { 2435 | // condition to putCallout 2436 | if (instruction.address <= endTrace && instruction.address >= startTrace) { 2437 | // print instruction & registers values 2438 | iterator.putCallout(function(context) { 2439 | var offset = ptr(context.pc).sub(base); 2440 | var inst = Instruction.parse(context.pc).toString(); 2441 | var modified_inst = inst; 2442 | inst.replace(/,/g, '').split(' ').forEach(op => { 2443 | if (op.startsWith('x')) 2444 | modified_inst = modified_inst.replace(op, context[op]); 2445 | else if (op.startsWith('w')) 2446 | modified_inst = modified_inst.replace(op, context[op.replace('w', 'x')]); 2447 | }); 2448 | modified_inst = '\x1b[35;01m' + modified_inst + '\x1b[0m'; 2449 | console.log(`x8=${context.x8} x25=${context.x25} x0=${context.x0} x21=${context.x21}`) 2450 | console.log(`${offset} ${inst} # ${modified_inst}`); 2451 | }); 2452 | } 2453 | iterator.keep(); 2454 | } 2455 | } 2456 | }) 2457 | }, 2458 | onLeave: function (retval) { 2459 | console.log(`onLeave [ ${this.tid} ]`); 2460 | // cleanup 2461 | Stalker.unfollow(this.tid); 2462 | Stalker.garbageCollect(); 2463 | } 2464 | }) 2465 | ``` 2466 | 2467 |
2468 | Output example 2469 | mul x5, x2, x21 # mul 0x3, 0x4, 0x5 2470 |
2471 | 2472 |
[⬆ Back to top](#table-of-contents) 2473 | 2474 | 2475 | 2476 | 2477 | 2478 | 2479 | #### Cpp demangler 2480 | 2481 | ```sh 2482 | $ npm i frida-compile demangler-js -g 2483 | ``` 2484 | 2485 | add to your script 2486 | 2487 | ```js 2488 | const demangle = require('demangler-js').demangle; 2489 | ... 2490 | Module.enumerateExportsSync('library.so') 2491 | .filter(x => x.name.startsWith('_Z')) 2492 | .forEach(x => { 2493 | Interceptor.attach(x.address, { 2494 | onEnter: function (args) { 2495 | console.log('[-] ' + demangle(x.name)); 2496 | } 2497 | }); 2498 | }); 2499 | ``` 2500 | 2501 | compile 2502 | 2503 | ```sh 2504 | $ frida-compile script.js -o out.js 2505 | ``` 2506 | 2507 | run 2508 | 2509 | ```sh 2510 | $ frida -Uf com.app -l out.js 2511 | ``` 2512 | 2513 | 2514 |
2515 | Output example 2516 | TODO 2517 |
2518 | 2519 |
[⬆ Back to top](#table-of-contents) 2520 | 2521 | 2522 | #### Early hook 2523 | 2524 | Set hooks before DT_INIT_ARRAY ( [source](https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_soinfo.cpp;l=386;drc=android-8.0.0_r1?q=call_constructor&ss=android%2Fplatform%2Fsuperproject) ) 2525 | 2526 | ```js 2527 | let base; 2528 | let do_dlopen = null; 2529 | let call_ctor = null; 2530 | const target_lib_name = 'targetlib.so'; 2531 | 2532 | Process.findModuleByName('linker64').enumerateSymbols().forEach(sym => { 2533 | if (sym.name.indexOf('do_dlopen') >= 0) { 2534 | do_dlopen = sym.address; 2535 | } else if (sym.name.indexOf('call_constructor') >= 0) { 2536 | call_ctor = sym.address; 2537 | } 2538 | }) 2539 | 2540 | Interceptor.attach(do_dlopen, function (args) { 2541 | if (args[0].readUtf8String().indexOf(target_lib_name) >= 0) { 2542 | Interceptor.attach(call_ctor, function () { 2543 | const module = Process.findModuleByName(target_lib_name); 2544 | base = module.base; 2545 | console.log('loading', target_lib_name, '- base @', base); 2546 | 2547 | // DoStuff 2548 | }) 2549 | } 2550 | }) 2551 | ``` 2552 | 2553 | 2554 | Credit: [iGio90](https://github.com/iGio90) 2555 | 2556 | 2557 |
2558 | Output example 2559 | TODO 2560 |
2561 | 2562 |
[⬆ Back to top](#table-of-contents) 2563 | 2564 | 2565 | 2566 | 2567 | 2568 | #### Device properties 2569 | Example of quick&dirty iOS device properties extraction 2570 | 2571 | ```js 2572 | var UIDevice = ObjC.classes.UIDevice.currentDevice(); 2573 | UIDevice.$ownMethods 2574 | .filter(function(method) { 2575 | return method.indexOf(':') == -1 /* filter out methods with parameters */ 2576 | && method.indexOf('+') == -1 /* filter out public methods */ 2577 | }) 2578 | .forEach(function(method) { 2579 | console.log(method, ':', UIDevice[method]()) 2580 | }) 2581 | console.log('executablePath =', ObjC.classes.NSBundle.mainBundle().executablePath().toString()); 2582 | ``` 2583 | 2584 | ```js 2585 | if (ObjC.available) { 2586 | var processInfo = ObjC.classes.NSProcessInfo.processInfo(); 2587 | var versionString = processInfo.operatingSystemVersionString().toString(); 2588 | // E.g. "Version 13.5 (Build 17F75)" 2589 | var ver = versionString.split(' '); 2590 | var version = ver[1]; 2591 | // E.g. 13.5 2592 | console.log("iOS version: " + version); 2593 | } 2594 | ``` 2595 | 2596 |
2597 | Output example 2598 | 2599 | ``` 2600 | - adjTrackingEnabled : true 2601 | - adjFbAttributionId : 2602 | - adjVendorId : 4AAAAAAA-CECC-4BBB-BDDD-DEEEEEEEED18 2603 | - adjDeviceType : iPhone 2604 | - adjDeviceName : iPhone8,2 2605 | - adjCreateUuid : dfaaaa2-ebbd-4ccc-addd-eaeeeeeeee7c 2606 | - adjIdForAdvertisers : 7AAAAA3A-4BBB-4CCC-BDDD-0EEEEEEEE8A6 2607 | - sbf_bannerGraphicsQuality : 100 2608 | - sbf_controlCenterGraphicsQuality : 100 2609 | - sbf_homeScreenFolderGraphicsQuality : 100 2610 | - sbf_searchTransitionGraphicsQuality : 100 2611 | - sbf_dashBoardPresentationGraphicsQuality : 100 2612 | - sbf_homeScreenBlurGraphicsQuality : 100 2613 | - userInterfaceIdiom : 0 2614 | - _supportsDeepColor : false 2615 | - name : iPhone 2616 | - _keyboardGraphicsQuality : 100 2617 | - isGeneratingDeviceOrientationNotifications : true 2618 | - orientation : 1 2619 | - _backlightLevel : 1 2620 | - isProximityMonitoringEnabled : false 2621 | - systemVersion : 11.1.1 2622 | - _graphicsQuality : 100 2623 | - beginGeneratingDeviceOrientationNotifications : undefined 2624 | - endGeneratingDeviceOrientationNotifications : undefined 2625 | - buildVersion : 15C222 2626 | - systemName : iOS 2627 | - _isSystemSoundEnabled : true 2628 | - _feedbackSupportLevel : 1 2629 | - model : iPhone 2630 | - _supportsForceTouch : true 2631 | - localizedModel : iPhone 2632 | - identifierForVendor : 4A7B44DB-AAAA-BBB-CCC-D8819581DDD 2633 | - isBatteryMonitoringEnabled : false 2634 | - batteryState : 0 2635 | - batteryLevel : -1 2636 | - proximityState : false 2637 | - isMultitaskingSupported : true 2638 | - playInputClick : undefined 2639 | - _softwareDimmingAlpha : 0 2640 | - _playInputSelectSound : undefined 2641 | - _playInputDeleteSound : undefined 2642 | - _hasGraphicsQualityOverride : false 2643 | - _hasTouchPad : false 2644 | - _clearGraphicsQualityOverride : undefined 2645 | - _predictionGraphicsQuality : 100 2646 | - _nativeScreenGamut : 0 2647 | - _tapticEngine : <_UITapticEngine: 0x1c06257c0> 2648 | ``` 2649 | 2650 |
2651 | 2652 |
[⬆ Back to top](#table-of-contents) 2653 | 2654 | 2655 | #### Take screenshot 2656 | 2657 | ```js 2658 | function screenshot() { 2659 | ObjC.schedule(ObjC.mainQueue, function() { 2660 | var getNativeFunction = function (ex, retVal, args) { 2661 | return new NativeFunction(Module.findExportByName('UIKit', ex), retVal, args); 2662 | }; 2663 | var api = { 2664 | UIWindow: ObjC.classes.UIWindow, 2665 | UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']), 2666 | UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']), 2667 | UIGraphicsEndImageContext: getNativeFunction('UIGraphicsEndImageContext', 'void', []), 2668 | UIGraphicsGetImageFromCurrentImageContext: getNativeFunction('UIGraphicsGetImageFromCurrentImageContext', 'pointer', []), 2669 | UIImagePNGRepresentation: getNativeFunction('UIImagePNGRepresentation', 'pointer', ['pointer']) 2670 | }; 2671 | var view = api.UIWindow.keyWindow(); 2672 | var bounds = view.bounds(); 2673 | var size = bounds[1]; 2674 | api.UIGraphicsBeginImageContextWithOptions(size, 0, 0); 2675 | view.drawViewHierarchyInRect_afterScreenUpdates_(bounds, true); 2676 | 2677 | var image = api.UIGraphicsGetImageFromCurrentImageContext(); 2678 | api.UIGraphicsEndImageContext(); 2679 | 2680 | var png = new ObjC.Object(api.UIImagePNGRepresentation(image)); 2681 | send('screenshot', Memory.readByteArray(png.bytes(), png.length())); 2682 | }); 2683 | } 2684 | 2685 | rpc.exports = { 2686 | takescreenshot: screenshot 2687 | } 2688 | ``` 2689 | 2690 | ```py 2691 | ... 2692 | def save_screenshot(d): 2693 | f = open('/tmp/screenshot.png', 'wb') 2694 | f.write(d) 2695 | f.close() 2696 | 2697 | def on_message(msg, data): 2698 | save_screenshot(data) 2699 | 2700 | script.exports.takescreenshot() 2701 | 2702 | # open screenshot & invoke rpc via input 2703 | # will take screenshot, open it with eog & wait for export function name to invoke via input 2704 | def on_message(msg, data): 2705 | if 'payload' in msg: 2706 | if msg['payload'] == 'screenshot': 2707 | i = '/tmp/screenshot.png' 2708 | f = open(i, 'wb') 2709 | f.write(data) 2710 | f.close() 2711 | subprocess.call(['eog', i]) 2712 | 2713 | while True: 2714 | try: 2715 | time.sleep(1) 2716 | except KeyboardInterrupt: 2717 | script.exports.takescreenshot() 2718 | try: 2719 | getattr(script.exports, input())() 2720 | except (KeyboardInterrupt, frida.core.RPCException) as e: 2721 | print('[!]', e) 2722 | 2723 | ``` 2724 | 2725 |
2726 | Output example 2727 | TODO 2728 |
2729 | 2730 |
[⬆ Back to top](#table-of-contents) 2731 | 2732 | 2733 | #### Log SSH Commands 2734 | 2735 | ```js 2736 | Interceptor.attach(ObjC.classes.NMSSHChannel['- execute:error:timeout:'].implementation, { 2737 | onEnter: function(args) { 2738 | this.cmd = ObjC.Object(args[2]).toString(); 2739 | this.timeout = args[4]; 2740 | }, 2741 | onLeave: function(retv) { 2742 | console.log(`CMD: ${ObjC.Object(args[2]).toString()} Timeout: ${args[4]} Ret: ${retv}`); 2743 | } 2744 | }); 2745 | ``` 2746 | 2747 |
2748 | Output example 2749 | TODO 2750 |
2751 | 2752 |
[⬆ Back to top](#table-of-contents) 2753 | 2754 | #### TODOs 2755 | - Add GIFs & examples 2756 | - Add links to /scripts 2757 | - Extend universal SSL unpinning for [ios](https://codeshare.frida.re/@dki/ios10-ssl-bypass/) [andoid 1](https://github.com/Fuzion24/JustTrustMe/blob/master/app/src/main/java/just/trust/me/Main.java) [android 2758 | -------------------------------------------------------------------------------- /gif/README.md: -------------------------------------------------------------------------------- 1 | . 2 | -------------------------------------------------------------------------------- /gif/intercept_open_chrome_android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iddoeldor/frida-snippets/773f89ce8fcd32b0c2e843d148436d6556e064e9/gif/intercept_open_chrome_android.gif -------------------------------------------------------------------------------- /scripts/FridaCodeGenerator.py: -------------------------------------------------------------------------------- 1 | #?shortcut=Mod1+Shift+Z 2 | #!/usr/bin/env python3 3 | # -*- coding: utf-8 -*- 4 | 5 | from com.pnfsoftware.jeb.client.api import IScript 6 | from com.pnfsoftware.jeb.core import RuntimeProjectUtil 7 | from com.pnfsoftware.jeb.core.units.code.android import IDexUnit 8 | from subprocess import Popen, PIPE 9 | 10 | 11 | def arg_format(i): 12 | return 'arg_%d' % i 13 | 14 | 15 | def generate_body_code(types, retval, method_name, orig_method_name, class_name): 16 | body_code = "\n\tconsole.log('[{}#{}] ' + JSON.strigify({{\n\t".format( 17 | FridaCodeGenerator.to_canonical_name(class_name), method_name) 18 | for i, typ in enumerate(types): 19 | body_code += '\t{}: {}, // {}\n\t'.format('a%d' % i, arg_format(i), typ) 20 | 21 | if retval != 'void': 22 | body_code = '\n\tvar retval = this.{}.apply(this, arguments);{}\tretv: retval\n\t}});'.format( 23 | orig_method_name, body_code) 24 | else: 25 | body_code += '}});\n\tthis.{}.apply(this, arguments);'.format(method_name) 26 | 27 | return body_code + '\n' 28 | 29 | 30 | class JavaMethod(object): 31 | def __init__(self): 32 | self.class_name = None 33 | self.class_orig_name = None 34 | self.name = None 35 | self.orig_name = None 36 | self.arg = [] 37 | self.retType = None 38 | 39 | def get_parameters(self): 40 | return self.arg 41 | 42 | def get_return_type(self): 43 | return self.retType 44 | 45 | def get_name(self): 46 | return self.name 47 | 48 | def get_orig_name(self): 49 | return self.orig_name 50 | 51 | def get_class_orig_name(self): 52 | return self.class_orig_name 53 | 54 | def get_class_name(self): 55 | return self.class_name 56 | 57 | def __str__(self): 58 | return 'JavaMethod[name: %s, orig_name: %s, args: %s, return type: %s]' % ( 59 | self.name, self.orig_name, self.arg, self.retType) 60 | 61 | 62 | class FridaCodeGenerator(IScript): 63 | 64 | @staticmethod 65 | def to_canonical_name(mname): 66 | mname = mname.replace('/', '.') 67 | return { 68 | 'C': 'char', 69 | 'I': 'int', 70 | 'B': 'byte', 71 | 'Z': 'boolean', 72 | 'F': 'float', 73 | 'D': 'double', 74 | 'S': 'short', 75 | 'J': 'long', 76 | 'V': 'void', 77 | 'L': mname[1:-1], 78 | '[': mname 79 | }[mname[0]] 80 | 81 | def run(self, ctx): 82 | project = ctx.getEnginesContext().getProjects()[0] # Get current project(IRuntimeProject) 83 | self.dexunit = RuntimeProjectUtil.findUnitsByType(project, IDexUnit, False)[0] # Get dex context, needs >=V2.2.1 84 | try: 85 | self.current_unit = ctx.getFocusedView().getActiveFragment().getUnit() # Get current Source Tab in Focus 86 | java_class = self.current_unit.getClassElement().getName() 87 | current_addr = ctx.getFocusedView().getActiveFragment().getActiveAddress() 88 | m = FridaCodeGenerator.get_decompiled_method(self.dexunit, current_addr, java_class) 89 | method_name = m.get_name() 90 | class_name = FridaCodeGenerator.to_canonical_name(m.get_class_orig_name()) 91 | return_type = FridaCodeGenerator.to_canonical_name(str(m.get_return_type())) 92 | if method_name == '': 93 | raise Exception('Class initializer') 94 | args_code = ', '.join([arg_format(i) for i in range(len(m.get_parameters()))]) 95 | 96 | if method_name == '': method_name = '$init' 97 | 98 | types = [FridaCodeGenerator.to_canonical_name(param) for param in m.get_parameters()] 99 | # TODO get original type class names 100 | type_code = ', '.join(["'{0}'".format(t) for t in types]) 101 | body_code = generate_body_code(types, return_type, method_name, m.get_orig_name(), m.get_class_name()) 102 | hook = "Java.use('{class_name}').{method}.overload({sig}).implementation = function({args}) {{{body}}}".format( 103 | class_name=class_name, 104 | method=m.get_orig_name() if method_name != '$init' else method_name, 105 | sig=type_code, 106 | args=args_code, 107 | body=body_code 108 | ) 109 | print(hook) 110 | # copy to system's clipboard 111 | Popen(['xclip', '-sel', 'c', '-i'], stdin=PIPE).communicate(input=(hook.encode())) 112 | except Exception as e: 113 | print(e) 114 | ctx.displayMessageBox(None, 'Place the cursor in the function you want to generate the Frida code', None, None) 115 | 116 | @staticmethod 117 | def get_decompiled_method(dex, addr, class_orig_name): 118 | method_info = JavaMethod() 119 | method_info.orig_name = dex.getMethod(addr).getName(False) 120 | msig = addr.split('+')[0] 121 | infos = str(msig).split('->') 122 | if len(infos) == 2: 123 | method_info.class_name = infos[0] 124 | method_info.class_orig_name = class_orig_name 125 | if len(infos[1].split('(')) == 2: 126 | method_info.name = infos[1].split('(')[0] 127 | if len(infos[1].split(')')) == 2: 128 | method_info.retType = infos[1].split(')')[1] 129 | if len(infos[1].split('(')) == 2 and len(infos[1].split(')')) == 2: 130 | args = infos[1].split('(')[-1].split(')')[0] 131 | while args: 132 | if args[0] in ['C', 'I', 'B', 'Z', 'F', 'D', 'S', 'J', 'V']: 133 | method_info.arg.append(str(args[0])) 134 | args = args[1:] 135 | elif args[0] == '[': 136 | if args[1] == 'L': 137 | offset = args.find(';') 138 | method_info.arg.append(str(args[0:offset + 1])) 139 | args = args[offset + 1:] 140 | else: 141 | method_info.arg.append(str(args[0:2])) 142 | args = args[2:] 143 | elif args[0] == 'L': 144 | offset = args.find(";") 145 | method_info.arg.append(str(args[0:offset + 1])) 146 | args = args[offset + 1:] 147 | print(method_info) 148 | return method_info 149 | -------------------------------------------------------------------------------- /scripts/WIP_android_ipc.js: -------------------------------------------------------------------------------- 1 | var ContextWrapper = Java.use("android.content.ContextWrapper"); 2 | 3 | ContextWrapper.sendBroadcast.overload("android.content.Intent").implementation = function(intent) { 4 | send(JSON.stringify({ 5 | _intent: intent.toString(), 6 | extras: intent.getExtras() ? intent.getExtras().toString() : 'null', 7 | flags: intent.getFlags().toString() 8 | })); 9 | return this.sendBroadcast.overload("android.content.Intent").apply(this, arguments); 10 | } 11 | 12 | ContextWrapper.sendBroadcast.overload("android.content.Intent", "java.lang.String").implementation = function(intent, receiverPermission) { 13 | send(JSON.stringify({ 14 | 15 | }); 16 | return this.sendBroadcast.overload("android.content.Intent", "java.lang.String").apply(this, arguments); 17 | } 18 | 19 | 20 | ContextWrapper.sendStickyBroadcast.overload("android.content.Intent").implementation = function(intent) { 21 | 22 | return this.sendStickyBroadcast.overload("android.content.Intent").apply(this, arguments); 23 | } 24 | 25 | ContextWrapper.startActivity.overload("android.content.Intent").implementation = function(intent) { 26 | 27 | return this.startActivity.overload("android.content.Intent").apply(this, arguments); 28 | } 29 | -------------------------------------------------------------------------------- /scripts/WIP_dump_dynamically_created_files.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Work in progress 3 | // TBD how to show diff.. use git or just git style 4 | const fs = require('fs'); 5 | const frida = require('frida'); 6 | 7 | const APP_ID = process.argv[2]; 8 | 9 | const source = ` 10 | Java.perform(function() { 11 | 12 | var openedfile = ""; 13 | var data = { 14 | "file": "", 15 | "content": [] 16 | }; 17 | var isOpen = false; 18 | var index = 0; 19 | 20 | var fos = Java.use('java.io.FileOutputStream'); 21 | 22 | var fos_construct_2 = fos.$init.overload('java.lang.String'); 23 | var fos_construct_3 = fos.$init.overload('java.io.File'); 24 | var fos_construct_4 = fos.$init.overload('java.lang.String', 'boolean'); 25 | var fos_construct_5 = fos.$init.overload('java.io.File', 'boolean'); 26 | 27 | var fos_write_1 = fos.write.overload('[B', 'int', 'int'); 28 | 29 | var fos_close = fos.close; 30 | 31 | function dump(data) { 32 | console.log("Got " + data["content"].length + " bytes!"); 33 | var tmp_name = openedfile.split("/"); 34 | tmp_name = tmp_name[tmp_name.length - 1]; 35 | data["file"] = tmp_name; 36 | send(data); 37 | data["content"] = []; 38 | index = 0; 39 | } 40 | 41 | fos_construct_2.implementation = function(file) { 42 | var filename = file; 43 | if (openedfile != filename) { 44 | openedfile = filename; 45 | console.log("File opened for write " + filename); 46 | isOpen = true; 47 | } 48 | return fos_construct_2.call(this, file); 49 | } 50 | 51 | fos_construct_3.implementation = function(file) { 52 | var filename = file.getAbsolutePath(); 53 | if (openedfile != filename) { 54 | openedfile = filename; 55 | console.log("File opened for write " + filename); 56 | isOpen = true; 57 | } 58 | return fos_construct_3.call(this, file); 59 | } 60 | 61 | fos_construct_4.implementation = function(file, true_false) { 62 | var filename = file; 63 | if (openedfile != filename) { 64 | openedfile = filename; 65 | console.log("File opened for write " + filename); 66 | isOpen = true; 67 | } 68 | return fos_construct_4.call(this, file, true_false); 69 | } 70 | 71 | fos_construct_5.implementation = function(file, true_false) { 72 | var filename = file.getAbsolutePath(); 73 | if (openedfile != filename) { 74 | openedfile = filename; 75 | console.log("File opened for write " + filename); 76 | isOpen = true; 77 | } 78 | return fos_construct_5.call(this, file, true_false); 79 | } 80 | 81 | fos_write_1.implementation = function(arr, offset, length) { 82 | var i = 0; 83 | for (i = offset; i < length; i = i + 1) { 84 | data["content"][index] = arr[i]; 85 | index = index + 1; 86 | } 87 | return fos_write_1.call(this, arr, offset, length); 88 | } 89 | 90 | fos_close.implementation = function() { 91 | dump(data); 92 | return fos_close.call(this); 93 | } 94 | 95 | }); 96 | `; 97 | 98 | function stop() { // cleanup, TODO add session.detach ? 99 | if (script !== null) { 100 | script.unload().then(() => { 101 | script = null; 102 | console.log('[!] Script unloaded'); 103 | }).catch(console.error); 104 | } 105 | } 106 | 107 | async function Main() { 108 | 109 | let device = await frida.getUsbDevice(); 110 | let pid = await device.spawn([APP_ID]); 111 | let session = await device.attach(pid); 112 | let script = await session.createScript(source); 113 | 114 | script.message.connect(msg => { 115 | if (msg['type'] === 'send') { 116 | let payload = msg['payload']; 117 | if (typeof payload === 'object') { 118 | console.log('[D]', payload['file'], '\n\n', payload['content']); 119 | } 120 | } else { 121 | console.error('[!]', msg, '\n', msg['stack']); 122 | } 123 | }); 124 | 125 | await script.load(); 126 | await device.resume(pid); 127 | 128 | process.stdin.resume(); // keep process running 129 | process.on('SIGTERM', stop); 130 | process.on('SIGINT', stop); 131 | console.log('...'); 132 | } 133 | 134 | Main().catch(console.error); 135 | -------------------------------------------------------------------------------- /scripts/WIP_ios_app_info.js: -------------------------------------------------------------------------------- 1 | function dictFromNSDictionary(nsDict) { 2 | var jsDict = {}; 3 | var keys = nsDict.allKeys(); 4 | var count = keys.count(); 5 | for (var i = 0; i < count; i++) { 6 | var key = keys.objectAtIndex_(i); 7 | var value = nsDict.objectForKey_(key); 8 | jsDict[key.toString()] = value.toString(); 9 | } 10 | return jsDict; 11 | } 12 | 13 | function arrayFromNSArray(nsArray) { 14 | var jsArray = []; 15 | var count = nsArray.count(); 16 | for (var i = 0; i < count; i++) { 17 | jsArray[i] = nsArray.objectAtIndex_(i).toString(); 18 | } 19 | return jsArray; 20 | } 21 | 22 | function infoDictionary() { 23 | if (ObjC.available && "NSBundle" in ObjC.classes) { 24 | var info = ObjC.classes.NSBundle.mainBundle().infoDictionary(); 25 | return dictFromNSDictionary(info); 26 | } 27 | return null; 28 | } 29 | 30 | function infoLookup(key) { 31 | if (ObjC.available && "NSBundle" in ObjC.classes) { 32 | var info = ObjC.classes.NSBundle.mainBundle().infoDictionary(); 33 | var value = info.objectForKey_(key); 34 | if (value === null) { 35 | return value; 36 | } else if (value.class().toString() === "__NSCFArray") { 37 | return arrayFromNSArray(value); 38 | } else if (value.class().toString() === "__NSCFDictionary") { 39 | return dictFromNSDictionary(value); 40 | } else { 41 | return value.toString(); 42 | } 43 | } 44 | return null; 45 | } 46 | 47 | console.warn(JSON.stringify({ 48 | name: infoLookup("CFBundleName"), 49 | bundleId: ObjC.classes.NSBundle.mainBundle().bundleIdentifier().toString(), 50 | version: infoLookup("CFBundleVersion"), 51 | path: { 52 | bundle: ObjC.classes.NSBundle.mainBundle().bundlePath().toString(), 53 | data: ObjC.classes.NSProcessInfo.processInfo().environment().objectForKey_("HOME").toString(), 54 | binary: ObjC.classes.NSBundle.mainBundle().executablePath().toString() 55 | }, 56 | info: infoDictionary() 57 | }, null, 2)) 58 | -------------------------------------------------------------------------------- /scripts/WIP_unpack_64.js: -------------------------------------------------------------------------------- 1 | var art_DexFile_OpenMemory = Module.findExportByName('libart.so','_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_'); // art::DexFile::OpenMemory > 64bit version 2 | console.log(art_DexFile_OpenMemory); 3 | Interceptor.attach(art_DexFile_OpenMemory, { 4 | onEnter: function (_args) { 5 | var begin = this.context.x0; 6 | this.o = {}; 7 | this.o.begin = begin; 8 | this.o.magic = Memory.readUtf8String(begin); 9 | var address = parseInt(begin, 16) + 0x20; 10 | var dexSize = Memory.readInt(ptr(address)); 11 | this.o.dexSize = dexSize; 12 | var file = new File('/sdcard/unpack/' + dexSize + '.dex', 'wb'); 13 | file.write(Memory.readByteArray(begin, dexSize)); 14 | file.flush(); 15 | file.close(); 16 | }, 17 | onLeave: function (retval) { 18 | this.o.retval = retval; 19 | console.log(JSON.stringify(this.o, null, 2)); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /scripts/android_proxy.js: -------------------------------------------------------------------------------- 1 | // open proxy (not working) 2 | Java.perform(function() { 3 | Java.use('android.net.Proxy').setHttpProxySystemProperty(Java.use('android.net.ProxyInfo').buildDirectProxy('1.0.0.1', 8081)); 4 | }); 5 | -------------------------------------------------------------------------------- /scripts/check_for_native_calls.py: -------------------------------------------------------------------------------- 1 | # Check for native library calls and return a stacktrace 2 | import sys 3 | import frida 4 | from pprint import pprint 5 | 6 | 7 | def on_message(m, _data): 8 | if m['type'] == 'send': 9 | print(m['payload']) 10 | else: 11 | if m['type'] == 'error': 12 | pprint(m) 13 | exit(2) 14 | 15 | 16 | jscode = """ 17 | Java.perform(function() { 18 | 19 | var SystemDef = Java.use('java.lang.System'); 20 | 21 | var RuntimeDef = Java.use('java.lang.Runtime'); 22 | 23 | var exceptionClass = Java.use('java.lang.Exception'); 24 | 25 | var SystemLoad_1 = SystemDef.load.overload('java.lang.String'); 26 | 27 | var SystemLoad_2 = SystemDef.loadLibrary.overload('java.lang.String'); 28 | 29 | var RuntimeLoad_1 = RuntimeDef.load.overload('java.lang.String'); 30 | 31 | var RuntimeLoad_2 = RuntimeDef.loadLibrary.overload('java.lang.String'); 32 | 33 | var ThreadDef = Java.use('java.lang.Thread'); 34 | 35 | var ThreadObj = ThreadDef.$new(); 36 | 37 | SystemLoad_1.implementation = function(library) { 38 | send("[1] Loading dynamic library => " + library); 39 | stackTrace(); 40 | return SystemLoad_1.call(this, library); 41 | } 42 | 43 | SystemLoad_2.implementation = function(library) { 44 | send("[2] Loading dynamic library => " + library); 45 | stackTrace(); 46 | SystemLoad_2.call(this, library); 47 | return; 48 | } 49 | 50 | RuntimeLoad_1.implementation = function(library) { 51 | send("[3] Loading dynamic library => " + library); 52 | stackTrace(); 53 | RuntimeLoad_1.call(this, library); 54 | return; 55 | } 56 | 57 | RuntimeLoad_2.implementation = function(library) { 58 | send("[4] Loading dynamic library => " + library); 59 | stackTrace(); 60 | RuntimeLoad_2.call(this, library); 61 | return; 62 | } 63 | 64 | function stackTrace() { 65 | var stack = ThreadObj.currentThread().getStackTrace(); 66 | for (var i = 0; i < stack.length; i++) { 67 | send(i + " => " + stack[i].toString()); 68 | } 69 | send("--------------------------------------------------------------------------"); 70 | } 71 | 72 | }); 73 | """ 74 | APP = 'com.app' 75 | device = frida.get_usb_device() 76 | pid = device.spawn([APP]) 77 | session = device.attach(pid) 78 | script = session.create_script(jscode) 79 | print("[*] Intercepting [{}]".format(pid)) 80 | script.on('message', on_message) 81 | script.load() 82 | device.resume(APP) 83 | sys.stdin.read() 84 | -------------------------------------------------------------------------------- /scripts/dump_dynamically_created_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import frida 4 | 5 | APP_NAME = 'com.package.name' 6 | 7 | 8 | def on_message(message, _ignored_data): 9 | if message['type'] == 'send': 10 | if type(message['payload']) == dict: 11 | os.makedirs(os.path.dirname('./dump/{}/'.format(APP_NAME)), exist_ok=True) # create sub folder if not exist 12 | with open('./dump/{}/{}'.format(APP_NAME, message['payload']['file']), 'w') as d: 13 | for element in message['payload']['content']: 14 | d.write(chr(element % 256)) 15 | d.close() 16 | print('[*] Successfully dumped to {0}'.format(message['payload']['file'])) 17 | else: 18 | print('[*] {0}'.format(message['payload'].encode('utf-8'))) 19 | else: 20 | print(message) 21 | 22 | 23 | js_code = """ 24 | Java.perform(function() { 25 | var openedfile = ""; 26 | var data = { 27 | "file": "", 28 | "content": [] 29 | }; 30 | var isOpen = false; 31 | var index = 0; 32 | 33 | var fos = Java.use('java.io.FileOutputStream'); 34 | 35 | var fos_construct_2 = fos.$init.overload('java.lang.String'); 36 | var fos_construct_3 = fos.$init.overload('java.io.File'); 37 | var fos_construct_4 = fos.$init.overload('java.lang.String', 'boolean'); 38 | var fos_construct_5 = fos.$init.overload('java.io.File', 'boolean'); 39 | 40 | var fos_write_1 = fos.write.overload('[B', 'int', 'int'); 41 | 42 | var fos_close = fos.close; 43 | 44 | function dump(data) { 45 | send("Got " + data["content"].length + " bytes!"); 46 | var tmp_name = openedfile.split("/"); 47 | tmp_name = tmp_name[tmp_name.length - 1]; 48 | data["file"] = tmp_name; 49 | send(data); 50 | data["content"] = []; 51 | index = 0; 52 | } 53 | 54 | fos_construct_2.implementation = function(file) { 55 | var filename = file; 56 | if (openedfile != filename) { 57 | openedfile = filename; 58 | send("File opened for write " + filename); 59 | isOpen = true; 60 | } 61 | return fos_construct_2.call(this, file); 62 | } 63 | 64 | fos_construct_3.implementation = function(file) { 65 | var filename = file.getAbsolutePath(); 66 | if (openedfile != filename) { 67 | openedfile = filename; 68 | send("File opened for write " + filename); 69 | isOpen = true; 70 | } 71 | return fos_construct_3.call(this, file); 72 | } 73 | 74 | fos_construct_4.implementation = function(file, true_false) { 75 | var filename = file; 76 | if (openedfile != filename) { 77 | openedfile = filename; 78 | send("File opened for write " + filename); 79 | isOpen = true; 80 | } 81 | return fos_construct_4.call(this, file, true_false); 82 | } 83 | 84 | fos_construct_5.implementation = function(file, true_false) { 85 | var filename = file.getAbsolutePath(); 86 | if (openedfile != filename) { 87 | openedfile = filename; 88 | send("File opened for write " + filename); 89 | isOpen = true; 90 | } 91 | return fos_construct_5.call(this, file, true_false); 92 | } 93 | 94 | fos_write_1.implementation = function(arr, offset, length) { 95 | var i = 0; 96 | for (i = offset; i < length; i = i + 1) { 97 | data["content"][index] = arr[i]; 98 | index = index + 1; 99 | } 100 | return fos_write_1.call(this, arr, offset, length); 101 | } 102 | 103 | fos_close.implementation = function() { 104 | dump(data); 105 | return fos_close.call(this); 106 | } 107 | 108 | }); 109 | """ 110 | 111 | device = frida.get_usb_device() 112 | pid = device.spawn([APP_NAME]) 113 | session = device.attach(pid) 114 | script = session.create_script(js_code) 115 | print("[*] Intercepting [{}]".format(pid)) 116 | script.on('message', on_message) 117 | script.load() 118 | device.resume(APP_NAME) 119 | sys.stdin.read() 120 | -------------------------------------------------------------------------------- /scripts/enable_remote_debugging.js: -------------------------------------------------------------------------------- 1 | /* 2 | Enable remote debugging of Android WebViews at Runtime using Frida 3 | run "adb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'" to get the current activity 4 | */ 5 | Java.perform(function() { 6 | Java.deoptimizeEverything(); 7 | var injected = false; 8 | Java.choose('com.app.SomeActivity', { 9 | 'onMatch': function(o) { 10 | var Runnable = Java.use('java.lang.Runnable'); 11 | var MyRunnable = Java.registerClass({ 12 | name: 'com.example.MyRunnable', 13 | implements: [Runnable], 14 | methods: { 15 | 'run': function() { 16 | Java.use('android.webkit.WebView').setWebContentsDebuggingEnabled(true); 17 | } 18 | } 19 | }); 20 | var runnable = MyRunnable.$new(); 21 | o.runOnUiThread(runnable); 22 | console.log('\nWebview debug enabled......'); 23 | 24 | }, 25 | 'onComplete': function() { 26 | console.log('completed'); 27 | } 28 | }) 29 | }); 30 | -------------------------------------------------------------------------------- /scripts/enumerateNativeMethods.js: -------------------------------------------------------------------------------- 1 | // $ frida -Uf com.app --no-pause -l scripts.js 2 | var fIntercepted = false; 3 | 4 | function revealNativeMethods() { 5 | if (fIntercepted === true) { 6 | return; 7 | } 8 | var jclassAddress2NameMap = {}; 9 | var androidRunTimeSharedLibrary = "libart.so"; // may change between devices 10 | Module.enumerateSymbolsSync(androidRunTimeSharedLibrary).forEach(function(symbol){ 11 | switch (symbol.name) { 12 | case "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib": 13 | /* 14 | $ c++filt "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib" 15 | art::JNI::RegisterNativeMethods(_JNIEnv*, _jclass*, JNINativeMethod const*, int, bool) 16 | */ 17 | var RegisterNativeMethodsPtr = symbol.address; 18 | console.log("RegisterNativeMethods is at " + RegisterNativeMethodsPtr); 19 | Interceptor.attach(RegisterNativeMethodsPtr, { 20 | onEnter: function(args) { 21 | var methodsPtr = ptr(args[2]); 22 | var methodCount = parseInt(args[3]); 23 | for (var i = 0; i < methodCount; i++) { 24 | var pSize = Process.pointerSize; 25 | /* 26 | https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 27 | typedef struct { 28 | const char* name; 29 | const char* signature; 30 | void* fnPtr; 31 | } JNINativeMethod; 32 | */ 33 | var structSize = pSize * 3; // JNINativeMethod contains 3 pointers 34 | var namePtr = Memory.readPointer(methodsPtr.add(i * structSize)); 35 | var sigPtr = Memory.readPointer(methodsPtr.add(i * structSize + pSize)); 36 | var fnPtrPtr = Memory.readPointer(methodsPtr.add(i * structSize + (pSize * 2))); 37 | // output schema: className#methodName(arguments)returnVal@address 38 | console.log( 39 | // package & class, replacing forward slash with dot for convenience 40 | jclassAddress2NameMap[args[0]].replace(/\//g, '.') + 41 | '#' + Memory.readCString(namePtr) + // method 42 | Memory.readCString(sigPtr) + // signature (arguments & return type) 43 | '@' + fnPtrPtr // C side address 44 | ); 45 | } 46 | }, 47 | onLeave: function (ignoredReturnValue) {} 48 | }); 49 | break; 50 | case "_ZN3art3JNI9FindClassEP7_JNIEnvPKc": // art::JNI::FindClass 51 | Interceptor.attach(symbol.address, { 52 | onEnter: function(args) { 53 | if (args[1] != null) { 54 | jclassAddress2NameMap[args[0]] = Memory.readCString(args[1]); 55 | } 56 | }, 57 | onLeave: function (ignoredReturnValue) {} 58 | }); 59 | break; 60 | } 61 | }); 62 | fIntercepted = true; 63 | } 64 | 65 | Java.perform(revealNativeMethods); 66 | 67 | // TODO update 68 | // https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05j-Testing-Resiliency-Against-Reverse-Engineering.md 69 | -------------------------------------------------------------------------------- /scripts/exec_shell_cmd.py: -------------------------------------------------------------------------------- 1 | """ 2 | Execute shell command 3 | For example, list directory contents: 4 | def ls(folder): 5 | cmd = Shell(['/bin/sh', '-c', 'ls -la ' + folder], None) 6 | cmd.exec() 7 | for chunk in cmd.output: 8 | print(chunk.strip().decode()) 9 | """ 10 | import frida 11 | from frida_tools.application import Reactor 12 | import threading 13 | import click 14 | 15 | 16 | class Shell(object): 17 | def __init__(self, argv, env): 18 | self._stop_requested = threading.Event() 19 | self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait()) 20 | 21 | self._device = frida.get_usb_device() 22 | self._sessions = set() 23 | 24 | self._device.on("child-added", lambda child: self._reactor.schedule(lambda: self._on_child_added(child))) 25 | self._device.on("child-removed", lambda child: self._reactor.schedule(lambda: self._on_child_removed(child))) 26 | self._device.on("output", lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))) 27 | 28 | self.argv = argv 29 | self.env = env 30 | self.output = [] # stdout will pushed into array 31 | 32 | def exec(self): 33 | self._reactor.schedule(lambda: self._start()) 34 | self._reactor.run() 35 | 36 | def _start(self): 37 | click.secho("✔ spawn(argv={})".format(self.argv), fg='green', dim=True) 38 | pid = self._device.spawn(self.argv, env=self.env, stdio='pipe') 39 | self._instrument(pid) 40 | 41 | def _stop_if_idle(self): 42 | if len(self._sessions) == 0: 43 | self._stop_requested.set() 44 | 45 | def _instrument(self, pid): 46 | click.secho("✔ attach(pid={})".format(pid), fg='green', dim=True) 47 | session = self._device.attach(pid) 48 | session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason))) 49 | click.secho("✔ enable_child_gating()", fg='green', dim=True) 50 | session.enable_child_gating() 51 | # print("✔ resume(pid={})".format(pid)) 52 | self._device.resume(pid) 53 | self._sessions.add(session) 54 | 55 | def _on_child_added(self, child): 56 | click.secho("⚡ child_added: {}".format(child), fg='green', dim=True) 57 | self._instrument(child.pid) 58 | 59 | @staticmethod 60 | def _on_child_removed(child): 61 | click.secho("⚡ child_removed: {}".format(child), fg='green', dim=True) 62 | 63 | def _on_output(self, pid, fd, data): 64 | # print("⚡ output: pid={}, fd={}, data={}".format(pid, fd, repr(data))) 65 | # fd=0 (input) fd=1(stdout) fd=2(stderr) 66 | if fd != 2: 67 | self.output.append(data) 68 | 69 | def _on_detached(self, pid, session, reason): 70 | click.secho("⚡ detached: pid={}, reason='{}'".format(pid, reason), fg='green', dim=True) 71 | self._sessions.remove(session) 72 | self._reactor.schedule(self._stop_if_idle, delay=0.5) 73 | 74 | @staticmethod 75 | def _on_message(pid, message): 76 | click.secho("⚡ message: pid={}, payload={}".format(pid, message), fg='green', dim=True) 77 | -------------------------------------------------------------------------------- /scripts/extact_ipa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Extracting IPA from Jailbroken +Frida device 3 | # The IPA will be @ /tmp/ios_ssh/iphonessh/python-client/frida-ios-dump/AppName.ipa 4 | mkdir /tmp/ios_ssh 5 | cd "$_" 6 | sudo apt-get install libgcrypt20-doc gnutls-doc gnutls-bin usbmuxd libimobiledevice* 7 | git clone https://github.com/rcg4u/iphonessh 8 | cd iphonessh/python-client/ 9 | chmod +x * 10 | python2.7 tcprelay.py -t 22:2222 & 11 | TCP_RELAY_PID=$! 12 | git clone https://github.com/AloneMonkey/frida-ios-dump.git 13 | cd frida-ios-dump 14 | git checkout origin/3.x 15 | sudo -H pip3 install -r requirements.txt --upgrade 16 | sudo python3.6 dump.py $1 # com.app.bundle.id 17 | kill $TCP_RELAY_PID 18 | -------------------------------------------------------------------------------- /scripts/how_to_access_inner_class_static_field.md: -------------------------------------------------------------------------------- 1 | ### How to access inner class static field 2 | ``` 3 | package tech.yusi.fridademo; 4 | 5 | public class Jingdong { 6 | private int intResult; 7 | 8 | private final static class a { 9 | final static Jingdong a = new Jingdong(); 10 | } 11 | 12 | 13 | public Jingdong() { 14 | intResult = 0; 15 | } 16 | 17 | public static Jingdong a() { 18 | return a.a; 19 | } 20 | 21 | public static int a(int arg0, int arg1) { 22 | return arg0 + arg1; 23 | } 24 | 25 | 26 | public String a(String arg0, String arg1) { 27 | return arg0 + arg1; 28 | } 29 | } 30 | ``` 31 | 32 | ``` 33 | #!/usr/bin/env python3 34 | # -*- coding: utf-8 -*- 35 | 36 | import frida,sys 37 | 38 | rdev = frida.get_remote_device() 39 | session = rdev.attach("tech.yusi.fridademo") 40 | 41 | def on_message(message ,data): 42 | if message['type'] == 'send': 43 | print(message['payload']) 44 | elif message['type'] == 'error': 45 | print(message['stack']) 46 | else: 47 | print(message) 48 | 49 | jscode = """ 50 | send(Java.available); 51 | Java.perform(function () { 52 | var JingdongA = Java.use("tech.yusi.fridademo.Jingdong$a"); 53 | var Jingdong = JingdongA.a; 54 | send(Jingdong.fieldType); 55 | 56 | var JingdongInstance = Jingdong.value; 57 | var ret = JingdongInstance.a("G8", "4tar"); 58 | send(ret); 59 | 60 | }); 61 | """ 62 | 63 | script = session.create_script(jscode) 64 | script.on("message" , on_message) 65 | script.load() 66 | 67 | try: 68 | sys.stdin.read() 69 | except KeyboardInterrupt as e: 70 | session.detach() 71 | sys.exit(0) 72 | ``` 73 | 74 | -------------------------------------------------------------------------------- /scripts/install_frida_server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Download latest frida-server, extract, push & run on android device/emulator 3 | # adb 1.0.32, jq 1.5, xz 5.1, wget 1.17.1 4 | # sudo apt install wget jq xz 5 | 6 | # PARCH = phone architecture 7 | # if oneliner [[ == "armeabi-v7a" ]] is a dirty fix because frida's release for armeabi-v7a is just "arm" 8 | 9 | # TODO fix adb root which does not work on phones, only emulators, use `adb shell su` instead 10 | 11 | PARCH=`adb shell getprop ro.product.cpu.abi`;\ 12 | [[ "${PARCH}" == "armeabi-v7a" ]] && PARCH="arm";\ 13 | wget -q -O - https://api.github.com/repos/frida/frida/releases \ 14 | | jq '.[0] | .assets[] | select(.browser_download_url | match("server(.*?)android-'${PARCH}'*\\.xz")).browser_download_url' \ 15 | | xargs wget -q --show-progress $1 \ 16 | && unxz frida-server* \ 17 | && adb root \ 18 | && adb push frida-server* /data/local/tmp/frida-server \ 19 | && adb shell "chmod 755 /data/local/tmp/frida-server" \ 20 | && adb shell "/data/local/tmp/frida-server &" 21 | -------------------------------------------------------------------------------- /scripts/ios.md: -------------------------------------------------------------------------------- 1 | 2 | On the iDevice the file `/System/Library/Backup/Domains.plist` determines what files to backup. 3 | 4 | There is a differentiation between "domains" and relative files. 5 | 6 | For [More Info](https://www.theiphonewiki.com/wiki/ITunes_Backup). 7 | 8 | From `Domains.plist` : 9 | ``` 10 | ... 11 | RelativePathsToBackupAndRestore = ( 12 | "Demo.mov", 13 | "Library/com.apple.itunesstored", 14 | "Library/AddressBook", 15 | "Library/Accounts # Twitter account isn't backed up (or restored)", 16 | "Library/Application Support/Front Row # ATV: paths that need to be backed up on AppleTV", 17 | "Library/Application Support/com.apple.Home/Wallpapers # ", 18 | "Library/BackBoard # App Push notification settings don't seem to be backed up/restored", 19 | "Library/BulletinBoard # Backup /var/mobile/Library/BulletinBoard", 20 | "Library/Caches/com.apple.WebAppCache # Should back up offline application cache and databases for WebKit", 21 | "Library/Calendar", 22 | >> "Library/CallHistoryDB # Backup request for CallHistory.framework.", 23 | >> "Library/CallHistoryTransactions # Backup request for CallHistory.framework.", 24 | ... 25 | RootPath = "/var/mobile"; 26 | .. 27 | ... 28 | ``` 29 | 30 | List device daemons w/ `$ launchctl list` 31 | ``` 32 | PID Status Label 33 | 2696 0 com.apple.CoreAuthentication.daemon 34 | 3719 0 com.apple.cloudphotod 35 | 535 0 com.apple.homed 36 | 513 0 com.apple.dataaccess.dataaccessd 37 | - 0 com.apple.iapauthd 38 | 618 0 com.apple.cache_delete 39 | - 0 com.apple.BTServer.avrcp 40 | 518 0 com.apple.CallHistorySyncHelper 41 | 3568 0 UIKitApplication:com.apple.InCallService[0x287] 42 | 502 0 com.apple.icloud.findmydeviced 43 | 443 0 com.apple.telephonyutilities.callservicesd 44 | 549 0 com.apple.icloud.fmfd 45 | .... 46 | ``` 47 | 48 | launchctl manual 49 | ``` 50 | Usage: launchctl ... | help [subcommand] 51 | Many subcommands take a target specifier that refers to a domain or service within that domain. 52 | The available specifier forms are: 53 | 54 | system/[service-name] 55 | Targets the system-wide domain or service within. Root privileges are required to make modifications. 56 | 57 | user//[service-name] 58 | Targets the user domain or service within. 59 | A process running as the target user may make modifications. Root may modify any user's domain. 60 | User domains do not exist on iOS. 61 | 62 | gui//[service-name] 63 | Targets the GUI domain or service within. Each GUI domain is associated with a user domain, and a process running as 64 | the owner of that user domain may make modifications. 65 | Root may modify any GUI domain. GUI domains do not exist on iOS. 66 | 67 | session//[service-name] 68 | Targets a session domain or service within. A process running within the target security audit session may make 69 | modifications. Root may modify any session domain. 70 | 71 | pid//[service-name] 72 | Targets a process domain or service within. Only the process which owns the domain may modify it. 73 | Even root may not do so. 74 | 75 | When using a legacy subcommand which manipulates a domain, the target domain is assumed to be the system domain. 76 | On iOS, there is no support for per-user domains, even though there is a mobile user. 77 | 78 | Subcommands: 79 | .. 80 | debug Configures the next invocation of a service for debugging. 81 | kill Sends a signal to the service instance. 82 | blame Prints the reason a service is running. 83 | print Prints a description of a domain or service. 84 | print-cache Prints information about the service cache. 85 | print-disabled Prints which services are disabled. 86 | plist Prints a property list embedded in a binary (targets the Info.plist by default). 87 | procinfo Prints port information about a process. 88 | hostinfo Prints port information about the host. 89 | runstats Prints performance statistics for a service. 90 | examine Runs the specified analysis tool against launchd in a non-reentrant manner. 91 | config Modifies persistent configuration parameters for launchd domains. 92 | dumpstate Dumps launchd state to stdout. 93 | list Lists information about services. 94 | start Starts the specified service. 95 | .. 96 | or a given subcommand. 97 | ``` 98 | Output of proccess info for CallHistorySyncHelper 99 | `$ launchctl procinfo 549` 100 | 101 | Added the content of referenced files (com.apple.CallHistorySyncHelper.plist) 102 | 103 | ``` 104 | com.apple.CallHistorySyncHelper = { 105 | active count = 5 106 | path = /System/Library/LaunchDaemons/com.apple.CallHistorySyncHelper.plist 107 | state = running 108 | program = /System/Library/PrivateFrameworks/CallHistory.framework/Support/CallHistorySyncHelper 109 | arguments = { 110 | /System/Library/PrivateFrameworks/CallHistory.framework/Support/CallHistorySyncHelper 111 | } 112 | default environment = { 113 | PATH => /usr/bin:/bin:/usr/sbin:/sbin 114 | } 115 | environment = { 116 | XPC_SERVICE_NAME => com.apple.CallHistorySyncHelper 117 | } 118 | domain = com.apple.xpc.launchd.domain.system 119 | username = mobile 120 | minimum runtime = 10 121 | exit timeout = 5 122 | runs = 1 123 | successive crashes = 0 124 | excessive crashing = 0 125 | pid = 518 126 | immediate reason = ipc (mach) 127 | forks = 1 128 | execs = 1 129 | trampolined = 1 130 | started suspended = 0 131 | proxy started suspended = 0 132 | last exit code = (never exited) 133 | event triggers = { 134 | com.apple.callhistorysync.idslaunchnotification => { 135 | state = 0 136 | service = com.apple.CallHistorySyncHelper 137 | stream = com.apple.notifyd.matching 138 | descriptor = { 139 | "Notification" => "com.apple.callhistorysync.idslaunchnotification" 140 | } 141 | } 142 | } 143 | endpoints = { 144 | "com.apple.callhistory.pairedsync" = { 145 | port = 0x46907 146 | active = 1 147 | managed = 1 148 | reset = 0 149 | hide = 0 150 | } 151 | "com.apple.CallHistorySyncHelper" = { 152 | port = 0x46607 153 | active = 1 154 | managed = 1 155 | reset = 0 156 | hide = 0 157 | } 158 | "com.apple.CallHistorySyncHelper.aps" = { 159 | port = 0x4627b 160 | active = 1 161 | managed = 1 162 | reset = 0 163 | hide = 0 164 | } 165 | } 166 | dynamic endpoints = { 167 | } 168 | pid-local endpoints = { 169 | } 170 | instance-specific endpoints = { 171 | } 172 | event channels = { 173 | "com.apple.notifyd.matching" = { 174 | port = 0x46707 175 | active = 1 176 | managed = 1 177 | reset = 0 178 | hide = 0 179 | } 180 | } 181 | sockets = { 182 | } 183 | spawn type = adaptive 184 | jetsam priority = 3 185 | jetsam memory limit (active) = 6 MB 186 | jetsam memory limit (inactive) = 6 MB 187 | jetsamproperties category = daemon 188 | allowed to execute = 1 189 | submitted job. ignore execute allowed 190 | cpumon = default 191 | properties = { 192 | partial import = 0 193 | launchd bundle = 0 194 | xpc bundle = 0 195 | keepalive = 0 196 | runatload = 0 197 | dirty at shutdown = 0 198 | low priority i/o = 0 199 | low priority background i/o = 0 200 | exception handler = 0 201 | multiple instances = 0 202 | supports transactions = 1 203 | supports pressured exit = 1 204 | enter kdp before kill = 0 205 | wait for debugger = 0 206 | app = 0 207 | system app = 0 208 | inetd-compatible = 0 209 | inetd listener = 0 210 | abandon process group = 0 211 | one-shot = 0 212 | requires reap = 0 213 | event monitor = 0 214 | penalty box = 0 215 | pended non-demand spawn = 0 216 | role account = 0 217 | launch only once = 0 218 | system support = 0 219 | app-like = 0 220 | inferred program = 1 221 | ios home screen app = 0 222 | abandon coalition = 0 223 | extension = 0 224 | nano allocator = 0 225 | no initgroups = 0 226 | endpoints initialized = 1 227 | platform binary = 1 228 | disallow all lookups = 0 229 | } 230 | } 231 | program path = /System/Library/PrivateFrameworks/CallHistory.framework/Support/CallHistorySyncHelper 232 | Could not print Mach info for pid 518: 0x5 233 | bsd proc info = { 234 | pid = 518 235 | unique pid = 518 236 | ppid = 1 237 | pgid = 518 238 | status = stopped 239 | flags = 64-bit|session leader 240 | uid = 501 241 | svuid = 501 242 | ruid = 501 243 | gid = 501 244 | svgid = 501 245 | ruid = 501 246 | comm name = CallHistorySync 247 | long name = CallHistorySyncHelper 248 | controlling tty devnode = 0xffffffff 249 | controlling tty pgid = 0 250 | } 251 | pressured exit info = { 252 | dirty state tracked = 1 253 | dirty = 0 254 | pressured-exit capable = 1 255 | } 256 | jetsam priority = 0: idle 257 | jetsam memory limit = 6 258 | jetsam flags = (none) 259 | jetsam state = tracked,idle-exit 260 | entitlements = { 261 | "com.apple.private.ids.messaging" = ( 262 | "com.apple.private.alloy.callhistorysync"; 263 | ); 264 | "com.apple.developer.icloud-services" = ( 265 | "CloudKit"; 266 | ); 267 | "com.apple.application-identifier" = "CALLSYNCDB.com.apple.callhistory.sync-helper"; 268 | "com.apple.developer.icloud-container-environment" = "production"; 269 | "com.apple.private.aps-environment" = "production"; 270 | "application-identifier" = "CALLSYNCDB.com.apple.callhistory.sync-helper"; 271 | "aps-connection-initiate" = true; 272 | "com.apple.private.aps-connection-initiate" = true; 273 | "com.apple.private.ids.messaging.high-priority" = ( 274 | "com.apple.private.alloy.callhistorysync"; 275 | ); 276 | "com.apple.accounts.appleaccount.fullaccess" = true; 277 | "aps-environment" = "production"; 278 | "com.apple.private.tcc.allow" = ( 279 | "kTCCServiceLiverpool"; 280 | "kTCCServiceAddressBook"; 281 | ); 282 | }; 283 | code signing info = valid 284 | ad-hoc signed 285 | get-task-allow entitlement 286 | installer entitlement 287 | require enforcement 288 | allowed mach-o 289 | platform dyld 290 | entitlements validated 291 | platform binary 292 | ``` 293 | Content of /System/Library/LaunchDaemons/com.apple.CallHistorySyncHelper.plist 294 | ``` 295 | { 296 | EnablePressuredExit = 1; 297 | EnableTransactions = 1; 298 | Label = "com.apple.CallHistorySyncHelper"; 299 | LaunchEvents = { 300 | "com.apple.notifyd.matching" = { 301 | "com.apple.callhistorysync.idslaunchnotification" = { 302 | Notification = "com.apple.callhistorysync.idslaunchnotification"; 303 | }; 304 | }; 305 | }; 306 | MachServices = { 307 | "com.apple.CallHistorySyncHelper" = 1; 308 | "com.apple.CallHistorySyncHelper.aps" = 1; 309 | "com.apple.callhistory.pairedsync" = 1; 310 | }; 311 | POSIXSpawnType = Adaptive; 312 | ProgramArguments = ( 313 | "/System/Library/PrivateFrameworks/CallHistory.framework/Support/CallHistorySyncHelper" 314 | ); 315 | UserName = mobile; 316 | } 317 | ``` 318 | Info about `CallHistorySyncHelper` 319 | ``` 320 | $ ls -la /System/Library/PrivateFrameworks/CallHistory.framework/Support/CallHistorySyncHelper 321 | -rwxr-xr-x 1 root wheel 279392 Aug 29 2016 CallHistorySyncHelper 322 | 323 | $ file CallHistorySyncHelper 324 | Mach-O 64-bit 64-bit architecture=12 executable 325 | ``` 326 | 327 | Frida REPL w/ `$ frida -U 518` 328 | 329 | [Dump ios class hierarchy](https://github.com/iddoeldor/frida-snippets#dump-ios-class-hierarchy) 330 | ``` 331 | [iOS Device::PID::518]-> tree 332 | { 333 | "NSObject": { 334 | "CHLogger": { 335 | "ApplyLocalTransactions": {}, 336 | "CHPairedSyncCoordinator": {}, 337 | "CHPushConnectionDelegate": {}, 338 | "MergeTransactions": {}, 339 | "SignalHandler": {}, 340 | "SyncXPCServer": {} 341 | }, 342 | "CHSynchronizedLoggable": { 343 | "AutoSync": {}, 344 | "CHIDSPeerDevice": {}, 345 | "CHIDSServiceDelegate": {}, 346 | "CloudKit": {}, 347 | "SyncEngine": {}, 348 | "TransactionLog": {} 349 | }, 350 | "PBCodable": { 351 | "CHRecentCallPb": {}, 352 | "TransactionsPb": {} 353 | } 354 | } 355 | } 356 | ``` 357 | 358 | Tried to print `ObjC.classes.AutoSync` and the daemon shut down 359 | ``` 360 | PID Status Label 361 | - -43 com.apple.CallHistorySyncHelper 362 | ``` 363 | Get binary w/ Frida 364 | ``` 365 | cmd = Shell(['/bin/sh', '-c', "cat /System/Library/PrivateFrameworks/CallHistory.framework/Support/CallHistorySyncHelper"], None) 366 | cmd.exec()/push 367 | with open('~/CallHistorySyncHelper', 'wb+') as f: 368 | f.writelines(cmd.output) 369 | ``` 370 | -------------------------------------------------------------------------------- /scripts/ios_ssl_unpin.js: -------------------------------------------------------------------------------- 1 | var SecTrustEvaluate_prt = Module.findExportByName("Security", "SecTrustEvaluate"); 2 | var SecTrustEvaluate = new NativeFunction(SecTrustEvaluate_prt, "int", ["pointer", "pointer"]); 3 | Interceptor.replace(SecTrustEvaluate_prt, new NativeCallback(function(trust, result) { 4 | console.log("[*] SecTrustEvaluate(...) hit!"); 5 | SecTrustEvaluate(trust, result); // call original method 6 | Memory.writeU8(result, 1); 7 | return 0; 8 | }, "int", ["pointer", "pointer"])); 9 | -------------------------------------------------------------------------------- /scripts/log_string_builders_and_string_compare.js: -------------------------------------------------------------------------------- 1 | Java.perform(function() { 2 | // string compare 3 | var str = Java.use('java.lang.String'), objectClass = 'java.lang.Object'; 4 | str.equals.overload(objectClass).implementation = function(obj) { 5 | var response = str.equals.overload(objectClass).call(this, obj); 6 | if (obj) { 7 | if (obj.toString().length > 5) { 8 | send(str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response); 9 | } 10 | } 11 | return response; 12 | } 13 | // log AbstractStringBuilder.toString() 14 | ['java.lang.StringBuilder', 'java.lang.StringBuffer'].forEach(function(clazz, i) { 15 | console.log('[?] ' + i + ' = ' + clazz); 16 | var func = 'toString'; 17 | Java.use(clazz)[func].implementation = function() { 18 | var ret = this[func](); 19 | send('[' + i + '] ' + ret); 20 | return ret; 21 | }; 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /scripts/objc_ssl_unppining_helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * By http://github.com/LotemBY * 3 | 4 | This is a frida script for unpinning and reversing of ObjC applications. 5 | Intercept method's which match regex. 6 | 7 | You may change the following regex arrays to match your needs: 8 | */ 9 | 10 | // The list of regexs for the moudle name 11 | var moduleKeyWords = [/.*/]; // (It is not recommended to search all the moudles) 12 | 13 | // The list of regexs for the method name 14 | var methodKeyWords = [/cert/i, /trust/i, /ssl/i, /verify/i, /509/]; 15 | 16 | // The list of regexs for the method to override their return value with "1" 17 | var overrideKeyWords = []; 18 | 19 | /* 20 | To run this script with frida on iPhone, follow these steps: 21 | 1. Make sure the iPhone is jailbreaked 22 | 2. Download the frida server from Cydia (package: re.frida.server) 23 | 3. Connect the iPhone to your computer with USB and open the application 24 | 4. Type in console "frida-ps -U" to get the list of running proccess on the iPhone, and find the proccess name of your app 25 | 5. Type in console "frida -U -l " to run this script 26 | 6. Now you should use the app to trigger some of the intercepted methods 27 | */ 28 | var onCompleteCallback = function (retval) {}; 29 | setImmediate(function () { 30 | if (!ObjC.available) { 31 | console.log("[-] Objective-C Runtime is not available!"); 32 | return; 33 | } 34 | 35 | console.log("=======================================================\n"); 36 | console.log("[*] Searching methods..."); 37 | 38 | var moduleUsed = false; 39 | 40 | Process.enumerateModules({ 41 | onMatch: function(module) { 42 | 43 | if (!matchesRegex(moduleKeyWords, module.name)) { 44 | return; 45 | } 46 | 47 | moduleUsed = false; 48 | Module.enumerateSymbols(module.name, { 49 | onMatch: function(exp) { 50 | if (matchesRegex(methodKeyWords, exp.name)) { 51 | if (!moduleUsed) { 52 | console.log("[*] In module \"" + module.name + "\""); 53 | moduleUsed = true; 54 | } 55 | console.log("\t[*] Matching method: \"" + exp.name + "\", Address: " + Module.findExportByName(module.name, exp.name)); 56 | 57 | if (intercept(module.name, exp.name)) { 58 | console.log("\t\t[+] Now intercepting " + exp.name); 59 | } else { 60 | console.log("\t\t[-] Could not intercept " + exp.name); 61 | } 62 | } 63 | }, 64 | onComplete: onCompleteCallback 65 | }); 66 | }, 67 | onComplete: onCompleteCallback 68 | }); 69 | 70 | console.log("[*] Completed!"); 71 | console.log("=======================================================\n\n"); 72 | }); 73 | 74 | // Return if 'str' match any of the regexs in the array 'regexList' 75 | function matchesRegex(regexList, str) { 76 | regexList.forEach(function(el) { 77 | if (str.search(el) != -1) 78 | return true; 79 | }); 80 | return false; 81 | } 82 | 83 | // Try to intercept a method by moudle name and function name. 84 | // Return 'true' on success and 'false' on failor. 85 | function intercept(module, func) { 86 | try { 87 | Interceptor.attach(Module.findExportByName(module, func), { 88 | onEnter: function(args) { 89 | console.log("[*] Method CALL:\t\"" + func + "\" called!"); 90 | }, 91 | onLeave: function (retval) { 92 | console.log("[*] Method RETURN:\t\"" + func + "\" (return value: " + retval + ")"); 93 | 94 | if (matchesRegex(overrideKeyWords, func)) { 95 | console.log("[!] CHANGED RETURN VALUE of method:\t\"" + func + "\" (new value: " + 1 + ")"); 96 | retval.replace(1); 97 | } 98 | } 99 | }); 100 | 101 | return true; 102 | } catch (err) { 103 | return false; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /scripts/print_native_method_arguments.py: -------------------------------------------------------------------------------- 1 | def on_message(m, _data): 2 | if m['type'] == 'send': 3 | print(m['payload']) 4 | elif m['type'] == 'error': 5 | print(m) 6 | 7 | 8 | def switch(argument_key, idx): 9 | """ 10 | c/c++ variable type to javascript reader switch implementation 11 | # TODO handle other arguments, [long, longlong..] 12 | :param argument_key: variable type 13 | :param idx: index in symbols array 14 | :return: javascript to read the type of variable 15 | """ 16 | argument_key = argument_key.replace(' ', '') 17 | return '%d: %s' % (idx, { 18 | 'int': 'args[%d].toInt32(),', 19 | 'unsignedint': 'args[%d].toInt32(),', 20 | 'std::string': 'Memory.readUtf8String(Memory.readPointer(args[%d])),', 21 | 'bool': 'Boolean(args[%d]),' 22 | }[argument_key] % idx) 23 | 24 | 25 | def list_symbols_from_object_files(module_id): 26 | import subprocess 27 | return subprocess.getoutput('nm --demangle --dynamic %s' % module_id) 28 | 29 | 30 | def parse_nm_output(nm_stdout, symbols): 31 | for line in nm_stdout.splitlines(): 32 | split = line.split() 33 | open_parenthesis_idx = line.find('(') 34 | raw_arguments = [] if open_parenthesis_idx == -1 else line[open_parenthesis_idx + 1:-1] 35 | if len(raw_arguments) > 0: # ignore methods without arguments 36 | raw_argument_list = raw_arguments.split(',') 37 | symbols.append({ 38 | 'address': split[0], 39 | 'type': split[1], # @see Symbol Type Table 40 | 'name': split[2][:split[2].find('(')], # method name 41 | 'args': raw_argument_list 42 | }) 43 | 44 | 45 | def get_js_script(method, module_id): 46 | js_script = """ 47 | var moduleName = "{{moduleName}}", nativeFuncAddr = {{methodAddress}}; 48 | Interceptor.attach(Module.findExportByName(null, "dlopen"), { 49 | onEnter: function(args) { 50 | this.lib = Memory.readUtf8String(args[0]); 51 | console.log("[*] dlopen called with: " + this.lib); 52 | }, 53 | onLeave: function(retval) { 54 | if (this.lib.endsWith(moduleName)) { 55 | Interceptor.attach(Module.findBaseAddress(moduleName).add(nativeFuncAddr), { 56 | onEnter: function(args) { 57 | console.log("[*] hook invoked", JSON.stringify({{arguments}}, null, '\t')); 58 | } 59 | }); 60 | } 61 | } 62 | }); 63 | """ 64 | replace_map = { 65 | '{{moduleName}}': module_id, 66 | '{{methodAddress}}': '0x' + method['address'], 67 | '{{arguments}}': '{' + ''.join([switch(method['args'][i], i + 1) for i in range(len(method['args']))]) + '}' 68 | } 69 | for k, v in replace_map.items(): 70 | js_script = js_script.replace(k, v) 71 | print('[+] JS Script:\n', js_script) 72 | return js_script 73 | 74 | 75 | def main(app_id, module_id, method): 76 | """ 77 | $ python3.x+ script.py --method SomeClass::someMethod --app com.company.app --module libfoo.so 78 | :param app_id: application identifier / bundle id 79 | :param module_id: shared object identifier / known suffix, will iterate loaded modules (@see dlopen) 80 | :param method: method/symbol name 81 | :return: hook native method and print arguments when invoked 82 | """ 83 | # TODO extract all app's modules via `adb shell -c 'ls -lR /data/app/' + app_if + '*' | grep "\.so"` 84 | 85 | nm_stdout = list_symbols_from_object_files(module_id) 86 | 87 | symbols = [] 88 | parse_nm_output(nm_stdout, symbols) 89 | 90 | selection_idx = None 91 | for idx, symbol in enumerate(symbols): 92 | if method is None: # if --method flag is not passed 93 | print("%4d) %s (%d)" % (idx, symbol['name'], len(symbol['args']))) 94 | elif method == symbol['name']: 95 | selection_idx = idx 96 | break 97 | if selection_idx is None: 98 | if method is None: 99 | selection_idx = input("Enter symbol number: ") 100 | else: 101 | print('[+] Method not found, remove method flag to get list of methods to select from, `nm` stdout:') 102 | print(nm_stdout) 103 | exit(2) 104 | 105 | method = symbols[int(selection_idx)] 106 | print('[+] Selected method: %s' % method['name']) 107 | print('[+] Method arguments: %s' % method['args']) 108 | 109 | from frida import get_usb_device 110 | device = get_usb_device() 111 | pid = device.spawn([app_id]) 112 | session = device.attach(pid) 113 | script = session.create_script(get_js_script(method, module_id)) 114 | script.on('message', on_message) 115 | script.load() 116 | device.resume(app_id) 117 | # keep hook alive 118 | from sys import stdin 119 | stdin.read() 120 | 121 | 122 | if __name__ == '__main__': 123 | from argparse import ArgumentParser 124 | parser = ArgumentParser() 125 | parser.add_argument('--app', help='app identifier "com.company.app"') 126 | parser.add_argument('--module', help='loaded module name "libfoo.2.so"') 127 | parser.add_argument('--method', help='method name "SomeClass::someMethod", if empty it will print select-list') 128 | args = parser.parse_args() 129 | main(args.app, args.module, args.method) 130 | 131 | 132 | """ 133 | Symbol Type Table: 134 | "A" The symbol's value is absolute, and will not be changed by further linking. 135 | "B" The symbol is in the uninitialized data section (known as BSS). 136 | "C" The symbol is common. Common symbols are uninitialized data. 137 | When linking, multiple common symbols may appear with the same name. 138 | If the symbol is defined anywhere, the common symbols are treated as undefined references. 139 | "D" The symbol is in the initialized data section. 140 | "G" The symbol is in an initialized data section for small objects. 141 | Some object file formats permit more efficient access to small data objects, such as a global int variable as 142 | opposed to a large global array. 143 | "I" The symbol is an indirect reference to another symbol. 144 | This is a GNU extension to the a.out object file format which is rarely used. 145 | "N" The symbol is a debugging symbol. 146 | "R" The symbol is in a read only data section. 147 | "S" The symbol is in an uninitialized data section for small objects. 148 | "T" The symbol is in the text (code) section. 149 | "U" The symbol is undefined. 150 | "V" The symbol is a weak object. When a weak defined symbol is linked with a normal defined symbol, 151 | the normal defined symbol is used with no error. 152 | When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes 153 | zero with no error. 154 | "W" The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. 155 | When a weak defined symbol is linked with a normal defined symbol, 156 | the normal defined symbol is used with no error. 157 | When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined 158 | in a system-specific manner without error. 159 | On some systems, uppercase indicates that a default value has been specified. 160 | "-" The symbol is a stabs symbol in an a.out object file. 161 | In this case, the next values printed are the stabs other field, the stabs desc field, and the stab type. 162 | Stabs symbols are used to hold debugging information. 163 | "?" The symbol type is unknown, or object file format specific. 164 | """ 165 | -------------------------------------------------------------------------------- /scripts/stalker.js: -------------------------------------------------------------------------------- 1 | Interceptor.attach(ObjC.classes.MyClass['- myMethod:param1'].implementation, { 2 | onEnter: function (args) { 3 | console.warn(JSON.stringify({ 4 | fname: args[1].readCString(), 5 | text: new ObjC.Object(args[2]).toString(), 6 | backtrace: Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).map(m => m.moduleName+'!'+m.name), 7 | ctx: this.context 8 | }, null, 2)); 9 | var tid = Process.getCurrentThreadId(); 10 | this.tid = tid; 11 | Stalker.follow(tid, { 12 | events: { 13 | call: true 14 | }, 15 | /* 16 | onCallSummary: function (summary) { 17 | Object.keys(summary).forEach(s => { 18 | var sym = DebugSymbol.fromAddress(ptr(s)); 19 | if (sym.moduleName == 'Viber') 20 | console.log(summary[s], sym.name); 21 | }) 22 | } 23 | */ 24 | transform: function (iterator) { 25 | var instruction; 26 | while ((instruction = iterator.next()) !== null) { 27 | iterator.keep(); 28 | if (instruction.mnemonic.startsWith('bl')) { 29 | try { 30 | console.log('#' + tid + ':' + DebugSymbol.fromAddress(ptr(instruction.operands[0].value))); 31 | } catch (e) { 32 | // ignoring branch&link to register 33 | } 34 | } 35 | } 36 | } 37 | }); 38 | }, 39 | onLeave: function (retval) { 40 | Stalker.unfollow(this.tid); 41 | Stalker.garbageCollect(); 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /scripts/trace_class.js: -------------------------------------------------------------------------------- 1 | // $ frida -Uf com.whatsapp --no-pause -l wa.js 2 | /* 3 | #!/bin/bash 4 | for fgbg in 38 48 ; do # Foreground / Background 5 | for color in {0..255} ; do # Colors 6 | # Display the color 7 | printf "\e[${fgbg};5;%sm %3s \e[0m" $color $color 8 | # Display 6 colors per lines 9 | if [ $((($color + 1) % 6)) == 4 ] ; then 10 | echo # New line 11 | fi 12 | done 13 | echo # New line 14 | done 15 | */ 16 | var Color = { 17 | RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", Green: "2;01", Purple: "5;01", Red: "1;01", Yellow: "3;01", 18 | Light: { 19 | Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", Green: "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11" 20 | } 21 | }; 22 | 23 | /** 24 | * 25 | * @param input. 26 | * If an object is passed it will print as json 27 | * @param kwargs options map { 28 | * -l level: string; log/warn/error 29 | * -i indent: boolean; print JSON prettify 30 | * -c color: @see ColorMap 31 | * } 32 | */ 33 | var LOG = function (input, kwargs) { 34 | kwargs = kwargs || {}; 35 | var logLevel = kwargs['l'] || 'log', colorPrefix = '\x1b[3', colorSuffix = 'm'; 36 | if (typeof input === 'object') 37 | input = JSON.stringify(input, null, kwargs['i'] ? 2 : null); 38 | if (kwargs['c']) 39 | input = colorPrefix + kwargs['c'] + colorSuffix + input + Color.RESET; 40 | console[logLevel](input); 41 | }; 42 | 43 | var printBacktrace = function () { 44 | Java.perform(function() { 45 | var android_util_Log = Java.use('android.util.Log'), java_lang_Exception = Java.use('java.lang.Exception'); 46 | // getting stacktrace by throwing an exception 47 | LOG(android_util_Log.getStackTraceString(java_lang_Exception.$new()), { c: Color.Gray }); 48 | }); 49 | }; 50 | 51 | function traceClass(targetClass) { 52 | var hook; 53 | try { 54 | hook = Java.use(targetClass); 55 | } catch (e) { 56 | console.error("trace class failed", e); 57 | return; 58 | } 59 | 60 | var methods = hook.class.getDeclaredMethods(); 61 | hook.$dispose(); 62 | 63 | var parsedMethods = []; 64 | methods.forEach(function (method) { 65 | var methodStr = method.toString(); 66 | var methodReplace = methodStr.replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]; 67 | parsedMethods.push(methodReplace); 68 | }); 69 | 70 | uniqBy(parsedMethods, JSON.stringify).forEach(function (targetMethod) { 71 | traceMethod(targetClass + '.' + targetMethod); 72 | }); 73 | } 74 | 75 | function traceMethod(targetClassMethod) { 76 | try { 77 | var delim = targetClassMethod.lastIndexOf('.'); 78 | if (delim === -1) 79 | return; 80 | 81 | var targetClass = targetClassMethod.slice(0, delim); 82 | var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length); 83 | 84 | var hook = Java.use(targetClass); 85 | var overloadCount = hook[targetMethod].overloads.length; 86 | 87 | LOG({ tracing: targetClassMethod, overloaded: overloadCount }, { c: Color.Green }); 88 | 89 | for (var i = 0; i < overloadCount; i++) { 90 | hook[targetMethod].overloads[i].implementation = function () { 91 | var log = { '#': targetClassMethod, args: [] }; 92 | 93 | for (var j = 0; j < arguments.length; j++) { 94 | var arg = arguments[j]; 95 | // quick&dirty fix for java.io.StringWriter char[].toString() impl because frida prints [object Object] 96 | if (j === 0 && arguments[j]) { 97 | if (arguments[j].toString() === '[object Object]') { 98 | var s = []; 99 | for (var k = 0, l = arguments[j].length; k < l; k++) { 100 | s.push(arguments[j][k]); 101 | } 102 | arg = s.join(''); 103 | } 104 | } 105 | log.args.push({ i: j, o: arg, s: arg ? arg.toString(): 'null'}); 106 | } 107 | 108 | var retval; 109 | try { 110 | retval = this[targetMethod].apply(this, arguments); // might crash (Frida bug?) 111 | log.returns = { val: retval, str: retval ? retval.toString() : null }; 112 | } catch (e) { 113 | console.error(e); 114 | } 115 | LOG(log, { c: Color.Blue }); 116 | return retval; 117 | } 118 | } 119 | } catch(error) { 120 | LOG({ tracing: targetClassMethod, "overloaded": 0}, { c: Color.Red }); 121 | } 122 | } 123 | 124 | // remove duplicates from array 125 | function uniqBy(array, key) { 126 | var seen = {}; 127 | return array.filter(function (item) { 128 | var k = key(item); 129 | return seen.hasOwnProperty(k) ? false : (seen[k] = true); 130 | }); 131 | } 132 | 133 | 134 | var Main = function() { 135 | Java.perform(function () { // avoid java.lang.ClassNotFoundException 136 | [ 137 | // "java.io.File", 138 | 'java.net.Socket' 139 | ].forEach(traceClass); 140 | 141 | Java.use('java.net.Socket').isConnected.overload().implementation = function () { 142 | LOG('Socket.isConnected.overload', { c: Color.Light.Cyan }); 143 | printBacktrace(); 144 | return true; 145 | } 146 | }); 147 | }; 148 | 149 | Java.perform(Main); 150 | 151 | // setTimeout(function () { 152 | // LOG('\n\t1. WiFi ON\n\t2. Wait for "Restore backup"\n\t3. WiFi OFF\n\t4. Main()\n\t5. Click RESTORE\n', 153 | // { color: ColorMap.Black }); 154 | // }, 0); 155 | 156 | -------------------------------------------------------------------------------- /scripts/unity.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO test if no need to compile again the method 3 | 4 | mono_object_get_virtual_method (obj, method); 5 | 6 | we need to free the result from mono_string_to_utf8 () 7 | mono_free (p) 8 | 9 | 10 | MonoObject 11 | https://github.com/mono/mono/blob/master/samples/embed/test-invoke.c#L284 12 | */ 13 | /* 14 | . Button to install Frida on (rooted) Android device and start via ADB 15 | . Select app to hook 16 | . spawn app 17 | . if it not uses mono: 18 | alert: "we hook Unity3d/Mono/Xamarin, this app seems to not use it.. 19 | Xamarin is a Microsoft-owned software company founded in May 2011 by the engineers that created Mono, 20 | Mono for Android and MonoTouch, which are cross-platform implementations of the Common Language 21 | Infrastructure (CLI) and Common Language Specifications (often called Microsoft .NET). 22 | else: 23 | . hook dlopen, send Assembly-CSharp.dll to python side & save 24 | . extract methods descriptions (name, arguments, return value, full signature) (w/ frida or static tool?) 25 | . let user select one or many methods to hook 26 | . for each methods selected, open select box to select which arguments to print or save into CSV 27 | . add exit button to unload frida and de-attach 28 | 29 | * https://kivy.org 30 | */ 31 | 32 | // https://github.com/freehuntx/frida-mono-api/blob/master/src/mono-api-helper.js#L21 33 | // https://github.com/freehuntx/frida-mono-api/blob/master/src/mono-api-helper.js#L53 34 | // https://github.com/freehuntx/frida-mono-api/blob/master/src/mono-api-helper.js#L34 35 | // https://github.com/freehuntx/frida-mono-api/blob/master/src/mono-api-helper.js#L18 36 | 37 | // await until Mono is loaded 38 | var awaitForCondition = function(callback) { 39 | var int = setInterval(function() { 40 | if (Module.findExportByName(null, "mono_get_root_domain")) { 41 | clearInterval(int); 42 | callback(); 43 | return; 44 | } 45 | }, 0); 46 | } 47 | 48 | function cb(funcName) { 49 | return { 50 | onEnter: function(args) { 51 | this.extra = { 52 | funcName: funcName, 53 | arg0: args[0] 54 | }; 55 | }, 56 | onLeave: function(retval) { 57 | this.extra.retval = retval; 58 | console.log(JSON.stringify(this.extra, null, 2)); 59 | console.log( hexdump(retval, { offset: 0, length: 0x60, header: true, ansi: true }) ); 60 | } 61 | } 62 | } 63 | 64 | function hookMethod(dll, name_space, klass, method, num_params, extra) { 65 | // var monoImage = mono_image_loaded(dll); 66 | // monoImage will be the same as this.extra.image 67 | var monoClass = mono_class_from_name(extra.image, name_space, klass); 68 | var monoMethod = mono_class_get_method_from_name(monoClass, method, num_params); 69 | // = mono_class_get_method_from_name(monoClass, "lastRecivedGameId", -1); // mono_class_get_field 70 | var compiledMethod = mono_compile_method(monoMethod); 71 | 72 | Interceptor.attach(monoMethod, cb("monoMethod")); 73 | Interceptor.attach(compiledMethod, cb("compiledMethod")); 74 | 75 | Object.assign(extra, { 76 | //MonoImage: monoImage, 77 | MonoClass: monoClass, 78 | monoMethod: monoMethod, 79 | compiledMethod: compiledMethod 80 | }); 81 | console.log(JSON.stringify(extra, null, 2)); 82 | } 83 | 84 | function hook() { 85 | Interceptor.attach(Module.findExportByName(null, "mono_assembly_load_from_full"), { 86 | onEnter: function(args) { 87 | this.extra = { 88 | image: args[0], 89 | fname: Memory.readUtf8String(args[1]), 90 | status: args[2], 91 | refonly: args[3], 92 | }; 93 | }, 94 | onLeave: function(retval) { 95 | if (this.extra.fname.endsWith("Assembly-CSharp.dll")) { 96 | this.extra.retval = retval; 97 | hookMethod(this.extra.fname, "", "NetworkDriver", "AskForQuestion", -1, this.extra); 98 | } 99 | } 100 | }); 101 | /* 102 | Interceptor.attach(Module.findExportByName(null, "mono_class_from_name"), { 103 | onEnter: function(args) { 104 | this.extra = { 105 | name_space: Memory.readUtf8String(args[1]), 106 | name: Memory.readUtf8String(args[2]) 107 | }; 108 | }, 109 | onLeave: function(retval) { 110 | if (this.extra.name_space.indexOf("UnityEngine.UI") != -1) { 111 | console.log(JSON.stringify(this.extra, null, 2)); 112 | } 113 | } 114 | }); 115 | */ 116 | } 117 | 118 | /** 119 | * MonoImage* mono_image_loaded (const char *name) 120 | * http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-image.html 121 | */ 122 | var mono_image_loaded = function(name) { 123 | return new NativeFunction( 124 | Module.findExportByName(null, "mono_image_loaded"), // pointer to method 125 | 'pointer', // return type, MonoImage* 126 | ['pointer'] // arguments, char *name 127 | )( 128 | Memory.allocUtf8String(name) // allocating & passing parameter's address 129 | ) 130 | } 131 | 132 | /** 133 | * MonoClass* mono_class_from_name (MonoImage *image, const char* name_space, const char *name) 134 | * http://docs.go-mono.com/?link=api%3amono_class_from_name 135 | */ 136 | var mono_class_from_name = function(image, name_space, name) { 137 | return new NativeFunction( 138 | Module.findExportByName(null, "mono_class_from_name"), 139 | 'pointer', 140 | ['pointer', 'pointer', 'pointer'] 141 | )( image, Memory.allocUtf8String(name_space), Memory.allocUtf8String(name) ) 142 | } 143 | 144 | /** 145 | * MonoMethod* mono_class_get_method_from_name (MonoClass *klass, const char *name, int param_count) 146 | * klass where to look for the method 147 | * name name of the method 148 | * param_count number of parameters. -1 for any number. 149 | * http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-class.html 150 | */ 151 | var mono_class_get_method_from_name = function(klass, name, param_count) { 152 | return new NativeFunction( 153 | Module.findExportByName(null, "mono_class_get_method_from_name"), 154 | 'pointer', 155 | ['pointer', 'pointer', 'int'] 156 | )( klass, Memory.allocUtf8String(name), param_count ) 157 | } 158 | 159 | /** 160 | * gpointer mono_compile_method (MonoMethod *method) 161 | * http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-unsorted.html 162 | */ 163 | var mono_compile_method = function(method) { 164 | return new NativeFunction( 165 | Module.findExportByName(null, "mono_compile_method"), 166 | 'pointer', 167 | ['pointer'] 168 | )( method ) 169 | } 170 | 171 | //////////////// Main //////////////// 172 | Java.perform(awaitForCondition(hook)); 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | /* 209 | 1. Get image by name [call mono_image_loaded] 210 | 2. Get class by name [call mono_class_from_name](#http://docs.go-mono.com/?link=api%3amono_class_from_name) 211 | 3. Get method in class by name [call mono_class_get_method_from_name](#http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-class.html) 212 | 4. Compile method to get address [call mono_compile_method](#http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-unsorted.html) 213 | 5. Intercept compiled method 214 | */ 215 | function Main() { 216 | var awaitForCondition = function(callback) { 217 | var int = setInterval(function() { 218 | if (Module.findExportByName(null, "mono_get_root_domain")) { 219 | clearInterval(int); 220 | callback(); 221 | return; 222 | } 223 | }, 0); 224 | } 225 | 226 | function hook() { 227 | Interceptor.attach(Module.findExportByName(null, "mono_assembly_load_from_full"), { 228 | onEnter: function(args) { 229 | this._image = args[0]; 230 | this._fname = Memory.readUtf8String(args[1]); 231 | this._status = args[2]; 232 | this._refonly = args[3]; 233 | console.log('[E]', args[0], Memory.readUtf8String(args[1])); 234 | }, 235 | onLeave: function(retval) { 236 | if (this._fname.indexOf("Assembly-CSharp.dll") != -1) { 237 | console.log("mono_class_from_name", Module.findExportByName(null, "mono_class_from_name") ); 238 | Interceptor.attach(Module.findExportByName(null, "mono_class_from_name"), { 239 | onEnter: function(args) { 240 | var name_space = Memory.readUtf8String(args[1]).toString(); 241 | if ( 242 | !name_space.startsWith("System") && 243 | !name_space.startsWith("Unity") && 244 | !name_space.startsWith("Facebook") && 245 | !name_space.startsWith("Google") 246 | ) { 247 | console.log('[E2]', args[0], name_space, Memory.readUtf8String(args[2]) ); 248 | this._namespace = name_space; 249 | } 250 | else this._namespace = null; 251 | }, 252 | onLeave: function(retval) { 253 | if (this._namespace) console.log('[L2]', this._namespace, retval); 254 | } 255 | }); 256 | } 257 | } 258 | }); 259 | 260 | } 261 | awaitForCondition(hook); 262 | } 263 | Java.perform(Main); 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | // apk/assets/bin/Data/Managed$ for i in *.dll; do echo "[*] $i"; rabin2 -zzz $i | grep -i certificate; done 287 | 288 | Java.perform(function() { 289 | 290 | var awaitForCondition = function(callback) { 291 | var int = setInterval(function() { 292 | if (Module.findExportByName(null, "mono_get_root_domain")) { 293 | clearInterval(int); 294 | callback(); 295 | return; 296 | } 297 | }, 0); 298 | } 299 | 300 | function hookSet() { 301 | Interceptor.attach(Module.findExportByName(null, "mono_assembly_load_from_full"), { 302 | onEnter: function(args) { 303 | var name = Memory.readUtf8String(ptr(args[1])); 304 | console.log('[1]', name); 305 | var parts = name.split('/'); 306 | if (parts.length < 2) { 307 | parts = name.split(','); 308 | } 309 | var dllName = parts[parts.length - 1]; 310 | this.dllName = dllName; 311 | }, 312 | onLeave: function(retval) { 313 | if (this.dllName == 'Assembly-CSharp.dll') { 314 | console.log('[2]', retval, this.dllName); 315 | console.log('[3]', Module.enumerateSymbolsSync(this.dllName)); 316 | } 317 | } 318 | }); 319 | } 320 | awaitForCondition(hookSet); 321 | 322 | }); 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | function binary2hex2ascii(array, readBytesNum) { 331 | var result = []; 332 | // performance wise to read 100 bytes 333 | readBytesNum = readBytesNum || 100; 334 | for (var i = 0; i < readBytesNum; ++i) { 335 | // TODO fix unicode for Hebrew and Math related symbols 336 | // * (double) doesn't work, but + (plus) works 337 | result.push(String.fromCharCode( 338 | parseInt( 339 | ('0' + (array[i] & 0xFF).toString(16) ).slice(-2), // binary2hex part 340 | 16 341 | ) 342 | )); 343 | } 344 | // TODO extract facebookID from previous_winners packet, #OSINT ? 345 | return result.join(''); 346 | } 347 | 348 | function hookInputStream() { 349 | Java.use('java.io.InputStream').read.overload('[B').implementation = function(b) { 350 | var retval = this.read(b); 351 | var resp = binary2hex2ascii(b); 352 | // conditions to not print garbage packets 353 | if ( 354 | resp.indexOf('isBot') == -1 355 | && resp.indexOf(' Answer') == -1 356 | && resp.indexOf('Pinged') == -1 357 | ) { 358 | console.log( resp ); 359 | } 360 | if (resp.indexOf('Waiting To Show Question') != -1) { 361 | console.log("\n\n\t{{ " + binary2hex2ascii( b , 1200) + " }}\n\n"); 362 | } 363 | // TODO mimic answer packet (hook OutputStream), send to get back the answer 364 | return retval; 365 | }; 366 | } 367 | 368 | function hookOutputStream() { 369 | var bClass = Java.use("java.io.OutputStream"); 370 | bClass.write.overload('int').implementation = function(x) { 371 | console.log("[1] " + x); 372 | return this.write(x); 373 | } 374 | bClass.write.overload('[B').implementation = function(b) { 375 | console.log("[2] " + binary2hex2ascii(b) ); 376 | return this.write(b); 377 | } 378 | bClass.write.overload('[B','int','int').implementation = function(b,y,z) { 379 | console.log("[3] " + binary2hex2ascii(b)); 380 | return this.write(b,y,z); 381 | } 382 | } 383 | 384 | function hookConstructor() { 385 | var Map = Java.use('java.util.Map'); 386 | Java.use('com.unity3d.player.UnityWebRequest').$init 387 | .overload('long', 'java.lang.String', 'java.util.Map', 'java.lang.String', 'int').implementation = function(long1, str2, map3, str4, int5) { 388 | console.log(this, JSON.stringify({ 389 | '#1': long1, 390 | method: str2, 391 | headers: Java.cast(map3, Map).toString(), 392 | url: str4, 393 | '#5': int5 394 | }, null, 2)); 395 | this.$init(long1, str2, map3, str4, int5); 396 | }; 397 | } 398 | 399 | function hookUploadCallback() { 400 | Java.use('com.unity3d.player.UnityWebRequest').uploadCallback.overload('java.nio.ByteBuffer').implementation = function(buf1) { 401 | console.log('uploadCallback', buf1); 402 | this.uploadCallback(buf1); 403 | }; 404 | } 405 | 406 | 407 | function traceClass(targetClass) { 408 | var hook = Java.use(targetClass); 409 | var methods = hook.class.getDeclaredMethods(); 410 | hook.$dispose; 411 | 412 | var parsedMethods = []; 413 | methods.forEach(function(method) { 414 | parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]); 415 | }); 416 | 417 | var targets = uniqBy(parsedMethods, JSON.stringify); 418 | targets.forEach(function(targetMethod) { 419 | traceMethod(targetClass + "." + targetMethod); 420 | }); 421 | } 422 | 423 | function traceMethod(targetClassMethod) { 424 | var delim = targetClassMethod.lastIndexOf("."); 425 | if (delim === -1) return; 426 | 427 | var targetClass = targetClassMethod.slice(0, delim) 428 | var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length) 429 | var hook = Java.use(targetClass); 430 | var overloadCount = hook[targetMethod].overloads.length; 431 | console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]"); 432 | for (var i = 0; i < overloadCount; i++) { 433 | hook[targetMethod].overloads[i].implementation = function() { 434 | console.warn("\n*** entered " + targetClassMethod); 435 | 436 | // print backtrace 437 | // Java.perform(function() { 438 | // var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()); 439 | // console.log("\nBacktrace:\n" + bt); 440 | // }); 441 | 442 | // print args 443 | if (arguments.length) console.log(); 444 | for (var j = 0; j < arguments.length; j++) { 445 | console.log("arg[" + j + "]: " + arguments[j]); 446 | } 447 | 448 | // print retval 449 | var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?) 450 | console.log("\nretval: " + retval); 451 | console.warn("\n*** exiting " + targetClassMethod); 452 | return retval; 453 | } 454 | } 455 | } 456 | 457 | function uniqBy(array, key) { // remove duplicates from array 458 | var seen = {}; 459 | return array.filter(function(item) { 460 | var k = key(item); 461 | return seen.hasOwnProperty(k) ? false : (seen[k] = true); 462 | }); 463 | } 464 | 465 | function trace(pattern) 466 | { 467 | var type = (pattern.toString().indexOf("!") === -1) ? "java" : "module"; 468 | 469 | if (type === "module") { 470 | 471 | // trace Module 472 | var res = new ApiResolver("module"); 473 | var matches = res.enumerateMatchesSync(pattern); 474 | var targets = uniqBy(matches, JSON.stringify); 475 | targets.forEach(function(target) { 476 | traceModule(target.address, target.name); 477 | }); 478 | 479 | } else if (type === "java") { 480 | 481 | // trace Java Class 482 | var found = false; 483 | Java.enumerateLoadedClasses({ 484 | onMatch: function(aClass) { 485 | if (aClass.match(pattern)) { 486 | found = true; 487 | var className = aClass.match(/[L](.*);/)[1].replace(/\//g, "."); 488 | traceClass(className); 489 | } 490 | }, 491 | onComplete: function() {} 492 | }); 493 | 494 | // trace Java Method 495 | if (!found) { 496 | try { 497 | traceMethod(pattern); 498 | } 499 | catch(err) { // catch non existing classes/methods 500 | console.error(err); 501 | } 502 | } 503 | } 504 | } 505 | 506 | function traceModule(impl, name) 507 | { 508 | console.log("Tracing " + name); 509 | 510 | Interceptor.attach(impl, { 511 | 512 | onEnter: function(args) { 513 | 514 | // debug only the intended calls 515 | this.flag = false; 516 | // var filename = Memory.readCString(ptr(args[0])); 517 | // if (filename.indexOf("XYZ") === -1 && filename.indexOf("ZYX") === -1) // exclusion list 518 | // if (filename.indexOf("my.interesting.file") !== -1) // inclusion list 519 | this.flag = true; 520 | 521 | if (this.flag) { 522 | console.warn("\n*** entered " + name); 523 | 524 | // print backtrace 525 | console.log("\nBacktrace:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE) 526 | .map(DebugSymbol.fromAddress).join("\n")); 527 | } 528 | }, 529 | 530 | onLeave: function(retval) { 531 | 532 | if (this.flag) { 533 | // print retval 534 | console.log("\nretval: " + retval); 535 | console.warn("\n*** exiting " + name); 536 | } 537 | } 538 | 539 | }); 540 | } 541 | 542 | // Main 543 | Java.perform(function() { 544 | try { 545 | // hookInputStream(); 546 | // hookOutputStream(); 547 | // hookConstructor(); 548 | // hookUploadCallback(); 549 | // https://blogs.unity3d.com/2014/06/11/all-about-the-unity-networking-transport-layer/ 550 | // traceClass('com.unity3d.player.WWW'); 551 | // trace("exports:*!*send*"); // Tracing /system/lib/libnetutils.so!send_packet 552 | // trace("exports:*!*packet*"); 553 | /* 554 | Tracing /system/lib/libnetutils.so!send_packet 555 | Tracing /system/lib/libnetutils.so!receive_packet 556 | // but no logs 557 | */ 558 | // Interceptor.attach(Module.findExportByName('/system/lib/libnetutils.so', 'send_packet'), { 559 | // onEnter: function(args) { 560 | // console.log('send_packet', args[0]); 561 | // }, 562 | // onLeave: function(retval) { 563 | // } 564 | // }); 565 | } catch (e) { 566 | console.error(e); 567 | } 568 | }); 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | function binary2hex2ascii(array, readBytesNum) { 611 | var result = []; 612 | // performance wise to read 100 bytes 613 | readBytesNum = readBytesNum || 100; 614 | for (var i = 0; i < readBytesNum; ++i) { 615 | // TODO fix unicode for Hebrew and Math related symbols 616 | // * (double) doesn't work, but + (plus) works 617 | result.push(String.fromCharCode( 618 | parseInt( 619 | ('0' + (array[i] & 0xFF).toString(16) ).slice(-2), // binary2hex part 620 | 16 621 | ) 622 | )); 623 | } 624 | // TODO extract facebookID from previous_winners packet, #OSINT ? 625 | return result.join(''); 626 | } 627 | 628 | function hookInputStream() { 629 | Java.use('java.io.InputStream').read.overload('[B').implementation = function(b) { 630 | var retval = this.read(b); 631 | var resp = binary2hex2ascii(b); 632 | // conditions to not print garbage packets 633 | if ( 634 | resp.indexOf('isBot') == -1 635 | && resp.indexOf(' Answer') == -1 636 | && resp.indexOf('Pinged') == -1 637 | ) { 638 | console.log( resp ); 639 | } 640 | if (resp.indexOf('Waiting To Show Question') != -1) { 641 | console.log("\n\n\t{{ " + binary2hex2ascii( b , 1200) + " }}\n\n"); 642 | } 643 | // TODO mimic answer packet (hook OutputStream), send to get back the answer 644 | return retval; 645 | }; 646 | } 647 | 648 | function hookOutputStream() { 649 | var bClass = Java.use("java.io.OutputStream"); 650 | bClass.write.overload('int').implementation = function(x) { 651 | console.log("[1] " + x); 652 | return this.write(x); 653 | } 654 | bClass.write.overload('[B').implementation = function(b) { 655 | console.log("[2] " + binary2hex2ascii(b) ); 656 | return this.write(b); 657 | } 658 | bClass.write.overload('[B','int','int').implementation = function(b,y,z) { 659 | console.log("[3] " + binary2hex2ascii(b)); 660 | return this.write(b,y,z); 661 | } 662 | } 663 | 664 | function hookConstructor() { 665 | var Map = Java.use('java.util.Map'); 666 | Java.use('com.unity3d.player.UnityWebRequest').$init 667 | .overload('long', 'java.lang.String', 'java.util.Map', 'java.lang.String', 'int').implementation = function(long1, str2, map3, str4, int5) { 668 | console.log(this, JSON.stringify({ 669 | '#1': long1, 670 | method: str2, 671 | headers: Java.cast(map3, Map).toString(), 672 | url: str4, 673 | '#5': int5 674 | }, null, 2)); 675 | this.$init(long1, str2, map3, str4, int5); 676 | }; 677 | } 678 | 679 | function hookUploadCallback() { 680 | Java.use('com.unity3d.player.UnityWebRequest').uploadCallback.overload('java.nio.ByteBuffer').implementation = function(buf1) { 681 | console.log('uploadCallback', buf1); 682 | this.uploadCallback(buf1); 683 | }; 684 | } 685 | 686 | 687 | function traceClass(targetClass) { 688 | var hook = Java.use(targetClass); 689 | var methods = hook.class.getDeclaredMethods(); 690 | hook.$dispose; 691 | 692 | var parsedMethods = []; 693 | methods.forEach(function(method) { 694 | parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]); 695 | }); 696 | 697 | var targets = uniqBy(parsedMethods, JSON.stringify); 698 | targets.forEach(function(targetMethod) { 699 | traceMethod(targetClass + "." + targetMethod); 700 | }); 701 | } 702 | 703 | function traceMethod(targetClassMethod) { 704 | var delim = targetClassMethod.lastIndexOf("."); 705 | if (delim === -1) return; 706 | 707 | var targetClass = targetClassMethod.slice(0, delim) 708 | var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length) 709 | var hook = Java.use(targetClass); 710 | var overloadCount = hook[targetMethod].overloads.length; 711 | console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]"); 712 | for (var i = 0; i < overloadCount; i++) { 713 | hook[targetMethod].overloads[i].implementation = function() { 714 | console.warn("\n*** entered " + targetClassMethod); 715 | 716 | // print backtrace 717 | // Java.perform(function() { 718 | // var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()); 719 | // console.log("\nBacktrace:\n" + bt); 720 | // }); 721 | 722 | // print args 723 | if (arguments.length) console.log(); 724 | for (var j = 0; j < arguments.length; j++) { 725 | console.log("arg[" + j + "]: " + arguments[j]); 726 | } 727 | 728 | // print retval 729 | var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?) 730 | console.log("\nretval: " + retval); 731 | console.warn("\n*** exiting " + targetClassMethod); 732 | return retval; 733 | } 734 | } 735 | } 736 | 737 | function uniqBy(array, key) { // remove duplicates from array 738 | var seen = {}; 739 | return array.filter(function(item) { 740 | var k = key(item); 741 | return seen.hasOwnProperty(k) ? false : (seen[k] = true); 742 | }); 743 | } 744 | 745 | // Main 746 | Java.perform(function() { 747 | try { 748 | // hookInputStream(); 749 | // hookOutputStream(); 750 | // hookConstructor(); 751 | // hookUploadCallback(); 752 | // https://blogs.unity3d.com/2014/06/11/all-about-the-unity-networking-transport-layer/ 753 | traceClass('com.unity3d.player.WWW'); 754 | } catch (e) { 755 | console.error(e); 756 | } 757 | }); 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | function binary2hex2ascii(array, readBytesNum) { 794 | var result = []; 795 | // performance wise to read 100 bytes 796 | readBytesNum = readBytesNum || 100; 797 | for (var i = 0; i < readBytesNum; ++i) { 798 | // TODO fix unicode for Hebrew and Math related symbols 799 | // * (double) doesn't work, but + (plus) works 800 | result.push(String.fromCharCode( 801 | parseInt( 802 | ('0' + (array[i] & 0xFF).toString(16) ).slice(-2), // binary2hex part 803 | 16 804 | ) 805 | )); 806 | } 807 | // TODO extract facebookID from previous_winners packet, #OSINT ? 808 | return result.join(''); 809 | } 810 | 811 | function hookInputStream() { 812 | Java.use('java.io.InputStream').read.overload('[B').implementation = function(b) { 813 | var retval = this.read(b); 814 | var resp = binary2hex2ascii(b); 815 | // conditions to not print garbage packets 816 | if ( 817 | resp.indexOf('isBot') == -1 818 | && resp.indexOf(' Answer') == -1 819 | && resp.indexOf('Pinged') == -1 820 | ) { 821 | console.log( resp ); 822 | } 823 | if (resp.indexOf('Waiting To Show Question') != -1) { 824 | console.log("\n\n\t{{ " + binary2hex2ascii( b , 1200) + " }}\n\n"); 825 | } 826 | // TODO mimic answer packet (hook OutputStream), send to get back the answer 827 | return retval; 828 | }; 829 | } 830 | 831 | function hookOutputStream() { 832 | var bClass = Java.use("java.io.OutputStream"); 833 | bClass.write.overload('int').implementation = function(x) { 834 | console.log("[1] " + x); 835 | return this.write(x); 836 | } 837 | bClass.write.overload('[B').implementation = function(b) { 838 | console.log("[2] " + binary2hex2ascii(b) ); 839 | return this.write(b); 840 | } 841 | bClass.write.overload('[B','int','int').implementation = function(b,y,z) { 842 | console.log("[3] " + binary2hex2ascii(b)); 843 | return this.write(b,y,z); 844 | } 845 | } 846 | 847 | function hookConstructor() { 848 | var Map = Java.use('java.util.Map'); 849 | Java.use('com.unity3d.player.UnityWebRequest').$init 850 | .overload('long', 'java.lang.String', 'java.util.Map', 'java.lang.String', 'int').implementation = function(long1, str2, map3, str4, int5) { 851 | console.log(this, JSON.stringify({ 852 | '#1': long1, 853 | method: str2, 854 | headers: Java.cast(map3, Map).toString(), 855 | url: str4, 856 | '#5': int5 857 | }, null, 2)); 858 | this.$init(long1, str2, map3, str4, int5); 859 | }; 860 | } 861 | 862 | function hookUploadCallback() { 863 | Java.use('com.unity3d.player.UnityWebRequest').uploadCallback.overload('java.nio.ByteBuffer').implementation = function(buf1) { 864 | console.log('uploadCallback', buf1); 865 | this.uploadCallback(buf1); 866 | }; 867 | } 868 | 869 | // Main 870 | Java.perform(function() { 871 | 872 | // hookInputStream(); 873 | hookOutputStream(); 874 | // hookConstructor(); 875 | // hookUploadCallback(); 876 | 877 | }); 878 | /* 879 | ! not invoked ! 880 | var oClass = Java.use('java.io.OutputStreamWriter'); 881 | oClass.write.overload('java.lang.String', 'int', 'int').implementation = function(s, i2, i3) { 882 | console.log('[4]'); 883 | this.write(s, i2, i3); 884 | }; 885 | oClass.write.overload('[C', 'int', 'int').implementation = function(c, i2, i3) { 886 | console.log('[5]'); 887 | this.write(c, i2, i3); 888 | }; 889 | oClass.write.overload('int').implementation = function(i) { 890 | console.log('[6]'); 891 | this.write(i); 892 | }; 893 | */ 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | function binary2hex2ascii(array, readBytesNum) { 938 | var result = []; 939 | // performance wise to read 100 bytes 940 | readBytesNum = readBytesNum || 100; 941 | for (var i = 0; i < readBytesNum; ++i) { 942 | // TODO fix unicode for Hebrew and Math related symbols 943 | // * (double) doesn't work, but + (plus) works 944 | result.push(String.fromCharCode( 945 | parseInt( 946 | ('0' + (array[i] & 0xFF).toString(16) ).slice(-2), // binary2hex part 947 | 16 948 | ) 949 | )); 950 | } 951 | // TODO extract facebookID from previous_winners packet, #OSINT ? 952 | return result.join(''); 953 | } 954 | 955 | Java.perform(function() { 956 | 957 | Java.use('java.io.InputStream').read.overload('[B').implementation = function(b) { 958 | var retval = this.read(b); 959 | var resp = binary2hex2ascii(b); 960 | // conditions to not print garbage packets 961 | if ( 962 | resp.indexOf('isBot') == -1 963 | && resp.indexOf(' Answer') == -1 964 | && resp.indexOf('Pinged') == -1 965 | ) { 966 | console.log( resp ); 967 | } 968 | if (resp.indexOf('Waiting To Show Question') != -1) { 969 | console.log("\n\n\t{{ " + binary2hex2ascii( b , 1200) + " }}\n\n"); 970 | } 971 | // TODO mimic answer packet (hook OutputStream), send to get back the answer 972 | return retval; 973 | }; 974 | 975 | }); 976 | 977 | 978 | /* 979 | {"status":"Success","gameState":"Waiting To Show Question","questionIndex":0,"gameID":"5b575b2bd3bff 980 | ****************************** 981 | 982 | 983 | 984 | {"status":"Success","gameState":"Waiting To Show Question","questionIndex":0,"gameID":"5b575b2bd3bff500048c6e91","player1":{"_id":"5b5617b69e848a00044765df","name":"test","facebookID":"","botImage":null,"points":3490,"characterIndex":0,"accuracy":71.54500693523188,"crowns":7},"player2":{"_id":"5a1d866a700dd2000431659d","name":"Zion A","facebookID":"","botImage":"https://s3-eu-west-1.amazonaws.com/data.dbrain.co.il/lhjkaf61mc81/questions/gar/group2/Zion A.jpg","points":13910,"characterIndex":-1,"accuracy":75,"crowns":3007}} 985 | 986 | 987 | 988 | {"status":"Success","gameState":"Waiting For One Answer","lastQuestionIndex":-1} 989 | 990 | {"status":"Success","gameState":"Waiting For One Answer","lastQuestionIndex":-1} 991 | 992 | {"status":"Success","secondsSinceOtherUserPinged":0.184} 993 | 994 | {"status":"Success","gameState":"Waiting For One Answer","lastQuestionIndex":-1} 995 | OK 996 | 997 | 998 | {"status":"Success","gameState":"Waiting For One Answer","lastQuestionIndex":-1} 999 | 1000 | {"status":"Success","gameState":"Waiting To Show Question","lastQuestionIndex":0,"lastQuestion":{"qu 1001 | ****************************** 1002 | 1003 | 1004 | 1005 | {"status":"Success","secondsSinceOtherUserPinged":0.536} 1006 | 1007 | {"status":"Success","gameState":"Waiting To Show Question","lastQuestionIndex":0,"lastQuestion":{"questionID":"sport_0000000348","question":"כמה סוגי אתלטיקה שונים יש בטריאתלון?","answer":3,"answerFromPlayer1":3,"accuracyFromPlayer1":100,"answerFromPlayer2":3,"accuracyFromPlayer2":100,"answerForBotValue":3,"answerForBotTime":10,"secondsTookToAnswerPlayer1":7.131}} 1008 | 1009 | 1010 | {"status":"Success","gameState":"Waiting To Show Question","lastQuestionIndex":1,"lastQuestion":{"qu 1011 | ****************************** 1012 | 1013 | 1014 | 1015 | {"status":"Error: Trying to answer wrong question"} 1016 | 1017 | {"status":"Success","gameState":"Waiting To Show Question","lastQuestionIndex":1,"lastQuestion":{"questionID":"israeli_music_0000000015","question":"להקת רוק ישראלית: קרח _","answer":9,"answerFromPlayer1":9,"accuracyFromPlayer1":100,"answerFromPlayer2":9,"accuracyFromPlayer2":100,"answerForBotValue":9,"answerForBotTime":7,"secondsTookToAnswerPlayer1":5.98}} 1018 | 1019 | */ 1020 | /* 1021 | Java.use('java.nio.ByteBuffer').wrap.overload('[B').implementation = function(byteArr) { 1022 | console.log('*', byteArr.toString()); 1023 | console.log('**', Object.getOwnPropertyNames(byteArr.__proto__).join('\n\t')); 1024 | return this.wrap(byteArr); 1025 | }; 1026 | console.log(hexdump(Memory.readByteArray(ptr(buf1.$handle), 32), {offset: 0, length: 32, header: true, ansi: true})); 1027 | buf3: buf1.asCharBuffer().toString(), 1028 | np: Memory.readByteArray(ptr(buf1.$handle), 64), 1029 | retval: retval, 1030 | 1031 | var Map = Java.use('java.util.Map'); 1032 | var UnityWebRequest = Java.use('com.unity3d.player.UnityWebRequest'); 1033 | 1034 | UnityWebRequest.downloadCallback.overload('java.nio.ByteBuffer', 'int').implementation = function(buf1, int2) { 1035 | var retval = this.downloadCallback(buf1, int2); 1036 | console.log('downloadCallback', JSON.stringify({ 1037 | buf1: buf1.toString(), 1038 | int2: int2 1039 | }, null, ' ')); 1040 | return retval; 1041 | }; 1042 | 1043 | 1044 | var bClass = Java.use("java.io.OutputStream"); 1045 | bClass.write.overload('int').implementation = function(x) { 1046 | console.log("[1] " + x); 1047 | return this.write(x); 1048 | } 1049 | bClass.write.overload('[B').implementation = function(b) { 1050 | console.log("[2] " + hex2string( bytes2hex( b ) ) ); 1051 | return this.write(b); 1052 | } 1053 | bClass.write.overload('[B','int','int').implementation = function(b,y,z) { 1054 | console.log("[3] " + hex2string( bytes2hex( b ) ) + " | " + y + " | " + z); 1055 | return this.write(b,y,z); 1056 | } 1057 | var ReadableByteChannel = Java.use('java.nio.channels.ReadableByteChannel'); 1058 | ReadableByteChannel.read.overload('java.nio.ByteBuffer').implementation = function(b) { 1059 | console.log('arg1', b); 1060 | var retval = this.read(b); 1061 | console.log('retval', retval); 1062 | return retval; 1063 | }; 1064 | 1065 | 1066 | UnityWebRequest.headerCallback.overload('java.util.Map').implementation = function(map1) { 1067 | console.log('headerCallback', Java.cast(map1, Map).toString()); 1068 | this.headerCallback(map1); 1069 | }; 1070 | UnityWebRequest.headerCallback.overload('java.lang.String', 'java.lang.String').implementation = function(s1, s2) { 1071 | console.log('headerCallback', s1, s2); 1072 | this.headerCallback(s1, s2); 1073 | }; 1074 | UnityWebRequest.uploadCallback.overload('java.nio.ByteBuffer').implementation = function(buf1) { 1075 | console.log('uploadCallback', buf1); 1076 | this.uploadCallback(buf1); 1077 | }; 1078 | 1079 | Java.use('javax.net.ssl.HttpsURLConnection')['setSSLSocketFactory'].overload('javax.net.ssl.SSLSocketFactory').implementation = function(s) { 1080 | console.log('invoked!', s); 1081 | return this.setSSLSocketFactory(null); 1082 | }; 1083 | Java.use('javax.net.ssl.SSLContext')['init'] 1084 | .overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom') 1085 | .implementation = function(a1, a2, a3) { 1086 | console.log('invoked!', a1, a2, a3); 1087 | return this.init(null, null, null); 1088 | }; 1089 | */ 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | function bytes2hex(array) { 1114 | var result = ''; 1115 | // console.log('len = ' + array.length); 1116 | for (var i = 0; i < array.length; ++i) { 1117 | result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2); 1118 | } 1119 | return result; 1120 | } 1121 | 1122 | function hex2string(hex) { 1123 | var string = ''; 1124 | for (var i = 0; i < hex.length; i += 2) { 1125 | string += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 1126 | } 1127 | return string; 1128 | } 1129 | 1130 | Java.perform(function() { 1131 | // var Map = Java.use('java.util.Map'); 1132 | // var UnityWebRequest = Java.use('com.unity3d.player.UnityWebRequest'); 1133 | // 1134 | // UnityWebRequest.downloadCallback.overload('java.nio.ByteBuffer', 'int').implementation = function(buf1, int2) { 1135 | // var retval = this.downloadCallback(buf1, int2); 1136 | // console.log('downloadCallback', JSON.stringify({ 1137 | // buf1: buf1.toString(), 1138 | // int2: int2 1139 | // }, null, ' ')); 1140 | // return retval; 1141 | // }; 1142 | 1143 | var bClass = Java.use("java.io.OutputStream"); 1144 | bClass.write.overload('int').implementation = function(x) { 1145 | console.log("[1] " + x); 1146 | return this.write(x); 1147 | } 1148 | bClass.write.overload('[B').implementation = function(b) { 1149 | console.log("[2] " + hex2string( bytes2hex( b ) ) ); 1150 | return this.write(b); 1151 | } 1152 | bClass.write.overload('[B','int','int').implementation = function(b,y,z) { 1153 | console.log("[2] " + hex2string( bytes2hex( b ) ) + " | " + y + " | " + z); 1154 | return this.write(b,y,z); 1155 | } 1156 | 1157 | }); 1158 | 1159 | 1160 | 1161 | Java.perform(function() { 1162 | var Map = Java.use('java.util.Map'); 1163 | var UnityWebRequest = Java.use('com.unity3d.player.UnityWebRequest'); 1164 | console.log( Object.getOwnPropertyNames(Test.__proto__).join('\n') ); 1165 | 1166 | /* 1167 | UnityWebRequest.$init 1168 | .overload('long', 'java.lang.String', 'java.util.Map', 'java.lang.String', 'int').implementation = function(long1, str2, map3, str4, int5) { 1169 | console.log(this, JSON.stringify({ 1170 | '#1': long1, 1171 | method: str2, 1172 | headers: Java.cast(map3, Map).toString(), 1173 | url: str4, 1174 | '#5': int5 1175 | }, null, ' ')); 1176 | this.$init(long1, str2, map3, str4, int5); 1177 | }; 1178 | Java.use('com.unity3d.player.WWW').$init.overload('int', 'java.lang.String', '[B', 'java.util.Map').implementation = function(int1, str2, bytes3, map4) { 1179 | console.log(this, JSON.stringify({ 1180 | '#1': int1, 1181 | str2: str2, 1182 | bytes3: bytes3, 1183 | map4: Java.cast(map4, Map).toString() 1184 | }, null, ' ')); 1185 | }; 1186 | */ 1187 | UnityWebRequest.headerCallback.overload('java.util.Map').implementation = function(map1) { 1188 | console.log('headerCallback', Java.cast(map1, Map).toString()); 1189 | this.headerCallback(map1); 1190 | }; 1191 | var Str = Java.use('java.lang.String'); 1192 | // var decoder = Java.use('java.nio.charset.Charset').forName("UTF-8"); 1193 | UnityWebRequest.downloadCallback.overload('java.nio.ByteBuffer', 'int').implementation = function(byteBuffer1, int2) { 1194 | console.log('downloadCallback', JSON.stringify({ 1195 | byteBuffer1: byteBuffer1.toString(), 1196 | int2: int2 1197 | }, null, ' ')); 1198 | // Java.perform(function(){ console.log('--', Java.cast(byteBuffer1, Java.use('java.lang.String'))); }); 1199 | return this.downloadCallback(byteBuffer1, int2); 1200 | }; 1201 | }); 1202 | /* 1203 | Interceptor.attach(Module.findExportByName(null, 'dlopen'), { 1204 | onEnter: function(args) { 1205 | this.lib = Memory.readUtf8String(args[0]); 1206 | // console.log("dlopen called with: " + this.lib); 1207 | }, 1208 | onLeave: function(retval) { 1209 | if (this.lib.indexOf("epsi") != -1) { 1210 | console.log( 1211 | this.lib.substr(this.lib.lastIndexOf('/') + 1, this.lib.length) + 1212 | ' [ ' + retval + ' ] \n' + 1213 | Module.enumerateExportsSync(this.lib).map(function(x){return x.name}) 1214 | ); 1215 | } 1216 | } 1217 | }); 1218 | */ 1219 | --------------------------------------------------------------------------------