├── .github
├── FUNDING.yml
└── workflows
│ └── release.yml
├── .gitignore
├── README.md
└── hexcopy.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: oalabs
2 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 |
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: ncipollo/release-action@v1
15 | with:
16 | artifacts: "hexcopy.py"
17 | omitBody: true
18 | token: ${{ secrets.GITHUB_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Apple crap
2 | .DS_Store
3 | .AppleDouble
4 | .LSOverride
5 |
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | pip-wheel-metadata/
29 | share/python-wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .nox/
49 | .coverage
50 | .coverage.*
51 | .cache
52 | nosetests.xml
53 | coverage.xml
54 | *.cover
55 | *.py,cover
56 | .hypothesis/
57 | .pytest_cache/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 | db.sqlite3-journal
68 |
69 | # Flask stuff:
70 | instance/
71 | .webassets-cache
72 |
73 | # Scrapy stuff:
74 | .scrapy
75 |
76 | # Sphinx documentation
77 | docs/_build/
78 |
79 | # PyBuilder
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | .python-version
91 |
92 | # pipenv
93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
96 | # install all needed dependencies.
97 | #Pipfile.lock
98 |
99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
100 | __pypackages__/
101 |
102 | # Celery stuff
103 | celerybeat-schedule
104 | celerybeat.pid
105 |
106 | # SageMath parsed files
107 | *.sage.py
108 |
109 | # Environments
110 | .env
111 | .venv
112 | env/
113 | venv/
114 | ENV/
115 | env.bak/
116 | venv.bak/
117 |
118 | # Spyder project settings
119 | .spyderproject
120 | .spyproject
121 |
122 | # Rope project settings
123 | .ropeproject
124 |
125 | # mkdocs documentation
126 | /site
127 |
128 | # mypy
129 | .mypy_cache/
130 | .dmypy.json
131 | dmypy.json
132 |
133 | # Pyre type checker
134 | .pyre/
135 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://github.com/OALabs/hexcopy-ida/releases) [](https://discord.gg/cw4U3WHvpn) [](https://www.patreon.com/oalabs)
6 |
7 | # HexCopy
8 | IDA plugin for quickly copying disassembly as encoded hex bytes. This whole plugin just saves you two extra clicks... but if you are frequently copying data from IDA into external tools and scripts this is a must have!
9 |
10 | ## Using HexCopy
11 | Highlight the data that you want to copy in the disassembly window then right click and select `Copy Hex`. You can also use the `Ctrl+H` hotkey to copy selected data without any clicks!
12 |
13 | The data will be automatically copied to your clipboard as a hex-encoded string. The hex-encoded string is also printed to the console for quick reference.
14 |
15 | ## Installing HexCopy
16 | Simply copy the latest release of [`hexcopy.py`](https://github.com/OALabs/hexcopy-ida/releases) into your IDA plugins directory and you are ready to start copying in hex!
17 |
18 | ## ❗Compatibility Issues
19 | HexCopy has been developed for use with the __IDA 7+__ and __Python 3__.
20 |
21 | I know many of you are still using Python 2.7 for compatibility with older plugins. We will attempt to continue to support Python 2.7 on IDA 7+ but there are no guarantees.
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/hexcopy.py:
--------------------------------------------------------------------------------
1 | ############################################################################################
2 | ##
3 | ## One-Click Hex Copy!
4 | ##
5 | ## Updated for IDA 7.xx and Python 3
6 | ##
7 | ## To install:
8 | ## Copy script into plugins directory, i.e: C:\Program Files\\plugins
9 | ##
10 | ## To run:
11 | ## Highlight disassembly instructions and right click and select "Hex Copy"
12 | ## The hex encoded bytes will be added to your clipboard
13 | ##
14 | ############################################################################################
15 |
16 |
17 | __AUTHOR__ = '@herrcore'
18 |
19 | PLUGIN_NAME = "Hex Copy"
20 | PLUGIN_HOTKEY = 'Ctrl+H'
21 | VERSION = '3.2.0'
22 |
23 |
24 | import os
25 | import sys
26 | import idc
27 | import idaapi
28 | import idautils
29 |
30 | major, minor = map(int, idaapi.get_kernel_version().split("."))
31 | using_ida7api = (major > 6)
32 | using_pyqt5 = using_ida7api or (major == 6 and minor >= 9)
33 |
34 | if using_pyqt5:
35 | import PyQt5.QtGui as QtGui
36 | import PyQt5.QtCore as QtCore
37 | import PyQt5.QtWidgets as QtWidgets
38 | from PyQt5.Qt import QApplication
39 |
40 | else:
41 | import PySide.QtGui as QtGui
42 | import PySide.QtCore as QtCore
43 | QtWidgets = QtGui
44 | QtCore.pyqtSignal = QtCore.Signal
45 | QtCore.pyqtSlot = QtCore.Slot
46 | from PySide.QtGui import QApplication
47 |
48 |
49 | def copy_to_clip(data):
50 | QApplication.clipboard().setText(data)
51 |
52 |
53 | def PLUGIN_ENTRY():
54 | """
55 | Required plugin entry point for IDAPython Plugins.
56 | """
57 | return hex_copy()
58 |
59 | class hex_copy(idaapi.plugin_t):
60 | """
61 | The IDA Plugin for hex copy.
62 | """
63 |
64 | flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
65 | comment = "Copy Hex Bytes"
66 | help = "Highlight Assembly and right-click 'Copy Hex'"
67 | wanted_name = PLUGIN_NAME
68 | wanted_hotkey = PLUGIN_HOTKEY
69 |
70 | #--------------------------------------------------------------------------
71 | # Plugin Overloads
72 | #--------------------------------------------------------------------------
73 |
74 | def init(self):
75 | """
76 | This is called by IDA when it is loading the plugin.
77 | """
78 |
79 | # initialize the menu actions our plugin will inject
80 | self._init_action_copy_bytes()
81 |
82 | # initialize plugin hooks
83 | self._init_hooks()
84 |
85 | # done
86 | idaapi.msg("%s %s initialized...\n" % (self.wanted_name, VERSION))
87 | return idaapi.PLUGIN_KEEP
88 |
89 | def run(self, arg):
90 | """
91 | This is called by IDA when this file is loaded as a script.
92 | """
93 | idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name)
94 |
95 | def term(self):
96 | """
97 | This is called by IDA when it is unloading the plugin.
98 | """
99 |
100 | # unhook our plugin hooks
101 | self._hooks.unhook()
102 |
103 | # unregister our actions & free their resources
104 | self._del_action_copy_bytes()
105 |
106 |
107 | # done
108 | idaapi.msg("%s terminated...\n" % self.wanted_name)
109 |
110 | #--------------------------------------------------------------------------
111 | # Plugin Hooks
112 | #--------------------------------------------------------------------------
113 |
114 | def _init_hooks(self):
115 | """
116 | Install plugin hooks into IDA.
117 | """
118 | self._hooks = Hooks()
119 | self._hooks.ready_to_run = self._init_hexrays_hooks
120 | self._hooks.hook()
121 |
122 | def _init_hexrays_hooks(self):
123 | """
124 | Install Hex-Rrays hooks (when available).
125 | NOTE: This is called when the ui_ready_to_run event fires.
126 | """
127 | if idaapi.init_hexrays_plugin():
128 | idaapi.install_hexrays_callback(self._hooks.hxe_callback)
129 |
130 | #--------------------------------------------------------------------------
131 | # IDA Actions
132 | #--------------------------------------------------------------------------
133 |
134 | ACTION_COPY_BYTES = "prefix:copy_bytes"
135 |
136 |
137 | def _init_action_copy_bytes(self):
138 | """
139 | Register the copy bytes action with IDA.
140 | """
141 | if (sys.version_info > (3, 0)):
142 | # Describe the action using python3 copy
143 | action_desc = idaapi.action_desc_t(
144 | self.ACTION_COPY_BYTES, # The action name.
145 | "Copy Hex", # The action text.
146 | IDACtxEntry(copy_bytes_py3), # The action handler.
147 | PLUGIN_HOTKEY, # Optional: action shortcut
148 | "Copy selected bytes as hex", # Optional: tooltip
149 | 31 # Copy icon
150 | )
151 | else:
152 | # Describe the action using python2 copy
153 | action_desc = idaapi.action_desc_t(
154 | self.ACTION_COPY_BYTES, # The action name.
155 | "Copy Hex", # The action text.
156 | IDACtxEntry(copy_bytes_py2), # The action handler.
157 | PLUGIN_HOTKEY, # Optional: action shortcut
158 | "Copy selected bytes as hex", # Optional: tooltip
159 | 31 # Copy icon
160 | )
161 |
162 |
163 | # register the action with IDA
164 | assert idaapi.register_action(action_desc), "Action registration failed"
165 |
166 |
167 | def _del_action_copy_bytes(self):
168 | """
169 | Delete the bulk prefix action from IDA.
170 | """
171 | idaapi.unregister_action(self.ACTION_COPY_BYTES)
172 |
173 |
174 |
175 |
176 | #------------------------------------------------------------------------------
177 | # Plugin Hooks
178 | #------------------------------------------------------------------------------
179 |
180 | class Hooks(idaapi.UI_Hooks):
181 |
182 | def finish_populating_widget_popup(self, widget, popup):
183 | """
184 | A right click menu is about to be shown. (IDA 7)
185 | """
186 | inject_hex_copy_actions(widget, popup, idaapi.get_widget_type(widget))
187 | return 0
188 |
189 | def finish_populating_tform_popup(self, form, popup):
190 | """
191 | A right click menu is about to be shown. (IDA 6.x)
192 | """
193 | inject_hex_copy_actions(form, popup, idaapi.get_tform_type(form))
194 | return 0
195 |
196 | def hxe_callback(self, event, *args):
197 | """
198 | HexRays event callback.
199 | We lump this under the (UI) Hooks class for organizational reasons.
200 | """
201 |
202 | #
203 | # if the event callback indicates that this is a popup menu event
204 | # (in the hexrays window), we may want to install our prefix menu
205 | # actions depending on what the cursor right clicked.
206 | #
207 |
208 | if event == idaapi.hxe_populating_popup:
209 | form, popup, vu = args
210 |
211 | idaapi.attach_action_to_popup(
212 | form,
213 | popup,
214 | hex_copy.ACTION_COPY_BYTES,
215 | "Copy Hex",
216 | idaapi.SETMENU_APP,
217 | )
218 |
219 | # done
220 | return 0
221 |
222 | #------------------------------------------------------------------------------
223 | # Prefix Wrappers
224 | #------------------------------------------------------------------------------
225 |
226 |
227 | def inject_hex_copy_actions(form, popup, form_type):
228 | """
229 | Inject prefix actions to popup menu(s) based on context.
230 | """
231 |
232 | #
233 | # disassembly window
234 | #
235 |
236 | if form_type == idaapi.BWN_DISASMS:
237 | # insert the prefix action entry into the menu
238 | #
239 |
240 | idaapi.attach_action_to_popup(
241 | form,
242 | popup,
243 | hex_copy.ACTION_COPY_BYTES,
244 | "Copy Hex",
245 | idaapi.SETMENU_APP
246 | )
247 |
248 | # done
249 | return 0
250 |
251 | #------------------------------------------------------------------------------
252 | # Byte copy
253 | #------------------------------------------------------------------------------
254 |
255 | def copy_bytes_py2():
256 | """
257 | Copy selected bytes to clipboard
258 | """
259 | if using_ida7api:
260 | start = idc.read_selection_start()
261 | end = idc.read_selection_end()
262 | if idaapi.BADADDR in (start, end):
263 | ea = idc.here()
264 | start = idaapi.get_item_head(ea)
265 | end = idaapi.get_item_end(ea)
266 | data = idc.get_bytes(start, end - start).encode('hex')
267 | print("Bytes copied: %s" % data)
268 | copy_to_clip(data)
269 | else:
270 | start = idc.SelStart()
271 | end = idc.SelEnd()
272 | if idaapi.BADADDR in (start, end):
273 | ea = idc.here()
274 | start = idaapi.get_item_head(ea)
275 | end = idaapi.get_item_end(ea)
276 | data = idc.GetManyBytes(start, end-start).encode('hex')
277 | print("Bytes copied: %s" % data)
278 | copy_to_clip(data)
279 | return
280 |
281 |
282 | def copy_bytes_py3():
283 | """
284 | Copy selected bytes to clipboard
285 | """
286 | if using_ida7api:
287 | start = idc.read_selection_start()
288 | end = idc.read_selection_end()
289 | if idaapi.BADADDR in (start, end):
290 | ea = idc.here()
291 | start = idaapi.get_item_head(ea)
292 | end = idaapi.get_item_end(ea)
293 | # fix encode bug reference
294 | # https://stackoverflow.com/questions/6624453/whats-the-correct-way-to-convert-bytes-to-a-hex-string-in-python-3
295 | data = idc.get_bytes(start, end - start).hex()
296 | print ("Bytes copied: %s" % data)
297 | copy_to_clip(data)
298 | else:
299 | start = idc.SelStart()
300 | end = idc.SelEnd()
301 | if idaapi.BADADDR in (start, end):
302 | ea = idc.here()
303 | start = idaapi.get_item_head(ea)
304 | end = idaapi.get_item_end(ea)
305 | data = idc.GetManyBytes(start, end-start).hex()
306 | print( "Bytes copied: %s" % data)
307 | copy_to_clip(data)
308 | return
309 |
310 |
311 | #------------------------------------------------------------------------------
312 | # IDA ctxt
313 | #------------------------------------------------------------------------------
314 |
315 | class IDACtxEntry(idaapi.action_handler_t):
316 | """
317 | A basic Context Menu class to utilize IDA's action handlers.
318 | """
319 |
320 | def __init__(self, action_function):
321 | idaapi.action_handler_t.__init__(self)
322 | self.action_function = action_function
323 |
324 | def activate(self, ctx):
325 | """
326 | Execute the embedded action_function when this context menu is invoked.
327 | """
328 | self.action_function()
329 | return 1
330 |
331 | def update(self, ctx):
332 | """
333 | Ensure the context menu is always available in IDA.
334 | """
335 | return idaapi.AST_ENABLE_ALWAYS
--------------------------------------------------------------------------------