python灰帽子--调试器基础3:获得线程相关寄存器状态

承接上文 进程调试基础2
一个调试器必须能够在任何时候都搜集到CPU的各个寄存器的状态。
当异常发生的时候这能让我们确定栈的状态,目前正在执行的指令是什么,以及其他一些非常有用的信息。

理论基础

1 - 要实现这个目标,首先要获取被调试目标内部的线程句柄

该功能由OpenThread()实现,函数原型:

HANDLE WINAPI OpenThread(
	DWORD dwDesiredAccess,
	BOOL bInheritHandle,
	DWORD dwThreadId
);

要获取线程id(TID),必须先获取目标程序内部所有线程的一个列表,然后选择我们想要的,再用OpenThread()获取它的句柄。下面讲解如何在一个系统里枚举线程。

2 - 枚举线程

(1)为了得到一个进程里寄存器的状态,我们必须枚举进程内部所有正在运行的线程。
实现这一功能的函数为CreateToolhelp32Snapshot(),它由kernel32.dll导出。
这个函数能枚举出一个进程内部所有线程的列表,已加载模块的列表,以及进程所有用的堆的列表。
函数原型:

HANDLE WINAPI CreateToolhelp32Snapshot(
   DWORD dwFlags,
   DWORD th32ProcessID
);

dwFlags参数标志了我们收集的数据类型(线程,进程,模块,或者堆)。
我们设置为TH32CS_SNAPTHREAD,也就是0x00000004,表示要手机快照snapshot中所有已经注册了的线程。
th32ProcessID传入我们要快照的进程PID

当函数调用成功,就会返回一个快照对象句柄,被接下来的函数调用以便收集更多的数据。

(2)一旦从快照中获取了线程的列表,就可以用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;
};

这里我们关注结构体中3个属性:
dwSize:该参数 必须在 Thread32First()调用之前初始化,只要把值设置成 THREADENTRY32 结构的大小就可以了。

th32ThreadID:是我们当前发现的这个线程的TID,这个参数可以被文章开头处介绍的OpenThread()函数调用以打开此线程,进行别的操作。

th32OwerProcessID:填充了当前线程所属进程的PID。

(3)一旦我们获得了第一条线程的信息,就可以通过调用Thread32Next()获取快照中下一个线程条目。
它的参数和 Thread32First()一样。循环调用 Thread32Next()直到列表的末端

3 - 获取寄存器状态
通过以上过程,我们即获取到了一个进程中所有线程的有效句柄,接下来就可以获取所有寄存器的值了。这需要GetThreadContext()来实现。同样我们也可以使用SetThreadContext()改变它们。
函数原型:

BOOL WINAPI GetThreadContext(
		HANDLE hThread,
		LPCONTEXT lpContext
	);
	BOOL WINAPI SetThreadContext(
		HANDLE hThread,
		LPCONTEXT lpContext
);

hThread:参数是从 OpenThread() 返回的线程句柄
lpContext:指向一个 CONTEXT 结构 ,
其中存储了所有寄存器的值。CONTEXT 非常重要,定义如下:

#64位系统CONTEXT结构体
class WOW64_CONTEXT(Structure):
    _pack_=16
    _fields_=[
        ("P1Home",DWORD64),
        ("P2Home",DWORD64),
        ("P3Home",DWORD64),
        ("P4Home",DWORD64),
        ("P5Home",DWORD64),
        ("P6Home",DWORD64),

        ("ContextFlags",DWORD),
        ("MxCsr",DWORD),

        ("SegCs",WORD),
        ("SegDs",WORD),
        ("SegEs",WORD),
        ("SegFs",WORD),
        ("SegGs",WORD),
        ("SegSs",WORD),
        ("EFlags",DWORD),

        ("Dr0",DWORD64),
        ("Dr1",DWORD64),
        ("Dr2",DWORD64),
        ("Dr3",DWORD64),
        ("Dr6",DWORD64),
        ("Dr7",DWORD64),

        ("Rax",DWORD64),
        ("Rcx",DWORD64),
        ("Rdx",DWORD64),
        ("Rbx",DWORD64),
        ("Rsp",DWORD64),
        ("Rbp",DWORD64),
        ("Rsi",DWORD64),
        ("Rdi",DWORD64),
        ("R8",DWORD64),
        ("R9",DWORD64),
        ("R10",DWORD64),
        ("R11", DWORD64),
        ("R12", DWORD64),
        ("R13", DWORD64),
        ("R14", DWORD64),
        ("R15", DWORD64),
        ("Rip",DWORD64),

        ("DebugControl",DWORD64),
        ("LastBranchToRip", DWORD64),
        ("LastBranchFromRip", DWORD64),
        ("LastExceptionToRip", DWORD64),
        ("LastExceptionFromRip", DWORD64),

        ("DUMMYUNIONNAME",DUMMYUNIONNAME),

        ("VectorRegister",M128A * 26),
        ("VectorControl",DWORD64)
    ]

注意:这个是64位系统的CONTEXT结构体定义,如果是32位的会有一些区别,具体请看WINDOWS常量结构体定义
如上所见,所有的寄存器都在这个结构体中了,包括调试寄存器和段寄存器。

接下来,在my_debugger.py中增加枚举线程和获取寄存器的功能

4 - 总结

总结一下以上的流程:
(1)获取进程ID,PID。
(2)以PID作为参数,通过CreateToolhelp32Snapshot()获取快照对象句柄,其实就是指定进程下所有线程的列表
(3)以快照对象句柄作为参数,通过Thread32First()函数获取快照下第一个条目的THREADENTRY32结构体实例,该结构体中的th32ThreadID属性的值即是当前线程的ID(TID)
(4)将TID作为参数,传递给OpenThread()函数,获取该线程的句柄
(5)通过Thread32Next()获取下一个条目的THREADENTRY32结构体实例,并重复第4步,获取线程句柄。
(6)将各线程的句柄作为参数,调用GetThreadContext()函数获取对应线程的CONTEXT结构体实例,即得到了各线程CPU寄存器的值

程序编写

常量和结构体定义在这里

#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"[*]Counld not obtain a valid thread handle"
			return False
	
	#枚举线程,获取线程ID列表
	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
	
	#获取寄存器的值(64位寄存器)
	def get_thread_context(self,thread_id):
		context = WOW64CONTEXT()
		context.ContextFlag = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
		h_thread = self.open_thread(thread_id)
		if kernel32.GetTheadContext(h_thread,byref(context)):
			kernel32.CloseHandle(h_thread)
			return context
		else:
			return False
	

下面编写测试程序
这里我的CPU是64位的,如果你的CPU是32位的,记得修改get_thread_context()函数中关于context变量的声明。

#get_cpu_reg_value.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 thread in list:
    thread_context = debugger.get_thread_context(thread)
    print "[*] Dumping registers for thread ID: 0x%08x" % thread
    print "[**] RDI: 0x%08x" % thread_context.Rdi
    print "[**] RSP: 0x%08x" % thread_context.Rsp
    print "[**] RBP: 0x%08x" % thread_context.Rbp
    print "[**] RAX: 0x%08x" % thread_context.Rax
    print "[**] RBX: 0x%08x" % thread_context.Rbx
    print "[**] RCX: 0x%08x" % thread_context.Rcx
    print "[**] RDX: 0x%08x" % thread_context.Rdx
    print "[*] END DUMP"
debugger.detach()

还是以计算器程序为例,打开计算器,获得计算器的PID。
然后运行上面的测试程序.终端中输入计算器的PID。
窗口中应该会显示出该进程下所有的线程context的结果。像下面这样
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值