Last active
February 17, 2026 16:25
-
-
Save ceres-c/a007997982ed8434283e50e64da89f8b to your computer and use it in GitHub Desktop.
IDA Pro 9.0 extract and copy instruction bytes to clipboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| """ | |
| IDAPython plugin for IDA 9.0+ (optimized for macOS) | |
| Copies selected instruction/data bytes as contiguous hexadecimal string. | |
| Install: Copy to your IDA python plugin folder. | |
| Tested with python 3.12.12 | |
| Hotkey: Cmd-Shift-H (Meta-Shift-H) | |
| Features: | |
| - Supports both code instructions and data items | |
| - Automatic clipboard copy with multiple fallback methods | |
| - IDA 9.0 API compliant | |
| - Comprehensive error handling | |
| - 100% AI slop, lol | |
| """ | |
| from typing import Optional, Tuple | |
| import subprocess | |
| import ida_idaapi | |
| import ida_kernwin | |
| import ida_bytes | |
| def _read_selection() -> Optional[Tuple[int, int]]: | |
| """ | |
| Read the current selection range from the active IDA view. | |
| Returns: | |
| Tuple of (start_ea, end_ea) if selection exists, None otherwise. | |
| Note: end_ea is exclusive. | |
| """ | |
| view = ida_kernwin.get_current_widget() | |
| if not view: | |
| return None | |
| try: | |
| result = ida_kernwin.read_range_selection(view) | |
| # IDA 9.0 consistent format: (success: bool, start_ea: int, end_ea: int) | |
| if isinstance(result, tuple) and len(result) == 3: | |
| success, start, end = result | |
| if success and start != end: | |
| return (start, end) | |
| # Fallback for older formats | |
| elif isinstance(result, tuple) and len(result) == 2: | |
| start, end = result | |
| if start != end: | |
| return (start, end) | |
| except Exception as e: | |
| ida_kernwin.msg(f"[sel_insn_bytes_hex] Selection read error: {e}\n") | |
| return None | |
| def _copy_to_clipboard(text: str) -> bool: | |
| """ | |
| Copy text to system clipboard using multiple fallback methods. | |
| Args: | |
| text: String to copy to clipboard | |
| Returns: | |
| True if copy succeeded, False otherwise | |
| """ | |
| # Method 1: Try IDA's native clipboard API | |
| try: | |
| if hasattr(ida_kernwin, "set_clipboard_text"): | |
| result = ida_kernwin.set_clipboard_text(text) | |
| # IDA's API may return None on success | |
| if result is None or result: | |
| return True | |
| except Exception: | |
| pass | |
| # Method 2: Try Qt clipboard (IDA 9.0+ uses PySide6 on macOS) | |
| for qt_module in ("PySide6", "PyQt5", "PySide2"): | |
| try: | |
| mod = __import__(qt_module, fromlist=["QtWidgets", "QtGui"]) | |
| QtWidgets = mod.QtWidgets | |
| QtGui = mod.QtGui | |
| # Get QApplication instance | |
| app = QtWidgets.QApplication.instance() | |
| if app is None: | |
| # Fallback: try direct clipboard access | |
| clipboard = QtGui.QGuiApplication.clipboard() | |
| if clipboard: | |
| clipboard.setText(text) | |
| return True | |
| continue | |
| # Use application's clipboard | |
| clipboard = app.clipboard() | |
| if clipboard: | |
| clipboard.setText(text) | |
| # Force event processing (macOS specific workaround) | |
| app.processEvents() | |
| return True | |
| except Exception: | |
| continue | |
| # Method 3: macOS-specific fallback using pbcopy | |
| try: | |
| process = subprocess.Popen( | |
| ['pbcopy'], | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| close_fds=True | |
| ) | |
| process.communicate(text.encode('utf-8')) | |
| return process.returncode == 0 | |
| except Exception: | |
| pass | |
| return False | |
| def _get_selected_bytes(include_data: bool = True) -> Optional[bytes]: | |
| """ | |
| Extract bytes from the selected address range. | |
| Args: | |
| include_data: If True, include both code and data items. | |
| If False, only include code instructions. | |
| Returns: | |
| Bytes from selected items, or None if no valid selection | |
| """ | |
| selection = _read_selection() | |
| if not selection: | |
| ida_kernwin.warning("No range selection in the current view.") | |
| return None | |
| start, end = selection | |
| # Ensure start < end | |
| if start > end: | |
| start, end = end, start | |
| output = bytearray() | |
| # Start from the first item head in the range | |
| ea = start | |
| if not ida_bytes.is_head(ida_bytes.get_full_flags(ea)): | |
| ea = ida_bytes.next_head(ea, end) | |
| if ea == ida_idaapi.BADADDR: | |
| ida_kernwin.warning("No valid items in selection.") | |
| return None | |
| # Iterate through all items in the range | |
| while ea != ida_idaapi.BADADDR and ea < end: | |
| flags = ida_bytes.get_full_flags(ea) | |
| # Check if we should include this item | |
| should_include = False | |
| if ida_bytes.is_code(flags): | |
| should_include = True | |
| elif include_data and ida_bytes.is_data(flags): | |
| should_include = True | |
| if should_include: | |
| item_size = ida_bytes.get_item_size(ea) | |
| # Truncate if item extends beyond selection | |
| if ea + item_size > end: | |
| item_size = end - ea | |
| item_bytes = ida_bytes.get_bytes(ea, item_size) | |
| if item_bytes: | |
| output.extend(item_bytes) | |
| # Move to next item | |
| next_ea = ida_bytes.next_head(ea, end) | |
| if next_ea == ida_idaapi.BADADDR or next_ea <= ea: | |
| break | |
| ea = next_ea | |
| if not output: | |
| ida_kernwin.warning("No bytes found in selection.") | |
| return None | |
| return bytes(output) | |
| class SelInsnBytesHexPlugin(ida_idaapi.plugin_t): | |
| """ | |
| IDA plugin to copy selected instruction/data bytes as hexadecimal string. | |
| """ | |
| flags = ida_idaapi.PLUGIN_KEEP | |
| comment = "Copy selected bytes as contiguous hexadecimal string" | |
| help = ( | |
| "Select an address range in an IDA View, then press the hotkey.\n" | |
| "Supports both code instructions and data items.\n" | |
| "Output is copied to clipboard and printed to the Output window." | |
| ) | |
| wanted_name = "Copy Bytes as Hex" | |
| wanted_hotkey = "Meta-Shift-H" # Cmd-Shift-H on macOS | |
| def init(self) -> int: | |
| """Initialize the plugin.""" | |
| ida_kernwin.msg("[sel_insn_bytes_hex] Plugin loaded successfully\n") | |
| return ida_idaapi.PLUGIN_OK | |
| def run(self, arg: int) -> None: | |
| """ | |
| Execute the plugin action. | |
| Args: | |
| arg: Plugin argument (0 for hotkey invocation) | |
| """ | |
| # Get bytes from selection (include both code and data) | |
| selected_bytes = _get_selected_bytes(include_data=True) | |
| if not selected_bytes: | |
| return | |
| # Convert to uppercase hex string (no spaces, no 0x prefix) | |
| hex_string = selected_bytes.hex().upper() | |
| # Always print to Output window | |
| print("[sel_insn_bytes_hex] Bytes:", hex_string) | |
| # Attempt to copy to clipboard | |
| copy_success = _copy_to_clipboard(hex_string) | |
| # Provide user feedback | |
| status = "copied to clipboard" if copy_success else "clipboard copy failed" | |
| ida_kernwin.msg( | |
| f"[sel_insn_bytes_hex] {len(selected_bytes)} bytes ({status})\n" | |
| ) | |
| if not copy_success: | |
| ida_kernwin.msg( | |
| "[sel_insn_bytes_hex] Output available in Output window\n" | |
| ) | |
| def term(self) -> None: | |
| """Clean up when plugin is unloaded.""" | |
| pass | |
| def PLUGIN_ENTRY() -> SelInsnBytesHexPlugin: | |
| """ | |
| IDA plugin entry point. | |
| Returns: | |
| Plugin instance | |
| """ | |
| return SelInsnBytesHexPlugin() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment