├── 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 |  & 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 | 
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 | 
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 |
--------------------------------------------------------------------------------