企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 3.2获得 CPU 寄存器状态 一个调试器必须能够在任何时候都搜集到 CPU 的各个寄存器的状态。当异常发生的时 候这能让我们确定栈的状态,目前正在执行的指令是什么,以及其他一些非常有用的信息。 要实现这个目的,首先要获取被调试目标内部的线程句柄,这个功能由 OpenThread()实现. 函数原型如下: ``` HANDLE WINAPI OpenThread( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId ); ``` 这看起来非常像 OpenProcess()的姐妹函数,除了这次是用线程标识符(thread identifier TID) 提到了进程标识符(PID)。 我们必须先获得一个执行着的程序内部所有线程的一个列表,然后选择我们想要的,再 用 OpenThread() 获 取 它 的 句 柄 。 让 我 研 究 下 如 何 在 一 个 系 统 里 枚 举 线 程 ( enumerate threads)。 ### 3.2.1 枚举线程 为了得到一个进程里寄存器的状态,我们必须枚举进程内部所有正在运行的线程。线程 是进程中真正的执行体(大部分活都是线程干的),即使一个程序不是多线程的,它也至少 有一个线程,主线程。实现这一功能的是一个强大的函数 CreateToolhelp32Snapshot(),它由 kernel32.dll 导出。这个函数能枚举出一个进程内部所有线程的列表,以加载的模块(DLLs) 的列表,以及进程所拥有的堆的列表。函数原型如下: ``` HANDLE WINAPI CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID ); ``` dwFlags 参数标志了我们需要收集的数据类型(线程,进程,模块,或者堆 )。这里我 们把它设置成 TH32CS_SNAPTHREAD,也就是 0x00000004,表示我们要搜集快照 snapshot 中 所 有 已 经 注 册 了 的 线 程 。 th32ProcessID 传 入 我 们 要 快 照 的 进 程 , 不 过 它 只 对 TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, TH32CS_SNAPHEAPLIST, and TH32CS_SNAPALL 这几个模块有用,对 TH32CS_SNAPTHREAD 可是没什么用的哦(后面 有说明)。当 CreateToolhelp32Snapshot()调用成功,就会返回一个快照对象的句柄,被接下 来的函数调以便搜集更多的数据。 一旦我们从快照中获得了线程的列表,我们就能用 Thread32First()枚举它们了。函数原型如下: ``` BOOL WINAPI Thread32First( HANDLE hSnapshot, LPTHREADENTRY32 lpte ); ``` hSnapshot 就 是 上 面 通 过 CreateToolhelp32Snapshot() 获 得 镜 像 句 柄 , lpte 指 向 一 个 THREADENTRY32 结构(必须初始化过)。这个结构在 Thread32First()在调用成功后自动填 充,其中包含了被发现的第一个线程的相关信息。结构定义如下: ``` typedef struct THREADENTRY32{ DWORD dwSize; DWORD cntUsage; DWORD th32ThreadID; DWORD th32OwnerProcessID; LONG tpBasePri; LONG tpDeltaPri; DWORD dwFlags; }; ``` 在这个结构中我们感兴趣的是 dwSize, th32ThreadID, 和 th32OwnerProcessID 3 个参数。 dwSize 必须在 Thread32First()调用之前初始化,只要把值设置成 THREADENTRY32 结构的 大小就可以了。th32ThreadID 是我们当前发现的这个线程的 TID,这个参数可以被前面说过 的 OpenThread() 函数调用以打开此线程,进行别的操作。 th32OwnerProcessID 填充了当前 线 程 所 属 进 程 的 PID 。 为 了 确 定 线 程 是 否 属 于 我 们 调 试 的 目 标 进 程 , 需 要 将 th32OwnerProcessID 的值和目标进程对比,相等则说明这个线程是我们正在调试的。一旦我 们获得了第一个线程的信息,我们就能通过调用 Thread32Next()获取快照中的下一个线程条 目。它的参数和 Thread32First()一样。循环调用 Thread32Next()直到列表的末端。 ### 3.2.2 把所有的组合起来 现在我们已经获得了一个线程的有效句柄,最后一步就是获取所有寄存器的值。这就需 要通过 GetThreadContext()来实现。同样我们也能用 SetThreadContext()改变它们。 ``` BOOL WINAPI GetThreadContext( HANDLE hThread, LPCONTEXT lpContext ); BOOL WINAPI SetThreadContext( HANDLE hThread, LPCONTEXT lpContext ); ``` hThread 参数是从 OpenThread() 返回的线程句柄,lpContext 指向一个 CONTEXT 结构, 其中存储了所有寄存器的值。CONTEXT 非常重要,定义如下: ``` typedef struct CONTEXT { DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_SAVE_AREA FloatSave; DWORD 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; BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; }; ``` 如你说见所有的寄存器都在这个列表中了,包括调试寄存器和段寄存器。在我们剩下的 工作中,将大量的使用到这个结构,所以尽快的实习起来。 让我们回来看看我们的老朋友 my_debugger.py 继续扩展它,增加枚举线程和获取寄存 器的功能。 ``` #my_debugger.py class debugger(): ... 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 "[*] Could not obtain a valid thread handle." return False def enumerate_threads(self): thread_entry = THREADENTRY32() 36 Chapter 3 thread_list = [] snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS _SNAPTHREAD, self.pid) if snapshot is not None: # You have to set the size of the struct # or the call will fail thread_entry.dwSize = sizeof(thread_entry) success = kernel32.Thread32First(snapshot, byref(thread_entry)) byref(thread_entry)) while success: if thread_entry.th32OwnerProcessID == self.pid: thread_list.append(thread_entry.th32ThreadID) success = kernel32.Thread32Next(snapshot, kernel32.CloseHandle(snapshot) return thread_list else: return False def get_thread_context (self, thread_id): context = CONTEXT() context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS # Obtain a handle to the thread h_thread = self.open_thread(thread_id) if kernel32.GetThreadContext(h_thread, byref(context)): kernel32.CloseHandle(h_thread) return context else: return False ``` 调试器已经扩展成功,让我们更新测试模块试验下新功能。 ``` #my_test.py import my_debugger debugger = my_debugger.debugger() pid = raw_input("Enter the PID of the process to attach to: ") debugger.attach(int(pid)) list = debugger.enumerate_threads() # For each thread in the list we want to # grab the value of each of the registers Building a Windows Debugger 37 for thread in list: thread_context = debugger.get_thread_context(thread) # Now let's output the contents of some of the registers print "[*] Dumping registers for thread ID: 0x%08x" % thread print "[**] EIP: 0x%08x" % thread_context.Eip print "[**] ESP: 0x%08x" % thread_context.Esp print "[**] EBP: 0x%08x" % thread_context.Ebp 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 "[*] END DUMP" debugger.detach() ``` 当你运行测试代码,你将看到如清单 3-1 显示的数据。 ``` Enter the PID of the process to attach to: 4028 [*] Dumping registers for thread ID: 0x00000550 [**] EIP: 0x7c90eb94 [**] ESP: 0x0007fde0 [**] EBP: 0x0007fdfc [**] EAX: 0x006ee208 [**] EBX: 0x00000000 [**] ECX: 0x0007fdd8 [**] EDX: 0x7c90eb94 [*] END DUMP [*] Dumping registers for thread ID: 0x000005c0 [**] EIP: 0x7c95077b [**] ESP: 0x0094fff8 [**] EBP: 0x00000000 [**] EAX: 0x00000000 [**] EBX: 0x00000001 [**] ECX: 0x00000002 [**] EDX: 0x00000003 [*] END DUMP [*] Finished debugging. Exiting... ``` Listing 3-1:每个线程的 CPU 寄存器值 太酷了 ! 我们现在能够在任何时候查询所有寄存器的状态了。试验下不同的进程 ,看看能得到什么结果。到此为止我们已经完成了我们调试器的核心部分,是时间实现一些基础 调试事件的处理函数了。