一个调试器应该可以提供任意某个时刻 CPU 上的寄存器状态信息;
从本质上来说, 进程是线程的集合, 线程才是CPU调度和分派的基本单位, 所以通常所说的 CPU 状态信息是针对线程而言的.
所以在获取寄存器信息之前, 首先要明确我们的获取目标 – 某个运行在 debugee 进程下的执行线程, 这一步通过 OpenThread() 函数来处理.
HANDLE WINAPI OpenThread(
_In_ DWORD dwDesiredAccess, // 渴望得到的访问权限(标志)应该为 PROCESS_ALL_ACCESS
_In_ BOOL bInheritHandle, // 是否继承句柄, 总为 FALSE
_In_ DWORD dwThreadId
);
为了得到 dwThreadId 这个参数, 我们就需要枚举目标进程中的线程列表.
借助 CreateToolhelp32Snapshot() 函数, 通过获取进程信息, 为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照, 说到底,就是可以获取系统中正在运行的进程信息、线程信息等.
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags, // 用来指定“快照”中需要返回的对象
DWORD th32ProcessID // 一个进程 ID 号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取当前进程快照时可以设为0
);
dwFlags 参数用于告知这个函数我们想要获取的信息的确切类型(进程列表、线程列表、模块列表还是堆列表).
th32ProcessID 这个参数只有在 TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE 和 TH32CS_SNAPALL 下才有意义, 这表示函数在提取线程信息时并不会理会这个参数, 它依然提取的是系统中的所有线程, 因此我们需要自行完成对线程的筛选.
CreateToolhelp32Snapshot 执行成功后, 我们将得到一个快照的句柄, 现在开始筛选这个快照:
BOOL WINAPI Thread32First(
_In_ HANDLE hSnapshot, // CreateToolhelp32Snapshot 的返回值
_Inout_ LPTHREADENTRY32 lpte // 指向 THREADENTRY32 的指针
);
lpte 参数将在 Thread32First() 返回时更新, 该结构体中包含着枚举到的首个线程的相关信息, 主要关注的结构体属性是 th32ThreadID 和 th32OwnerProcessID (具体见后面的代码).
一旦从系统快照中成功地提取首个线程信息, 接下来就可以通过另一个 api 来遍历下一个线程的相关信息.
BOOL WINAPI Thread32Next(
_In_ HANDLE hSnapshot, // CreateToolhelp32Snapshot 的返回值
_Inout_ LPTHREADENTRY32 lpte // 指向 THREADENTRY32 的指针
);
我们现在只需要循环地调用 Thread32Next() 直到遍历完成.
一旦我们取得目标线程的句柄后, 就可以通过 GetThreadContext() 来获取目标线程的上下文信息:
BOOL GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
lpContext 是输出函数, 保存的就是目标线程的上下文信息, 具体内容见后面的代码.
同时, 我们还可以通过 SetThreadContext() 来设置目标线程的上下文.
数据结构定义:
class FLOATING_SAVE_AREA(Structure):
_fields_ = [
('ControlWord', DWORD),
('StatusWord', DWORD),
('TagWord', DWORD),
('ErrorOffset', DWORD),
('ErrorSelector', DWORD),
('DataOffset', DWORD),
('DataSelector', DWORD),
('RegisterArea', BYTE * 80),
('Cr0NpxState', DWORD),
]
class CONTEXT(Structure):
_fields_ = [
('ContextFlags', DWORD),
('Dr0', DWORD),
('Dr1', DWORD),
('Dr2', DWORD),
('Dr3', DWORD),
('Dr6', DWORD),
('Dr7', DWORD),
('FloatSave', FLOATING_SAVE_AREA),
('SegGs', DWORD),
('SegFs', DWORD),
('SegEs', DWORD),
('SegDs', DWORD),
('Edi', DWORD),
('Esi', DWORD),
('Ebx', DWORD),
('Edx', DWORD),
('Ecx', DWORD),
('Eax', DWORD),
('Ebp', DWORD),
('Eip', DWORD),
('SegCs', DWORD),
('EFlags', DWORD),
('Esp', DWORD),
('SegSs', DWORD),
('ExtendedRegisters', BYTE * 512),
]
class THREADENTRY32(Structure):
_fields_ = [
('dwSize', DWORD),
('cntUsage', DWORD),
('th32ThreadID', DWORD),
('th32OwnerProcessID', DWORD),
('tpBasePri', DWORD),
('tpDeltaPri', DWORD),
('dwFlags', DWORD),
]
核心代码:
# -*- coding: utf-8 -*-
from ctypes import *
from my_debugger_defines import *
kernel32 = windll.kernel32
advapi32 = windll.advapi32
class debugger():
def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False # 用来控制循环的开关
def load(self, path_to_exe):
//...
def open_process(self, pid):
h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
return h_process
def attach(self, pid):
self.h_process = self.open_process(pid)
if kernel32.DebugActiveProcess(pid):
self.pid = int(pid)
//...
else:
print 'Unable to attach to the process.'
def run(self):
//...
def get_debug_event(self):
//...
def detach(self):
if kernel32.DebugActiveProcessStop(self.pid):
print 'Finished debugging. Exiting ...'
return True
else:
print 'There was an error'
return False
def open_thread(self, tid):
h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, tid)
if h_thread is not None:
return h_thread
else:
print 'Could not obtain a valid thread handle.'
return False
def enumerate_threads(self):
thread_entry = THREADENTRY32()
thread_list = []
snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, self.pid)
if snapshot is not None:
thread_entry.dwSize = sizeof(thread_entry)
success = kernel32.Thread32First(snapshot, byref(thread_entry))
while success:
if thread_entry.th32OwnerProcessID == self.pid:
thread_list.append(thread_entry.th32ThreadID)
success = kernel32.Thread32Next(snapshot, byref(thread_entry))
kernel32.CloseHandle(snapshot)
return thread_list
else:
return False
def get_thread_context(self, thread_id = None, h_thread = None):
context = CONTEXT()
context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
h_thread = self.open_thread(thread_id)
if kernel32.GetThreadContext(h_thread, byref(context)):
kernel32.CloseHandle(h_thread)
return context
else:
return False
测试代码:
# -*- coding: utf-8 -*-
import my_debugger
debugger = my_debugger.debugger()
pid = raw_input('Enter the pid: ')
debugger.attach(int(pid))
list_threads = debugger.enumerate_threads()
for thread in list_threads:
thread_context = debugger.get_thread_context(thread)
print thread
print 'eax: 0x%08x' % thread_context.Eax
print 'ebx: 0x%08x' % thread_context.Ebx
print 'ecx: 0x%08x' % thread_context.Ecx
print 'edx: 0x%08x' % thread_context.Edx
print 'esp: 0x%08x' % thread_context.Esp
debugger.detach()