Skip to content

Instantly share code, notes, and snippets.

@ceres-c
Last active February 17, 2026 16:25
Show Gist options
  • Select an option

  • Save ceres-c/a007997982ed8434283e50e64da89f8b to your computer and use it in GitHub Desktop.

Select an option

Save ceres-c/a007997982ed8434283e50e64da89f8b to your computer and use it in GitHub Desktop.
IDA Pro 9.0 extract and copy instruction bytes to clipboard
# -*- 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