fridaforge 0.3.0__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.
Files changed (42) hide show
  1. fridaforge-0.3.0/LICENSE +21 -0
  2. fridaforge-0.3.0/PKG-INFO +166 -0
  3. fridaforge-0.3.0/README.md +148 -0
  4. fridaforge-0.3.0/fridaforge/__init__.py +5 -0
  5. fridaforge-0.3.0/fridaforge/_version.py +1 -0
  6. fridaforge-0.3.0/fridaforge/backend.py +74 -0
  7. fridaforge-0.3.0/fridaforge/commands/__init__.py +24 -0
  8. fridaforge-0.3.0/fridaforge/commands/base.py +31 -0
  9. fridaforge-0.3.0/fridaforge/commands/hook_dlopen_functions.py +22 -0
  10. fridaforge-0.3.0/fridaforge/commands/hook_function.py +26 -0
  11. fridaforge-0.3.0/fridaforge/commands/modify_section_protection.py +39 -0
  12. fridaforge-0.3.0/fridaforge/commands/negate_cond_branch_arm64.py +42 -0
  13. fridaforge-0.3.0/fridaforge/commands/negate_cond_branch_x86.py +44 -0
  14. fridaforge-0.3.0/fridaforge/core/__init__.py +63 -0
  15. fridaforge-0.3.0/fridaforge/core/hook_dlopen_functions.py +14 -0
  16. fridaforge-0.3.0/fridaforge/core/hook_function.py +21 -0
  17. fridaforge-0.3.0/fridaforge/core/modify_section_protection.py +15 -0
  18. fridaforge-0.3.0/fridaforge/core/negate_cond_branch_arm64.py +15 -0
  19. fridaforge-0.3.0/fridaforge/core/negate_cond_branch_x86.py +15 -0
  20. fridaforge-0.3.0/fridaforge/core/utils.py +33 -0
  21. fridaforge-0.3.0/fridaforge/templates/hook_dlopen_functions.js +32 -0
  22. fridaforge-0.3.0/fridaforge/templates/hook_function.js +27 -0
  23. fridaforge-0.3.0/fridaforge/templates/modify_section_protection.js +36 -0
  24. fridaforge-0.3.0/fridaforge/templates/negate_cond_branch_arm64.js +30 -0
  25. fridaforge-0.3.0/fridaforge/templates/negate_cond_branch_x86.js +35 -0
  26. fridaforge-0.3.0/fridaforge.egg-info/PKG-INFO +166 -0
  27. fridaforge-0.3.0/fridaforge.egg-info/SOURCES.txt +40 -0
  28. fridaforge-0.3.0/fridaforge.egg-info/dependency_links.txt +1 -0
  29. fridaforge-0.3.0/fridaforge.egg-info/top_level.txt +1 -0
  30. fridaforge-0.3.0/pyproject.toml +37 -0
  31. fridaforge-0.3.0/setup.cfg +4 -0
  32. fridaforge-0.3.0/tests/test_backend.py +27 -0
  33. fridaforge-0.3.0/tests/test_backend_ghidra.py +181 -0
  34. fridaforge-0.3.0/tests/test_binja_entry.py +83 -0
  35. fridaforge-0.3.0/tests/test_binja_manifest.py +44 -0
  36. fridaforge-0.3.0/tests/test_build.py +84 -0
  37. fridaforge-0.3.0/tests/test_bump_version.py +64 -0
  38. fridaforge-0.3.0/tests/test_check_wheel.py +45 -0
  39. fridaforge-0.3.0/tests/test_commands.py +153 -0
  40. fridaforge-0.3.0/tests/test_generators.py +65 -0
  41. fridaforge-0.3.0/tests/test_ghidra_scripts.py +47 -0
  42. fridaforge-0.3.0/tests/test_readme.py +21 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 noobexon1
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: fridaforge
3
+ Version: 0.3.0
4
+ Summary: Generate Frida scripts from IDA Pro, Binary Ninja, and Ghidra
5
+ Author: noobexon1
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/noobexon1/FridaForge
8
+ Project-URL: Issues, https://github.com/noobexon1/FridaForge/issues
9
+ Keywords: frida,binary-ninja,ida-pro,ghidra,reverse-engineering
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Security
13
+ Classifier: Topic :: Software Development :: Disassemblers
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ <div align="center">
20
+
21
+ <img src="https://raw.githubusercontent.com/noobexon1/FridaForge/main/assets/logo.jpg" alt="FridaForge logo"/>
22
+
23
+ **Generate Frida scripts directly from IDA Pro, Binary Ninja, and Ghidra.** ⚡
24
+
25
+ [![GitHub release](https://img.shields.io/github/v/release/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/releases)
26
+ [![GitHub stars](https://img.shields.io/github/stars/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/stargazers)
27
+ [![GitHub last commit](https://img.shields.io/github/last-commit/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/commits)
28
+ [![GitHub issues](https://img.shields.io/github/issues/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/issues)
29
+ [![GitHub downloads](https://img.shields.io/github/downloads/noobexon1/FridaForge/total)](https://github.com/noobexon1/FridaForge/releases)
30
+ [![Release](https://img.shields.io/github/actions/workflow/status/noobexon1/FridaForge/release.yml?label=release)](https://github.com/noobexon1/FridaForge/actions/workflows/release.yml)
31
+ ![IDA Pro](https://img.shields.io/badge/IDA%20Pro-Plugin-blue)
32
+ ![Binary Ninja](https://img.shields.io/badge/Binary%20Ninja-Plugin-orange)
33
+ ![Ghidra](https://img.shields.io/badge/Ghidra-Plugin-green)
34
+ ![Frida](https://img.shields.io/badge/Frida-Instrumentation-red)
35
+ [![GitHub License](https://img.shields.io/github/license/noobexon1/FridaForge)](LICENSE)
36
+
37
+ </div>
38
+
39
+ ## 📸 Examples
40
+
41
+ **Generate a function hook** — right-click in the disassembly/pseudocode view and pick an action:
42
+
43
+ ![Generating a function hook from the FridaForge context menu](https://raw.githubusercontent.com/noobexon1/FridaForge/main/assets/Screenshot_1.jpg)
44
+
45
+ **Get a ready-to-use Frida script** — copied straight to your clipboard:
46
+
47
+ ![The generated Frida hook script](https://raw.githubusercontent.com/noobexon1/FridaForge/main/assets/Screenshot_2.jpg)
48
+
49
+ ## ✨ Features
50
+
51
+ Right-click (or hit a hotkey) in your disassembler to generate ready-to-use Frida snippets — the result is instantly copied to your clipboard.
52
+
53
+ - 🧩 **Composable scripts** — snippets are independent but can be nested to build complex instrumentation with a few clicks
54
+ - 🎯 **Context-aware** — scripts auto-fill module name, function address, section, and any other detail available from your current context
55
+ - 🧰 **Three hosts, one experience** — identical features, hotkeys, and menus in IDA Pro, Binary Ninja, and Ghidra
56
+ - 📋 **Clipboard integration** — generated scripts are copied the moment you run an action
57
+
58
+ ### Supported Actions
59
+
60
+ #### 🪝 Hooks
61
+
62
+ | Action | Description | Hotkey |
63
+ |---|---|---|
64
+ | Generate function hook | Intercept calls to the selected function, log entry/exit | `Shift+F` |
65
+ | Generate dlopen hooks | Monitor `dlopen` (including Android variants) to detect module loading | `Shift+D` |
66
+
67
+ #### 🧠 Memory
68
+
69
+ | Action | Description | Hotkey |
70
+ |---|---|---|
71
+ | Modify section protection | Change memory protection of the section at the current address | `Shift+M` |
72
+
73
+ #### 🩹 Patching
74
+
75
+ | Action | Description | Hotkey |
76
+ |---|---|---|
77
+ | Negate cond branch (ARM64) | Flip the condition of an ARM64 conditional branch | `Shift+A` |
78
+ | Negate cond branch (x86/x64) | Flip the condition of an x86/x64 conditional branch | `Shift+X` |
79
+
80
+ ## 🔧 How it Works
81
+
82
+ Each feature follows a simple pipeline: a **Frida template** (`fridaforge/templates/`) provides the JavaScript skeleton with placeholders, a **command** (`fridaforge/commands/`) gathers context through a host-agnostic **Backend**, and a **generator** (`fridaforge/core/`) fills the template to produce the final script.
83
+
84
+ The shared core knows nothing about IDA, Binary Ninja, or Ghidra. Each host lives in `hosts/` and provides a thin **adapter** that implements the `Backend` interface (data extraction, clipboard, menu + hotkey registration). Add a feature once and it works in all three.
85
+
86
+ ## 📦 Installation
87
+
88
+ Binary Ninja users can install straight from the Extension Manager. For everything else, grab the archive for your tool from the [latest release](https://github.com/noobexon1/FridaForge/releases). There's one per host — `fridaforge-binja-<version>.zip`, `fridaforge-ida-<version>.zip`, and `fridaforge-ghidra-<version>.zip`.
89
+
90
+ ### 🟠 Binary Ninja
91
+
92
+ **Extension Manager (recommended):** open the Extension Manager (**Plugins → Manage Plugins**), search for **FridaForge**, and click **Install**. The shared core (`fridaforge` on PyPI) is installed automatically. Restart Binary Ninja if prompted.
93
+
94
+ **Manual install:**
95
+
96
+ 1. Find your user plugins directory by running this in Binary Ninja's Python console:
97
+ ```python
98
+ binaryninja.user_plugin_path()
99
+ ```
100
+ 2. Extract `fridaforge-binja-<version>.zip` into that directory so that `<binja user plugins>/fridaforge/` exists.
101
+ 3. Restart Binary Ninja.
102
+
103
+ > Switching from a manual install to the Extension Manager? Delete `<binja user plugins>/fridaforge/` first. Otherwise both copies load and every command appears twice.
104
+
105
+ ### 🔵 IDA Pro
106
+
107
+ 1. **Prerequisites:**
108
+ - IDA Pro (tested with 9.0, but any version with Python 3 and `idaapi` should work).
109
+ - PyQt5 for clipboard functionality (usually bundled with IDA's Python).
110
+ 2. Locate your IDA Pro user directory by running this in IDA's Python console:
111
+ ```python
112
+ import ida_diskio; print(ida_diskio.get_user_idadir())
113
+ ```
114
+ If not already there, create a `plugins` directory in the user directory. This is where the plugin will be installed.
115
+ 3. Extract `fridaforge-ida-<version>.zip` into the `plugins` directory.
116
+ 4. Restart IDA Pro. The plugin loads automatically. You should see FridaForge as an option after right-clicking in the disassembly view.
117
+
118
+ ### 🐉 Ghidra
119
+
120
+ 1. **Prerequisites:** Ghidra **11.3 or later** (PyGhidra ships bundled; no extra install needed).
121
+ 2. Unzip `fridaforge-ghidra-<version>.zip` into a folder of your choice.
122
+ 3. In Ghidra's CodeBrowser, open **Window → Script Manager**, click **Manage Script Directories** (the toolbar icon), and add the unzipped folder.
123
+ 4. The five actions appear under **Tools → FridaForge** (categorized into Hooks / Memory / Patching) and respond to the hotkeys `Shift+F/D/M/A/X`. The generated Frida script is copied to your clipboard; `[FridaForge]` messages print to the Ghidra console.
124
+
125
+ > Ghidra actions are delivered as menu items + hotkeys (not a nested right-click popup), which is the supported integration path for PyGhidra scripts.
126
+
127
+ ### 🧱 Script Composition
128
+
129
+ Snippets are designed to be independent building blocks. You can nest scripts inside each other — for example, hook a function, negate a conditional branch on entry, and restore it on exit. Complex instrumentation from a few clicks.
130
+
131
+ ## 🛠️ Building from Source
132
+
133
+ ```bash
134
+ pip install pytest build
135
+ pytest -v # run the host-agnostic test suite
136
+ python build/build.py # writes dist/fridaforge-binja-*.zip, dist/fridaforge-ida-*.zip and dist/fridaforge-ghidra-*.zip
137
+ python -m build # builds the fridaforge core package (sdist + wheel) published to PyPI
138
+ ```
139
+
140
+ Builds are deterministic — the same source produces byte-identical archives.
141
+
142
+ To release, run `python build/bump_version.py X.Y.Z`, commit, and push tag `vX.Y.Z`. CI publishes the core to PyPI, then attaches the three archives to a GitHub release.
143
+
144
+ ## 🤝 Contributing
145
+
146
+ Contributions welcome! Got a useful Frida snippet? Turn it into a template so everyone can use it.
147
+
148
+ 1. Fork the repository.
149
+ 2. Create a new branch for your feature or fix.
150
+ 3. Make your changes (and add a test — the core is fully unit-tested without needing a host).
151
+ 4. Submit a pull request.
152
+
153
+ | Adding... | Where |
154
+ |---|---|
155
+ | New Frida snippet | `fridaforge/templates/` (JS template) + `fridaforge/core/` (generator) + `fridaforge/commands/` (command) |
156
+ | Host-specific behavior | `hosts/binja/` or `hosts/ida/` |
157
+
158
+ Each command and generator lives in its own file — easy to contribute without merge conflicts.
159
+
160
+ ## 🗺️ Roadmap
161
+
162
+ See [open issues](https://github.com/noobexon1/FridaForge/issues) for planned features and known bugs.
163
+
164
+ ## 📄 License
165
+
166
+ [MIT](LICENSE)
@@ -0,0 +1,148 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/noobexon1/FridaForge/main/assets/logo.jpg" alt="FridaForge logo"/>
4
+
5
+ **Generate Frida scripts directly from IDA Pro, Binary Ninja, and Ghidra.** ⚡
6
+
7
+ [![GitHub release](https://img.shields.io/github/v/release/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/releases)
8
+ [![GitHub stars](https://img.shields.io/github/stars/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/stargazers)
9
+ [![GitHub last commit](https://img.shields.io/github/last-commit/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/commits)
10
+ [![GitHub issues](https://img.shields.io/github/issues/noobexon1/FridaForge)](https://github.com/noobexon1/FridaForge/issues)
11
+ [![GitHub downloads](https://img.shields.io/github/downloads/noobexon1/FridaForge/total)](https://github.com/noobexon1/FridaForge/releases)
12
+ [![Release](https://img.shields.io/github/actions/workflow/status/noobexon1/FridaForge/release.yml?label=release)](https://github.com/noobexon1/FridaForge/actions/workflows/release.yml)
13
+ ![IDA Pro](https://img.shields.io/badge/IDA%20Pro-Plugin-blue)
14
+ ![Binary Ninja](https://img.shields.io/badge/Binary%20Ninja-Plugin-orange)
15
+ ![Ghidra](https://img.shields.io/badge/Ghidra-Plugin-green)
16
+ ![Frida](https://img.shields.io/badge/Frida-Instrumentation-red)
17
+ [![GitHub License](https://img.shields.io/github/license/noobexon1/FridaForge)](LICENSE)
18
+
19
+ </div>
20
+
21
+ ## 📸 Examples
22
+
23
+ **Generate a function hook** — right-click in the disassembly/pseudocode view and pick an action:
24
+
25
+ ![Generating a function hook from the FridaForge context menu](https://raw.githubusercontent.com/noobexon1/FridaForge/main/assets/Screenshot_1.jpg)
26
+
27
+ **Get a ready-to-use Frida script** — copied straight to your clipboard:
28
+
29
+ ![The generated Frida hook script](https://raw.githubusercontent.com/noobexon1/FridaForge/main/assets/Screenshot_2.jpg)
30
+
31
+ ## ✨ Features
32
+
33
+ Right-click (or hit a hotkey) in your disassembler to generate ready-to-use Frida snippets — the result is instantly copied to your clipboard.
34
+
35
+ - 🧩 **Composable scripts** — snippets are independent but can be nested to build complex instrumentation with a few clicks
36
+ - 🎯 **Context-aware** — scripts auto-fill module name, function address, section, and any other detail available from your current context
37
+ - 🧰 **Three hosts, one experience** — identical features, hotkeys, and menus in IDA Pro, Binary Ninja, and Ghidra
38
+ - 📋 **Clipboard integration** — generated scripts are copied the moment you run an action
39
+
40
+ ### Supported Actions
41
+
42
+ #### 🪝 Hooks
43
+
44
+ | Action | Description | Hotkey |
45
+ |---|---|---|
46
+ | Generate function hook | Intercept calls to the selected function, log entry/exit | `Shift+F` |
47
+ | Generate dlopen hooks | Monitor `dlopen` (including Android variants) to detect module loading | `Shift+D` |
48
+
49
+ #### 🧠 Memory
50
+
51
+ | Action | Description | Hotkey |
52
+ |---|---|---|
53
+ | Modify section protection | Change memory protection of the section at the current address | `Shift+M` |
54
+
55
+ #### 🩹 Patching
56
+
57
+ | Action | Description | Hotkey |
58
+ |---|---|---|
59
+ | Negate cond branch (ARM64) | Flip the condition of an ARM64 conditional branch | `Shift+A` |
60
+ | Negate cond branch (x86/x64) | Flip the condition of an x86/x64 conditional branch | `Shift+X` |
61
+
62
+ ## 🔧 How it Works
63
+
64
+ Each feature follows a simple pipeline: a **Frida template** (`fridaforge/templates/`) provides the JavaScript skeleton with placeholders, a **command** (`fridaforge/commands/`) gathers context through a host-agnostic **Backend**, and a **generator** (`fridaforge/core/`) fills the template to produce the final script.
65
+
66
+ The shared core knows nothing about IDA, Binary Ninja, or Ghidra. Each host lives in `hosts/` and provides a thin **adapter** that implements the `Backend` interface (data extraction, clipboard, menu + hotkey registration). Add a feature once and it works in all three.
67
+
68
+ ## 📦 Installation
69
+
70
+ Binary Ninja users can install straight from the Extension Manager. For everything else, grab the archive for your tool from the [latest release](https://github.com/noobexon1/FridaForge/releases). There's one per host — `fridaforge-binja-<version>.zip`, `fridaforge-ida-<version>.zip`, and `fridaforge-ghidra-<version>.zip`.
71
+
72
+ ### 🟠 Binary Ninja
73
+
74
+ **Extension Manager (recommended):** open the Extension Manager (**Plugins → Manage Plugins**), search for **FridaForge**, and click **Install**. The shared core (`fridaforge` on PyPI) is installed automatically. Restart Binary Ninja if prompted.
75
+
76
+ **Manual install:**
77
+
78
+ 1. Find your user plugins directory by running this in Binary Ninja's Python console:
79
+ ```python
80
+ binaryninja.user_plugin_path()
81
+ ```
82
+ 2. Extract `fridaforge-binja-<version>.zip` into that directory so that `<binja user plugins>/fridaforge/` exists.
83
+ 3. Restart Binary Ninja.
84
+
85
+ > Switching from a manual install to the Extension Manager? Delete `<binja user plugins>/fridaforge/` first. Otherwise both copies load and every command appears twice.
86
+
87
+ ### 🔵 IDA Pro
88
+
89
+ 1. **Prerequisites:**
90
+ - IDA Pro (tested with 9.0, but any version with Python 3 and `idaapi` should work).
91
+ - PyQt5 for clipboard functionality (usually bundled with IDA's Python).
92
+ 2. Locate your IDA Pro user directory by running this in IDA's Python console:
93
+ ```python
94
+ import ida_diskio; print(ida_diskio.get_user_idadir())
95
+ ```
96
+ If not already there, create a `plugins` directory in the user directory. This is where the plugin will be installed.
97
+ 3. Extract `fridaforge-ida-<version>.zip` into the `plugins` directory.
98
+ 4. Restart IDA Pro. The plugin loads automatically. You should see FridaForge as an option after right-clicking in the disassembly view.
99
+
100
+ ### 🐉 Ghidra
101
+
102
+ 1. **Prerequisites:** Ghidra **11.3 or later** (PyGhidra ships bundled; no extra install needed).
103
+ 2. Unzip `fridaforge-ghidra-<version>.zip` into a folder of your choice.
104
+ 3. In Ghidra's CodeBrowser, open **Window → Script Manager**, click **Manage Script Directories** (the toolbar icon), and add the unzipped folder.
105
+ 4. The five actions appear under **Tools → FridaForge** (categorized into Hooks / Memory / Patching) and respond to the hotkeys `Shift+F/D/M/A/X`. The generated Frida script is copied to your clipboard; `[FridaForge]` messages print to the Ghidra console.
106
+
107
+ > Ghidra actions are delivered as menu items + hotkeys (not a nested right-click popup), which is the supported integration path for PyGhidra scripts.
108
+
109
+ ### 🧱 Script Composition
110
+
111
+ Snippets are designed to be independent building blocks. You can nest scripts inside each other — for example, hook a function, negate a conditional branch on entry, and restore it on exit. Complex instrumentation from a few clicks.
112
+
113
+ ## 🛠️ Building from Source
114
+
115
+ ```bash
116
+ pip install pytest build
117
+ pytest -v # run the host-agnostic test suite
118
+ python build/build.py # writes dist/fridaforge-binja-*.zip, dist/fridaforge-ida-*.zip and dist/fridaforge-ghidra-*.zip
119
+ python -m build # builds the fridaforge core package (sdist + wheel) published to PyPI
120
+ ```
121
+
122
+ Builds are deterministic — the same source produces byte-identical archives.
123
+
124
+ To release, run `python build/bump_version.py X.Y.Z`, commit, and push tag `vX.Y.Z`. CI publishes the core to PyPI, then attaches the three archives to a GitHub release.
125
+
126
+ ## 🤝 Contributing
127
+
128
+ Contributions welcome! Got a useful Frida snippet? Turn it into a template so everyone can use it.
129
+
130
+ 1. Fork the repository.
131
+ 2. Create a new branch for your feature or fix.
132
+ 3. Make your changes (and add a test — the core is fully unit-tested without needing a host).
133
+ 4. Submit a pull request.
134
+
135
+ | Adding... | Where |
136
+ |---|---|
137
+ | New Frida snippet | `fridaforge/templates/` (JS template) + `fridaforge/core/` (generator) + `fridaforge/commands/` (command) |
138
+ | Host-specific behavior | `hosts/binja/` or `hosts/ida/` |
139
+
140
+ Each command and generator lives in its own file — easy to contribute without merge conflicts.
141
+
142
+ ## 🗺️ Roadmap
143
+
144
+ See [open issues](https://github.com/noobexon1/FridaForge/issues) for planned features and known bugs.
145
+
146
+ ## 📄 License
147
+
148
+ [MIT](LICENSE)
@@ -0,0 +1,5 @@
1
+ """FridaForge: generate Frida scripts from IDA Pro and Binary Ninja."""
2
+
3
+ from fridaforge._version import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -0,0 +1,74 @@
1
+ """Host-agnostic backend interface for FridaForge commands."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass
5
+ from enum import Enum, auto
6
+
7
+
8
+ class ContextKind(Enum):
9
+ """The host context a command needs to run."""
10
+
11
+ GLOBAL = auto() # only the module is needed
12
+ ADDRESS = auto() # the current address is needed
13
+ FUNCTION = auto() # the current function is needed
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class FunctionRef:
18
+ """A resolved reference to a function in the loaded binary."""
19
+
20
+ start: int
21
+ name: str
22
+
23
+
24
+ class Backend(ABC):
25
+ """The port each host (Binary Ninja, IDA) must implement."""
26
+
27
+ @abstractmethod
28
+ def get_module_name(self) -> str:
29
+ """Return the loaded module's base file name."""
30
+
31
+ @abstractmethod
32
+ def get_image_base(self) -> int:
33
+ """Return the image base address of the loaded binary."""
34
+
35
+ @abstractmethod
36
+ def get_current_address(self) -> int | None:
37
+ """Return the currently selected address, or ``None``."""
38
+
39
+ @abstractmethod
40
+ def get_current_function(self) -> FunctionRef | None:
41
+ """Return the currently selected function, or ``None``."""
42
+
43
+ @abstractmethod
44
+ def get_section_name_at(self, addr: int) -> str | None:
45
+ """Return the name of the section/segment containing *addr*."""
46
+
47
+ @abstractmethod
48
+ def get_instruction_mnemonic(self, addr: int) -> str | None:
49
+ """Return the mnemonic of the instruction at *addr*, or ``None``."""
50
+
51
+ @abstractmethod
52
+ def copy_to_clipboard(self, text: str) -> bool:
53
+ """Copy *text* to the system clipboard. Return success."""
54
+
55
+ @abstractmethod
56
+ def log_info(self, msg: str) -> None:
57
+ """Emit an informational message in the host's idiom."""
58
+
59
+ @abstractmethod
60
+ def log_warn(self, msg: str) -> None:
61
+ """Emit a warning message in the host's idiom."""
62
+
63
+ # --- concrete helpers shared by all hosts ---
64
+
65
+ def get_relative_address(self, addr: int) -> str:
66
+ """Return *addr*'s offset from the image base as a hex string."""
67
+ return hex(addr - self.get_image_base())
68
+
69
+ def _resolve_function_name(self, start: int, raw_name: str) -> str:
70
+ """Return *raw_name*, or a ``sub_<offset>`` fallback when empty."""
71
+ if raw_name:
72
+ return raw_name
73
+ relative_address = self.get_relative_address(start)
74
+ return f"sub_{relative_address.removeprefix('0x')}"
@@ -0,0 +1,24 @@
1
+ """FridaForge command registry."""
2
+
3
+ from fridaforge.commands.base import Command
4
+ from fridaforge.commands.hook_dlopen_functions import DlopenHooksCommand
5
+ from fridaforge.commands.hook_function import FunctionHookCommand
6
+ from fridaforge.commands.modify_section_protection import (
7
+ ModifySectionProtectionCommand,
8
+ )
9
+ from fridaforge.commands.negate_cond_branch_arm64 import (
10
+ NegateArm64CondBranchCommand,
11
+ )
12
+ from fridaforge.commands.negate_cond_branch_x86 import (
13
+ NegateX86CondBranchCommand,
14
+ )
15
+
16
+ ALL_COMMANDS: list[Command] = [
17
+ FunctionHookCommand(),
18
+ DlopenHooksCommand(),
19
+ ModifySectionProtectionCommand(),
20
+ NegateArm64CondBranchCommand(),
21
+ NegateX86CondBranchCommand(),
22
+ ]
23
+
24
+ __all__ = ["Command", "ALL_COMMANDS"]
@@ -0,0 +1,31 @@
1
+ """Base class for host-agnostic FridaForge commands."""
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from fridaforge.backend import Backend, ContextKind
6
+
7
+
8
+ class Command(ABC):
9
+ """A single user-facing feature, independent of the host.
10
+
11
+ Subclasses set the metadata class attributes and implement
12
+ :meth:`run`, which pulls data from the *backend* and copies the
13
+ generated snippet to the clipboard.
14
+ """
15
+
16
+ id: str
17
+ description: str
18
+ category: str
19
+ hotkey: str | None = None
20
+ context: ContextKind
21
+
22
+ @abstractmethod
23
+ def run(self, backend: Backend) -> None:
24
+ """Execute the command against the given backend."""
25
+
26
+ def _deliver(self, backend: Backend, snippet: str) -> None:
27
+ """Copy the snippet to the clipboard and report the outcome."""
28
+ if backend.copy_to_clipboard(snippet):
29
+ backend.log_info("Frida snippet copied to clipboard.")
30
+ else:
31
+ backend.log_warn("Failed to copy snippet to clipboard.")
@@ -0,0 +1,22 @@
1
+ """Command: generate Frida dlopen-family hooks."""
2
+
3
+ from fridaforge.backend import Backend, ContextKind
4
+ from fridaforge.commands.base import Command
5
+ from fridaforge.core import DlopenHookGenerator
6
+
7
+
8
+ class DlopenHooksCommand(Command):
9
+ id = "hook_dlopen_functions"
10
+ description = "Generate dlopen hooks (Basic for Android native libs)"
11
+ category = "Hooks"
12
+ hotkey = "Shift+D"
13
+ context = ContextKind.GLOBAL
14
+
15
+ def run(self, backend: Backend) -> None:
16
+ module_name = backend.get_module_name()
17
+ if not module_name:
18
+ backend.log_warn("Could not determine the module name.")
19
+ return
20
+
21
+ snippet = DlopenHookGenerator().generate(module_name)
22
+ self._deliver(backend, snippet)
@@ -0,0 +1,26 @@
1
+ """Command: generate a Frida function hook."""
2
+
3
+ from fridaforge.backend import Backend, ContextKind
4
+ from fridaforge.commands.base import Command
5
+ from fridaforge.core import FunctionHookGenerator
6
+
7
+
8
+ class FunctionHookCommand(Command):
9
+ id = "hook_function"
10
+ description = "Generate function hook"
11
+ category = "Hooks"
12
+ hotkey = "Shift+F"
13
+ context = ContextKind.FUNCTION
14
+
15
+ def run(self, backend: Backend) -> None:
16
+ func = backend.get_current_function()
17
+ if func is None:
18
+ backend.log_warn("No function at the current location.")
19
+ return
20
+
21
+ module_name = backend.get_module_name()
22
+ relative_address = backend.get_relative_address(func.start)
23
+ snippet = FunctionHookGenerator().generate(
24
+ module_name, relative_address, func.name
25
+ )
26
+ self._deliver(backend, snippet)
@@ -0,0 +1,39 @@
1
+ """Command: generate a Frida section protection modification."""
2
+
3
+ from fridaforge.backend import Backend, ContextKind
4
+ from fridaforge.commands.base import Command
5
+ from fridaforge.core import ModifySectionProtectionGenerator
6
+
7
+
8
+ class ModifySectionProtectionCommand(Command):
9
+ id = "modify_section_protection"
10
+ description = "Modify current section permissions"
11
+ category = "Memory"
12
+ hotkey = "Shift+M"
13
+ context = ContextKind.ADDRESS
14
+
15
+ def run(self, backend: Backend) -> None:
16
+ addr = backend.get_current_address()
17
+ if addr is None:
18
+ backend.log_warn("Could not determine the current address.")
19
+ return
20
+
21
+ section_name = backend.get_section_name_at(addr)
22
+ if not section_name:
23
+ backend.log_warn(
24
+ f"Could not determine the containing section for {hex(addr)}."
25
+ )
26
+ return
27
+
28
+ if not section_name.startswith("."):
29
+ backend.log_warn(
30
+ f"Address is not in a standard section. "
31
+ f"(Section name: {section_name})"
32
+ )
33
+ return
34
+
35
+ module_name = backend.get_module_name()
36
+ snippet = ModifySectionProtectionGenerator().generate(
37
+ module_name, section_name
38
+ )
39
+ self._deliver(backend, snippet)
@@ -0,0 +1,42 @@
1
+ """Command: negate an ARM64 conditional branch."""
2
+
3
+ from fridaforge.backend import Backend, ContextKind
4
+ from fridaforge.commands.base import Command
5
+ from fridaforge.core import NegateArm64CondBranchGenerator
6
+
7
+ ARM64_COND_BRANCHES = {
8
+ "b.eq", "b.ne", "b.hs", "b.lo", "b.cs",
9
+ "b.mi", "b.pl", "b.vs", "b.vc",
10
+ "b.hi", "b.ls", "b.ge", "b.lt",
11
+ "b.gt", "b.le", "tbz", "tbnz",
12
+ "cbz", "cbnz",
13
+ }
14
+
15
+
16
+ class NegateArm64CondBranchCommand(Command):
17
+ id = "negate_cond_branch_arm64"
18
+ description = "Negate cond branch instruction (ARM64)"
19
+ category = "Patching"
20
+ hotkey = "Shift+A"
21
+ context = ContextKind.ADDRESS
22
+
23
+ def run(self, backend: Backend) -> None:
24
+ addr = backend.get_current_address()
25
+ if addr is None:
26
+ backend.log_warn("Could not determine the current address.")
27
+ return
28
+
29
+ mnemonic = (backend.get_instruction_mnemonic(addr) or "").lower()
30
+ if mnemonic not in ARM64_COND_BRANCHES:
31
+ backend.log_warn(
32
+ f"Instruction at {hex(addr)} ('{mnemonic}') "
33
+ f"is not a recognized ARM64 conditional branch."
34
+ )
35
+ return
36
+
37
+ module_name = backend.get_module_name()
38
+ relative_address = backend.get_relative_address(addr)
39
+ snippet = NegateArm64CondBranchGenerator().generate(
40
+ module_name, relative_address
41
+ )
42
+ self._deliver(backend, snippet)
@@ -0,0 +1,44 @@
1
+ """Command: negate an x86 conditional branch."""
2
+
3
+ from fridaforge.backend import Backend, ContextKind
4
+ from fridaforge.commands.base import Command
5
+ from fridaforge.core import NegateX86CondBranchGenerator
6
+
7
+ X86_COND_BRANCHES = {
8
+ "je", "jne", "jz", "jnz",
9
+ "ja", "jnbe", "jae", "jnb", "jnc",
10
+ "jb", "jnae", "jc", "jbe", "jna",
11
+ "jg", "jnle", "jge", "jnl",
12
+ "jl", "jnge", "jle", "jng",
13
+ "jo", "jno", "js", "jns",
14
+ "jp", "jpe", "jnp", "jpo",
15
+ }
16
+
17
+
18
+ class NegateX86CondBranchCommand(Command):
19
+ id = "negate_cond_branch_x86"
20
+ description = "Negate cond branch instruction (x86)"
21
+ category = "Patching"
22
+ hotkey = "Shift+X"
23
+ context = ContextKind.ADDRESS
24
+
25
+ def run(self, backend: Backend) -> None:
26
+ addr = backend.get_current_address()
27
+ if addr is None:
28
+ backend.log_warn("Could not determine the current address.")
29
+ return
30
+
31
+ mnemonic = (backend.get_instruction_mnemonic(addr) or "").lower()
32
+ if mnemonic not in X86_COND_BRANCHES:
33
+ backend.log_warn(
34
+ f"Instruction at {hex(addr)} ('{mnemonic}') "
35
+ f"is not a recognized x86 conditional branch."
36
+ )
37
+ return
38
+
39
+ module_name = backend.get_module_name()
40
+ relative_address = backend.get_relative_address(addr)
41
+ snippet = NegateX86CondBranchGenerator().generate(
42
+ module_name, relative_address
43
+ )
44
+ self._deliver(backend, snippet)