手把手教你写Windows 64位平台调试器
本文网页排版有些差,已上传了doc,可以下载阅读。本文中的所有代码已打包,下载地址在此。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
手写一个调试器有助于我们理解hook、进程注入等底层黑客技术具体实现,在编写过程中需要涉及大量Windows内核编程知识,因此手写调试器也可以作为启发式学习内核编程的任务驱动。(本文中代码大量参考《Gray hat python》(Python灰帽子),此书中详细讲解了调试器的基本原理,因此本文假设读者已具备基本调试技能,能理解调试器大致原理以及断点原理,读者在不理解本文内容时可参考此书,不过《Gray hat python》中的代码是32位的,并且有不少错误,笔者花了一周的时间调试修复了书中的bug,并且将其修改成了64位版本,本文旨在总结该书中第三章的内容并提供一份64位版本的代码,该代码在本人64位Win7,i3-2310M CPU主机上能成功运行)
首先来看看一个调试器需要实现哪些基本需求:
- 以调试级状态启动目标进程,或附加在目标进程上,监听调试事件;
- 为程序打断点,包括int3断点(软断点),硬件断点,内存断点;
- 遇到断点后挂起目标进程,并能做相应处理;
- 挂起目标进程后获取上下文,并能修改上下文
完成这些需求都需要调用kernel32.dll里的函数,这些函数都是用C写成的,在MSDN里可以查到这些函数的官方文档, 因此用C语言调用它们会更方便,但是C语言的开发效率不高,我们选择用Python来编写调试器。
映射C语言数据类型
在调用kernel32.dll里的函数时需要传入C语言中的数据类型声明的变量和结构体,Python提供ctypes模块来与C语言对接,C语言里的数据类型都可以映射到Python里,因此先让我们从映射数据类型着手开始我们的调试器编写。
上图展示了C语言中各数据类型在ctypes中对应的类型,而调用kernel32.dll需要使用微软自己宏定义的数据类型,这些数据类型实际上就是C语言中的基本数据类型。
建立Python工程my_debugger,再创建一个包main_package,在main_package下新建文件my_debugger_defines.py,该文件主要用来储存数据类型和结构体的定义,输入以下代码:
from ctypes import * BYTE = c_ubyte
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte)
LPTSTR = POINTER(c_char)
HANDLE = c_void_p
PVOID = c_void_p
ULONG_PTR = POINTER(c_ulong)
LPVOID = c_void_p
UINT_PTR = c_ulong
SIZE_T = c_ulong
DWORD64 = c_uint64
初步构建调试器
新建文件my_debugger.py,我们用该文件实现调试器。输入以下代码:
from ctypes import *
from main_package.my_debugger_defines import * kernel32 = windll.LoadLibrary("kernel32.dll")
我们调用LoadLibrary函数将kernel32.dll装载进来,然后就可以用kernel32来引用它了。
接下来我们要开始构造调试器,我们将调试器的功能封装进一个类,用h_process来保存调试器附加的进程的进程句柄,用pid来保存目标进程的pid,用debugger_active来作为调试器是否启动的标志,我们在__init__里声明这三个变量:
class debugger(): def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
接下来考虑我们的第一个需求:以调试级状态启动目标进程,或附加在目标进程上,监听调试事件。
以调试级状态启动目标进程
进程运行分调试级状态和非调试级状态,当进程为调试级状态,触发调试事件或是抛出异常时,操作系统会将该进程挂起,并通知附加它的调试进程。
我们编写一个load函数来让我们的调试器以调试级状态启动目标进程,这样我们就能用debugger监听目标进程的调试事件了。kernel32.dll提供CreateProcessA函数来创建进程,有关该函数的信息请自行查阅MSDN。调用CreateProcessA需要用到两个关键参数:
- STARTUPINFO和PROCESS_INFORMATION两个结构体,在MSDN里同样可以找到它们的文档,我们需要把它们映射到my_debugger_defines.py里面来;
- creation_flags,该参数我们设置成DEBUG_PROCESS,这样就使得目标进程为调试状态启动
在my_debugger_defines.py里添加DEBUG_PROCESS的声明:
DEBUG_PROCESS = 0X00000001
继续映射两个结构体:
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)
]
kernel32提供OpenProcess来为指定的pid打开进程,返回句柄,我们写一个函数来封装它:
首先定义PROCESS_ALL_ACCESS:
PROCESS_ALL_ACCESS = 0X1F0FFF
接下来编写open_process:
1 #get process handle
2 def open_process(self,pid):
3 h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,False,pid)
4 return h_process
接下来在my_debugger.py中编写load函数:
def load(self, path_to_exe): creation_flags = DEBUG_PROCESS startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION() startupinfo.dwFlags = 0X1
startupinfo.wShowWindow = 0X0
startupinfo.cb = sizeof(startupinfo) if kernel32.CreateProcessA(path_to_exe,
None,
None,
None,
None,
creation_flags,
None,
None,
byref(startupinfo),
byref(process_information)):
print "[*] We have successfully launched the process!!"
print "[*] PID: %d" % process_information.dwProcessId
self.h_process = self.open_process(process_information.dwProcessId) #keep a process handle
self.debugger_active = True
else:
print "[*] Error: 0x%08x." % kernel32.GetLastError()
该代码调用CreateProcessA创建一个进程,并且成功后打印进程的pid,调用open_process打开该进程的句柄并保存。至于下面三行代码的意思请自行MSDN:
startupinfo.dwFlags = 0X1
startupinfo.wShowWindow = 0X0
startupinfo.cb = sizeof(startupinfo)
至此我们初步实现了需求1的第一个功能:以调试状态启动目标进程。你可以创建一个debugger,像这样来测试它debugger.load(‘C:\Windows\System32\calc.exe’)。
有时我们需要将调试器附加在已启动的进程上,因此我们还需要编写一个attach(pid)。
kernel32提供DebugActiveProcess来将本进程附加在指定pid的进程上:
def attach(self,pid):
self.h_process = self.open_process(pid)
if kernel32.DebugActiveProcess(pid):
self.debugger_active = True
self.pid = int(pid)
else:
print "[*] Unable to attach the process."
值得注意的是,pid必须是整数,如果你用raw_input来输入pid的话,记得把它转换成整数。
我们现在只剩下最后一个功能(监听调试事件)就能完成需求1了。
监听调试事件
Kernel32.dll提供WaitForDebugEvent来监听调试事件,当目标进程发生调试事件时会通知我们的调试器进行处理,我们用一个循环不断调用此函数来在处理完一个调试事件后立即监听下一个调试事件。
def run(self):
while self.debugger_active == True:
self.get_debug_event()
只要调试器是启动的(self.debugger_active == True),则不断获取调试事件。
编写get_debug_event(),调用WaitForDebugEvent需要用DEBUG_EVENT结构体来保存调试事件信息,我们把它映射进来:
class DEBUG_EVENT(Structure):
_fields_ = [
("dwDebugEventCode", DWORD),
("dwProcessId", DWORD),
("dwThreadId", DWORD),
("u", _DEBUG_EVENT_UNION)
]
该结构体需要用到联合体_DEBUG_EVENT_UNION,我们也把它映射进来:
class _DEBUG_EVENT_UNION(Union):
_fields_ = [
("Exception", EXCEPTION_DEBUG_INFO),
]
该联合体实际上包含许多成员,但我们的调试器只需要用到EXCEPTION_DEBUG_INFO一个就足够了。
class EXCEPTION_DEBUG_INFO(Structure):
_fields_ = [
("ExceptionRecord", EXCEPTION_RECORD),
("dwFirstChance", DWORD)
]
class EXCEPTION_RECORD(Structure):
Pass
EXCEPTION_RECORD._fields_ = [
("ExceptionCode", DWORD),
("ExceptionFlags", DWORD),
("ExceptionRecord", POINTER(EXCEPTION_RECORD)),
("ExceptionAddress", PVOID),
("NumberParameters", DWORD),
("ExceptionInformation", UINT_PTR * 15),
]
接下来就可以开始编写get_debug_event了:
def get_debug_event(self): debug_event = DEBUG_EVENT()
continue_status = DBG_CONTINUE
if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE):
kernel32.ContinueDebugEvent(debug_event.dwProcessId,debug_event.dwThreadId, continue_status )
其中:
INFINITE = 0xFFFFFFFF
DBG_CONTINUE = 0X00010002
我们在获取到调试事件后直接调用ContinueDebugEvent来使挂起的目标继续执行。
笔者在WaitForDebugEvent监听到一个调试事件后曾将debug_event保存下来,结果导致程序出现了一个非常诡异的bug,调试了很久才发现WaitForDebugEvent并不会等debug_event的所有成员变量释放锁后才通知调试进程,因此千万不要对debug_event进行操作,否则会导致死锁发生。
现在我们的调试器已完成了需求1,你可以在监测到调试事件后添加输出一些信息的代码,attach到一个计算器上测试一下。
设置断点
作为一个调试器,最重要的功能就是打断点。首先来实现最简单的软断点。
软断点
软断点就是int3断点,当程序执行到int3指令时会触发一个异常中断下来,并查看是否有调试器附加在该程序上,如果有,则交给调试进程处理。因此打软断点就是将我们要中断的地址的字节修改为’\xCC’(int3指令),在断下来后将该字节改回去,让程序正常执行。
首先要实现的是读取内存和修改内存:
def read_process_memory(self,address,length):
data = ""
read_buf = create_string_buffer(length)
count = c_ulong(0)
if not kernel32.ReadProcessMemory(self.h_process,
address,
read_buf,
length,
byref(count)):
return False
else:
data += read_buf.raw
return data def write_process_memory(self,address,data):
count = c_ulong(0)
length = len(data)
c_data = c_char_p(data[count.value:])
if not kernel32.WriteProcessMemory(self.h_process,
address,
c_data,
length,
byref(count)):
return False
else:
return True
这样我们就能将要打断点的地址的内容修改成’\xCC’了。
我们用self.breakpoints来保存软断点,在__init__中添加:
self.breakpoints = {}
然后编写bp_set来打软断点:
def bp_set(self,address):
print "[*] Setting breakpoint at: 0x%08x" % address
if not self.breakpoints.has_key(address):
try:
original_byte = self.read_process_memory(address, 1)
self.write_process_memory(address, '\xCC')
self.breakpoints[address] = original_byte
except:
return False
return True
该函数先检查断点字典中是否有该地址,如果没有,则记录该地址首字节,并修改成’\xCC’,将其添加进self.breakpoints字典。
硬件断点
软断点最大的缺点是需要修改进程内存,这会破坏CRC,因此软断点是极其容易被反调试技术Anti的。硬件断点由于只修改寄存器,因此不容易被目标进程察觉。
硬件断点是用8个调试寄存器DR0-DR7实现的。其中DR0-DR3用来储存断点地址,DR4-DR5保留,DR6是调试状态寄存器,在进程触发硬件断点时返回触发的是哪一个断点给调试器,DR7是调试控制寄存器。
打硬件断点首先需要在DR0-DR3中找一个空闲的寄存器,将断点地址写进去,然后修改DR7相应标志位来设置断点长度和断点条件。
断点条件有三个:读、写、执行。分别表示在读该地址、写该地址、执行该地址的时候中断:
HW_ACCESS = 0x00000003
HW_EXECUTE = 0x00000000
HW_WRITE = 0x00000001
我们还需要知道断点的长度才能判断是否应该中断,断点长度也有三个选择:1字节、2字节、4字节。
如何修改寄存器呢?操作系统为每一个线程维护了一个结构体来保存上下文,当线程中断时,操作系统会将所有寄存器放进该结构体里保存起来,当线程恢复执行时将该结构体取出,恢复寄存器的值。因此我们可以通过修改这个结构体来实现修改寄存器的目的。
线程上下文结构体如下:
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)
] class DUMMYUNIONNAME(Union):
_fields_=[
("FltSave", XMM_SAVE_AREA32),
("DummyStruct", DUMMYSTRUCTNAME)
] class DUMMYSTRUCTNAME(Structure):
_fields_=[
("Header", M128A * 2),
("Legacy", M128A * 8),
("Xmm0", M128A),
("Xmm1", M128A),
("Xmm2", M128A),
("Xmm3", M128A),
("Xmm4", M128A),
("Xmm5", M128A),
("Xmm6", M128A),
("Xmm7", M128A),
("Xmm8", M128A),
("Xmm9", M128A),
("Xmm10", M128A),
("Xmm11", M128A),
("Xmm12", M128A),
("Xmm13", M128A),
("Xmm14", M128A),
("Xmm15", M128A)
] class XMM_SAVE_AREA32(Structure):
_pack_ = 1
_fields_ = [
('ControlWord', WORD),
('StatusWord', WORD),
('TagWord', BYTE),
('Reserved1', BYTE),
('ErrorOpcode', WORD),
('ErrorOffset', DWORD),
('ErrorSelector', WORD),
('Reserved2', WORD),
('DataOffset', DWORD),
('DataSelector', WORD),
('Reserved3', WORD),
('MxCsr', DWORD),
('MxCsr_Mask', DWORD),
('FloatRegisters', M128A * 8),
('XmmRegisters', M128A * 16),
('Reserved4', BYTE * 96)
] class M128A(Structure):
_fields_ = [
("Low", DWORD64),
("High", DWORD64)
]
注意,在《Gay hat python》一书中所使用的线程上下文是32位的,如果你在64位平台下使用32位的结构体来保存线程上下文,将会得到一个寄存器值全为0的空的线程上下文。
接下来编写一个用来获取线程上下文的函数,根据MSDN,在调用kernel32.GetThreadContext前需要对结构体进行初始化:
# Context flags for GetThreadContext()
CONTEXT_FULL = 0x00010007
CONTEXT_DEBUG_REGISTERS = 0x00010010 #get thread context
def get_thread_context(self, thread_id): #64-bit context
context64 = WOW64_CONTEXT()
context64.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS self.h_thread = self.open_thread(thread_id)
if kernel32.GetThreadContext(self.h_thread, byref(context64)):
kernel32.CloseHandle(self.h_thread)
return context64
else:
print '[*] Get thread context error. Error code: %d' % kernel32.GetLastError()
return False
虽然是64位,但是我用kernel32.GetThreadContext来获取线程上下文却并没任何问题,反倒是调用kernel32.Wow64GetThreadContext却会发生87号错误(参数不正确),我试了很久也没弄清楚为什么,如果有大神知道这是什么情况请联系我,谢谢!
通常硬件断点都是针对整个进程的,因此我们需要对目标进程中的所有线程逐一修改线程上下文,这就涉及到一个枚举线程的问题,kernel32仍然提供API帮助我们做这件事。每个进程都保存了一张线程快照表来保存所有线程的状态信息,有了这张表我们可以利用kernel32.Thread32First获取到第一个线程,先后调用kernel32.Thread32Next就能继续遍历线程了。
保存线程信息的结构体和获取线程快照表所需的常量参数如下所示:
class THREADENTRY32(Structure):
_fields_ = [
("dwSize", DWORD),
("cntUsage", DWORD),
("th32ThreadID", DWORD),
("th32OwnerProcessID", DWORD),
("tpBasePri", DWORD),
("tpDeltaPri", DWORD),
("dwFlags", DWORD),
] TH32CS_SNAPTHREAD = 0x00000004
枚举线程的函数如下:
# enumerate threads
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)
kernel32.CloseHandle(snapshot)
success = kernel32.Thread32Next(snapshot, byref(thread_entry))
return thread_list
else:
return False
注意,《Gay hat python》源码中此处有bug,以上代码已将其修复。
现在我们就可以开始编写打硬件断点的函数了。我们用self.hardware_breakpoints来保存硬件断点,然后枚举线程,逐一修改调试寄存器。
怎么修改调试寄存器呢?对于DR0-DR3,我们可以简单地找一个空闲的寄存器,将我们要打断点的地址写进去即可。对于DR7,需要仔细研究标志位构造。注意,我们是在64位平台下,因此寄存器是64位的,《Gay hat python》中修改DR7的代码是32位的,那么我们的代码是否应与此书不同呢?实际上,64位的DR6和DR7的高32位是用不到的,低32位构造与32位寄存器完全一致,因此此书上的代码在64位环境下兼容。
我们看看64位的DR6与DR7的构造:
DR7的0、2、4、6位代表DR0、DR1、DR2、DR3,置1表示此寄存器被打上了硬件断点。16、20、24、28位分别保存DR0、DR1、DR2、DR3的硬件断点的条件,18、22、26、30位分别保存DR0、DR1、DR2、DR3的硬件断点的长度。
举个例子,如果我们要打一个0x77284地址的内存长度为1,条件为执行的硬件断点,该怎么修改寄存器呢。首先在DR0-DR3中找一个空闲的寄存器(假设是DR2)赋值为0x77284,接着将DR7的第24位赋值为HW_EXCUTE,将26位赋值为0(长度减1)。
现在可以开始写硬件断点了:
def bp_set_hw(self, address, length, condition):
if length not in (1,2,4):
return False
else:
length -= 1
if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE):
return False
if not self.hardware_breakpoints.has_key(0):
available = 0
elif not self.hardware_breakpoints.has_key(1):
available = 1
elif not self.hardware_breakpoints.has_key(2):
available = 2
elif not self.hardware_breakpoints.has_key(3):
available = 3
else:
return False
for thread_id in self.enumerate_threads():
context64 = self.get_thread_context(thread_id)
context64.Dr7 |= 1 << (available * 2)
if available == 0:
context64.Dr0 = address
elif available == 1:
context64.Dr1 = address
elif available == 2:
context64.Dr2 = address
elif available == 3:
context64.Dr3 = address
#set condition
context64.Dr7 |= condition << ((available * 4) + 16)
#set length
context64.Dr7 |= length << ((available * 4) + 18)
#update context
h_thread = self.open_thread(thread_id)
if not kernel32.SetThreadContext(h_thread, byref(context64)):
print '[*] Set thread context error.' #update breakpoint list
self.hardware_breakpoints[available] = (address, length, condition)
return True
移除硬件断点就只需要将调试寄存器改回来就可以了,下面只提供代码:
def bp_del_hw(self, slot):
for thread_id in self.enumerate_threads():
context = self.get_thread_context(thread_id)
context.Dr7 &= ~(1 << (slot * 2))
if slot == 0:
context.Dr0 = 0x00000000
elif slot == 1:
context.Dr1 = 0x00000000
elif slot == 2:
context.Dr2 = 0x00000000
elif slot == 3:
context.Dr3 = 0x00000000
#condition
context.Dr7 &= ~(3 << ((slot * 4) + 16))
#length
context.Dr7 &= ~(3 << ((slot * 4) + 18)) h_thread = self.open_thread(thread_id)
kernel32.SetThreadContext(h_thread,byref(context))
del self.hardware_breakpoints[slot]
return True
内存断点
内存断点是最特殊的一类断点,它实际上并不是被设计来作为断点使用的,不过我们可以利用它能产生中断的特性当成断点来使用。
内存在操作系统中是分页管理的,每个内存页都有读和写的权限,如果试图对一个内存页做权限之外的事情就会触发一个异常导致中断,因此打内存断点就是修改该内存所在内存页的权限。
我们可以调用kernel32.VirtualQueryEx获取目标进程指定内存地址的内存页的基址,然后调用kernel32.VirtualProtectEx修改权限。
这一部分代码与《Gray hat python》上一样,《Gray hat python》上有详尽解释这里只给出代码:
映射所需的结构体:
class MEMORY_BASIC_INFORMATION(Structure):
_fields_ = [
("BaseAddress", PVOID),
("AllocationBase", PVOID),
("AllocationProtect", DWORD),
("RegionSize", SIZE_T),
("State", DWORD),
("Protect", DWORD),
("Type", DWORD),
]
权限常量:
# Memory page permissions, used by VirtualProtect()
PAGE_NOACCESS = 0x00000001
PAGE_READONLY = 0x00000002
PAGE_READWRITE = 0x00000004
PAGE_WRITECOPY = 0x00000008
PAGE_EXECUTE = 0x00000010
PAGE_EXECUTE_READ = 0x00000020
PAGE_EXECUTE_READWRITE = 0x00000040
PAGE_EXECUTE_WRITECOPY = 0x00000080
PAGE_GUARD = 0x00000100
PAGE_NOCACHE = 0x00000200
PAGE_WRITECOMBINE = 0x00000400
断点代码:
def bp_set_mem(self, address, size):
mbi = MEMORY_BASIC_INFORMATION() if kernel32.VirtualQueryEx(self.h_process, address, byref(mbi), sizeof(mbi)) < sizeof(mbi):
return False current_page = mbi.BaseAddress while current_page <= address + size:
self.guarded_pages.append(current_page)
old_protection = c_ulong(0)
if not kernel32.VirtualProtectEx(self.h_process, current_page,size,mbi.Protect | PAGE_GUARD,byref(old_protection)):
return False
current_page += self.page_size
self.memory_breakpoints[address] = (address, size, mbi)
return True
至此,我们实现了调试器打断点的需求,接下来我们要监听异常,在断点触发的时候中断,并调用相应例程进行处理。
断点例程
我们最开始写的get_debug_event只是在监听到调试事件后简单的让目标进程继续运行,并没有做任何事情,现在我们修改这个函数如下:
def get_debug_event(self): debug_event = DEBUG_EVENT()
continue_status = DBG_CONTINUE
bpflag = False if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE): self.thread_id = debug_event.dwThreadId
self.h_thread = self.open_thread(self.thread_id)
self.context = self.get_thread_context(self.thread_id) print 'Event code: %s Thread ID: %d' % (EVENTCODE_MAP[debug_event.dwDebugEventCode],
debug_event.dwThreadId) if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT: self.exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode
self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress if self.exception == EXCEPTION_ACCESS_VIOLATION:
print 'Access Violation Detected.' elif self.exception == EXCEPTION_BREAKPOINT:
print 'EXCEPTION_BREAKPOINT'
bpflag = not self.first_breakpoint
continue_status = self.exception_handler_breakpoint() elif self.exception == EXCEPTION_GUARD_PAGE:
print 'Guard Page Access Detected.'
continue_status == self.exception_handler_guard_page() elif self.exception == EXCEPTION_SINGLE_STEP:
print 'Single Stepping.'
continue_status = self.exception_handler_single_step() kernel32.ContinueDebugEvent(debug_event.dwProcessId,debug_event.dwThreadId, continue_status ) #if it is int3 breakpoint
if bpflag == True:
self.write_process_memory(self.exception_address,'\xCC')
值得一提的是,《Gray hat python》在int3断点中断后是直接将‘\xCC’修改回原字节,并没有在恢复目标进程执行后重新将int3
断点打回去,因此断点只生效一次,笔者的代码则做了相应处理,使断点能够继续生效。
相应断点例程如下:
#deal with memory breakpoint exception
def exception_handler_guard_page(self):
print '[*] Hit the memory breakpoint.'
print '[**] Exception address: 0x%08x' % self.exception_address
return DBG_CONTINUE #deal with breakpoint exception
def exception_handler_breakpoint(self):
print '[*] Inside the int3 breakpoint handler'
print '[**] Exception address: 0x%08x' % self.exception_address
if self.first_breakpoint == True:
self.first_breakpoint = False
print '[**] Hit the first breakpoint.'
else:
print '[**] Hit the user defined breakpoint.'
# put the original byte back
self.write_process_memory(self.exception_address,
self.breakpoints[self.exception_address]
)
return DBG_CONTINUE #deal with single step exception
def exception_handler_single_step(self): if self.context == False:
print '[*] Exception_handler_single_step get context error.'
else:
if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(0):
slot = 0
elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_key(1):
slot = 1
elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_key(2):
slot = 2
elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_key(3):
slot = 3
else:
continue_status = DBG_EXCEPTION_NOT_HANDLED
# remove this hardware breakpoint
if self.bp_del_hw(slot):
continue_status = DBG_CONTINUE
print '[*] Hardware breakpoint removed.'
else :
print '[*] Hardware breakpoint remove failed.'
#raw_input('[*] Press any key to continue.')
return continue_status
总结
本文对《Gray hat python》一书中第三章做了归纳,并且修改了源代码为64位版本,目前在笔者的电脑上运行没有任何问题。笔者初学Python和Windows内核编程,在StackOverflow查了很多问题都是由于书中的代码在64位环境下不兼容导致的,又没在网上找到一份64位版本的代码,因此写了本文。在学会编写调试器后,对一些像hook和进程注入这样的底层黑客技术也有了自己的思路,将来找时间将其实现。本文也是作为笔者的心得体会而写的,如果有大神发现代码中的问题欢迎指正,也可以与我交流讨论,我的QQ是83488773。
手把手教你写Windows 64位平台调试器的更多相关文章
- Windows 64位系统下安装JAVA环境
文件名称:jdk-7u51-windows-x64 下载Windows 64位平台的JDK安装文件. 安装 1.双击打开下载好的文件,点击“下一步”开始安装. 2. ①默认选择安装全部功能,不做调整. ...
- 手把手教你在Windows下使用MinGW编译libav(参考libx264的编入)
转自:http://www.th7.cn/Program/cp/201407/242762.shtml 手把手教你在Windows下使用MinGW编译libav libav是在Linux下使用纯c语言 ...
- [原创]手把手教你写网络爬虫(7):URL去重
手把手教你写网络爬虫(7) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 本期我们来聊聊URL去重那些事儿.以前我们曾使用Python的字典来保存抓取过的URL,目的是将重复抓取的UR ...
- 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接
本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...
- Windows 64 位系统下 Python 环境的搭建
Windows 64 位开发环境 注意:本教程适用于 Windows 7 64 位操作系统 及 Windows 10 64 位操作系统,其他系统尚未经过校验. 安装 IDE PyCharm 下载:ht ...
- [原创]手把手教你写网络爬虫(5):PhantomJS实战
手把手教你写网络爬虫(5) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 大家好!从今天开始,我要与大家一起打造一个属于我们自己的分布式爬虫平台,同时也会对涉及到的技术进行详细介绍.大 ...
- 64位平台C/C++容易犯的错误
64位平台的介绍 IA-64 is a 64-bit microprocessor architecture developed by Intel and Hewlett Packard compa ...
- 手把手教你写Sublime中的Snippet
手把手教你写Sublime中的Snippet Sublime Text号称最性感的编辑器, 并且越来越多人使用, 美观, 高效 关于如何使用Sublime text可以参考我的另一篇文章, 相信你会喜 ...
- IIS7+windows 64位配置注意事项
问题和解决办法 1 如果网站为Asp:再asp中注意启用父路径 2 操作必须使用一个可更新的查询:给用户iis_iusrs 一个完全控制的权限 3 Windows(64位IIS)未在本地计算机上 ...
随机推荐
- 深入了解MongoDB
一.介绍: 数据库分为关系型数据库和非关系型数据库 关系型数据库是建立在关系模型上的数据库,主要的有Oracle MySQL Microsoft SQL Server NoSQL是非关系型数据存储的广 ...
- Linux MySQL单实例源码编译安装5.5.32
cmake软件 tar -zxvf cmake-2.8.8.tar.gz cd cmake-2.8.8 ./bootstrap make make install cd ../ 依赖包 yum i ...
- 昂贵的聘礼,(最短路的应用),Poj(1063)
题目链接:http://poj.org/problem?id=1062 很好的一道中文题. 思路: 把每种替换当做一条边,权重为交易优惠,就是求原点0到物品1的最短路. 这里有限制条件,每个节点还有等 ...
- 【转】android四大组件--ContentProvider详解
一.相关ContentProvider概念解析: 1.ContentProvider简介在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences. ...
- tensorflow与android编译
我的过程: 1.下载tensorflow 2.下载ndk.sdk然后放到了tensorflow的目录下 3,修改workspace 4.运行命令:bazel build -c opt //tensor ...
- java设计模式——外观模式(门面模式)
一. 定义与类型 定义:门面模式,提供一个统一的接口,用来访问子系统中的一群接口,门面模式定义了一个高层接口,让子系统更容易使用 类型:结构性 二. 使用场景 子系统越来越复杂,增加外观模式提供简单调 ...
- 第51章 设置FLASH的读写保护及解除—零死角玩转STM32-F429系列
第51章 设置FLASH的读写保护及解除 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.co ...
- web的监听器,你需要知道这些...
一.简介 Listener是Servlet规范的另一个高级特性,它用于监听java web程序的事件,例如创建.修改.删除session,request,context等,并触发相应的处理事件,这个处 ...
- 原生Ajax发送请求
ajax get&post 1.使用get发送请求,会有请求缓存 1)什么叫请求缓存,请求信息相同浏览器不会再向服务器发送请求,导致访问服务器失败. 2)解决:将随机数添加到请求路径后面参数 ...
- MySQL - Linux下安装
本安装方式仅对5.7.21版本负责. 下载地址:wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.21-linux-glibc2 ...