├── .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 | [![GitHub release](https://img.shields.io/github/v/release/oalabs/hexcopy-ida.svg)](https://github.com/OALabs/hexcopy-ida/releases) [![Chat](https://img.shields.io/badge/chat-Discord-blueviolet)](https://discord.gg/cw4U3WHvpn) [![Support](https://img.shields.io/badge/Support-Patreon-FF424D)](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 --------------------------------------------------------------------------------