frida-fusion 0.1.4__tar.gz → 0.1.6__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 (31) hide show
  1. frida_fusion-0.1.6/LICENSE +25 -0
  2. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/PKG-INFO +65 -3
  3. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/README.md +57 -0
  4. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/__meta__.py +2 -2
  5. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/config.py +21 -9
  6. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/fusion.py +24 -8
  7. frida_fusion-0.1.6/frida_fusion/module.py +272 -0
  8. frida_fusion-0.1.6/frida_fusion/modules/crypto/__init__.py +0 -0
  9. {frida-fusion-0.1.4/frida_fusion/modules → frida_fusion-0.1.6/frida_fusion/modules/crypto}/crypto.py +9 -6
  10. frida_fusion-0.1.6/frida_fusion/modules/tls_unpinning/__init__.py +0 -0
  11. frida_fusion-0.1.6/frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py +66 -0
  12. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion.egg-info/PKG-INFO +65 -3
  13. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion.egg-info/SOURCES.txt +6 -2
  14. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/pyproject.toml +2 -3
  15. frida-fusion-0.1.4/frida_fusion/module.py +0 -116
  16. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/__init__.py +0 -0
  17. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/__main__.py +0 -0
  18. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/args.py +0 -0
  19. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/libs/__init__.py +0 -0
  20. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/libs/color.py +0 -0
  21. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/libs/database.py +0 -0
  22. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/libs/helpers.js +0 -0
  23. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/libs/logger.py +0 -0
  24. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion/modules/__init__.py +0 -0
  25. {frida-fusion-0.1.4/frida_fusion/modules → frida_fusion-0.1.6/frida_fusion/modules/crypto}/crypto.js +0 -0
  26. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion.egg-info/dependency_links.txt +0 -0
  27. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion.egg-info/entry_points.txt +0 -0
  28. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion.egg-info/requires.txt +0 -0
  29. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/frida_fusion.egg-info/top_level.txt +0 -0
  30. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/setup.cfg +0 -0
  31. {frida-fusion-0.1.4 → frida_fusion-0.1.6}/setup.py +0 -0
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2022, Helvio Junior
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: frida-fusion
3
- Version: 0.1.4
3
+ Version: 0.1.6
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>
@@ -12,7 +12,6 @@ Keywords: Frida Fusion,Frida,Frida Scripts,development,red team
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: System Administrators
15
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
15
  Classifier: Natural Language :: English
17
16
  Classifier: Operating System :: OS Independent
18
17
  Classifier: Programming Language :: Python
@@ -27,6 +26,12 @@ Classifier: Topic :: Security
27
26
  Classifier: Topic :: Utilities
28
27
  Requires-Python: <4,>=3.9
29
28
  Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: colorama
31
+ Requires-Dist: requests>=2.23.0
32
+ Requires-Dist: frida>=15.1.17
33
+ Requires-Dist: frida-tools>=10.8.0
34
+ Dynamic: license-file
30
35
 
31
36
  # Frida Fusion
32
37
 
@@ -68,4 +73,61 @@ Modules:
68
73
  pip3 install frida-fusion
69
74
  ```
70
75
 
76
+ ## Module engine
71
77
 
78
+ You can check available modules with `frida-fusion --list-modules` command.
79
+
80
+ ```bash
81
+ frida-fusion --list-modules
82
+
83
+ [ FRIDA ]—o—( FUSION )—o—[ MOBILE TESTS ] // v0.1.4
84
+ > hook your mobile tests with Frida
85
+
86
+
87
+ Available modules
88
+ Module Name : Description
89
+ Crypto : Hook cryptography/hashing functions
90
+ ```
91
+
92
+ ### External modules
93
+
94
+ You can develop or download community modules and load into frida-fusion.
95
+
96
+ To pass to the Frida Fusion the external module path you can use the environment variable `FUSION_MODULES` with the full path of modules
97
+
98
+ At linux:
99
+
100
+ ```bash
101
+ export FUSION_MODULES=/tmp/modules
102
+
103
+ # List all modules
104
+ frida-fusion --list-modules
105
+
106
+ # Using available module
107
+ frida-fusion -f [app_id] -U --script-path . -m [module_name]
108
+ ```
109
+
110
+ At windows:
111
+
112
+ ```bash
113
+ $env:FUSION_MODULES = "C:\extra_mods"
114
+
115
+ # List all modules
116
+ frida-fusion --list-modules
117
+
118
+ # Using available module
119
+ frida-fusion -f [app_id] -U --script-path . -m [module_name]
120
+ ```
121
+
122
+ ### Community modules
123
+
124
+ You can also use one of community developed modules
125
+
126
+ ```bash
127
+ cd /tmp/
128
+ git clone https://github.com/helviojunior/frida-fusion-community-modules
129
+ export FUSION_MODULES=/tmp/frida-fusion-community-modules
130
+
131
+ # List all modules
132
+ frida-fusion --list-modules
133
+ ```
@@ -38,4 +38,61 @@ Modules:
38
38
  pip3 install frida-fusion
39
39
  ```
40
40
 
41
+ ## Module engine
41
42
 
43
+ You can check available modules with `frida-fusion --list-modules` command.
44
+
45
+ ```bash
46
+ frida-fusion --list-modules
47
+
48
+ [ FRIDA ]—o—( FUSION )—o—[ MOBILE TESTS ] // v0.1.4
49
+ > hook your mobile tests with Frida
50
+
51
+
52
+ Available modules
53
+ Module Name : Description
54
+ Crypto : Hook cryptography/hashing functions
55
+ ```
56
+
57
+ ### External modules
58
+
59
+ You can develop or download community modules and load into frida-fusion.
60
+
61
+ To pass to the Frida Fusion the external module path you can use the environment variable `FUSION_MODULES` with the full path of modules
62
+
63
+ At linux:
64
+
65
+ ```bash
66
+ export FUSION_MODULES=/tmp/modules
67
+
68
+ # List all modules
69
+ frida-fusion --list-modules
70
+
71
+ # Using available module
72
+ frida-fusion -f [app_id] -U --script-path . -m [module_name]
73
+ ```
74
+
75
+ At windows:
76
+
77
+ ```bash
78
+ $env:FUSION_MODULES = "C:\extra_mods"
79
+
80
+ # List all modules
81
+ frida-fusion --list-modules
82
+
83
+ # Using available module
84
+ frida-fusion -f [app_id] -U --script-path . -m [module_name]
85
+ ```
86
+
87
+ ### Community modules
88
+
89
+ You can also use one of community developed modules
90
+
91
+ ```bash
92
+ cd /tmp/
93
+ git clone https://github.com/helviojunior/frida-fusion-community-modules
94
+ export FUSION_MODULES=/tmp/frida-fusion-community-modules
95
+
96
+ # List all modules
97
+ frida-fusion --list-modules
98
+ ```
@@ -1,8 +1,8 @@
1
- __version__ = '0.1.4'
1
+ __version__ = '0.1.6'
2
2
  __title__ = "Frida Fusion"
3
3
  __description__ = "📱 frida-fusion - runtime mobile exploration"
4
4
  __url__ = "https://github.com/helviojunior/frida-fusion"
5
- __build__ = 0x63c974a8
5
+ __build__ = 0x9f83cd2
6
6
  __author__ = "Helvio Junior (M4v3r1ck)"
7
7
  __author_email__ = "helvio_junior@hotmail.com"
8
8
  __license__ = "GPL-3.0"
@@ -6,7 +6,7 @@ import sys
6
6
  import signal
7
7
  from pathlib import Path
8
8
 
9
- from .module import Module
9
+ from .module import Module, ModuleManager, InternalModule, ExternalModule
10
10
  from .libs.color import Color
11
11
  from .libs.logger import Logger
12
12
  from .__meta__ import __version__
@@ -71,15 +71,27 @@ class Configuration(object):
71
71
  #show_help = any(['-h' == word for word in sys.argv])
72
72
 
73
73
  if list_modules:
74
- mods = Module.list_modules()
74
+ mods = ModuleManager.list_modules()
75
+
76
+ if len(mods) == 0:
77
+ Color.pl('{!} {R}error: no modules found{R}{W}\r\n')
78
+ sys.exit(1)
79
+
75
80
  max_name = max(iter([
76
81
  len(m.name) + 3
77
82
  for _, m in mods.items()
78
83
  ] + [15]))
79
- Color.pl(f"Available modules")
80
- Color.pl(f" {'Module Name'.ljust(max_name)} : Description")
81
- for _, m in mods.items():
82
- Color.pl(f" {m.name.ljust(max_name)} : {m.description}")
84
+ Color.pl(f"Available internal modules")
85
+ for m in [m for _, m in mods.items() if isinstance(m, InternalModule)]:
86
+ Color.pl(f" {m.safe_name().ljust(max_name)} : {m.description}")
87
+
88
+ Color.pl(f"\nAvailable external modules")
89
+ ext_mods = [m for _, m in mods.items() if isinstance(m, ExternalModule)]
90
+ if len(ext_mods) == 0:
91
+ Color.pl((" No external modules available. You can set {G}FUSION_MODULES{W} environment variable "
92
+ "to set an external modules Path"))
93
+ for m in ext_mods:
94
+ Color.pl(f" {m.safe_name().ljust(max_name)} : {m.description}")
83
95
 
84
96
  print("")
85
97
  sys.exit(0)
@@ -167,19 +179,19 @@ class Configuration(object):
167
179
  Logger.pl(' {C}min debug level:{O} %s{W}' % str(args.debug_level).upper())
168
180
 
169
181
  if args.enabled_modules is not None and isinstance(args.enabled_modules, list):
170
- mods = Module.list_modules()
182
+ mods = ModuleManager.list_modules()
171
183
  for mod in args.enabled_modules:
172
184
  fm = next(iter([
173
185
  m
174
186
  for _, m in mods.items()
175
- if m.name.lower() == mod.lower()
187
+ if m.safe_name() == mod.lower()
176
188
  ]), None)
177
189
  if fm is None:
178
190
  Color.pl(
179
191
  '{!} {R}error: module {O}%s{R} not found{W}\r\n' % mod)
180
192
  sys.exit(1)
181
193
 
182
- name = fm.name.lower()
194
+ name = fm.safe_name()
183
195
  if name not in Configuration.enabled_modules.keys():
184
196
  Configuration.enabled_modules[name] = fm
185
197
 
@@ -216,7 +216,7 @@ class Fusion(object):
216
216
  self.session = self.device.attach(self.pid)
217
217
  self.session.on("detached", self.on_detached)
218
218
 
219
- Logger.pl("{+} Iniciando scripts frida...")
219
+ Logger.pl("{+} Starting frida scripts")
220
220
  self.load_all_scripts()
221
221
  self.device.resume(self.pid)
222
222
 
@@ -227,7 +227,7 @@ class Fusion(object):
227
227
  self.session = self.device.attach(self.pid)
228
228
  self.session.on("detached", self.on_detached)
229
229
 
230
- Logger.pl("{+} Iniciando scripts frida...")
230
+ Logger.pl("{+} Starting frida scripts")
231
231
  self.load_all_scripts()
232
232
  self.device.resume(self.pid)
233
233
 
@@ -242,7 +242,7 @@ class Fusion(object):
242
242
  self.session = self.device.attach(self.pid)
243
243
  self.session.on("detached", self.on_detached)
244
244
 
245
- Logger.pl("{+} Iniciando scripts frida...")
245
+ Logger.pl("{+} Starting frida scripts")
246
246
  self.load_all_scripts()
247
247
 
248
248
  def make_handler(self, script_name):
@@ -261,6 +261,19 @@ class Fusion(object):
261
261
  jData = jData.get("payload", {})
262
262
  script_location = self.translate_location(location)
263
263
 
264
+ if isinstance(jData, str):
265
+ msg = jData
266
+ try:
267
+ msg = base64.b64encode(jData.encode("UTF-8"))
268
+ except Exception:
269
+ pass
270
+
271
+ jData = {
272
+ "type": "message",
273
+ "level": "I",
274
+ "message": msg
275
+ }
276
+
264
277
  if script_location.file_name == "<unknown>":
265
278
  script_location.file_name = script_name
266
279
 
@@ -631,16 +644,19 @@ class Fusion(object):
631
644
  Configuration.initialize()
632
645
 
633
646
  try:
647
+ print(f" 🛠️ Starting Frida Fusion instrumentation")
648
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
649
+ Logger.pl('{+} {C}Start time {O}%s{W}' % timestamp)
650
+
634
651
  self._modules = [
635
652
  m.create_instance()
636
653
  for _, m in Configuration.enabled_modules.items()
637
654
  ]
638
- for m in self._modules:
639
- m.start_db(db_path=Configuration.db_path)
640
655
 
641
- print(f" 🛠️ Iniciando instrumentação Frida")
642
- timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
643
- Logger.pl('{+} {C}Start time {O}%s{W}' % timestamp)
656
+ if len(self._modules) > 0:
657
+ Logger.pl("{+} Starting selected modules")
658
+ for m in self._modules:
659
+ m.start_module(db_path=Configuration.db_path)
644
660
 
645
661
  self.get_device()
646
662
  if self.device is not None:
@@ -0,0 +1,272 @@
1
+ import os
2
+ import sys
3
+ import re
4
+ import frida
5
+ import pkgutil
6
+ import importlib
7
+ import requests
8
+ import importlib.util
9
+ from pathlib import Path
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ from .__meta__ import __version__
14
+ from .libs.logger import Logger
15
+
16
+ if TYPE_CHECKING:
17
+ from .fusion import Fusion # só no type checker
18
+
19
+
20
+ class ModuleLoaderError(Exception):
21
+ pass
22
+
23
+
24
+ class ModuleBase(object):
25
+
26
+ name = ''
27
+ description = ''
28
+ mod_path = ''
29
+
30
+ def __init__(self, name, description):
31
+ self.name = name
32
+ self.description = description
33
+ self.mod_path = str(Path(__file__).resolve().parent)
34
+ pass
35
+
36
+ def safe_name(self):
37
+ return ModuleBase.get_safe_name(self.name)
38
+
39
+ def start_module(self, **kwargs) -> bool:
40
+ raise Exception('Method "start_module" is not yet implemented.')
41
+
42
+ def js_files(self) -> list:
43
+ return []
44
+
45
+ def key_value_event(self,
46
+ script_location: "Fusion.ScriptLocation" = None,
47
+ stack_trace: str = None,
48
+ module: str = None,
49
+ received_data: dict = None
50
+ ) -> bool:
51
+ raise Exception('Method "key_value_event" is not yet implemented.')
52
+
53
+ def data_event(self,
54
+ script_location: "Fusion.ScriptLocation" = None,
55
+ stack_trace: str = None,
56
+ received_data: str = None
57
+ ) -> bool:
58
+ raise Exception('Method "data_event" is not yet implemented.')
59
+
60
+ @staticmethod
61
+ def get_safe_name(name):
62
+ name = name.replace(" ", "_").lower()
63
+ return re.sub(r'[^a-zA-Z0-9_.-]+', '', name)
64
+
65
+ @classmethod
66
+ def _get_codeshare(cls, uri: str) -> dict:
67
+
68
+ if uri is None or len(uri) <= 10:
69
+ raise Exception("Invalid codeshare uri. Uri must be only user/project_name.")
70
+
71
+ uri = uri.strip(" /@.")
72
+
73
+ headers = {
74
+ "Accept": "application/vnd.github+json",
75
+ "User-Agent": f"Frida-fusion v{__version__}, Frida v{frida.__version__}"
76
+ }
77
+
78
+ try:
79
+ resp = requests.get(f"https://codeshare.frida.re/api/project/{uri}", headers=headers, timeout=30)
80
+ resp.raise_for_status()
81
+ data = resp.json()
82
+ if data is None:
83
+ raise Exception("data is empty")
84
+
85
+ if data.get('source', None) is None or data.get('source', '').strip(" \r\n") == "":
86
+ raise Exception("source code is empty")
87
+
88
+ return data
89
+
90
+ except Exception as e:
91
+ raise ModuleLoaderError("Error getting codeshare data") from e
92
+
93
+
94
+ class Module(object):
95
+ modules = {}
96
+
97
+ def __init__(self, name, description, module, qualname, class_name):
98
+ self.name = name
99
+ self.description = description
100
+ self.module = module
101
+ self.qualname = qualname
102
+ self._class = class_name
103
+ pass
104
+
105
+ def safe_name(self):
106
+ return ModuleBase.get_safe_name(self.name)
107
+
108
+ def create_instance(self):
109
+ return self._class()
110
+
111
+ @classmethod
112
+ def get_base_module(cls) -> str:
113
+ file = Path(__file__).stem
114
+
115
+ parent_module = f'.{cls.__module__}.'.replace(f'.{file}.', '').strip(' .')
116
+
117
+ return '.'.join((parent_module, 'modules'))
118
+
119
+
120
+ class InternalModule(Module):
121
+ pass
122
+
123
+
124
+ class ExternalModule(Module):
125
+ pass
126
+
127
+
128
+ class ModuleManager:
129
+ @classmethod
130
+ def _safe_import_from_path(cls, path: Path, loaded_files: set):
131
+ """
132
+ Importa um .py arbitrário usando um nome único e registra o arquivo
133
+ para não ser importado duas vezes.
134
+ """
135
+ real = path.resolve()
136
+
137
+ try:
138
+ if real in loaded_files:
139
+ return
140
+
141
+ # nome único, estável, baseado no caminho
142
+ pseudo_name = (
143
+ "fusion_ext_"
144
+ + "_".join(real.parts).replace(":", "_").replace("\\", "_").replace("/", "_")
145
+ .replace(".", "_")
146
+ )
147
+ spec = importlib.util.spec_from_file_location(pseudo_name, real)
148
+ if spec and spec.loader:
149
+ mod = importlib.util.module_from_spec(spec)
150
+ sys.modules[pseudo_name] = mod
151
+ spec.loader.exec_module(mod)
152
+ loaded_files.add(real)
153
+ except Exception as ie:
154
+ Logger.pl('\n{!} {R}Error loading external module: {G}%s{R}\n {O} %s{W}' % (str(ie), str(real)))
155
+ pass
156
+
157
+ @classmethod
158
+ def _import_via_pkgutil(cls, roots: list[Path], loaded_files: set):
159
+ """
160
+ Varre roots com pkgutil.walk_packages. Isso encontra
161
+ - módulos .py no nível do root
162
+ - pacotes (pastas com __init__.py) e seus submódulos
163
+ NÃO entra em subpastas sem __init__.py (por isso depois complementamos).
164
+ """
165
+ str_roots = [str(p) for p in roots]
166
+ for loader, modname, is_pkg in pkgutil.walk_packages(str_roots):
167
+ try:
168
+ mod = importlib.import_module(modname)
169
+ mfile = getattr(mod, "__file__", None)
170
+ if mfile:
171
+ loaded_files.add(Path(mfile).resolve())
172
+ except Exception as ie:
173
+ Logger.pl(
174
+ '\n{!} {R}Error loading internal module: {G}%s{R}\n {O} %s{W}' % (
175
+ str(ie), str(loader.path)))
176
+ pass
177
+
178
+ @classmethod
179
+ def _load_any_py_recursively(cls, root: Path, loaded_files: set):
180
+ """
181
+ Carrega *todo* arquivo .py sob root (rglob), incluindo subpastas sem __init__.py,
182
+ sem duplicar o que já foi importado.
183
+ """
184
+ for py in root.rglob("*.py"):
185
+ # exclui caches e similares
186
+ if any(part in {"__pycache__"} for part in py.parts):
187
+ continue
188
+ cls._safe_import_from_path(py, loaded_files)
189
+
190
+ @classmethod
191
+ def list_modules(cls) -> dict:
192
+ try:
193
+ base_module = Module.get_base_module()
194
+ modules: dict[str, Module] = {}
195
+
196
+ # --- 1) Varredura padrão do seu pacote interno: <este_arquivo>/modules ---
197
+ base_path = Path(__file__).resolve().parent / "modules"
198
+ internal_mod_roots = [p for p in base_path.iterdir() if p.is_dir()]
199
+
200
+ internal_mods = []
201
+
202
+ # Vamos usar pkgutil para o pacote interno (mantém o comportamento)
203
+ loaded_files: set[Path] = set()
204
+ mods = [str(p) for p in internal_mod_roots]
205
+ for loader, modname, is_pkg in pkgutil.walk_packages(mods):
206
+ if not is_pkg:
207
+ # Reconstrói o caminho relativo para montar o import dentro do pacote base
208
+ mod_path = Path(getattr(loader, "path", ""))
209
+ try:
210
+ rel = mod_path.resolve().relative_to(base_path.resolve())
211
+ dotted = "." + ".".join(rel.parts) if rel.parts else ""
212
+ except Exception:
213
+ dotted = ""
214
+ importlib.import_module(f"{base_module}{dotted}.{modname}")
215
+ internal_mods.append(f"{base_module}{dotted}.{modname}")
216
+
217
+ # --- 2) Varredura de caminhos externos via FUSION_MODULES ---
218
+ env_value = os.environ.get("FUSION_MODULES", "").strip()
219
+ if env_value:
220
+ extra_roots = [Path(p).expanduser() for p in env_value.split(os.pathsep) if p.strip()]
221
+ existing_roots = [p for p in extra_roots if p.exists() and p.is_dir()]
222
+
223
+ # Para que pkgutil encontre módulos top-level nesses roots
224
+ # (sem precisar de nomes de pacote), colocamos cada root no sys.path
225
+ # durante a varredura. Usamos um conjunto para restaurar depois se preferir.
226
+ original_sys_path = list(sys.path)
227
+ try:
228
+ for root in existing_roots:
229
+ if str(root) not in sys.path:
230
+ sys.path.insert(0, str(root))
231
+
232
+ # 2a) Encontrar módulos top-level e pacotes (com __init__.py)
233
+ cls._import_via_pkgutil(existing_roots, loaded_files)
234
+
235
+ # 2b) Complementar: carregar QUALQUER .py (inclusive subpastas sem __init__.py)
236
+ for root in existing_roots:
237
+ cls._load_any_py_recursively(root, loaded_files)
238
+ finally:
239
+ # opcional: restaurar sys.path (seguro para evitar vazamentos)
240
+ sys.path[:] = original_sys_path
241
+
242
+ # --- 3) Instanciar subclasses de ModuleBase e montar o registry ---
243
+ for i_class in ModuleBase.__subclasses__():
244
+ t = i_class()
245
+ key = t.safe_name()
246
+ if key in modules:
247
+ raise ModuleLoaderError(
248
+ f"Duplicated Module name: {i_class.__module__}.{i_class.__qualname__}"
249
+ )
250
+
251
+ if str(i_class.__module__) in internal_mods:
252
+ modules[key] = InternalModule(
253
+ name=t.name,
254
+ description=t.description,
255
+ module=str(i_class.__module__),
256
+ qualname=str(i_class.__qualname__),
257
+ class_name=i_class,
258
+ )
259
+ else:
260
+ modules[key] = ExternalModule(
261
+ name=t.name,
262
+ description=t.description,
263
+ module=str(i_class.__module__),
264
+ qualname=str(i_class.__qualname__),
265
+ class_name=i_class,
266
+ )
267
+
268
+ return modules
269
+
270
+ except Exception as e:
271
+ # Envolve a exceção original para manter contexto
272
+ raise ModuleLoaderError("Error listing modules") from e
@@ -3,13 +3,13 @@ from pathlib import Path
3
3
  import base64
4
4
  import string
5
5
 
6
- from ..libs.color import Color
7
- from ..libs.database import Database
8
- from ..module import ModuleBase
6
+ from frida_fusion.libs.logger import Logger
7
+ from frida_fusion.libs.database import Database
8
+ from frida_fusion.module import ModuleBase
9
9
 
10
10
  from typing import TYPE_CHECKING
11
11
  if TYPE_CHECKING:
12
- from ..fusion import Fusion # só no type checker
12
+ from frida_fusion.fusion import Fusion # só no type checker
13
13
 
14
14
 
15
15
  class Crypto(ModuleBase):
@@ -274,8 +274,11 @@ class Crypto(ModuleBase):
274
274
  self._crypto_db = None
275
275
  self.mod_path = str(Path(__file__).resolve().parent)
276
276
 
277
- def start_db(self, db_path: str) -> bool:
278
- self._crypto_db = Crypto.CryptoDB(db_name=db_path)
277
+ def start_module(self, **kwargs) -> bool:
278
+ if 'db_path' not in kwargs:
279
+ raise Exception("parameter db_path not found")
280
+
281
+ self._crypto_db = Crypto.CryptoDB(db_name=kwargs['db_path'])
279
282
  return True
280
283
 
281
284
  def js_files(self) -> list:
@@ -0,0 +1,66 @@
1
+ import errno
2
+ import os.path
3
+ import tempfile
4
+ from pathlib import Path
5
+ from frida_fusion.libs.logger import Logger
6
+ from frida_fusion.module import ModuleBase
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
+
14
+ class TlsUnpinning(ModuleBase):
15
+
16
+ def __init__(self):
17
+ super().__init__('Multiple unpinning', 'Use frida_multiple_unpinning by Maurizio Siddu (@akabe1)')
18
+ self.mod_path = str(Path(__file__).resolve().parent)
19
+ self.js_file = os.path.join(self.mod_path, "frida_multiple_unpinning.js")
20
+
21
+ def start_module(self, **kwargs) -> bool:
22
+ if not os.path.isfile(self.js_file):
23
+ Logger.pl("{+} Downloading CodeShare script from @akabe1/frida-multiple-unpinning")
24
+ data = self._get_codeshare("@akabe1/frida-multiple-unpinning/")
25
+ if data.get('source', None) is None or data.get('source', '').strip(" \r\n") == "":
26
+ raise Exception("source code is empty")
27
+
28
+ try:
29
+ with open(self.js_file, "w", encoding='utf-8') as f:
30
+ f.write(data.get('source', ''))
31
+ except IOError as x:
32
+ if x.errno == errno.EACCES:
33
+ Logger.pl('{!} {R}error: could not open output file to write {O}permission denied{R}{W}\r\n')
34
+ elif x.errno == errno.EISDIR:
35
+ Logger.pl('{!} {R}error: could not open output file to write {O}it is an directory{R}{W}\r\n')
36
+ else:
37
+ Logger.pl('{!} {R}error: could not open output file to write{W}\r\n')
38
+
39
+ # Try to save locally
40
+ self.js_file = str(Path("frida_multiple_unpinning.js").resolve().absolute())
41
+ with open(self.js_file, "w", encoding='utf-8') as f:
42
+ f.write(data.get('source', ''))
43
+
44
+ return True
45
+
46
+ def js_files(self) -> list:
47
+ return [
48
+ self.js_file
49
+ ]
50
+
51
+ def key_value_event(self,
52
+ script_location: "Fusion.ScriptLocation" = None,
53
+ stack_trace: str = None,
54
+ module: str = None,
55
+ received_data: dict = None
56
+ ) -> bool:
57
+ return True
58
+
59
+ def data_event(self,
60
+ script_location: "Fusion.ScriptLocation" = None,
61
+ stack_trace: str = None,
62
+ received_data: str = None
63
+ ) -> bool:
64
+ return True
65
+
66
+
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: frida-fusion
3
- Version: 0.1.4
3
+ Version: 0.1.6
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>
@@ -12,7 +12,6 @@ Keywords: Frida Fusion,Frida,Frida Scripts,development,red team
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: System Administrators
15
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
15
  Classifier: Natural Language :: English
17
16
  Classifier: Operating System :: OS Independent
18
17
  Classifier: Programming Language :: Python
@@ -27,6 +26,12 @@ Classifier: Topic :: Security
27
26
  Classifier: Topic :: Utilities
28
27
  Requires-Python: <4,>=3.9
29
28
  Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: colorama
31
+ Requires-Dist: requests>=2.23.0
32
+ Requires-Dist: frida>=15.1.17
33
+ Requires-Dist: frida-tools>=10.8.0
34
+ Dynamic: license-file
30
35
 
31
36
  # Frida Fusion
32
37
 
@@ -68,4 +73,61 @@ Modules:
68
73
  pip3 install frida-fusion
69
74
  ```
70
75
 
76
+ ## Module engine
71
77
 
78
+ You can check available modules with `frida-fusion --list-modules` command.
79
+
80
+ ```bash
81
+ frida-fusion --list-modules
82
+
83
+ [ FRIDA ]—o—( FUSION )—o—[ MOBILE TESTS ] // v0.1.4
84
+ > hook your mobile tests with Frida
85
+
86
+
87
+ Available modules
88
+ Module Name : Description
89
+ Crypto : Hook cryptography/hashing functions
90
+ ```
91
+
92
+ ### External modules
93
+
94
+ You can develop or download community modules and load into frida-fusion.
95
+
96
+ To pass to the Frida Fusion the external module path you can use the environment variable `FUSION_MODULES` with the full path of modules
97
+
98
+ At linux:
99
+
100
+ ```bash
101
+ export FUSION_MODULES=/tmp/modules
102
+
103
+ # List all modules
104
+ frida-fusion --list-modules
105
+
106
+ # Using available module
107
+ frida-fusion -f [app_id] -U --script-path . -m [module_name]
108
+ ```
109
+
110
+ At windows:
111
+
112
+ ```bash
113
+ $env:FUSION_MODULES = "C:\extra_mods"
114
+
115
+ # List all modules
116
+ frida-fusion --list-modules
117
+
118
+ # Using available module
119
+ frida-fusion -f [app_id] -U --script-path . -m [module_name]
120
+ ```
121
+
122
+ ### Community modules
123
+
124
+ You can also use one of community developed modules
125
+
126
+ ```bash
127
+ cd /tmp/
128
+ git clone https://github.com/helviojunior/frida-fusion-community-modules
129
+ export FUSION_MODULES=/tmp/frida-fusion-community-modules
130
+
131
+ # List all modules
132
+ frida-fusion --list-modules
133
+ ```
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  setup.py
@@ -20,5 +21,8 @@ frida_fusion/libs/database.py
20
21
  frida_fusion/libs/helpers.js
21
22
  frida_fusion/libs/logger.py
22
23
  frida_fusion/modules/__init__.py
23
- frida_fusion/modules/crypto.js
24
- frida_fusion/modules/crypto.py
24
+ frida_fusion/modules/crypto/__init__.py
25
+ frida_fusion/modules/crypto/crypto.js
26
+ frida_fusion/modules/crypto/crypto.py
27
+ frida_fusion/modules/tls_unpinning/__init__.py
28
+ frida_fusion/modules/tls_unpinning/frida_multiple_unpinning.py
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["setuptools", "setuptools-scm"]
2
+ requires = ["setuptools>=80.0.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
@@ -8,13 +8,12 @@ name = "frida-fusion"
8
8
  description = "Hook your mobile tests with Frida"
9
9
  readme = {file = "README.md", content-type = "text/markdown"}
10
10
  requires-python = ">=3.9,<4"
11
- license = {file = "LICENSE"}
11
+ license-files = ["LICEN[CS]E*"]
12
12
  keywords = ["Frida Fusion", "Frida", "Frida Scripts", "development", "red team"]
13
13
  classifiers = [
14
14
  "Development Status :: 4 - Beta",
15
15
  "Environment :: Console",
16
16
  "Intended Audience :: System Administrators",
17
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
18
17
  "Natural Language :: English",
19
18
  "Operating System :: OS Independent",
20
19
  "Programming Language :: Python",
@@ -1,116 +0,0 @@
1
- import os
2
- import pkgutil
3
- import importlib
4
- from pathlib import Path
5
-
6
- from .libs.database import Database
7
- from typing import TYPE_CHECKING
8
- if TYPE_CHECKING:
9
- from .fusion import Fusion # só no type checker
10
-
11
-
12
- class ModuleBase(object):
13
-
14
- name = ''
15
- description = ''
16
- mod_path = ''
17
-
18
- def __init__(self, name, description):
19
- self.name = name
20
- self.description = description
21
- self.mod_path = str(Path(__file__).resolve().parent)
22
- pass
23
-
24
- def start_db(self, db_path: str) -> bool:
25
- raise Exception('Method "start_db" is not yet implemented.')
26
-
27
- def js_files(self) -> list:
28
- return []
29
-
30
- def key_value_event(self,
31
- script_location: "Fusion.ScriptLocation" = None,
32
- stack_trace: str = None,
33
- module: str = None,
34
- received_data: dict = None
35
- ) -> bool:
36
- raise Exception('Method "key_value_event" is not yet implemented.')
37
-
38
- def data_event(self,
39
- script_location: "Fusion.ScriptLocation" = None,
40
- stack_trace: str = None,
41
- received_data: str = None
42
- ) -> bool:
43
- raise Exception('Method "data_event" is not yet implemented.')
44
-
45
-
46
- class Module(object):
47
- modules = {}
48
-
49
- def __init__(self, name, description, module, qualname, class_name):
50
- self.name = name
51
- self.description = description
52
- self.module = module
53
- self.qualname = qualname
54
- self._class = class_name
55
- pass
56
-
57
- def create_instance(self):
58
- return self._class()
59
-
60
- @classmethod
61
- def get_instance(cls, name: str):
62
- if len(Module.modules) == 0:
63
- Module.modules = Module.list_modules()
64
-
65
- selected_modules = [
66
- mod for mod in Module.modules
67
- if mod == name
68
- ]
69
-
70
- mod = None
71
- if len(selected_modules) == 1:
72
- mod = Module.modules[selected_modules[0]].create_instance()
73
-
74
- return mod
75
-
76
- @classmethod
77
- def get_base_module(cls) -> str:
78
- file = Path(__file__).stem
79
-
80
- parent_module = f'.{cls.__module__}.'.replace(f'.{file}.', '').strip(' .')
81
-
82
- return '.'.join((parent_module, 'modules'))
83
-
84
- @classmethod
85
- def list_modules(cls) -> dict:
86
- try:
87
-
88
- base_module = Module.get_base_module()
89
-
90
- modules = {}
91
-
92
- base_path = os.path.join(
93
- Path(__file__).resolve().parent, 'modules'
94
- )
95
-
96
- for loader, modname, ispkg in pkgutil.walk_packages([base_path]):
97
- if not ispkg:
98
- importlib.import_module(f'{base_module}.{modname}')
99
-
100
- for iclass in ModuleBase.__subclasses__():
101
- t = iclass()
102
- if t.name in modules:
103
- raise Exception(f'Duplicated Module name: {iclass.__module__}.{iclass.__qualname__}')
104
-
105
- modules[t.name.lower()] = Module(
106
- name=t.name,
107
- description=t.description,
108
- module=str(iclass.__module__),
109
- qualname=str(iclass.__qualname__),
110
- class_name=iclass
111
- )
112
-
113
- return modules
114
-
115
- except Exception as e:
116
- raise Exception('Error listing command modules', e)
File without changes
File without changes