frida-fusion 0.1.13__tar.gz → 0.1.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of frida-fusion might be problematic. Click here for more details.
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/PKG-INFO +2 -2
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/README.md +1 -1
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/__meta__.py +2 -2
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/args.py +31 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/config.py +3 -0
- frida_fusion-0.1.15/frida_fusion/exceptions.py +19 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/fusion.py +56 -7
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/libs/helpers.js +54 -4
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/module.py +23 -5
- frida_fusion-0.1.15/frida_fusion/modules/reflection/reflection-stalker.js +293 -0
- frida_fusion-0.1.15/frida_fusion/modules/reflection/reflection-stalker.py +206 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion.egg-info/PKG-INFO +2 -2
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion.egg-info/SOURCES.txt +3 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/LICENSE +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/__init__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/__main__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/libs/__init__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/libs/color.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/libs/database.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/libs/logger.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/libs/scriptlocation.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/__init__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/android_setings/__init__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/android_setings/settings.js +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/android_setings/settings.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/crypto/__init__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/crypto/crypto.js +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/crypto/crypto.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/tls_unpinning/__init__.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion.egg-info/dependency_links.txt +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion.egg-info/entry_points.txt +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion.egg-info/requires.txt +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion.egg-info/top_level.txt +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/pyproject.toml +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/setup.cfg +0 -0
- {frida_fusion-0.1.13 → frida_fusion-0.1.15}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frida-fusion
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Hook your mobile tests with Frida
|
|
5
5
|
Author-email: "Helvio Junior (M4v3r1ck)" <helvio_junior@hotmail.com>
|
|
6
6
|
Maintainer-email: "Helvio Junior (M4v3r1ck)" <helvio_junior@hotmail.com>
|
|
@@ -105,7 +105,7 @@ void fusion_printMethods(String name);
|
|
|
105
105
|
void fusion_waitForClass(String name, CallbackFunction onReady)
|
|
106
106
|
|
|
107
107
|
# Conversions
|
|
108
|
-
|
|
108
|
+
String fusion_stringToBase64(String message);
|
|
109
109
|
String fusion_bytesToBase64(byte[] byteArray);
|
|
110
110
|
String fusion_encodeHex(byte[] byteArray);
|
|
111
111
|
```
|
|
@@ -70,7 +70,7 @@ void fusion_printMethods(String name);
|
|
|
70
70
|
void fusion_waitForClass(String name, CallbackFunction onReady)
|
|
71
71
|
|
|
72
72
|
# Conversions
|
|
73
|
-
|
|
73
|
+
String fusion_stringToBase64(String message);
|
|
74
74
|
String fusion_bytesToBase64(byte[] byteArray);
|
|
75
75
|
String fusion_encodeHex(byte[] byteArray);
|
|
76
76
|
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = '0.1.
|
|
1
|
+
__version__ = '0.1.15'
|
|
2
2
|
__title__ = "Frida Fusion"
|
|
3
3
|
__description__ = "📱 frida-fusion - runtime mobile exploration"
|
|
4
4
|
__url__ = "https://github.com/helviojunior/frida-fusion"
|
|
5
|
-
__build__ =
|
|
5
|
+
__build__ = 0xef43fce
|
|
6
6
|
__author__ = "Helvio Junior (M4v3r1ck)"
|
|
7
7
|
__author_email__ = "helvio_junior@hotmail.com"
|
|
8
8
|
__license__ = "GPL-3.0"
|
|
@@ -6,6 +6,8 @@ import sys
|
|
|
6
6
|
|
|
7
7
|
from .libs.color import Color
|
|
8
8
|
from .__meta__ import __description__
|
|
9
|
+
from .module import ModuleManager
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
class Arguments(object):
|
|
11
13
|
''' Holds arguments used by the Frida Fusion '''
|
|
@@ -43,8 +45,37 @@ class Arguments(object):
|
|
|
43
45
|
modules_group = parser.add_argument_group('Modules')
|
|
44
46
|
self._add_modules_args(modules_group)
|
|
45
47
|
|
|
48
|
+
# Add module args
|
|
49
|
+
for mod in self._get_requested_module_list():
|
|
50
|
+
flags = parser.add_argument_group(f'{mod.name} Flags')
|
|
51
|
+
mod.create_instance().add_params(flags)
|
|
52
|
+
|
|
46
53
|
return parser.parse_args()
|
|
47
54
|
|
|
55
|
+
def _get_requested_module_list(self):
|
|
56
|
+
mods = ModuleManager.list_modules()
|
|
57
|
+
parser = argparse.ArgumentParser()
|
|
58
|
+
parser.add_argument('-m',
|
|
59
|
+
'--module',
|
|
60
|
+
action='append',
|
|
61
|
+
dest='enabled_modules')
|
|
62
|
+
|
|
63
|
+
t_args, _ = parser.parse_known_args([
|
|
64
|
+
word
|
|
65
|
+
for word in sys.argv
|
|
66
|
+
if word != "-h" and word != "--list-modules"
|
|
67
|
+
])
|
|
68
|
+
if t_args is None or t_args.enabled_modules is None:
|
|
69
|
+
return []
|
|
70
|
+
return [
|
|
71
|
+
m
|
|
72
|
+
for md in t_args.enabled_modules
|
|
73
|
+
for mn in md.split(",")
|
|
74
|
+
if mn.strip() != ""
|
|
75
|
+
for _, m in mods.items()
|
|
76
|
+
if m.safe_name() == mn.lower()
|
|
77
|
+
]
|
|
78
|
+
|
|
48
79
|
def _add_app_args(self, app):
|
|
49
80
|
app.add_argument('-f',
|
|
50
81
|
'--package',
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import errno
|
|
5
5
|
import sys
|
|
6
6
|
import signal
|
|
7
|
+
from argparse import Namespace
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from .module import Module, ModuleManager, InternalModule, ExternalModule
|
|
@@ -17,6 +18,7 @@ class Configuration(object):
|
|
|
17
18
|
version = '0.0.0'
|
|
18
19
|
|
|
19
20
|
initialized = False # Flag indicating config has been initialized
|
|
21
|
+
args: Namespace = {}
|
|
20
22
|
debug_level = 0
|
|
21
23
|
cmd_line = ''
|
|
22
24
|
base_path = str(Path(__file__).resolve().parent)
|
|
@@ -98,6 +100,7 @@ class Configuration(object):
|
|
|
98
100
|
sys.exit(0)
|
|
99
101
|
|
|
100
102
|
args = Arguments().args
|
|
103
|
+
Configuration.args = args
|
|
101
104
|
|
|
102
105
|
Color.pl('{+} {W}Startup parameters')
|
|
103
106
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
class SilentKillError(Exception):
|
|
3
|
+
def __init__(self, *args, **kwargs):
|
|
4
|
+
# args -> vão para a Exception (ex.: a mensagem)
|
|
5
|
+
# kwargs -> metadados opcionais seus
|
|
6
|
+
self.meta = kwargs
|
|
7
|
+
self._msg = kwargs.get("message") or ""
|
|
8
|
+
if args is not None and len(args) > 0 and self._msg == "":
|
|
9
|
+
self._msg = args[0]
|
|
10
|
+
super().__init__(*args)
|
|
11
|
+
|
|
12
|
+
def __str__(self):
|
|
13
|
+
if self._msg != "":
|
|
14
|
+
return self._msg
|
|
15
|
+
else:
|
|
16
|
+
return str(super(SilentKillError, self).__str__())
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return str(self)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# -*- coding: UTF-8 -*-
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from .exceptions import SilentKillError
|
|
5
6
|
from .libs.color import Color
|
|
6
7
|
from .libs.scriptlocation import ScriptLocation
|
|
7
8
|
|
|
@@ -64,10 +65,12 @@ class Fusion(object):
|
|
|
64
65
|
try:
|
|
65
66
|
self.session.detach()
|
|
66
67
|
except:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
self.device.kill(self.pid)
|
|
72
|
+
except:
|
|
73
|
+
pass
|
|
71
74
|
|
|
72
75
|
def get_device(self):
|
|
73
76
|
try:
|
|
@@ -150,6 +153,13 @@ class Fusion(object):
|
|
|
150
153
|
|
|
151
154
|
for m in self._modules:
|
|
152
155
|
files_js += m.js_files()
|
|
156
|
+
dyn = m.dynamic_script()
|
|
157
|
+
if dyn is not None and dyn.strip(" \r\n") != "":
|
|
158
|
+
dyn += "\n\n"
|
|
159
|
+
line_cnt = len(dyn.split("\n")) - 1
|
|
160
|
+
self.script_trace[f"dyn_{m.safe_name()}.js"] = (offset, offset + line_cnt)
|
|
161
|
+
offset += line_cnt
|
|
162
|
+
src += dyn
|
|
153
163
|
|
|
154
164
|
if os.path.isfile(Configuration.frida_scripts):
|
|
155
165
|
files_js += [Configuration.frida_scripts]
|
|
@@ -220,9 +230,16 @@ class Fusion(object):
|
|
|
220
230
|
sys.exit(1)
|
|
221
231
|
|
|
222
232
|
def attach(self, pid: int):
|
|
223
|
-
|
|
233
|
+
Fusion.running = True
|
|
224
234
|
self.pid = pid
|
|
225
235
|
|
|
236
|
+
if self.session is not None:
|
|
237
|
+
try:
|
|
238
|
+
self.session.off("detached", self.on_detached)
|
|
239
|
+
except:
|
|
240
|
+
pass
|
|
241
|
+
self.session = None
|
|
242
|
+
|
|
226
243
|
self.session = self.device.attach(self.pid)
|
|
227
244
|
self.session.on("detached", self.on_detached)
|
|
228
245
|
|
|
@@ -231,7 +248,14 @@ class Fusion(object):
|
|
|
231
248
|
self.device.resume(self.pid)
|
|
232
249
|
|
|
233
250
|
def std_spawn(self):
|
|
234
|
-
|
|
251
|
+
Fusion.running = True
|
|
252
|
+
|
|
253
|
+
if self.session is not None:
|
|
254
|
+
try:
|
|
255
|
+
self.session.off("detached", self.on_detached)
|
|
256
|
+
except:
|
|
257
|
+
pass
|
|
258
|
+
self.session = None
|
|
235
259
|
|
|
236
260
|
self.pid = self.device.spawn([Configuration.package])
|
|
237
261
|
self.session = self.device.attach(self.pid)
|
|
@@ -242,7 +266,14 @@ class Fusion(object):
|
|
|
242
266
|
self.device.resume(self.pid)
|
|
243
267
|
|
|
244
268
|
def wait_spawn(self):
|
|
245
|
-
|
|
269
|
+
Fusion.running = True
|
|
270
|
+
|
|
271
|
+
if self.session is not None:
|
|
272
|
+
try:
|
|
273
|
+
self.session.off("detached", self.on_detached)
|
|
274
|
+
except:
|
|
275
|
+
pass
|
|
276
|
+
self.session = None
|
|
246
277
|
|
|
247
278
|
self.pid = self.device.spawn([Configuration.package])
|
|
248
279
|
self.device.resume(self.pid)
|
|
@@ -365,6 +396,18 @@ class Fusion(object):
|
|
|
365
396
|
else:
|
|
366
397
|
self.print_message(mLevel, message, script_location=script_location)
|
|
367
398
|
|
|
399
|
+
except SilentKillError as sk:
|
|
400
|
+
skm = str(sk)
|
|
401
|
+
|
|
402
|
+
self.print_message("D", "Silent kill requested",
|
|
403
|
+
script_location=Logger.get_caller_info(stack_index=1))
|
|
404
|
+
Fusion.running = False
|
|
405
|
+
time.sleep(0.2)
|
|
406
|
+
if skm != "":
|
|
407
|
+
Logger.pl(f'\n{skm}')
|
|
408
|
+
Logger.pl('\n{+} {O}Exiting...{O}{W}')
|
|
409
|
+
self.done.set()
|
|
410
|
+
|
|
368
411
|
except Exception as err:
|
|
369
412
|
script_location = ScriptLocation(file_name=Fusion._script_name)
|
|
370
413
|
self.print_message("E", message, script_location=script_location)
|
|
@@ -436,6 +479,7 @@ class Fusion(object):
|
|
|
436
479
|
elif crash is not None:
|
|
437
480
|
Logger.pl("[CRASH] details: " + str(crash))
|
|
438
481
|
|
|
482
|
+
Logger.pl("")
|
|
439
483
|
self.done.set()
|
|
440
484
|
|
|
441
485
|
def _raise_key_value_event(self,
|
|
@@ -451,6 +495,8 @@ class Fusion(object):
|
|
|
451
495
|
module=module,
|
|
452
496
|
received_data=received_data
|
|
453
497
|
)
|
|
498
|
+
except SilentKillError as ske:
|
|
499
|
+
raise ske
|
|
454
500
|
except Exception as e:
|
|
455
501
|
if Configuration.debug_level >= 2:
|
|
456
502
|
self.print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
|
|
@@ -468,6 +514,8 @@ class Fusion(object):
|
|
|
468
514
|
stack_trace=stack_trace,
|
|
469
515
|
received_data=received_data
|
|
470
516
|
)
|
|
517
|
+
except SilentKillError as ske:
|
|
518
|
+
raise ske
|
|
471
519
|
except Exception as e:
|
|
472
520
|
if Configuration.debug_level >= 2:
|
|
473
521
|
self.print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
|
|
@@ -626,6 +674,7 @@ class Fusion(object):
|
|
|
626
674
|
if len(self._modules) > 0:
|
|
627
675
|
Logger.pl("{+} Starting selected modules")
|
|
628
676
|
for m in self._modules:
|
|
677
|
+
m.load_from_arguments(Configuration.args)
|
|
629
678
|
m.start_module(
|
|
630
679
|
package=Configuration.package,
|
|
631
680
|
db_path=Configuration.db_path
|
|
@@ -2,12 +2,24 @@
|
|
|
2
2
|
Author: Helvio Junior - M4v3r1ck
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
function fusion_rawSend(payload1){
|
|
6
|
+
send(payload1);
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
function fusion_Send(payload1, payload2){
|
|
6
10
|
const info = fusion_getCallerInfo();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
const message = {
|
|
13
|
+
payload: payload1,
|
|
14
|
+
location: info
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// if payload1 is objet and has "type"
|
|
18
|
+
if (payload1 && typeof payload1 === 'object' && 'type' in payload1) {
|
|
19
|
+
message.type = payload1.type;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
send(message, payload2);
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
function fusion_waitForClass(name, onReady) {
|
|
@@ -80,6 +92,12 @@ function fusion_bytesToBase64(byteArray){
|
|
|
80
92
|
}
|
|
81
93
|
}
|
|
82
94
|
|
|
95
|
+
function fusion_normalizePtr(addr) {
|
|
96
|
+
let p = ptr(addr);
|
|
97
|
+
if (Process.arch === 'arm64') p = p.and('0x00FFFFFFFFFFFFFF'); // limpa TBI
|
|
98
|
+
return p;
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
function fusion_getCallerInfo() {
|
|
84
102
|
try{
|
|
85
103
|
const stack = new Error().stack.split("\n");
|
|
@@ -244,6 +262,38 @@ function fusion_getClassName(obj)
|
|
|
244
262
|
|
|
245
263
|
}
|
|
246
264
|
|
|
265
|
+
function fusion_getReadableRange(p) {
|
|
266
|
+
try { p = ptr(p); } catch (_) { return null; }
|
|
267
|
+
const range = Process.findRangeByAddress(p); // não lança exceção
|
|
268
|
+
if (!range) return null;
|
|
269
|
+
// range.protection exemplo: 'r-x', 'rw-'
|
|
270
|
+
return range.protection.indexOf('r') !== -1 ? range : null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function fusion_isAddressReadable(p) {
|
|
274
|
+
const r = fusion_getReadableRange(p);
|
|
275
|
+
if (!r) return false;
|
|
276
|
+
// tenta ler 1 byte para confirmar acessibilidade
|
|
277
|
+
try { Memory.readU8(ptr(p)); return true; }
|
|
278
|
+
catch (_) { return false; }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function fusion_describeAddress(p) {
|
|
282
|
+
try { p = ptr(p); } catch (_) { return { ok:false, reason:'not a pointer' }; }
|
|
283
|
+
if (Process.arch === 'arm64') p = p.and('0x00FFFFFFFFFFFFFF'); // remove top byte
|
|
284
|
+
if (!fusion_isAddressReadable(p)) return { ok:false, reason:'invalid pointer' };
|
|
285
|
+
const range = Process.findRangeByAddress(p);
|
|
286
|
+
if (!range) return { ok:false, reason:'unmapped' };
|
|
287
|
+
return {
|
|
288
|
+
ok: true,
|
|
289
|
+
base: range.base,
|
|
290
|
+
size: range.size,
|
|
291
|
+
protection: range.protection,
|
|
292
|
+
file: range.file ? range.file.path : null
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
247
297
|
Java.perform(function () {
|
|
248
298
|
const Thread = Java.use('java.lang.Thread');
|
|
249
299
|
const UEH = Java.registerClass({
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import re
|
|
4
|
+
from argparse import _ArgumentGroup, Namespace
|
|
5
|
+
|
|
4
6
|
import frida
|
|
5
7
|
import pkgutil
|
|
6
8
|
import importlib
|
|
@@ -35,12 +37,21 @@ class ModuleBase(object):
|
|
|
35
37
|
def start_module(self, **kwargs) -> bool:
|
|
36
38
|
raise Exception('Method "start_module" is not yet implemented.')
|
|
37
39
|
|
|
40
|
+
def load_from_arguments(self, args: Namespace) -> bool:
|
|
41
|
+
return True
|
|
42
|
+
|
|
38
43
|
def js_files(self) -> list:
|
|
39
44
|
return []
|
|
40
45
|
|
|
46
|
+
def dynamic_script(self) -> str:
|
|
47
|
+
return ""
|
|
48
|
+
|
|
41
49
|
def suppress_messages(self):
|
|
42
50
|
pass
|
|
43
51
|
|
|
52
|
+
def add_params(self, flags: _ArgumentGroup):
|
|
53
|
+
pass
|
|
54
|
+
|
|
44
55
|
def key_value_event(self,
|
|
45
56
|
script_location: ScriptLocation = None,
|
|
46
57
|
stack_trace: str = None,
|
|
@@ -125,6 +136,9 @@ class ExternalModule(Module):
|
|
|
125
136
|
|
|
126
137
|
|
|
127
138
|
class ModuleManager:
|
|
139
|
+
initialized = False # Flag indicating modules has been initialized
|
|
140
|
+
modules: dict[str, Module] = {}
|
|
141
|
+
|
|
128
142
|
@classmethod
|
|
129
143
|
def _safe_import_from_path(cls, path: Path, loaded_files: set):
|
|
130
144
|
"""
|
|
@@ -188,9 +202,12 @@ class ModuleManager:
|
|
|
188
202
|
|
|
189
203
|
@classmethod
|
|
190
204
|
def list_modules(cls) -> dict:
|
|
205
|
+
if ModuleManager.initialized:
|
|
206
|
+
return ModuleManager.modules
|
|
207
|
+
|
|
191
208
|
try:
|
|
192
209
|
base_module = Module.get_base_module()
|
|
193
|
-
modules
|
|
210
|
+
ModuleManager.modules = {}
|
|
194
211
|
|
|
195
212
|
# --- 1) Varredura padrão do seu pacote interno: <este_arquivo>/modules ---
|
|
196
213
|
base_path = Path(__file__).resolve().parent / "modules"
|
|
@@ -242,13 +259,13 @@ class ModuleManager:
|
|
|
242
259
|
for i_class in ModuleBase.__subclasses__():
|
|
243
260
|
t = i_class()
|
|
244
261
|
key = t.safe_name()
|
|
245
|
-
if key in modules:
|
|
262
|
+
if key in ModuleManager.modules:
|
|
246
263
|
raise ModuleLoaderError(
|
|
247
264
|
f"Duplicated Module name: {i_class.__module__}.{i_class.__qualname__}"
|
|
248
265
|
)
|
|
249
266
|
|
|
250
267
|
if str(i_class.__module__) in internal_mods:
|
|
251
|
-
modules[key] = InternalModule(
|
|
268
|
+
ModuleManager.modules[key] = InternalModule(
|
|
252
269
|
name=t.name,
|
|
253
270
|
description=t.description,
|
|
254
271
|
module=str(i_class.__module__),
|
|
@@ -256,7 +273,7 @@ class ModuleManager:
|
|
|
256
273
|
class_name=i_class,
|
|
257
274
|
)
|
|
258
275
|
else:
|
|
259
|
-
modules[key] = ExternalModule(
|
|
276
|
+
ModuleManager.modules[key] = ExternalModule(
|
|
260
277
|
name=t.name,
|
|
261
278
|
description=t.description,
|
|
262
279
|
module=str(i_class.__module__),
|
|
@@ -264,7 +281,8 @@ class ModuleManager:
|
|
|
264
281
|
class_name=i_class,
|
|
265
282
|
)
|
|
266
283
|
|
|
267
|
-
|
|
284
|
+
ModuleManager.initialized = True
|
|
285
|
+
return ModuleManager.modules
|
|
268
286
|
|
|
269
287
|
except Exception as e:
|
|
270
288
|
# Envolve a exceção original para manter contexto
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// Script Frida: Method.invoke -> build object + enviar via fusion_sendKeyValueData
|
|
2
|
+
Java.perform(function () {
|
|
3
|
+
|
|
4
|
+
var jlrmethod = Java.use("java.lang.reflect.Method");
|
|
5
|
+
var origInvoke = jlrmethod.invoke; // referência original
|
|
6
|
+
|
|
7
|
+
function safeGetClassName(obj) {
|
|
8
|
+
try {
|
|
9
|
+
if (obj === null || obj === undefined) return null;
|
|
10
|
+
var cls = obj.getClass();
|
|
11
|
+
return cls.getName();
|
|
12
|
+
} catch (e) {
|
|
13
|
+
try { if (obj && obj.$className) return obj.$className; } catch(e2) {}
|
|
14
|
+
try { return Object.prototype.toString.call(obj); } catch(_) { return typeof obj; }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildParamInfo(param, depth) {
|
|
19
|
+
depth = depth || 0;
|
|
20
|
+
var maxDepth = 3;
|
|
21
|
+
var info = { type: null, class_name: null, value: null };
|
|
22
|
+
|
|
23
|
+
if (param === null || param === undefined) {
|
|
24
|
+
info.type = 'null';
|
|
25
|
+
return info;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var className = safeGetClassName(param);
|
|
29
|
+
info.class_name = className;
|
|
30
|
+
info.type = className;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (className && className.charAt(0) === '[') {
|
|
34
|
+
info.type = className;
|
|
35
|
+
if (className === '[B') {
|
|
36
|
+
try {
|
|
37
|
+
var buffer = Java.array('byte', param);
|
|
38
|
+
try { info.value = toBase64(buffer); } catch (e) {
|
|
39
|
+
var sb = []; for (var i=0;i<Math.min(buffer.length,256);i++) sb.push((buffer[i]&0xff).toString(16));
|
|
40
|
+
info.value = sb.join('');
|
|
41
|
+
}
|
|
42
|
+
} catch (errByte) { info.value = '[unreadable byte array]'; }
|
|
43
|
+
return info;
|
|
44
|
+
}
|
|
45
|
+
if (className.indexOf('Ljava.lang.String') !== -1) {
|
|
46
|
+
try {
|
|
47
|
+
var arrLen = param.length;
|
|
48
|
+
var vals = [];
|
|
49
|
+
for (var i=0;i<Math.min(arrLen,50);i++) { vals.push(String(param[i])); }
|
|
50
|
+
info.value = { length: arrLen, preview: vals };
|
|
51
|
+
} catch(e) { info.value = '[unreadable string array]'; }
|
|
52
|
+
return info;
|
|
53
|
+
}
|
|
54
|
+
if (depth >= maxDepth) { info.value = '[array - depth limit]'; return info; }
|
|
55
|
+
try {
|
|
56
|
+
var n = param.length;
|
|
57
|
+
var items = [];
|
|
58
|
+
for (var j=0; j<Math.min(n,50); j++) items.push(buildParamInfo(param[j], depth+1));
|
|
59
|
+
info.value = { length: n, preview: items };
|
|
60
|
+
} catch(e) { info.value = '[unreadable array]'; }
|
|
61
|
+
return info;
|
|
62
|
+
}
|
|
63
|
+
} catch(eArrDetect) {}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
if (className === 'java.lang.String' || (typeof param === 'string')) { info.value = String(param); return info; }
|
|
67
|
+
if (className && (
|
|
68
|
+
className.indexOf('java.lang.Number') !== -1 ||
|
|
69
|
+
className.indexOf('java.lang.Integer') !== -1 ||
|
|
70
|
+
className.indexOf('java.lang.Long') !== -1 ||
|
|
71
|
+
className.indexOf('java.lang.Boolean') !== -1 ||
|
|
72
|
+
className.indexOf('java.lang.Double') !== -1 ||
|
|
73
|
+
className.indexOf('java.lang.Float') !== -1 ||
|
|
74
|
+
className.indexOf('java.lang.Short') !== -1 ||
|
|
75
|
+
className.indexOf('java.lang.Character') !== -1)) {
|
|
76
|
+
try { info.value = param.toString(); } catch(e){ info.value = String(param); }
|
|
77
|
+
return info;
|
|
78
|
+
}
|
|
79
|
+
try { info.value = param.toString(); } catch (e) { info.value = '[toString failed]'; }
|
|
80
|
+
return info;
|
|
81
|
+
} catch(errGeneric) {
|
|
82
|
+
info.value = '[error building param]';
|
|
83
|
+
return info;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildInvokeObject(methodRef, targetObject, parameters) {
|
|
88
|
+
var obj = {
|
|
89
|
+
timestamp: (new Date()).toISOString(),
|
|
90
|
+
method: {
|
|
91
|
+
toString: String(methodRef),
|
|
92
|
+
name: null,
|
|
93
|
+
declaring_class: null,
|
|
94
|
+
b64: null
|
|
95
|
+
},
|
|
96
|
+
target: {
|
|
97
|
+
class_name: null,
|
|
98
|
+
toString: null
|
|
99
|
+
},
|
|
100
|
+
parameters: [],
|
|
101
|
+
return: {
|
|
102
|
+
class_name: null,
|
|
103
|
+
value: null
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (methodRef && methodRef.getName) obj.method.name = methodRef.getName(); else obj.method.name = String(methodRef);
|
|
109
|
+
try {
|
|
110
|
+
var decl = methodRef.getDeclaringClass();
|
|
111
|
+
obj.method.declaring_class = decl ? decl.getName() : null;
|
|
112
|
+
} catch(e) { obj.method.declaring_class = null; }
|
|
113
|
+
} catch(_) {}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
var tName = "" + methodRef;
|
|
117
|
+
try { obj.method.b64 = fusion_stringToBase64(tName); } catch(e){ obj.method.b64 = null; }
|
|
118
|
+
} catch(_) { obj.method.b64 = null; }
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
if (targetObject === null || targetObject === undefined) {
|
|
122
|
+
obj.target.class_name = null; obj.target.toString = null;
|
|
123
|
+
} else {
|
|
124
|
+
obj.target.class_name = safeGetClassName(targetObject);
|
|
125
|
+
try { obj.target.toString = targetObject.toString(); } catch(e){ obj.target.toString = '[toString failed]'; }
|
|
126
|
+
}
|
|
127
|
+
} catch(e) {}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
var type = Object.prototype.toString.call(parameters);
|
|
131
|
+
if (parameters === null || parameters === undefined) {
|
|
132
|
+
obj.parameters = [];
|
|
133
|
+
} else if (type === '[object Array]') {
|
|
134
|
+
var arrLen = parameters.length;
|
|
135
|
+
for (var i=0; i<Math.min(arrLen,200); i++) {
|
|
136
|
+
obj.parameters.push({ index: i, info: buildParamInfo(parameters[i], 0) });
|
|
137
|
+
}
|
|
138
|
+
if (arrLen > 200) obj.parameters_truncated = true;
|
|
139
|
+
} else {
|
|
140
|
+
try {
|
|
141
|
+
var maybeLen = parameters.length;
|
|
142
|
+
if (typeof maybeLen === 'number') {
|
|
143
|
+
for (var k=0; k<Math.min(maybeLen,100); k++) obj.parameters.push({ index: k, info: buildParamInfo(parameters[k],0) });
|
|
144
|
+
} else {
|
|
145
|
+
obj.parameters.push({ index: 0, info: buildParamInfo(parameters,0) });
|
|
146
|
+
}
|
|
147
|
+
} catch(e2) {
|
|
148
|
+
obj.parameters.push({ index: 0, info: buildParamInfo(parameters,0) });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch(errParams) {
|
|
152
|
+
obj.parameters = [{ index:0, info: { type: '[error enumerating parameters]' } }];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return obj;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// coleta backtrace da Thread Java atual e retorna [pretty, raw]
|
|
159
|
+
function collectBacktrace() {
|
|
160
|
+
var pretty = "[unavailable]";
|
|
161
|
+
var raw = [];
|
|
162
|
+
try {
|
|
163
|
+
var Thread = Java.use('java.lang.Thread');
|
|
164
|
+
var trace = Thread.currentThread().getStackTrace(); // array of StackTraceElement
|
|
165
|
+
var prettyLines = [];
|
|
166
|
+
for (var i=0; i<trace.length; i++) {
|
|
167
|
+
try {
|
|
168
|
+
var ste = trace[i];
|
|
169
|
+
var line = ste.toString(); // ex: com.foo.Bar.method(File.java:123)
|
|
170
|
+
prettyLines.push(line);
|
|
171
|
+
raw.push(String(ste));
|
|
172
|
+
} catch(eSte) { /* ignore */ }
|
|
173
|
+
}
|
|
174
|
+
pretty = prettyLines.join('\n');
|
|
175
|
+
} catch(e) {
|
|
176
|
+
try {
|
|
177
|
+
// fallback: usar Java.perform stack (menos informativo)
|
|
178
|
+
pretty = (new Error()).stack;
|
|
179
|
+
raw = [pretty];
|
|
180
|
+
} catch(e2) {}
|
|
181
|
+
}
|
|
182
|
+
return { pretty: pretty, raw: raw };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// sobrescreve invoke para montar objeto e enviar via fusion_sendKeyValueData
|
|
186
|
+
jlrmethod.invoke.implementation = function(object, parameters) {
|
|
187
|
+
// monta objeto
|
|
188
|
+
var infoObj = buildInvokeObject(this, object, parameters);
|
|
189
|
+
|
|
190
|
+
// coletar backtrace
|
|
191
|
+
var bt = collectBacktrace();
|
|
192
|
+
|
|
193
|
+
// chamar o método original e capturar retorno
|
|
194
|
+
var retvalue;
|
|
195
|
+
try {
|
|
196
|
+
retvalue = origInvoke.call(this, object, parameters);
|
|
197
|
+
} catch (invokeErr) {
|
|
198
|
+
// enviar também quando lançar (pode ser útil)
|
|
199
|
+
try {
|
|
200
|
+
fusion_sendKeyValueData("java.lang.reflect.Method!invoke!throw", [
|
|
201
|
+
{ key: "method", value: String(infoObj.method.toString) },
|
|
202
|
+
{ key: "declaring_class", value: String(infoObj.method.declaring_class) },
|
|
203
|
+
{ key: "method_b64", value: String(infoObj.method.b64) },
|
|
204
|
+
{ key: "target_class", value: String(infoObj.target.class_name) },
|
|
205
|
+
{ key: "params_count", value: String(infoObj.parameters.length) },
|
|
206
|
+
{ key: "params_preview", value: JSON.stringify(infoObj.parameters) },
|
|
207
|
+
{ key: "backtrace", value: bt.pretty },
|
|
208
|
+
{ key: "backtrace_raw", value: JSON.stringify(bt.raw) },
|
|
209
|
+
{ key: "error", value: String(invokeErr) }
|
|
210
|
+
]);
|
|
211
|
+
} catch(eSendErr) { fusion_sendMessage("W", "fusion_sendKeyValueData error: " + eSendErr); }
|
|
212
|
+
|
|
213
|
+
throw invokeErr; // rethrow para manter comportamento
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// preencher retorno infoObj.return
|
|
217
|
+
try {
|
|
218
|
+
if (retvalue === null || retvalue === undefined) {
|
|
219
|
+
infoObj.return.class_name = null; infoObj.return.value = null;
|
|
220
|
+
} else {
|
|
221
|
+
infoObj.return.class_name = safeGetClassName(retvalue);
|
|
222
|
+
if (infoObj.return.class_name && infoObj.return.class_name.charAt(0) === '[') {
|
|
223
|
+
if (infoObj.return.class_name === '[B') {
|
|
224
|
+
try { infoObj.return.value = toBase64(Java.array('byte', retvalue)); } catch(e) { infoObj.return.value = '[unreadable byte array]'; }
|
|
225
|
+
} else {
|
|
226
|
+
try {
|
|
227
|
+
var lenR = retvalue.length, itemsR = [];
|
|
228
|
+
for (var ri=0; ri<Math.min(lenR,50); ri++) { itemsR.push(String(retvalue[ri])); }
|
|
229
|
+
infoObj.return.value = { length: lenR, preview: itemsR };
|
|
230
|
+
} catch(eArrR) { infoObj.return.value = '[unreadable array]'; }
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
try { infoObj.return.value = retvalue.toString(); } catch(e) { infoObj.return.value = '[toString failed]'; }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} catch(eRetFill) { infoObj.return.value = '[error reading return]'; }
|
|
237
|
+
|
|
238
|
+
// preparar payload para fusion_sendKeyValueData (array de {key, value})
|
|
239
|
+
var payload = [];
|
|
240
|
+
try {
|
|
241
|
+
payload.push({ key: "method", value: String(infoObj.method.toString) });
|
|
242
|
+
payload.push({ key: "method_name", value: String(infoObj.method.name) });
|
|
243
|
+
payload.push({ key: "declaring_class", value: String(infoObj.method.declaring_class) });
|
|
244
|
+
payload.push({ key: "method_b64", value: String(infoObj.method.b64) });
|
|
245
|
+
payload.push({ key: "target_class", value: String(infoObj.target.class_name) });
|
|
246
|
+
payload.push({ key: "target_tostring", value: String(infoObj.target.toString) });
|
|
247
|
+
payload.push({ key: "params_count", value: String(infoObj.parameters.length) });
|
|
248
|
+
|
|
249
|
+
// params_preview como JSON string (pode truncar se muito grande)
|
|
250
|
+
try {
|
|
251
|
+
var paramsJson = JSON.stringify(infoObj.parameters);
|
|
252
|
+
if (paramsJson.length > 20000) paramsJson = paramsJson.slice(0,20000) + "...(truncated)";
|
|
253
|
+
payload.push({ key: "params_preview", value: paramsJson });
|
|
254
|
+
} catch(ePJ) {
|
|
255
|
+
payload.push({ key: "params_preview", value: "[error serializing params]" });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// retorno
|
|
259
|
+
try {
|
|
260
|
+
var retSummary = (infoObj.return.class_name === null && infoObj.return.value === null) ? "null" :
|
|
261
|
+
("(" + String(infoObj.return.class_name) + ") " + String(infoObj.return.value));
|
|
262
|
+
if (retSummary.length > 4000) retSummary = retSummary.slice(0,4000) + "...(truncated)";
|
|
263
|
+
payload.push({ key: "return_summary", value: retSummary });
|
|
264
|
+
payload.push({ key: "return_class", value: String(infoObj.return.class_name) });
|
|
265
|
+
} catch(eRetPush) {
|
|
266
|
+
payload.push({ key: "return_summary", value: "[error]" });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// backtrace
|
|
270
|
+
payload.push({ key: "backtrace", value: bt.pretty });
|
|
271
|
+
payload.push({ key: "backtrace_raw", value: JSON.stringify(bt.raw) });
|
|
272
|
+
|
|
273
|
+
// timestamp
|
|
274
|
+
payload.push({ key: "timestamp", value: infoObj.timestamp });
|
|
275
|
+
|
|
276
|
+
} catch(ePayload) {
|
|
277
|
+
fusion_sendMessage("W", "Erro ao preparar payload: " + ePayload);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// enviar pelo canal customizado
|
|
281
|
+
try {
|
|
282
|
+
fusion_sendKeyValueData("java.lang.reflect.Method!invoke!call", payload);
|
|
283
|
+
} catch(eSend) {
|
|
284
|
+
// se fusion_sendKeyValueData não existir, fallback para console + fusion_sendMessage (se existir)
|
|
285
|
+
try { fusion_sendMessage("E", "fusion_sendKeyValueData missing or failed"); } catch(_) {}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return retvalue;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
try { fusion_sendMessage("I", "Reflection module with fusion_sendKeyValueData loaded!"); } catch(e) { fusion_sendMessage("W", "Reflection module loaded!"); }
|
|
292
|
+
|
|
293
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import errno
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import os.path
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from argparse import _ArgumentGroup, Namespace
|
|
7
|
+
from frida_fusion.libs.logger import Logger
|
|
8
|
+
from frida_fusion.libs.database import Database
|
|
9
|
+
from frida_fusion.module import ModuleBase
|
|
10
|
+
from frida_fusion.libs.scriptlocation import ScriptLocation
|
|
11
|
+
from frida_fusion.exceptions import SilentKillError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Reflection(ModuleBase):
|
|
15
|
+
|
|
16
|
+
_EXCLUSION_LIST = [
|
|
17
|
+
"android.view.View.onDraw",
|
|
18
|
+
"android.graphics.Picture",
|
|
19
|
+
"com.facebook.fbreact.specs.NativeAnimatedModuleSpec",
|
|
20
|
+
"com.facebook.react.uimanager.BaseViewManager.setTransform",
|
|
21
|
+
"com.facebook.react.uimanager.ReanimatedUIManager.updateView",
|
|
22
|
+
"com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
class StalkerDB(Database):
|
|
26
|
+
dbName = ""
|
|
27
|
+
|
|
28
|
+
def __init__(self, db_name: str):
|
|
29
|
+
super().__init__(
|
|
30
|
+
auto_create=True,
|
|
31
|
+
db_name=db_name
|
|
32
|
+
)
|
|
33
|
+
self.create_db()
|
|
34
|
+
|
|
35
|
+
def create_db(self):
|
|
36
|
+
super().create_db()
|
|
37
|
+
conn = self.connect_to_db(check=False)
|
|
38
|
+
|
|
39
|
+
# definindo um cursor
|
|
40
|
+
cursor = conn.cursor()
|
|
41
|
+
|
|
42
|
+
# criando a tabela (schema)
|
|
43
|
+
cursor.execute("""
|
|
44
|
+
CREATE TABLE IF NOT EXISTS [reflection_stalker] (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
method TEXT NOT NULL,
|
|
47
|
+
method_b64 TEXT NULL,
|
|
48
|
+
method_name TEXT NULL,
|
|
49
|
+
target_class TEXT NULL,
|
|
50
|
+
target_tostring TEXT NULL,
|
|
51
|
+
params_count TEXT NULL,
|
|
52
|
+
params TEXT NULL,
|
|
53
|
+
error TEXT NULL,
|
|
54
|
+
return_summary TEXT NULL,
|
|
55
|
+
return_class TEXT NULL,
|
|
56
|
+
stack_trace TEXT NULL,
|
|
57
|
+
created_date datetime not null DEFAULT (datetime('now','localtime'))
|
|
58
|
+
);
|
|
59
|
+
""")
|
|
60
|
+
|
|
61
|
+
conn.commit()
|
|
62
|
+
|
|
63
|
+
# Must get the constraints
|
|
64
|
+
self.get_constraints(conn)
|
|
65
|
+
|
|
66
|
+
def __init__(self):
|
|
67
|
+
super().__init__('Reflection Stalker', 'Monitor reflection calls/invoke by Helvio Junior (M4v3r1ck)')
|
|
68
|
+
self.mod_path = str(Path(__file__).resolve().parent)
|
|
69
|
+
self._stalker_db = None
|
|
70
|
+
self._suppress_messages = False
|
|
71
|
+
self._ignore_list = []
|
|
72
|
+
self.js_file = os.path.join(self.mod_path, "reflection-stalker.js")
|
|
73
|
+
|
|
74
|
+
def start_module(self, **kwargs) -> bool:
|
|
75
|
+
if 'db_path' not in kwargs:
|
|
76
|
+
raise Exception("parameter db_path not found")
|
|
77
|
+
|
|
78
|
+
self._stalker_db = Reflection.StalkerDB(db_name=kwargs['db_path'])
|
|
79
|
+
|
|
80
|
+
def js_files(self) -> list:
|
|
81
|
+
return [
|
|
82
|
+
self.js_file
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
def suppress_messages(self):
|
|
86
|
+
self._suppress_messages = True
|
|
87
|
+
|
|
88
|
+
def dynamic_script(self) -> str:
|
|
89
|
+
return f""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def add_params(self, flags: _ArgumentGroup):
|
|
93
|
+
flags.add_argument('--ignore-stalker-text',
|
|
94
|
+
dest='reflection_stalker_ignore',
|
|
95
|
+
metavar='text',
|
|
96
|
+
action='append',
|
|
97
|
+
help='Text to ignore at screen output. You can specify multiple values repeating the flag.')
|
|
98
|
+
|
|
99
|
+
def load_from_arguments(self, args: Namespace) -> bool:
|
|
100
|
+
self._ignore_list = []
|
|
101
|
+
|
|
102
|
+
if args.reflection_stalker_ignore is None or len(args.reflection_stalker_ignore) == 0:
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
self._ignore_list = list(set([
|
|
106
|
+
txt.lower()
|
|
107
|
+
for t1 in args.reflection_stalker_ignore
|
|
108
|
+
if (txt := t1.strip()) != ''
|
|
109
|
+
]))
|
|
110
|
+
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
def key_value_event(self,
|
|
114
|
+
script_location: ScriptLocation = None,
|
|
115
|
+
stack_trace: str = None,
|
|
116
|
+
module: str = None,
|
|
117
|
+
received_data: dict = None
|
|
118
|
+
) -> bool:
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if module in ["java.lang.reflect.Method!invoke!throw",
|
|
122
|
+
"java.lang.reflect.Method!invoke!call"
|
|
123
|
+
]:
|
|
124
|
+
|
|
125
|
+
# Exclusion list
|
|
126
|
+
method = received_data.get('method', '')
|
|
127
|
+
for em in Reflection._EXCLUSION_LIST:
|
|
128
|
+
if em in method:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
params_preview = received_data.get('params_preview', '[]')
|
|
132
|
+
try:
|
|
133
|
+
params = json.dumps(json.loads(params_preview), default=Logger.json_serial)
|
|
134
|
+
except Exception:
|
|
135
|
+
params = params_preview
|
|
136
|
+
|
|
137
|
+
return_summary = received_data.get('return_summary', None)
|
|
138
|
+
try:
|
|
139
|
+
return_summary = json.dumps(json.loads(return_summary), default=Logger.json_serial)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
backtrace = received_data.get('backtrace_raw', None)
|
|
144
|
+
try:
|
|
145
|
+
if backtrace is None:
|
|
146
|
+
raise Exception()
|
|
147
|
+
|
|
148
|
+
if isinstance(backtrace, str):
|
|
149
|
+
backtrace = json.loads(backtrace)
|
|
150
|
+
|
|
151
|
+
bt = "Stack trace:\n at "
|
|
152
|
+
bt += '\n at '.join([
|
|
153
|
+
ln
|
|
154
|
+
for ln in backtrace
|
|
155
|
+
if 'dalvik.system.VMStack.getThreadStackTrace' not in ln
|
|
156
|
+
and 'java.lang.Thread.getStackTrace' not in ln
|
|
157
|
+
])
|
|
158
|
+
|
|
159
|
+
backtrace = bt
|
|
160
|
+
except Exception:
|
|
161
|
+
backtrace = received_data.get('backtrace', stack_trace)
|
|
162
|
+
|
|
163
|
+
self._stalker_db.insert_one(
|
|
164
|
+
table_name='reflection_stalker',
|
|
165
|
+
stack_trace=backtrace,
|
|
166
|
+
error=received_data.get('error', ''),
|
|
167
|
+
method=received_data.get('method', ''),
|
|
168
|
+
method_b64=received_data.get('method_b64', ''),
|
|
169
|
+
method_name=received_data.get('method_name', ''),
|
|
170
|
+
target_class=received_data.get('target_class', ''),
|
|
171
|
+
target_tostring=received_data.get('target_tostring', ''),
|
|
172
|
+
params_count=received_data.get('params_count', ''),
|
|
173
|
+
params=params,
|
|
174
|
+
return_summary=return_summary,
|
|
175
|
+
return_class=received_data.get('return_class', '')
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if not self._suppress_messages:
|
|
179
|
+
txt = json.dumps(received_data, indent=4)
|
|
180
|
+
for em in self._ignore_list:
|
|
181
|
+
if em in txt.lower():
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
Logger.print_message(
|
|
185
|
+
level="I",
|
|
186
|
+
message=f"Reflection: {received_data.get('method', '')}",
|
|
187
|
+
script_location=script_location
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
Logger.print_message(
|
|
192
|
+
level="D",
|
|
193
|
+
message=f"Reflection: {module}\n{txt}",
|
|
194
|
+
script_location=script_location
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
def data_event(self,
|
|
200
|
+
script_location: ScriptLocation = None,
|
|
201
|
+
stack_trace: str = None,
|
|
202
|
+
received_data: str = None
|
|
203
|
+
) -> bool:
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frida-fusion
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Hook your mobile tests with Frida
|
|
5
5
|
Author-email: "Helvio Junior (M4v3r1ck)" <helvio_junior@hotmail.com>
|
|
6
6
|
Maintainer-email: "Helvio Junior (M4v3r1ck)" <helvio_junior@hotmail.com>
|
|
@@ -105,7 +105,7 @@ void fusion_printMethods(String name);
|
|
|
105
105
|
void fusion_waitForClass(String name, CallbackFunction onReady)
|
|
106
106
|
|
|
107
107
|
# Conversions
|
|
108
|
-
|
|
108
|
+
String fusion_stringToBase64(String message);
|
|
109
109
|
String fusion_bytesToBase64(byte[] byteArray);
|
|
110
110
|
String fusion_encodeHex(byte[] byteArray);
|
|
111
111
|
```
|
|
@@ -7,6 +7,7 @@ frida_fusion/__main__.py
|
|
|
7
7
|
frida_fusion/__meta__.py
|
|
8
8
|
frida_fusion/args.py
|
|
9
9
|
frida_fusion/config.py
|
|
10
|
+
frida_fusion/exceptions.py
|
|
10
11
|
frida_fusion/fusion.py
|
|
11
12
|
frida_fusion/module.py
|
|
12
13
|
frida_fusion.egg-info/PKG-INFO
|
|
@@ -28,5 +29,7 @@ frida_fusion/modules/android_setings/settings.py
|
|
|
28
29
|
frida_fusion/modules/crypto/__init__.py
|
|
29
30
|
frida_fusion/modules/crypto/crypto.js
|
|
30
31
|
frida_fusion/modules/crypto/crypto.py
|
|
32
|
+
frida_fusion/modules/reflection/reflection-stalker.js
|
|
33
|
+
frida_fusion/modules/reflection/reflection-stalker.py
|
|
31
34
|
frida_fusion/modules/tls_unpinning/__init__.py
|
|
32
35
|
frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/android_setings/__init__.py
RENAMED
|
File without changes
|
{frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/android_setings/settings.js
RENAMED
|
File without changes
|
{frida_fusion-0.1.13 → frida_fusion-0.1.15}/frida_fusion/modules/android_setings/settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|