Hooks¶
Hooks let you inject assembly into a running process at a pattern-matched address, execute custom code, and optionally capture values back to Python. All hook classes require a Windows process.
JmpHook¶
JmpHook patches a small trampoline at a byte pattern in the target process. You subclass it,
set PATTERN and MODULE, and implement get_code() to return the instructions that run
inside the trampoline. The hook head/tail (the jump in, original displaced instructions, and
jump back) are generated automatically.
Capturing a register value¶
A common use-case is capturing the value of a register at a specific point in the target’s code.
allocate_variable reserves memory in the target process and wait_variable_non_zero blocks
until the target writes a non-zero value there.
import regex
from iced_x86 import Code, Instruction, MemoryOperand, Register
from memobj import WindowsProcess
from memobj.hook import JmpHook
class PlayerPtrHook(JmpHook):
# Unique byte sequence that identifies the instruction site to hook.
# Use regex.escape() for literal bytes or a regex.Pattern for wildcards.
PATTERN = regex.escape(bytes.fromhex("48 8B 81 E0 01 00 00 48 85 C0"))
MODULE = "game.exe"
def get_code(self) -> list[Instruction]:
# Allocate 8 bytes in the target process to hold the captured pointer.
player_ptr = self.allocate_variable("player_ptr", 8)
return [
# mov [player_ptr_addr], rcx
Instruction.create_mem_reg(
Code.MOV_MOFFS64_RAX,
MemoryOperand(displ=player_ptr.address, displ_size=8),
Register.RCX,
)
]
process = WindowsProcess.from_name("game.exe")
with PlayerPtrHook(process) as hook:
# Block until the game writes a non-zero value (i.e. the hooked code runs).
player_address = hook.wait_variable_non_zero("player_ptr")
print(f"player object at: {hex(player_address)}")
Reading the captured value later¶
If you need to poll the captured value across multiple frames rather than waiting once, keep the
hook active and call read_typed on the allocation directly:
from memobj.utils import Type
hook = PlayerPtrHook(process)
hook.activate()
# ... later, in a game loop:
alloc = hook.get_variable("player_ptr")
player_address = alloc.read_typed(Type.unsigned8)
hook.deactivate()
Modifying a value in-place¶
get_code() can also write back to the target process. Here the hook doubles a counter each
time the target reaches the pattern:
from iced_x86 import Code, Instruction, Register
from memobj.hook import JmpHook
import regex
class DoubleCounterHook(JmpHook):
PATTERN = regex.escape(bytes.fromhex("8B 05 AA BB CC DD"))
MODULE = "app.exe"
def get_code(self) -> list[Instruction]:
return [
# add eax, eax — doubles whatever EAX holds at this point
Instruction.create_reg_reg(Code.ADD_RM32_R32, Register.EAX, Register.EAX),
]
create_capture_hook¶
create_capture_hook is a factory that builds a JmpHook subclass which captures one or more
registers at a pattern address. It handles the get_code() implementation for you.
Basic register capture¶
import regex
from iced_x86 import Register
from memobj import WindowsProcess
from memobj.hook import create_capture_hook, RegisterCaptureSettings
# Build a hook class that captures RCX and RDX at the matched address.
EntityHook = create_capture_hook(
pattern=regex.escape(bytes.fromhex("48 83 EC 28 F3 0F 10 41 04")),
module="game.exe",
bitness=64,
register_captures=[
RegisterCaptureSettings(Register.RCX), # raw register value
RegisterCaptureSettings(Register.RDX),
],
)
process = WindowsProcess.from_name("game.exe")
hook = EntityHook(process)
hook.activate()
rcx_alloc = hook.get_variable("RCX_capture")
rdx_alloc = hook.get_variable("RDX_capture")
rcx_value = hook.wait_variable_non_zero("RCX_capture")
print(f"RCX = {hex(rcx_value)}")
hook.deactivate()
Variable names follow the pattern <REGISTER_NAME>_capture (e.g. RCX_capture, RDX_capture).
Dereferencing a register¶
Set derefference=True (and optionally offset) on RegisterCaptureSettings to capture the
value pointed to by the register rather than the register itself:
from iced_x86 import Register
from memobj.hook import create_capture_hook, RegisterCaptureSettings
import regex
# Capture *(RCX + 0x10) instead of RCX.
ValueHook = create_capture_hook(
pattern=regex.escape(bytes.fromhex("48 89 4C 24 08")),
module="app.exe",
bitness=64,
register_captures=[
RegisterCaptureSettings(Register.RCX, derefference=True, offset=0x10),
],
)
Using the hook as a context manager¶
All hook classes support the context manager protocol, which calls activate() on entry and
deactivate() on exit:
with EntityHook(process) as hook:
address = hook.wait_variable_non_zero("RCX_capture")
print(f"entity at {hex(address)}")
# hook is automatically deactivated here