frida-fusion 0.1.6__tar.gz → 0.1.7__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.6 → frida_fusion-0.1.7}/PKG-INFO +1 -1
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/__meta__.py +2 -2
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/config.py +4 -2
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/fusion.py +63 -131
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/libs/color.py +0 -15
- frida_fusion-0.1.7/frida_fusion/libs/logger.py +169 -0
- frida_fusion-0.1.7/frida_fusion/libs/scriptlocation.py +39 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/modules/crypto/crypto.py +27 -6
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py +3 -8
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion.egg-info/PKG-INFO +1 -1
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion.egg-info/SOURCES.txt +1 -0
- frida_fusion-0.1.6/frida_fusion/libs/logger.py +0 -36
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/LICENSE +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/README.md +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/__init__.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/__main__.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/args.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/libs/__init__.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/libs/database.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/libs/helpers.js +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/module.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/modules/__init__.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/modules/crypto/__init__.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/modules/crypto/crypto.js +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion/modules/tls_unpinning/__init__.py +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion.egg-info/dependency_links.txt +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion.egg-info/entry_points.txt +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion.egg-info/requires.txt +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/frida_fusion.egg-info/top_level.txt +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/pyproject.toml +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/setup.cfg +0 -0
- {frida_fusion-0.1.6 → frida_fusion-0.1.7}/setup.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = '0.1.
|
|
1
|
+
__version__ = '0.1.7'
|
|
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__ = 0x4760258
|
|
6
6
|
__author__ = "Helvio Junior (M4v3r1ck)"
|
|
7
7
|
__author_email__ = "helvio_junior@hotmail.com"
|
|
8
8
|
__license__ = "GPL-3.0"
|
|
@@ -168,13 +168,15 @@ class Configuration(object):
|
|
|
168
168
|
|
|
169
169
|
if args.show_time:
|
|
170
170
|
Configuration.print_timestamp = True
|
|
171
|
+
Logger.print_timestamp = Configuration.print_timestamp
|
|
171
172
|
|
|
172
|
-
if str(args.debug_level).upper() not in
|
|
173
|
+
if str(args.debug_level).upper() not in Logger.level_map.keys():
|
|
173
174
|
Color.pl(
|
|
174
175
|
'{!} {R}error: invalid debug level{R}{W}\r\n')
|
|
175
176
|
Configuration.mandatory()
|
|
176
177
|
|
|
177
|
-
Configuration.debug_level =
|
|
178
|
+
Configuration.debug_level = Logger.level_map.get(str(args.debug_level).upper(), 0)
|
|
179
|
+
Logger.debug_level = Configuration.debug_level
|
|
178
180
|
|
|
179
181
|
Logger.pl(' {C}min debug level:{O} %s{W}' % str(args.debug_level).upper())
|
|
180
182
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from .libs.color import Color
|
|
6
|
+
from .libs.scriptlocation import ScriptLocation
|
|
6
7
|
|
|
7
8
|
try:
|
|
8
9
|
from .config import Configuration
|
|
@@ -15,7 +16,6 @@ import frida
|
|
|
15
16
|
import json
|
|
16
17
|
import base64
|
|
17
18
|
import os
|
|
18
|
-
import threading
|
|
19
19
|
import signal
|
|
20
20
|
import sys
|
|
21
21
|
import re
|
|
@@ -29,43 +29,6 @@ from .libs.logger import Logger
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class Fusion(object):
|
|
32
|
-
class ScriptLocation(object):
|
|
33
|
-
def __init__(self, file_name: str = "<unknown>", function_name: str = "<unknown>", line: str = "<unknown>"):
|
|
34
|
-
self.file_name = file_name if file_name is not None else "<unknown>"
|
|
35
|
-
self.function_name = function_name if function_name is not None else "<unknown>"
|
|
36
|
-
self.line = str(line) if line is not None else "<unknown>"
|
|
37
|
-
|
|
38
|
-
def get_int_line(self):
|
|
39
|
-
try:
|
|
40
|
-
return int(self.line)
|
|
41
|
-
except:
|
|
42
|
-
return 0
|
|
43
|
-
|
|
44
|
-
def __str__(self):
|
|
45
|
-
return f"{self.file_name}:{self.line} ({self.function_name})"
|
|
46
|
-
|
|
47
|
-
def __repr__(self):
|
|
48
|
-
return str(self)
|
|
49
|
-
|
|
50
|
-
@staticmethod
|
|
51
|
-
def parse_from_dict(data: dict):
|
|
52
|
-
|
|
53
|
-
file_name = Path(data.get("file_name", "unknown")).name
|
|
54
|
-
function_name = data.get("function_name", "unknown")
|
|
55
|
-
line = str(data.get("line", -1))
|
|
56
|
-
|
|
57
|
-
if 'fileName' in data.keys():
|
|
58
|
-
file_name = Path(data.get("fileName", file_name)).name
|
|
59
|
-
|
|
60
|
-
if 'lineNumber' in data.keys():
|
|
61
|
-
line = str(data.get("lineNumber", line))
|
|
62
|
-
|
|
63
|
-
return Fusion.ScriptLocation(
|
|
64
|
-
file_name=file_name,
|
|
65
|
-
function_name=function_name,
|
|
66
|
-
line=line
|
|
67
|
-
)
|
|
68
|
-
|
|
69
32
|
running = True
|
|
70
33
|
debug = True
|
|
71
34
|
print_timestamp = False
|
|
@@ -87,7 +50,12 @@ class Fusion(object):
|
|
|
87
50
|
t.start()
|
|
88
51
|
|
|
89
52
|
def signal_handler(self, sig, frame):
|
|
53
|
+
if Logger.debug_level <= 2:
|
|
54
|
+
Logger.debug_level = 2
|
|
55
|
+
|
|
56
|
+
Fusion.running = False
|
|
90
57
|
Logger.pl('\n{!} {O}interrupted, shutting down...{W}\n')
|
|
58
|
+
time.sleep(0.5)
|
|
91
59
|
self.done.set()
|
|
92
60
|
|
|
93
61
|
def wait(self):
|
|
@@ -117,17 +85,17 @@ class Fusion(object):
|
|
|
117
85
|
|
|
118
86
|
return self.device
|
|
119
87
|
|
|
120
|
-
def translate_location(self, location: dict) ->
|
|
88
|
+
def translate_location(self, location: dict) -> ScriptLocation:
|
|
121
89
|
if location is None or not isinstance(location, dict):
|
|
122
|
-
return
|
|
90
|
+
return ScriptLocation()
|
|
123
91
|
|
|
124
|
-
loc =
|
|
92
|
+
loc = ScriptLocation.parse_from_dict(location)
|
|
125
93
|
|
|
126
94
|
if loc.file_name != "fusion_bundle.js":
|
|
127
95
|
return loc
|
|
128
96
|
|
|
129
97
|
return next(iter([
|
|
130
|
-
|
|
98
|
+
ScriptLocation(
|
|
131
99
|
file_name=k,
|
|
132
100
|
function_name=loc.function_name,
|
|
133
101
|
line=str(1 + loc.get_int_line() - v[0])
|
|
@@ -136,6 +104,25 @@ class Fusion(object):
|
|
|
136
104
|
if v[0] <= loc.get_int_line() <= v[1]
|
|
137
105
|
]), loc)
|
|
138
106
|
|
|
107
|
+
@classmethod
|
|
108
|
+
def print_message(cls, level: str = "*", message: str = "",
|
|
109
|
+
script_location: ScriptLocation = None):
|
|
110
|
+
|
|
111
|
+
if Fusion.running is False and Logger.debug_level >= 2:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
if script_location is None:
|
|
115
|
+
script_location = ScriptLocation(
|
|
116
|
+
file_name=Fusion._script_name
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
Logger.print_message(
|
|
120
|
+
level=level,
|
|
121
|
+
message=message,
|
|
122
|
+
script_location=script_location,
|
|
123
|
+
filename_col_len=Fusion.max_filename
|
|
124
|
+
)
|
|
125
|
+
|
|
139
126
|
def load_all_scripts(self):
|
|
140
127
|
self.script_trace = {}
|
|
141
128
|
offset = 1
|
|
@@ -200,6 +187,8 @@ class Fusion(object):
|
|
|
200
187
|
if len(file_name) > Fusion.max_filename:
|
|
201
188
|
Fusion.max_filename = len(file_name)
|
|
202
189
|
|
|
190
|
+
Logger.filename_col_len = Fusion.max_filename
|
|
191
|
+
|
|
203
192
|
try:
|
|
204
193
|
s = self.session.create_script(src, name="fusion_bundle")
|
|
205
194
|
s.on("message", self.make_handler("fusion_bundle.js")) # register the message handler
|
|
@@ -247,9 +236,13 @@ class Fusion(object):
|
|
|
247
236
|
|
|
248
237
|
def make_handler(self, script_name):
|
|
249
238
|
def handler(message, payload):
|
|
239
|
+
|
|
240
|
+
if not Fusion.running:
|
|
241
|
+
return
|
|
242
|
+
|
|
250
243
|
if message["type"] == "send":
|
|
251
244
|
try:
|
|
252
|
-
script_location =
|
|
245
|
+
script_location = ScriptLocation()
|
|
253
246
|
jData = message.get("payload", {})
|
|
254
247
|
if isinstance(jData, str):
|
|
255
248
|
jData = json.loads(message["payload"])
|
|
@@ -285,10 +278,10 @@ class Fusion(object):
|
|
|
285
278
|
msg = base64.b64decode(msg).decode("UTF-8")
|
|
286
279
|
except:
|
|
287
280
|
pass
|
|
288
|
-
self.
|
|
281
|
+
self.print_message(mLevel, msg, script_location=script_location)
|
|
289
282
|
|
|
290
283
|
elif mType == "key_value_data":
|
|
291
|
-
self.
|
|
284
|
+
self.print_message("V", "RAW JSON:\n %s" % (
|
|
292
285
|
json.dumps(jData, indent=4).replace("\n", "\n ")
|
|
293
286
|
), script_location=script_location)
|
|
294
287
|
|
|
@@ -318,7 +311,7 @@ class Fusion(object):
|
|
|
318
311
|
|
|
319
312
|
# Legacy
|
|
320
313
|
elif mType == "data":
|
|
321
|
-
self.
|
|
314
|
+
self.print_message("V", "RAW JSON:\n %s" % (
|
|
322
315
|
json.dumps(jData, indent=4).replace("\n", "\n ")
|
|
323
316
|
), script_location=script_location)
|
|
324
317
|
|
|
@@ -342,19 +335,19 @@ class Fusion(object):
|
|
|
342
335
|
|
|
343
336
|
elif mType == "java-uncaught":
|
|
344
337
|
self.insert_history('frida', json.dumps(jData))
|
|
345
|
-
self.
|
|
338
|
+
self.print_message("E", jData.get('stack', ''), script_location=script_location)
|
|
346
339
|
|
|
347
340
|
else:
|
|
348
|
-
self.
|
|
341
|
+
self.print_message(mLevel, message, script_location=script_location)
|
|
349
342
|
|
|
350
343
|
except Exception as err:
|
|
351
|
-
script_location =
|
|
352
|
-
self.
|
|
353
|
-
self.
|
|
344
|
+
script_location = ScriptLocation(file_name=Fusion._script_name)
|
|
345
|
+
self.print_message("E", message, script_location=script_location)
|
|
346
|
+
self.print_message("E", payload, script_location=script_location)
|
|
354
347
|
self.print_exception(err)
|
|
355
348
|
|
|
356
349
|
else:
|
|
357
|
-
script_location =
|
|
350
|
+
script_location = ScriptLocation.parse_from_dict(message)
|
|
358
351
|
try:
|
|
359
352
|
if message["type"] == "error":
|
|
360
353
|
description = message.get('description', '')
|
|
@@ -388,18 +381,18 @@ class Fusion(object):
|
|
|
388
381
|
"stack": stack
|
|
389
382
|
}))
|
|
390
383
|
|
|
391
|
-
self.
|
|
392
|
-
|
|
384
|
+
self.print_message("F", description + stack,
|
|
385
|
+
script_location=script_location)
|
|
393
386
|
Fusion.running = False
|
|
394
387
|
time.sleep(0.2)
|
|
395
388
|
Logger.pl('\n{+} {O}Exiting...{O}{W}')
|
|
396
389
|
self.done.set()
|
|
397
390
|
else:
|
|
398
|
-
self.
|
|
399
|
-
self.
|
|
391
|
+
self.print_message("I", message, script_location=script_location)
|
|
392
|
+
self.print_message("I", payload, script_location=script_location)
|
|
400
393
|
except:
|
|
401
|
-
self.
|
|
402
|
-
self.
|
|
394
|
+
self.print_message("I", message, script_location=script_location)
|
|
395
|
+
self.print_message("I", payload, script_location=script_location)
|
|
403
396
|
|
|
404
397
|
return handler
|
|
405
398
|
|
|
@@ -417,7 +410,7 @@ class Fusion(object):
|
|
|
417
410
|
self.done.set()
|
|
418
411
|
|
|
419
412
|
def _raise_key_value_event(self,
|
|
420
|
-
script_location:
|
|
413
|
+
script_location: ScriptLocation = None,
|
|
421
414
|
stack_trace: str = None,
|
|
422
415
|
module: str = None,
|
|
423
416
|
received_data: dict = None):
|
|
@@ -430,10 +423,10 @@ class Fusion(object):
|
|
|
430
423
|
received_data=received_data
|
|
431
424
|
)
|
|
432
425
|
except Exception as e:
|
|
433
|
-
self.
|
|
426
|
+
self.print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
|
|
434
427
|
|
|
435
428
|
def _raise_data_event(self,
|
|
436
|
-
script_location:
|
|
429
|
+
script_location: ScriptLocation = None,
|
|
437
430
|
stack_trace: str = None,
|
|
438
431
|
received_data: str = None):
|
|
439
432
|
for m in self._modules:
|
|
@@ -444,7 +437,7 @@ class Fusion(object):
|
|
|
444
437
|
received_data=received_data
|
|
445
438
|
)
|
|
446
439
|
except Exception as e:
|
|
447
|
-
self.
|
|
440
|
+
self.print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
|
|
448
441
|
|
|
449
442
|
@classmethod
|
|
450
443
|
def insert_history(cls, source: str, data: str, stack_trace: str = ''):
|
|
@@ -462,81 +455,20 @@ class Fusion(object):
|
|
|
462
455
|
kwargs = Fusion._db_jobs.get()
|
|
463
456
|
db.insert_history(**kwargs)
|
|
464
457
|
|
|
465
|
-
@classmethod
|
|
466
|
-
def _print_message(cls, level: str = "*", message: str = "",
|
|
467
|
-
script_location: Fusion.ScriptLocation = None):
|
|
468
|
-
|
|
469
|
-
if not Fusion.running and not Configuration.debug_level >= 2:
|
|
470
|
-
return
|
|
471
|
-
|
|
472
|
-
if level is None:
|
|
473
|
-
level = "*"
|
|
474
|
-
|
|
475
|
-
dbg_tag = next(iter([
|
|
476
|
-
k
|
|
477
|
-
for k in Color.level_map.keys()
|
|
478
|
-
if level.upper() == k
|
|
479
|
-
]), Color.level_tag.get(level, "I"))
|
|
480
|
-
|
|
481
|
-
dbg_idx = Color.level_map.get(dbg_tag, 0)
|
|
482
|
-
|
|
483
|
-
if Configuration.debug_level > dbg_idx:
|
|
484
|
-
return
|
|
485
|
-
|
|
486
|
-
prefix = ""
|
|
487
|
-
if Configuration.print_timestamp:
|
|
488
|
-
ts = datetime.now()
|
|
489
|
-
stamp = f"{ts:%H:%M:%S}.{int(ts.microsecond / 1000):03d}"
|
|
490
|
-
prefix += f"\033[2m{stamp.ljust(13)}{Color.color_reset}"
|
|
491
|
-
|
|
492
|
-
fg_color = Color.color_level[dbg_idx]
|
|
493
|
-
tag_color = Color.color_tags[dbg_idx]
|
|
494
|
-
|
|
495
|
-
if script_location is None:
|
|
496
|
-
script_location = Fusion.ScriptLocation(
|
|
497
|
-
file_name=Fusion._script_name
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
if script_location.file_name == "frida/node_modules/frida-java-bridge/lib/class-factory.js":
|
|
501
|
-
file_name = "frida/.../class-factory.js"
|
|
502
|
-
else:
|
|
503
|
-
file_name = str(Path(script_location.file_name).name)
|
|
504
|
-
|
|
505
|
-
prefix += (f"{fg_color}{file_name.rjust(Fusion.max_filename)}"
|
|
506
|
-
f"{Color.color_reset}\033[2m:{str(script_location.line).ljust(10)}"
|
|
507
|
-
f"{Color.color_reset} ")
|
|
508
|
-
prefix_len = len(Color.escape_ansi(prefix))
|
|
509
|
-
|
|
510
|
-
f_message = ""
|
|
511
|
-
if message is None:
|
|
512
|
-
message = ""
|
|
513
|
-
|
|
514
|
-
if isinstance(message, dict):
|
|
515
|
-
try:
|
|
516
|
-
message = json.dumps(message, ident=4)
|
|
517
|
-
except:
|
|
518
|
-
message = str(message)
|
|
519
|
-
|
|
520
|
-
for line in message.split("\n"):
|
|
521
|
-
if f_message == "":
|
|
522
|
-
f_message += (f"{prefix}{tag_color} {dbg_tag} {Color.color_reset} "
|
|
523
|
-
f"{fg_color}{line}{Color.color_reset}")
|
|
524
|
-
else:
|
|
525
|
-
f_message += (f"\n{''.rjust(prefix_len)}{tag_color} {dbg_tag} "
|
|
526
|
-
f"{Color.color_reset} {fg_color}{line}{Color.color_reset}")
|
|
527
|
-
|
|
528
|
-
Logger.pl(f_message)
|
|
529
|
-
|
|
530
458
|
@classmethod
|
|
531
459
|
def print_exception(cls, err):
|
|
532
|
-
Logger.pl('\n{!} {R}Error:{O} %s{W}' % str(err))
|
|
533
|
-
Logger.pl('\n{!} {O}Full stack trace below')
|
|
534
460
|
from traceback import format_exc
|
|
535
|
-
err_txt =
|
|
461
|
+
err_txt = '{R}Error:{O} %s{W}' % str(err)
|
|
462
|
+
err_txt += '{O}Full stack trace below'
|
|
463
|
+
err_txt += format_exc().strip()
|
|
464
|
+
|
|
536
465
|
err_txt = err_txt.replace('\n', '\n{W}{!} {W} ')
|
|
537
466
|
err_txt = err_txt.replace(' File', '{W}{D}File')
|
|
538
467
|
err_txt = err_txt.replace(' Exception: ', '{R}Exception: {O}')
|
|
539
|
-
|
|
468
|
+
cls.print_message(
|
|
469
|
+
level="E",
|
|
470
|
+
message=Color.s('{!} ' + err_txt)
|
|
471
|
+
)
|
|
540
472
|
|
|
541
473
|
@classmethod
|
|
542
474
|
def check_frida_native_exception(cls, evt: dict) -> bool:
|
|
@@ -52,21 +52,6 @@ class Color(object):
|
|
|
52
52
|
"\033[31m\033[48;2;60;60;60m", # Fatal
|
|
53
53
|
]
|
|
54
54
|
|
|
55
|
-
level_map = {
|
|
56
|
-
"V": 0,
|
|
57
|
-
"D": 1,
|
|
58
|
-
"I": 2,
|
|
59
|
-
"W": 3,
|
|
60
|
-
"E": 4,
|
|
61
|
-
"F": 5,
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
level_tag = {
|
|
65
|
-
"*": "D",
|
|
66
|
-
"+": "I",
|
|
67
|
-
"-": "W",
|
|
68
|
-
}
|
|
69
|
-
|
|
70
55
|
last_sameline_length = 0
|
|
71
56
|
|
|
72
57
|
@staticmethod
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
import base64
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .scriptlocation import ScriptLocation
|
|
10
|
+
from ..libs.color import Color
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Logger(object):
|
|
14
|
+
''' Helper object for easily printing colored text to the terminal. '''
|
|
15
|
+
|
|
16
|
+
out_file = ''
|
|
17
|
+
debug_level = 0
|
|
18
|
+
print_timestamp = True
|
|
19
|
+
filename_col_len = 0
|
|
20
|
+
|
|
21
|
+
level_map = {
|
|
22
|
+
"V": 0,
|
|
23
|
+
"D": 1,
|
|
24
|
+
"I": 2,
|
|
25
|
+
"W": 3,
|
|
26
|
+
"E": 4,
|
|
27
|
+
"F": 5,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
level_tag = {
|
|
31
|
+
"*": "D",
|
|
32
|
+
"+": "I",
|
|
33
|
+
"-": "W",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def pl(text):
|
|
38
|
+
'''Prints text using colored format with trailing new line.'''
|
|
39
|
+
Color.pl(text)
|
|
40
|
+
|
|
41
|
+
if Logger.out_file != '':
|
|
42
|
+
try:
|
|
43
|
+
with open(Logger.out_file, "a") as text_file:
|
|
44
|
+
text_file.write(Color.sc(text) + '\n')
|
|
45
|
+
except:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def pl_file(text):
|
|
50
|
+
'''Prints text using colored format with trailing new line.'''
|
|
51
|
+
|
|
52
|
+
if Logger.out_file != '':
|
|
53
|
+
try:
|
|
54
|
+
with open(Logger.out_file, "a") as text_file:
|
|
55
|
+
text_file.write(Color.escape_ansi(Color.sc(text)) + '\n')
|
|
56
|
+
except:
|
|
57
|
+
Color.pl(text)
|
|
58
|
+
else:
|
|
59
|
+
Color.pl(text)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def _get_caller_info(cls, stack_index: int = 1) -> ScriptLocation:
|
|
63
|
+
"""Retrieves information about the calling script, function, and line number."""
|
|
64
|
+
# inspect.stack() returns a list of frame records.
|
|
65
|
+
# Each frame record is a tuple containing:
|
|
66
|
+
# (frame object, filename, line number, function name, list of lines of context, index of current line in context)
|
|
67
|
+
|
|
68
|
+
# The first element (index 0) is the current function (get_caller_info).
|
|
69
|
+
# The second element (index 1) is the caller of get_caller_info.
|
|
70
|
+
caller_frame = inspect.stack()[stack_index]
|
|
71
|
+
|
|
72
|
+
# Extract information from the caller's frame
|
|
73
|
+
filename = caller_frame.filename
|
|
74
|
+
line_number = caller_frame.lineno
|
|
75
|
+
function_name = caller_frame.function
|
|
76
|
+
|
|
77
|
+
# Optionally, get the base name of the script for cleaner output
|
|
78
|
+
script_name = Path(filename).name
|
|
79
|
+
|
|
80
|
+
return ScriptLocation(
|
|
81
|
+
file_name=script_name,
|
|
82
|
+
function_name=function_name,
|
|
83
|
+
line=str(line_number)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def json_serial(obj):
|
|
88
|
+
"""JSON serializer for objects not serializable by default json code"""
|
|
89
|
+
|
|
90
|
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
91
|
+
# obj = obj.astimezone(datetime.timezone(datetime.timedelta(hours=0), 'Z'))
|
|
92
|
+
return obj.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
93
|
+
|
|
94
|
+
if isinstance(obj, bytes):
|
|
95
|
+
return base64.b64encode(obj).decode("UTF-8")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
return str(obj)
|
|
99
|
+
except:
|
|
100
|
+
return f"[ERROR] Type %s not serializable{type(obj)}"
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def print_message(cls, level: str = "*", message: str = "",
|
|
104
|
+
script_location: ScriptLocation = None, filename_col_len: int = 26):
|
|
105
|
+
|
|
106
|
+
if level is None:
|
|
107
|
+
level = "*"
|
|
108
|
+
|
|
109
|
+
dbg_tag = next(iter([
|
|
110
|
+
k
|
|
111
|
+
for k in Logger.level_map.keys()
|
|
112
|
+
if level.upper() == k
|
|
113
|
+
]), Logger.level_tag.get(level, "I"))
|
|
114
|
+
|
|
115
|
+
dbg_idx = Logger.level_map.get(dbg_tag, 0)
|
|
116
|
+
|
|
117
|
+
if Logger.debug_level > dbg_idx:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
if filename_col_len < 26:
|
|
121
|
+
filename_col_len = 26
|
|
122
|
+
|
|
123
|
+
if Logger.filename_col_len > filename_col_len:
|
|
124
|
+
filename_col_len = Logger.filename_col_len
|
|
125
|
+
|
|
126
|
+
prefix = ""
|
|
127
|
+
if Logger.print_timestamp:
|
|
128
|
+
ts = datetime.datetime.now()
|
|
129
|
+
stamp = f"{ts:%H:%M:%S}.{int(ts.microsecond / 1000):03d}"
|
|
130
|
+
prefix += f"\033[2m{stamp.ljust(13)}{Color.color_reset}"
|
|
131
|
+
|
|
132
|
+
fg_color = Color.color_level[dbg_idx]
|
|
133
|
+
tag_color = Color.color_tags[dbg_idx]
|
|
134
|
+
|
|
135
|
+
if script_location is None:
|
|
136
|
+
script_location = cls._get_caller_info(stack_index=2)
|
|
137
|
+
|
|
138
|
+
if script_location.file_name == "frida/node_modules/frida-java-bridge/lib/class-factory.js":
|
|
139
|
+
file_name = "frida/.../class-factory.js"
|
|
140
|
+
else:
|
|
141
|
+
file_name = str(Path(script_location.file_name).name)
|
|
142
|
+
|
|
143
|
+
if len(file_name) > filename_col_len:
|
|
144
|
+
file_name = f"{file_name[0:filename_col_len-3]}..."
|
|
145
|
+
|
|
146
|
+
prefix += (f"{fg_color}{file_name.rjust(filename_col_len)}"
|
|
147
|
+
f"{Color.color_reset}\033[2m:{str(script_location.line).ljust(10)}"
|
|
148
|
+
f"{Color.color_reset} ")
|
|
149
|
+
prefix_len = len(Color.escape_ansi(prefix))
|
|
150
|
+
|
|
151
|
+
f_message = ""
|
|
152
|
+
if message is None:
|
|
153
|
+
message = ""
|
|
154
|
+
|
|
155
|
+
if isinstance(message, dict):
|
|
156
|
+
try:
|
|
157
|
+
message = json.dumps(message, ident=4, default=Logger.json_serial)
|
|
158
|
+
except:
|
|
159
|
+
message = str(message)
|
|
160
|
+
|
|
161
|
+
for line in message.split("\n"):
|
|
162
|
+
if f_message == "":
|
|
163
|
+
f_message += (f"{prefix}{tag_color} {dbg_tag} {Color.color_reset} "
|
|
164
|
+
f"{fg_color}{line}{Color.color_reset}")
|
|
165
|
+
else:
|
|
166
|
+
f_message += (f"\n{''.rjust(prefix_len)}{tag_color} {dbg_tag} "
|
|
167
|
+
f"{Color.color_reset} {fg_color}{line}{Color.color_reset}")
|
|
168
|
+
|
|
169
|
+
Logger.pl(f_message)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ScriptLocation(object):
|
|
5
|
+
def __init__(self, file_name: str = "<unknown>", function_name: str = "<unknown>", line: str = "<unknown>"):
|
|
6
|
+
self.file_name = file_name if file_name is not None else "<unknown>"
|
|
7
|
+
self.function_name = function_name if function_name is not None else "<unknown>"
|
|
8
|
+
self.line = str(line) if line is not None else "<unknown>"
|
|
9
|
+
|
|
10
|
+
def get_int_line(self):
|
|
11
|
+
try:
|
|
12
|
+
return int(self.line)
|
|
13
|
+
except Exception:
|
|
14
|
+
return 0
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return f"{self.file_name}:{self.line} ({self.function_name})"
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return str(self)
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def parse_from_dict(data: dict):
|
|
24
|
+
|
|
25
|
+
file_name = Path(data.get("file_name", "unknown")).name
|
|
26
|
+
function_name = data.get("function_name", "unknown")
|
|
27
|
+
line = str(data.get("line", -1))
|
|
28
|
+
|
|
29
|
+
if 'fileName' in data.keys():
|
|
30
|
+
file_name = Path(data.get("fileName", file_name)).name
|
|
31
|
+
|
|
32
|
+
if 'lineNumber' in data.keys():
|
|
33
|
+
line = str(data.get("lineNumber", line))
|
|
34
|
+
|
|
35
|
+
return ScriptLocation(
|
|
36
|
+
file_name=file_name,
|
|
37
|
+
function_name=function_name,
|
|
38
|
+
line=line
|
|
39
|
+
)
|
|
@@ -5,12 +5,9 @@ import string
|
|
|
5
5
|
|
|
6
6
|
from frida_fusion.libs.logger import Logger
|
|
7
7
|
from frida_fusion.libs.database import Database
|
|
8
|
+
from frida_fusion.libs.scriptlocation import ScriptLocation
|
|
8
9
|
from frida_fusion.module import ModuleBase
|
|
9
10
|
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from frida_fusion.fusion import Fusion # só no type checker
|
|
13
|
-
|
|
14
11
|
|
|
15
12
|
class Crypto(ModuleBase):
|
|
16
13
|
class CryptoDB(Database):
|
|
@@ -287,7 +284,7 @@ class Crypto(ModuleBase):
|
|
|
287
284
|
]
|
|
288
285
|
|
|
289
286
|
def key_value_event(self,
|
|
290
|
-
script_location:
|
|
287
|
+
script_location: ScriptLocation = None,
|
|
291
288
|
stack_trace: str = None,
|
|
292
289
|
module: str = None,
|
|
293
290
|
received_data: dict = None
|
|
@@ -319,6 +316,12 @@ class Crypto(ModuleBase):
|
|
|
319
316
|
received_data.get('output', ''),
|
|
320
317
|
stack_trace=stack_trace)
|
|
321
318
|
|
|
319
|
+
Logger.print_message(
|
|
320
|
+
level="D",
|
|
321
|
+
message=f"Cipher doFinal received\n{stack_trace}",
|
|
322
|
+
script_location=script_location
|
|
323
|
+
)
|
|
324
|
+
|
|
322
325
|
elif module == "messageDigest.update":
|
|
323
326
|
hashcode = received_data.get('hashcode', None)
|
|
324
327
|
algorithm = received_data.get('algorithm', None)
|
|
@@ -332,10 +335,28 @@ class Crypto(ModuleBase):
|
|
|
332
335
|
bOutput = received_data.get('output', None)
|
|
333
336
|
self._crypto_db.insert_digest(hashcode, algorithm, bInput, bOutput, stack_trace=stack_trace)
|
|
334
337
|
|
|
338
|
+
hash_hex = ""
|
|
339
|
+
if bOutput is not None:
|
|
340
|
+
try:
|
|
341
|
+
if isinstance(bOutput, bytes):
|
|
342
|
+
hash_hex = ''.join('{:02x}'.format(b) for b in bOutput)
|
|
343
|
+
else:
|
|
344
|
+
hash_hex = ''.join('{:02x}'.format(b) for b in base64.b64decode(bOutput))
|
|
345
|
+
except:
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
# Do not print TLS certificate verification hash
|
|
349
|
+
if 'com.android.org.conscrypt.ConscryptEngine.verifyCertificateChain' not in stack_trace:
|
|
350
|
+
Logger.print_message(
|
|
351
|
+
level="D",
|
|
352
|
+
message=f"Message digest\nAlgorithm: {algorithm}\nHash: {hash_hex}\n{stack_trace}",
|
|
353
|
+
script_location=script_location
|
|
354
|
+
)
|
|
355
|
+
|
|
335
356
|
return True
|
|
336
357
|
|
|
337
358
|
def data_event(self,
|
|
338
|
-
script_location:
|
|
359
|
+
script_location: ScriptLocation = None,
|
|
339
360
|
stack_trace: str = None,
|
|
340
361
|
received_data: str = None
|
|
341
362
|
) -> bool:
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import errno
|
|
2
2
|
import os.path
|
|
3
|
-
import tempfile
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from frida_fusion.libs.logger import Logger
|
|
5
|
+
from frida_fusion.libs.scriptlocation import ScriptLocation
|
|
6
6
|
from frida_fusion.module import ModuleBase
|
|
7
7
|
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from frida_fusion.fusion import Fusion # só no type checker
|
|
12
|
-
|
|
13
8
|
|
|
14
9
|
class TlsUnpinning(ModuleBase):
|
|
15
10
|
|
|
@@ -49,7 +44,7 @@ class TlsUnpinning(ModuleBase):
|
|
|
49
44
|
]
|
|
50
45
|
|
|
51
46
|
def key_value_event(self,
|
|
52
|
-
script_location:
|
|
47
|
+
script_location: ScriptLocation = None,
|
|
53
48
|
stack_trace: str = None,
|
|
54
49
|
module: str = None,
|
|
55
50
|
received_data: dict = None
|
|
@@ -57,7 +52,7 @@ class TlsUnpinning(ModuleBase):
|
|
|
57
52
|
return True
|
|
58
53
|
|
|
59
54
|
def data_event(self,
|
|
60
|
-
script_location:
|
|
55
|
+
script_location: ScriptLocation = None,
|
|
61
56
|
stack_trace: str = None,
|
|
62
57
|
received_data: str = None
|
|
63
58
|
) -> bool:
|
|
@@ -20,6 +20,7 @@ frida_fusion/libs/color.py
|
|
|
20
20
|
frida_fusion/libs/database.py
|
|
21
21
|
frida_fusion/libs/helpers.js
|
|
22
22
|
frida_fusion/libs/logger.py
|
|
23
|
+
frida_fusion/libs/scriptlocation.py
|
|
23
24
|
frida_fusion/modules/__init__.py
|
|
24
25
|
frida_fusion/modules/crypto/__init__.py
|
|
25
26
|
frida_fusion/modules/crypto/crypto.js
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
|
-
# -*- coding: UTF-8 -*-
|
|
3
|
-
|
|
4
|
-
import sys
|
|
5
|
-
from ..libs.color import Color
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Logger(object):
|
|
9
|
-
''' Helper object for easily printing colored text to the terminal. '''
|
|
10
|
-
|
|
11
|
-
out_file = ''
|
|
12
|
-
|
|
13
|
-
@staticmethod
|
|
14
|
-
def pl(text):
|
|
15
|
-
'''Prints text using colored format with trailing new line.'''
|
|
16
|
-
Color.pl(text)
|
|
17
|
-
|
|
18
|
-
if Logger.out_file != '':
|
|
19
|
-
try:
|
|
20
|
-
with open(Logger.out_file, "a") as text_file:
|
|
21
|
-
text_file.write(Color.sc(text) + '\n')
|
|
22
|
-
except:
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
@staticmethod
|
|
26
|
-
def pl_file(text):
|
|
27
|
-
'''Prints text using colored format with trailing new line.'''
|
|
28
|
-
|
|
29
|
-
if Logger.out_file != '':
|
|
30
|
-
try:
|
|
31
|
-
with open(Logger.out_file, "a") as text_file:
|
|
32
|
-
text_file.write(Color.escape_ansi(Color.sc(text)) + '\n')
|
|
33
|
-
except:
|
|
34
|
-
Color.pl(text)
|
|
35
|
-
else:
|
|
36
|
-
Color.pl(text)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|