frida-fusion 0.1.6__tar.gz → 0.1.8__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.

Files changed (32) hide show
  1. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/PKG-INFO +1 -1
  2. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/__meta__.py +2 -2
  3. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/config.py +4 -2
  4. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/fusion.py +73 -132
  5. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/libs/color.py +0 -15
  6. frida_fusion-0.1.8/frida_fusion/libs/logger.py +169 -0
  7. frida_fusion-0.1.8/frida_fusion/libs/scriptlocation.py +39 -0
  8. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/modules/crypto/crypto.py +27 -6
  9. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py +3 -8
  10. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion.egg-info/PKG-INFO +1 -1
  11. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion.egg-info/SOURCES.txt +1 -0
  12. frida_fusion-0.1.6/frida_fusion/libs/logger.py +0 -36
  13. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/LICENSE +0 -0
  14. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/README.md +0 -0
  15. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/__init__.py +0 -0
  16. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/__main__.py +0 -0
  17. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/args.py +0 -0
  18. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/libs/__init__.py +0 -0
  19. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/libs/database.py +0 -0
  20. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/libs/helpers.js +0 -0
  21. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/module.py +0 -0
  22. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/modules/__init__.py +0 -0
  23. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/modules/crypto/__init__.py +0 -0
  24. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/modules/crypto/crypto.js +0 -0
  25. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion/modules/tls_unpinning/__init__.py +0 -0
  26. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion.egg-info/dependency_links.txt +0 -0
  27. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion.egg-info/entry_points.txt +0 -0
  28. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion.egg-info/requires.txt +0 -0
  29. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/frida_fusion.egg-info/top_level.txt +0 -0
  30. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/pyproject.toml +0 -0
  31. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/setup.cfg +0 -0
  32. {frida_fusion-0.1.6 → frida_fusion-0.1.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frida-fusion
3
- Version: 0.1.6
3
+ Version: 0.1.8
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>
@@ -1,8 +1,8 @@
1
- __version__ = '0.1.6'
1
+ __version__ = '0.1.8'
2
2
  __title__ = "Frida Fusion"
3
3
  __description__ = "📱 frida-fusion - runtime mobile exploration"
4
4
  __url__ = "https://github.com/helviojunior/frida-fusion"
5
- __build__ = 0x9f83cd2
5
+ __build__ = 0x36275f5
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 Color.level_map.keys():
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 = Color.level_map.get(str(args.debug_level).upper(), 0)
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) -> Fusion.ScriptLocation:
88
+ def translate_location(self, location: dict) -> ScriptLocation:
121
89
  if location is None or not isinstance(location, dict):
122
- return Fusion.ScriptLocation()
90
+ return ScriptLocation()
123
91
 
124
- loc = Fusion.ScriptLocation.parse_from_dict(location)
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
- Fusion.ScriptLocation(
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 = Fusion.ScriptLocation()
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._print_message(mLevel, msg, script_location=script_location)
281
+ self.print_message(mLevel, msg, script_location=script_location)
289
282
 
290
283
  elif mType == "key_value_data":
291
- self._print_message("V", "RAW JSON:\n %s" % (
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._print_message("V", "RAW JSON:\n %s" % (
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._print_message("E", jData.get('stack', ''), script_location=script_location)
338
+ self.print_message("E", jData.get('stack', ''), script_location=script_location)
346
339
 
347
340
  else:
348
- self._print_message(mLevel, message, script_location=script_location)
341
+ self.print_message(mLevel, message, script_location=script_location)
349
342
 
350
343
  except Exception as err:
351
- script_location = Fusion.ScriptLocation(file_name=Fusion._script_name)
352
- self._print_message("E", message, script_location=script_location)
353
- self._print_message("E", payload, script_location=script_location)
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 = Fusion.ScriptLocation.parse_from_dict(message)
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._print_message("F", description + stack,
392
- script_location=script_location)
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._print_message("I", message, script_location=script_location)
399
- self._print_message("I", payload, script_location=script_location)
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._print_message("I", message, script_location=script_location)
402
- self._print_message("I", payload, script_location=script_location)
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: Fusion.ScriptLocation = None,
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,13 @@ class Fusion(object):
430
423
  received_data=received_data
431
424
  )
432
425
  except Exception as e:
433
- self._print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
426
+ if Configuration.debug_level >= 2:
427
+ self.print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
428
+ else:
429
+ self.print_exception(e)
434
430
 
435
431
  def _raise_data_event(self,
436
- script_location: Fusion.ScriptLocation = None,
432
+ script_location: ScriptLocation = None,
437
433
  stack_trace: str = None,
438
434
  received_data: str = None):
439
435
  for m in self._modules:
@@ -444,7 +440,10 @@ class Fusion(object):
444
440
  received_data=received_data
445
441
  )
446
442
  except Exception as e:
447
- self._print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
443
+ if Configuration.debug_level >= 2:
444
+ self.print_message("E", f"Error resizing event to module {m.name}: {str(e)}")
445
+ else:
446
+ self.print_exception(e)
448
447
 
449
448
  @classmethod
450
449
  def insert_history(cls, source: str, data: str, stack_trace: str = ''):
@@ -462,81 +461,23 @@ class Fusion(object):
462
461
  kwargs = Fusion._db_jobs.get()
463
462
  db.insert_history(**kwargs)
464
463
 
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
464
  @classmethod
531
465
  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
466
  from traceback import format_exc
535
- err_txt = format_exc().strip()
536
- err_txt = err_txt.replace('\n', '\n{W}{!} {W} ')
467
+ err_txt = 'Error:{O} %s{W}' % str(err)
468
+ err_txt += '\n{O}Full stack trace below\n'
469
+ err_txt += format_exc().strip()
470
+
471
+ err_txt = err_txt.replace('\n', '\n{W} ')
537
472
  err_txt = err_txt.replace(' File', '{W}{D}File')
538
473
  err_txt = err_txt.replace(' Exception: ', '{R}Exception: {O}')
539
- Logger.pl('{!} ' + err_txt)
474
+
475
+ Logger.print_message(
476
+ level="E",
477
+ message=Color.s(err_txt),
478
+ filename_col_len=Fusion.max_filename,
479
+ script_location=Logger.get_caller_info(stack_index=2)
480
+ )
540
481
 
541
482
  @classmethod
542
483
  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: "Fusion.ScriptLocation" = None,
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: "Fusion.ScriptLocation" = None,
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: "Fusion.ScriptLocation" = None,
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: "Fusion.ScriptLocation" = None,
55
+ script_location: ScriptLocation = None,
61
56
  stack_trace: str = None,
62
57
  received_data: str = None
63
58
  ) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frida-fusion
3
- Version: 0.1.6
3
+ Version: 0.1.8
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>
@@ -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