Gray Hat Python
Python for Windows security tooling — debugger internals, all three breakpoint types, function hooking, DLL/code injection, fuzzing with Sulley, and IDAPython automation. Uses ctypes to interface directly with the Windows API.
- › Use ctypes to call Windows API functions from Python for security tooling
- › Implement Windows debugger loop with WaitForDebugEvent and debug event handlers
- › Implement three breakpoint types: software (INT3), hardware (DR registers), memory (page guard)
- › Hook functions using JMP patching with trampoline for original function call
- › Inject DLLs into remote processes via VirtualAllocEx + WriteProcessMemory + CreateRemoteThread
- › Build mutation fuzzers for network protocols with crash detection
- › Use Sulley framework for structured protocol fuzzing with blocks and primitives
- › Script IDA Pro with IDAPython for automated analysis, function naming, and xref traversal
- › Use PyEmu for safe x86 emulation and malware analysis without code execution
Install this skill and Claude can design Windows security tooling in Python using ctypes — including debugger event loops, all three breakpoint types, function hook trampolines, DLL injection sequences, mutation fuzzers, and IDAPython automation scripts
Building custom security tools that interface directly with the Windows API produces instrumentation that is harder to detect and more precisely targeted than generic frameworks, while understanding debugger and injection mechanics enables analysts to recognize and detect these patterns in malware
- › Implementing a hardware breakpoint on a target function to log call arguments without patching memory (avoiding INT3 detection by the monitored process)
- › Building a mutation fuzzer for a proprietary binary protocol that catches access violations to identify the crash offset for further exploit development
- › Writing an IDAPython script to scan all functions for known cryptographic S-box constants and automatically rename matching functions with a crypto_ prefix
Gray Hat Python Skill
Core Philosophy
Python’s ctypes library lets you call Windows API functions directly, making it ideal for building security tools: debuggers, hooks, fuzzers, and injectors. Write Python to do what would otherwise require C.
ctypes Fundamentals for Windows Hacking
from ctypes import *
import ctypes
# Load a DLL
kernel32 = windll.kernel32
# Call a function
pid = kernel32.GetCurrentProcessId()
# C struct in Python
class CONTEXT(Structure):
_fields_ = [
("ContextFlags", c_ulong),
("Eax", c_ulong),
("Ebx", c_ulong),
# ... etc
]
# Pass by reference
pid = c_ulong(0)
kernel32.GetWindowThreadProcessId(hwnd, byref(pid))
Debugger Design Fundamentals
Debug Events
A debugger receives events from the OS when debugging a process:
| Event Code | Meaning |
|---|---|
| EXCEPTION_DEBUG_EVENT | Breakpoint hit, access violation, etc. |
| CREATE_THREAD_DEBUG_EVENT | New thread created |
| CREATE_PROCESS_DEBUG_EVENT | Process created |
| EXIT_THREAD_DEBUG_EVENT | Thread exited |
| EXIT_PROCESS_DEBUG_EVENT | Process exited |
| LOAD_DLL_DEBUG_EVENT | DLL loaded |
| OUTPUT_DEBUG_STRING_EVENT | OutputDebugString called |
Debugger Loop
def run(self):
while self.debugger_active:
self.get_debug_event()
def get_debug_event(self):
debug_event = DEBUG_EVENT()
if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE):
if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
self.handle_exception(debug_event)
kernel32.ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
DBG_CONTINUE
)
Breakpoint Types
Software Breakpoints (INT 3 / 0xCC)
- How: replace first byte of instruction with
0xCC(INT 3 opcode) - When CPU hits INT 3: raises EXCEPTION_BREAKPOINT
- Debugger catches exception, restores original byte, handles event
- Detectable: reading from the patched memory shows 0xCC
def bp_set(self, address):
if address not in self.breakpoints:
original_byte = self.read_process_memory(address, 1)
self.write_process_memory(address, "\xCC")
self.breakpoints[address] = original_byte
def handle_breakpoint(self):
current_ip = self.get_instruction_pointer()
current_ip -= 1 # INT3 advances EIP by 1
self.set_instruction_pointer(current_ip)
if current_ip in self.breakpoints:
self.write_process_memory(current_ip, self.breakpoints[current_ip])
Hardware Breakpoints
- How: use x86 debug registers (DR0–DR3 for addresses, DR7 for control)
- Triggered by CPU itself (no byte modification)
- Types: execution, write, read/write (data watchpoints)
- Maximum 4 active at once
- Stealthy: can’t be detected by reading memory
Memory Breakpoints
- How: set page permissions to PAGE_NOACCESS or PAGE_GUARD
- Any access to the page triggers an exception
- Used to monitor large memory regions
Hooking
Soft Hooking (User-mode)
Replace function prologue with JMP to your hook handler:
Original: push ebp; mov ebp, esp; ...
Hooked: jmp [hook_function]; ...
Hook receives original arguments, can modify them, then call original function (trampoline pattern).
PyDbg Hook Example
def my_hook(dbg, args):
print "CreateFile called: %s" % dbg.smart_dereference(args[0])
return DBG_CONTINUE
dbg.bp_set_mem(CreateFileA_address, "my_CreateFileA_hook")
dbg.set_callback(LOAD_DLL_DEBUG_EVENT, my_hook)
DLL and Code Injection
DLL Injection (Windows)
1. OpenProcess(PROCESS_ALL_ACCESS, target_pid)
2. VirtualAllocEx(process_handle, ..., len(dll_path), MEM_COMMIT, PAGE_READWRITE)
3. WriteProcessMemory(process_handle, remote_addr, dll_path, len(dll_path))
4. CreateRemoteThread(process_handle, ..., LoadLibraryA, remote_addr, ...)
The remote thread calls LoadLibraryA with the DLL path → DLL’s DllMain executes in the target process.
Code Injection
Instead of a DLL path, write shellcode directly to remote process memory and execute it:
shellcode = "\x90\x90\xcc" # NOPs + INT3
remote_mem = kernel32.VirtualAllocEx(h_process, 0, len(shellcode),
MEM_COMMIT, PAGE_EXECUTE_READWRITE)
kernel32.WriteProcessMemory(h_process, remote_mem, shellcode, len(shellcode), 0)
kernel32.CreateRemoteThread(h_process, None, 0, remote_mem, None, 0, 0)
Fuzzing
Dumb Fuzzer Pattern
import socket, struct
def send_request(data):
s = socket.socket()
s.connect(("target", 1234))
s.send(data)
response = s.recv(1024)
s.close()
return response
# Mutation fuzzing
original = "GET / HTTP/1.0\r\n\r\n"
for i in range(100):
fuzz_data = original + "A" * (i * 100)
try:
send_request(fuzz_data)
except:
print "Crash at %d bytes" % (i * 100)
break
Sulley Framework Concepts
Sulley is a Python fuzzing framework that generates structured mutations:
- Primitives: s_string, s_int, s_binary, s_delim
- Blocks: group related fields, apply transforms (length calculation, checksum)
- Sessions: manage connections, track state, detect crashes
import sulley
s_initialize("request")
if s_block_start("header"):
s_string("HELO", fuzzable=True)
s_delim(" ", fuzzable=False)
s_string("target", fuzzable=True)
s_static("\r\n")
s_block_end()
IDAPython
Script IDA Pro for automated analysis:
# Get function name at address
print idc.GetFunctionName(0x401000)
# Iterate all functions
for func_ea in idautils.Functions():
print "%x: %s" % (func_ea, idc.GetFunctionName(func_ea))
# Find all calls to a function
for xref in idautils.CodeRefsTo(0x401234, 0):
print "Called from: %x" % xref.frm
# Rename function based on analysis
idc.MakeNameEx(0x401000, "my_function_name", SN_NOWARN)
# Comment at address
idc.MakeComm(0x401020, "Buffer overflow here")
Use cases: automated vulnerability finding, function naming via heuristics, crypto constant identification, batch analysis of malware samples.
PyEmu: Emulation for Analysis
PyEmu executes x86 instructions without running actual code — useful for:
- Analyzing malware without executing it
- Tracing through obfuscated code
- Understanding algorithm behavior
emu = PEPyEmu("target.exe")
emu.execute(start_address)
emu.set_memory_handler(0x1000, my_mem_read_handler, my_mem_write_handler)
emu.set_register_handler("EAX", my_eax_write_handler)
Windows Debugger Attachment Patterns
# Attach to running process
def attach(self, pid):
h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
if kernel32.DebugActiveProcess(pid):
self.debugger_active = True
self.h_process = h_process
# Launch new process under debugger
def load(self, path_to_exe):
creation_flags = DEBUG_PROCESS
startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION()
kernel32.CreateProcessA(path_to_exe, None, None, None, None,
creation_flags, None, None,
byref(startupinfo), byref(process_information))
self.h_process = self.open_process(process_information.dwProcessId)