python的系统底层操作4—寄存器 ∞
python上一篇博客中, 我们可以新建一个进程,附加另一个正在运行的进程,使原来的挂起。而对于调试一个进程,我们最感兴趣的就是能看到它运行的汇编代码和寄存器上的信息,特别是后者,是我们了解当前进程运行环境的关键,而且我们可以根据寄存器上的EIP得到当前正在运行的代码所在的内存地址。。
本文将用python语言来获取正在运行的一个进程的寄存器信息。。 前面我们已经可以获得一个进程的句柄了,而对于我们所说的CPU状态是相对线程来说的,所以这里还要去获取在这个进程下运行的线程句柄。OpenThread函数可以帮我们办到:
HANDLE WINAPI OpenThread(
__in DWORD dwDesiredAccess,
__in BOOL bInheritHandle,
__in DWORD dwThreadId
);
我本地MSDN地址 第一个参数,用THREAD_ALL_ACCESS,它的值是0x001F03FF,表示获取所有控制权限,其它选项可以查MSDN。有的MSDN版本没有这些相应的数值,怎么办呢?查winNT.h,这个文件在:C:\Program Files\Microsoft SDKs\Windows\v7.0A\Include 这个目录下,当然路径名不一定完全相同,读者根据这个自己查找。如果懒得找或找不到的话,我上传了:金山快盘附件:WinNT.h(491.31KB) 第二个参数NULL,第三个参数就是这个线程的ID,所以下面我们就要想办法去获取这个TID。 进程没有获取它内部线程的方法,怎么办?遍历所有正在运行的线程,找出其中PID(所在进程的ID)值与当前进程PID相等的线程。 CreateToolhelp32Snapshot ,这个API可以帮助我们实现线程的枚举,在kernel32里面:
HANDLE WINAPI CreateToolhelp32Snapshot(
__in DWORD dwFlags,
__in DWORD th32ProcessID
);
我本地MSDN地址 第一个参数用TH32CS_SNAPTHREAD,值0x00000004,表示枚举所有的thread。第二个指当前进程的PID。 返回的是一个HANDLER,通过Thread32First()来迈出整个线程枚举的第一步:
BOOL WINAPI Thread32First(
__in HANDLE hSnapshot,
__inout LPTHREADENTRY32 lpte
);
我本地MSDN地址 第一个参数就是上面取得的句柄,第二个参数是一个结构体,定义如下:
typedef struct tagTHREADENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ThreadID;
DWORD th32OwnerProcessID;
LONG tpBasePri;
LONG tpDeltaPri;
DWORD dwFlags;
} THREADENTRY32, *PTHREADENTRY32;
我本地MSDN地址 对于这个结构体,我们在意的就三个字段:dwSize,这个我们赋给它这个结构体的大小就行,th32ThreadID和th32OwnerProcessID,这两个是Thread32First函数帮我们填进去的,分别表示当前枚举到的线程TID和它所在的进程PID。我们根据这个进程的PID来判断,等于当前进程的PID,则把线程TID加入到一个队列里面。为什么要等于当前进程呢?因为我们当前的进程(即我们运行的这个代码)它已经附加到要调试的进程里面了。 这里,我们得到了First Thread,第一个线程,接着,我们再用Thread32Next来往下遍历。得到的线程同上处理。最后会输出一个线程队列,这个就是我们想要的线程的集合。 接下去我们的工作就是OpenThread,到里面去看看,获取我们想要的寄存器上的数据。这里就要用到GetThreadContext来获得:
BOOL WINAPI GetThreadContext(
__in HANDLE hThread,
__inout LPCONTEXT lpContext
);
我本地MSDN地址 第一个参数是OpenThread得到的线程的句柄,第二个参数就麻烦了。它是CONTEXT结构体的指针,这个结构体在MSDN中查不到完整的定义,怎么办? 直接看winNT.h源码。对于32位和64位的系统,它们的定义还不一样,想想也知道,因为它们的CPU工作原理有点差异。。不过,64位是兼容32位的,所以说,我们用32位的结构就可以了。 从winNT.h源码中摘录出32位的定义,如下:
typedef struct _CONTEXT {
//
// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.
//
DWORD ContextFlags;
//
// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
//
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
//
FLOATING_SAVE_AREA FloatSave;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;
//
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
//
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
typedef CONTEXT *PCONTEXT;
位于winNT.h的4193行开始的位置。 这里做个说明:作者的系统是win7 64位的,对于xp/win8等系统,以及32位的系统,这个文件可能会有所差异。 MSDN查不到详细信息的结构体最麻烦,你还得去找它里面的每一个字段所对应的值。还好上面的CONTEXT我们只需要给它的第一个字段赋值就可以了,其它字段GetThreadContext会帮我们填充。第一个字段:ContextFlags,在源码中查到,它的值:
#define CONTEXT_AMD64 0x100000
// end_wx86
#define CONTEXT_CONTROL (CONTEXT_AMD64 | 0x1L)
#define CONTEXT_INTEGER (CONTEXT_AMD64 | 0x2L)
#define CONTEXT_SEGMENTS (CONTEXT_AMD64 | 0x4L)
#define CONTEXT_FLOATING_POINT (CONTEXT_AMD64 | 0x8L)
#define CONTEXT_DEBUG_REGISTERS (CONTEXT_AMD64 | 0x10L)
#define CONTEXT_FULL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT)
#define CONTEXT_ALL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS)
#define CONTEXT_XSTATE (CONTEXT_AMD64 | 0x20L)
#define CONTEXT_EXCEPTION_ACTIVE 0x8000000
#define CONTEXT_SERVICE_ACTIVE 0x10000000
#define CONTEXT_EXCEPTION_REQUEST 0x40000000
#define CONTEXT_EXCEPTION_REPORTING 0x80000000
这里我们需要的值有两个: CONTEXT_FULL = 0x00010007 # 得到所有的CPU寄存器CONTEXT_DEBUG_REGISTERS = 0x00010010 # CPU的调试寄存器 到这里,基本上完事了,不过,还要记得CloseHandle(线程) ,有始有终。 最后我们把一些常用的寄存器里面的当前值打出来。所有的代码如下,和前面几篇博客一样,三个文件,一个定义结构体等,一个实现,一个测试。 定义,debug_define.py:
# -*- coding: utf-8 -*-
from ctypes import *
# 定义window编程中常用的类型
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte)
LPTSTR = POINTER(c_char)
HANDLE = c_void_p
PVOID = c_void_p
LPVOID = c_void_p
ULONG_PTR = c_ulong
SIZE_T = c_ulong
BYTE = c_ubyte
# 常量
DEBUG_PROCESS = 0x0001
CREATE_NEW_CONSOLE = 0x0010
PROCESS_ALL_ACCESS = 0x001F0FFF
THREAD_ALL_ACCESS = 0x001F03FF
INFINITE = 0xFFFFFFFF
DBG_CONTINUE = 0x00010002
STARTF_USESHOWWINDOW = 0x0001
SW_HIDE = 0
TH32CS_SNAPTHREAD = 0x00000004
CONTEXT_FULL = 0x00010007 # 得到所有的CPU寄存器
CONTEXT_DEBUG_REGISTERS = 0x00010010 # CPU的调试寄存器
# CreateProcessA函数所需结构体
class STARTUPINFO(Structure):
_fields_ = [
("cb",DWORD),
("lpReserved",LPTSTR),
("lpDesktop",LPTSTR),
("lpTitle",LPTSTR),
("dwX",DWORD),
("dwY",DWORD),
("dwXSize",DWORD),
("dwYSize",DWORD),
("dwXCountChars",DWORD),
("dwYCountChars",DWORD),
("dwFillAttribute",DWORD),
("dwFlags",DWORD),
("wShowWindow",WORD),
("cbReserved2",WORD),
("lpReserved2",LPBYTE),
("hStdInput",HANDLE),
("hStdOutput",HANDLE),
("hStdError",HANDLE)
]
class PROCESS_INFORMATION(Structure):
_fields_ = [
("hProcess",HANDLE),
("hThread",HANDLE),
("dwProcessId",DWORD),
("dwThreadId",DWORD)
]
class EXCEPTION_RECORD(Structure):
pass
EXCEPTION_RECORD._fields_ = [
("ExceptionCode",DWORD),
("ExceptionFlags",DWORD),
("ExceptionRecord",POINTER(EXCEPTION_RECORD)),
("ExceptionAddress",PVOID),
("NumberParameters",DWORD),
("ExceptionInformation",ULONG_PTR*15) # 最多允许15条错误信息
]
class EXCEPTION_DEBUG_INFO (Structure):
_fields_ = [
("ExceptionRecord", EXCEPTION_RECORD),
("dwFirstChance",DWORD)
]
class DEBUG_EVENT_UNION(Union):
_fields_ = [
("Exception",EXCEPTION_DEBUG_INFO), #只用到这一个,下面的不用定义了
# ("CreateThread",CREATE_THREAD_DEBUG_INFO),
# ("CreateProcessInfo",CREATE_PROCESS_DEBUG_INFO),
# ("ExitThread",EXIT_THREAD_DEBUG_INFO),
# ("ExitProcess",EXIT_PROCESS_DEBUG_INFO),
# ("LoadDll",LOAD_DLL_DEBUG_INFO),
# ("UnloadDll",UNLOAD_DLL_DEBUG_INFO),
# ("DebugString",OUTPUT_DEBUG_STRING_INFO),
# ("RipInfo",RIP_INFO)
]
class DEBUG_EVENT(Structure):
_fields_ = [
("dwDebugEventCode",DWORD),
("dwProcessId",DWORD),
("dwThreadId",DWORD),
("u",DEBUG_EVENT_UNION)
]
class THREADENTRY32(Structure):
_fields_ = [
("dwSize",DWORD),
("cntUsage",DWORD),
("th32ThreadID",DWORD),
("th32OwnerProcessID",DWORD),
("tpBasePri",DWORD), # 本来是LONG,它和DWORD一样的位数
("tpDeltaPri",DWORD), # LONG
("dwFlags",DWORD)
]
# Used by the CONTEXT structure
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)
]
实现,debugger.py:
# -*- coding: utf-8 -*-
from ctypes import *
from debug_define import *
kernel32 = windll.kernel32
class debugger():
def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
def loadExe(self, exe_path):
creation_flags = DEBUG_PROCESS
startupinfo = STARTUPINFO() #相当于c的alloc这么大一个空间,初始化为0或NULL
processinfo = PROCESS_INFORMATION() #同上
startupinfo.dwFlags = STARTF_USESHOWWINDOW
startupinfo.wShowWindow = SW_HIDE
startupinfo.cb = sizeof(startupinfo)
# 开始创建
if kernel32.CreateProcessA(exe_path, #lpApplicationName
None, #lpCommandLine
None, #lpProcessAttributes
None, #lpThreadAttributes
None, #bInheritHandles
creation_flags, #dwCreationFlags
None, #lpEnvironment
None, #lpCurrentDirectory
byref(startupinfo), #lpStartupInfo
byref(processinfo)): #lpProcessInformation
print "进程ID:%d" % processinfo.dwProcessId
else:
print "进程创建失败。"
# 打开这个进程
self.h_process = self.open_process(processinfo.dwProcessId)
# 由id取得进程
def open_process(self, pid):
h_progress = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
return h_progress
# 附加进程
def attach(self, pid):
self.h_process = self.open_process(pid)
# 调用DebugActiveProcess,附加到pid进程
if kernel32.DebugActiveProcess(pid):
self.debugger_active = True # 可以开始调试了
self.pid = int(pid)
self.run()
else:
print "进程附加失败!"
def run(self):
while self.debugger_active == True:
self.fire_debug_event()
# 等待触发调试事件。。
def fire_debug_event(self):
debug_event = DEBUG_EVENT()
continue_status = DBG_CONTINUE
# 接下去就是等待调试的事件被触发
if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE): #一直等
# 到这里,说明调试的条件被触发了
# 。。。(在这里做些调试相关的事情)
raw_input("按回车键继续...")
# 这个调试事件处理结束,继续
self.debugger_active = False
kernel32.ContinueDebugEvent(debug_event.dwProcessId,
debug_event.dwThreadId,
continue_status)
# 解除对进程的附加
def detach(self):
if kernel32.DebugActiveProcessStop(self.pid):
print "完成调试,退出..."
return True
else:
print "调试过程中出错..."
return False
# 打开线程
def open_thread(self, thread_id):
h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS,
None,
thread_id)
if h_thread is not None:
return h_thread
else:
print "获取线程ID时出错!"
return False
def enumerate_threads(self):
thread_entry = THREADENTRY32()
thread_list = []
snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,
self.pid)
# 上面这个函数要注意help第一个字母是小写CreateToolhelp32Snapshot
if snapshot is not None:
thread_entry.dwSize = sizeof(thread_entry)
# 遍历
# 先取得第一个线程
next = kernel32.Thread32First(snapshot, byref(thread_entry))
# 循环获取属于本进程的线程
while next:
print "------>%d" % thread_entry.th32OwnerProcessID
if thread_entry.th32OwnerProcessID == self.pid:
thread_list.append(thread_entry.th32ThreadID)
next = 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
测试,testMyDebugger.py:
# -*- coding: utf-8 -*-
import debugger.debugger as debugger
deb = debugger.debugger()
pid = raw_input("请输入要调试(附加)的进程ID:")
deb.attach(int(pid))
list = deb.enumerate_threads()
# 对于上面获得的列表中的每个线程,打印出它的寄存器信息
if list:
for thread in list:
thread_context = deb.get_thread_context(thread)
if thread_context:
# 打印信息
print "====================================="
print "[**] 线程ID: %d" % 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 "===="
print "[**] ESI: 0x%08x" % thread_context.Esi
print "[**] EDI: 0x%08x" % thread_context.Edi
print "===="
print "[**] EBP: 0x%08x" % thread_context.Ebp
print "[**] ESP: 0x%08x" % thread_context.Esp
print "[**] EIP: 0x%08x" % thread_context.Eip
print "====================================="
deb.detach()
测试结果: 请输入要调试(附加)的进程ID:11912 按回车键继续… ——>0 ………. # 这里省略很长的打印出来的进程号。。。 ——>12804 ===================================== [] 线程ID: 13124 [] EAX: 0x007d3f80 [] EBX: 0x00000000 [] ECX: 0x00000000 [] EDX: 0x00000000 ==== [] ESI: 0x007c31b8 [] EDI: 0x007c3188 ==== [] EBP: 0x0018fea8 [] ESP: 0x0018fe8c [] EIP: 0x75de78d7 ===================================== ===================================== [] 线程ID: 9224 [] EAX: 0x7716f85a [] EBX: 0x00000000 [] ECX: 0x00000000 [] EDX: 0x00000000 ==== [] ESI: 0x00000000 [] EDI: 0x00000000 ==== [] EBP: 0x00000000 [] ESP: 0x02abfff0 [] EIP: 0x770e01b4 ===================================== 完成调试,退出…