亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

手把手教你寫Windows64位平臺調試器

2019-11-14 17:41:59
字體:
來源:轉載
供稿:網友

本文網頁排版有些差,已上傳了doc,可以下載閱讀。本文中的所有代碼已打包,下載地址在此

--------------------------------------------------------------------------------------------------------------------------------------------------------------

 

手寫一個調試器有助于我們理解hook、進程注入等底層黑客技術具體實現,在編寫過程中需要涉及大量Windows內核編程知識,因此手寫調試器也可以作為啟發式學習內核編程的任務驅動。(本文中代碼大量參考《Gray hat python》(Python灰帽子),此書中詳細講解了調試器的基本原理,因此本文假設讀者已具備基本調試技能,能理解調試器大致原理以及斷點原理,讀者在不理解本文內容時可參考此書,不過《Gray hat python》中的代碼是32位的,并且有不少錯誤,筆者花了一周的時間調試修復了書中的bug,并且將其修改成了64位版本,本文旨在總結該書中第三章的內容并提供一份64位版本的代碼,該代碼在本人64Win7,i3-2310M CPU主機上能成功運行)

首先來看看一個調試器需要實現哪些基本需求:

  1. 以調試級狀態啟動目標進程,或附加在目標進程上,監聽調試事件;
  2. 為程序打斷點,包括int3斷點(軟斷點),硬件斷點,內存斷點;
  3. 遇到斷點后掛起目標進程,并能做相應處理;
  4. 掛起目標進程后獲取上下文,并能修改上下文

 完成這些需求都需要調用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,該文件主要用來儲存數據類型和結構體的定義,輸入以下代碼:

 

 1 from ctypes import * 2  3 BYTE = c_ubyte 4 Word = c_ushort 5 DWORD = c_ulong 6 LPBYTE = POINTER(c_ubyte) 7 LPTSTR = POINTER(c_char) 8 HANDLE = c_void_p 9 PVOID = c_void_p10 ULONG_PTR = POINTER(c_ulong)11 LPVOID    = c_void_p12 UINT_PTR  = c_ulong13 SIZE_T    = c_ulong14 DWORD64 = c_uint64

 

 

 

 

初步構建調試器

新建文件my_debugger.py,我們用該文件實現調試器。輸入以下代碼:

 

1 from ctypes import *2 from main_package.my_debugger_defines import *3  4 kernel32 = windll.LoadLibrary("kernel32.dll")

 

 

 

我們調用LoadLibrary函數將kernel32.dll裝載進來,然后就可以用kernel32來引用它了。

接下來我們要開始構造調試器,我們將調試器的功能封裝進一個類,用h_PRocess來保存調試器附加的進程的進程句柄,用pid來保存目標進程的pid,用debugger_active來作為調試器是否啟動的標志,我們在__init__里聲明這三個變量:

1 class debugger():2     3     def __init__(self):4         self.h_process = None5         self.pid = None6     self.debugger_active = False

 

接下來考慮我們的第一個需求:以調試級狀態啟動目標進程,或附加在目標進程上,監聽調試事件。

 

以調試級狀態啟動目標進程

進程運行分調試級狀態和非調試級狀態,當進程為調試級狀態,觸發調試事件或是拋出異常時,操作系統會將該進程掛起,并通知附加它的調試進程。

我們編寫一個load函數來讓我們的調試器以調試級狀態啟動目標進程,這樣我們就能用debugger監聽目標進程的調試事件了。kernel32.dll提供CreateProcessA函數來創建進程,有關該函數的信息請自行查閱MSDN。調用CreateProcessA需要用到兩個關鍵參數:

  1. STARTUPINFOPROCESS_INFORMATION兩個結構體,在MSDN里同樣可以找到它們的文檔,我們需要把它們映射到my_debugger_defines.py里面來;
  2. creation_flags,該參數我們設置成DEBUG_PROCESS,這樣就使得目標進程為調試狀態啟動

my_debugger_defines.py里添加DEBUG_PROCESS的聲明:

1 DEBUG_PROCESS             = 0X00000001

 

 

繼續映射兩個結構體:

 1 class STARTUPINFO(Structure): 2     _fields_ = [ 3         ("cb", DWORD), 4         ("lpReserved", LPTSTR), 5         ("lpDesktop", LPTSTR), 6         ("lpTitle", LPTSTR), 7         ("dwX", DWORD), 8         ("dwY", DWORD), 9         ("dwXSize", DWORD),10         ("dwYSize", DWORD),11         ("dwXCountChars", DWORD),12         ("dwYCountChars", DWORD),13         ("dwFillAttribute", DWORD),14         ("dwFlags", DWORD),15         ("wShowWindow", WORD),16         ("cbReserved2", WORD),17         ("lpReserved2", LPBYTE),18         ("hStdInput", HANDLE),19         ("hStdOutput", HANDLE),20         ("hStdError", HANDLE)21 ] 22  23 class PROCESS_INFORMATION(Structure):24     _fields_ = [25         ("hProcess", HANDLE),26         ("hThread", HANDLE),27         ("dwProcessId", DWORD),28         ("dwThreadId", DWORD)29 ]

 

kernel32提供OpenProcess來為指定的pid打開進程,返回句柄,我們寫一個函數來封裝它:

首先定義PROCESS_ALL_access

1 PROCESS_ALL_ACCESS        = 0X1F0FFF

 

 

接下來編寫open_process:

1 1     #get process handle2 2     def open_process(self,pid):3 3         h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,False,pid)4 4         return h_process

 

 

接下來在my_debugger.py中編寫load函數:

 

 1   def load(self, path_to_exe): 2          3         creation_flags = DEBUG_PROCESS 4          5         startupinfo = STARTUPINFO() 6         process_information = PROCESS_INFORMATION() 7          8         startupinfo.dwFlags = 0X1 9         startupinfo.wShowWindow = 0X010         startupinfo.cb = sizeof(startupinfo)11  12         if kernel32.CreateProcessA(path_to_exe,13                                    None,14                                    None,15                                    None,16                                    None,17                                    creation_flags,18                                    None,19                                    None,20                                    byref(startupinfo),21                                    byref(process_information)):22             print "[*] We have successfully launched the process!!"23             print "[*] PID: %d" % process_information.dwProcessId24             self.h_process =  self.open_process(process_information.dwProcessId)   #keep a process handle25 self.debugger_active = True26         else:27             print "[*] Error: 0x%08x." % kernel32.GetLastError()

 

 

 

該代碼調用CreateProcessA創建一個進程,并且成功后打印進程的pid,調用open_process打開該進程的句柄并保存。至于下面三行代碼的意思請自行MSDN

1         startupinfo.dwFlags = 0X12         startupinfo.wShowWindow = 0X03       startupinfo.cb = sizeof(startupinfo)

 

 

至此我們初步實現了需求1的第一個功能:以調試狀態啟動目標進程。你可以創建一個debugger,像這樣來測試它debugger.load(‘C:/Windows/System32/calc.exe’)。

有時我們需要將調試器附加在已啟動的進程上,因此我們還需要編寫一個attach(pid)。

kernel32提供DebugActiveProcess來將本進程附加在指定pid的進程上:

 

1    def attach(self,pid):2         self.h_process = self.open_process(pid)3         if kernel32.DebugActiveProcess(pid):4             self.debugger_active = True5             self.pid = int(pid)6         else:7             print "[*] Unable to attach the process."

 

 

 

值得注意的是,pid必須是整數,如果你用raw_input來輸入pid的話,記得把它轉換成整數。

我們現在只剩下最后一個功能(監聽調試事件)就能完成需求1了。

監聽調試事件

Kernel32.dll提供WaitForDebugEvent來監聽調試事件,當目標進程發生調試事件時會通知我們的調試器進行處理,我們用一個循環不斷調用此函數來在處理完一個調試事件后立即監聽下一個調試事件。

1     def run(self):2         while self.debugger_active == True:3             self.get_debug_event()

 

只要調試器是啟動的(self.debugger_active == True),則不斷獲取調試事件。

編寫get_debug_event(),調用WaitForDebugEvent需要用DEBUG_EVENT結構體來保存調試事件信息,我們把它映射進來:

1 class DEBUG_EVENT(Structure):2     _fields_ = [3         ("dwDebugEventCode", DWORD),4         ("dwProcessId", DWORD),5         ("dwThreadId", DWORD),6         ("u", _DEBUG_EVENT_UNION)7     ]

 

該結構體需要用到聯合體_DEBUG_EVENT_UNION,我們也把它映射進來:

1 class _DEBUG_EVENT_UNION(Union):2     _fields_ = [3         ("Exception", EXCEPTION_DEBUG_INFO),4     ]

 

該聯合體實際上包含許多成員,但我們的調試器只需要用到EXCEPTION_DEBUG_INFO一個就足夠了。

 1 class EXCEPTION_DEBUG_INFO(Structure): 2     _fields_ = [ 3         ("ExceptionRecord", EXCEPTION_RECORD), 4         ("dwFirstChance", DWORD) 5     ] 6 class EXCEPTION_RECORD(Structure): 7 Pass 8 EXCEPTION_RECORD._fields_ = [ 9         ("ExceptionCode",        DWORD),10         ("ExceptionFlags",       DWORD),11         ("ExceptionRecord",      POINTER(EXCEPTION_RECORD)),12         ("ExceptionAddress",     PVOID),13         ("NumberParameters",     DWORD),14         ("ExceptionInformation", UINT_PTR * 15),15         ]

 

接下來就可以開始編寫get_debug_event了:

1     def get_debug_event(self):2         3         debug_event = DEBUG_EVENT()4         continue_status = DBG_CONTINUE5         if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE):6         kernel32.ContinueDebugEvent(debug_event.dwProcessId,debug_event.dwThreadId, continue_status )

 

其中:

 

1 INFINITE                  = 0xFFFFFFFF2 DBG_CONTINUE              = 0X00010002

 

 

 

我們在獲取到調試事件后直接調用ContinueDebugEvent來使掛起的目標繼續執行。

筆者在WaitForDebugEvent監聽到一個調試事件后曾將debug_event保存下來,結果導致程序出現了一個非常詭異的bug,調試了很久才發現WaitForDebugEvent并不會等debug_event的所有成員變量釋放鎖后才通知調試進程,因此千萬不要對debug_event進行操作,否則會導致死鎖發生。

現在我們的調試器已完成了需求1,你可以在監測到調試事件后添加輸出一些信息的代碼,attach到一個計算器上測試一下。

 

設置斷點

作為一個調試器,最重要的功能就是打斷點。首先來實現最簡單的軟斷點。

軟斷點

軟斷點就是int3斷點,當程序執行到int3指令時會觸發一個異常中斷下來,并查看是否有調試器附加在該程序上,如果有,則交給調試進程處理。因此打軟斷點就是將我們要中斷的地址的字節修改為’/xCC’(int3指令),在斷下來后將該字節改回去,讓程序正常執行。

首先要實現的是讀取內存和修改內存:

 1     def read_process_memory(self,address,length): 2         data = "" 3         read_buf = create_string_buffer(length) 4         count = c_ulong(0) 5         if not kernel32.ReadProcessMemory(self.h_process, 6                                           address, 7                                           read_buf, 8                                           length, 9                                           byref(count)):10             return False11         else:12             data += read_buf.raw13             return data14         15     def write_process_memory(self,address,data):16         count = c_ulong(0)17         length = len(data)18         c_data = c_char_p(data[count.value:])19         if not kernel32.WriteProcessMemory(self.h_process,20                                           address,21                                           c_data,22                                           length,23                                           byref(count)):24             return False25         else:26             return True 

 

這樣我們就能將要打斷點的地址的內容修改成’/xCC’了。

我們用self.breakpoints來保存軟斷點,在__init__中添加:

1 self.breakpoints = {}

 

然后編寫bp_set來打軟斷點:

 1     def bp_set(self,address): 2         print "[*] Setting breakpoint at: 0x%08x" % address 3         if not self.breakpoints.has_key(address): 4             try: 5                 original_byte = self.read_process_memory(address, 1) 6                 self.write_process_memory(address, '/xCC') 7                 self.breakpoints[address] = original_byte 8             except: 9                 return False10             return True

 

該函數先檢查斷點字典中是否有該地址,如果沒有,則記錄該地址首字節,并修改成’/xCC’,將其添加進self.breakpoints字典。

 

硬件斷點

軟斷點最大的缺點是需要修改進程內存,這會破壞CRC,因此軟斷點是極其容易被反調試技術Anti的。硬件斷點由于只修改寄存器,因此不容易被目標進程察覺。

硬件斷點是用8個調試寄存器DR0-DR7實現的。其中DR0-DR3用來儲存斷點地址,DR4-DR5保留,DR6是調試狀態寄存器,在進程觸發硬件斷點時返回觸發的是哪一個斷點給調試器,DR7是調試控制寄存器。

打硬件斷點首先需要在DR0-DR3中找一個空閑的寄存器,將斷點地址寫進去,然后修改DR7相應標志位來設置斷點長度和斷點條件。

斷點條件有三個:讀、寫、執行。分別表示在讀該地址、寫該地址、執行該地址的時候中斷:

1 HW_ACCESS                      = 0x000000032 HW_EXECUTE                     = 0x000000003 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前需要對結構體進行初始化:

 1 # Context flags for GetThreadContext() 2 CONTEXT_FULL                   = 0x00010007 3 CONTEXT_DEBUG_REGISTERS        = 0x00010010 4   5     #get thread context            6     def get_thread_context(self, thread_id): 7   8         #64-bit context 9         context64 = WOW64_CONTEXT()10         context64.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS11         12         self.h_thread = self.open_thread(thread_id)13         if kernel32.GetThreadContext(self.h_thread, byref(context64)):14             kernel32.CloseHandle(self.h_thread)15             return context6416         else:17             print '[*] Get thread context error. Error code: %d' % kernel32.GetLastError()18             return False

 

雖然是64位,但是我用kernel32.GetThreadContext來獲取線程上下文卻并沒任何問題,反倒是調用kernel32.Wow64GetThreadContext卻會發生87號錯誤(參數不正確),我試了很久也沒弄清楚為什么,如果有大神知道這是什么情況請聯系我,謝謝!

通常硬件斷點都是針對整個進程的,因此我們需要對目標進程中的所有線程逐一修改線程上下文,這就涉及到一個枚舉線程的問題,kernel32仍然提供API幫助我們做這件事。每個進程都保存了一張線程快照表來保存所有線程的狀態信息,有了這張表我們可以利用kernel32.Thread32First獲取到第一個線程,先后調用kernel32.Thread32Next就能繼續遍歷線程了。

保存線程信息的結構體和獲取線程快照表所需的常量參數如下所示:

 1 class THREADENTRY32(Structure): 2     _fields_ = [ 3         ("dwSize",             DWORD), 4         ("cntUsage",           DWORD), 5         ("th32ThreadID",       DWORD), 6         ("th32OwnerProcessID", DWORD), 7         ("tpBasePri",          DWORD), 8         ("tpDeltaPri",         DWORD), 9         ("dwFlags",            DWORD),10 ]11  12 TH32CS_SNAPTHREAD   = 0x00000004

 

枚舉線程的函數如下:

 

 1    # enumerate threads 2     def enumerate_threads(self): 3         thread_entry = THREADENTRY32() 4         thread_list = [] 5         snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, self.pid) 6         if snapshot is not None: 7             thread_entry.dwSize = sizeof(thread_entry) 8             success = kernel32.Thread32First(snapshot, byref(thread_entry)) 9             while success:10                 if thread_entry.th32OwnerProcessID == self.pid:11                     thread_list.append(thread_entry.th32ThreadID)12                     kernel32.CloseHandle(snapshot)13                 success = kernel32.Thread32Next(snapshot, byref(thread_entry))14             return thread_list15         else:16             return False

 

 

 

注意,《Gay hat python源碼中此處有bug,以上代碼已將其修復。

現在我們就可以開始編寫打硬件斷點的函數了。我們用self.hardware_breakpoints來保存硬件斷點,然后枚舉線程,逐一修改調試寄存器。

怎么修改調試寄存器呢?對于DR0-DR3,我們可以簡單地找一個空閑的寄存器,將我們要打斷點的地址寫進去即可。對于DR7,需要仔細研究標志位構造。注意,我們是在64位平臺下,因此寄存器是64位的,《Gay hat python》中修改DR7的代碼是32位的,那么我們的代碼是否應與此書不同呢?實際上,64位的DR6DR7的高32位是用不到的,低32位構造與32位寄存器完全一致,因此此書上的代碼在64位環境下兼容。

我們看看64位的DR6DR7的構造:

 

DR70、2、4、6位代表DR0、DR1、DR2、DR3,置1表示此寄存器被打上了硬件斷點。1620、2428位分別保存DR0、DR1DR2、DR3的硬件斷點的條件,18、22、2630位分別保存DR0、DR1、DR2、DR3的硬件斷點的長度。

舉個例子,如果我們要打一個0x77284地址的內存長度為1,條件為執行的硬件斷點,該怎么修改寄存器呢。首先在DR0-DR3中找一個空閑的寄存器(假設是DR2)賦值為0x77284,接著將DR7的第24位賦值為HW_EXCUTE,將26位賦值為0(長度減1)。

現在可以開始寫硬件斷點了:

 

 1   def bp_set_hw(self, address, length, condition): 2         if length not in (1,2,4): 3             return False 4         else: 5             length -= 1 6         if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE): 7             return False 8         if not self.hardware_breakpoints.has_key(0): 9             available = 010         elif not self.hardware_breakpoints.has_key(1):11             available = 112         elif not self.hardware_breakpoints.has_key(2):13             available = 214         elif not self.hardware_breakpoints.has_key(3):15             available = 316         else:17             return False18         for thread_id in self.enumerate_threads():19             context64 = self.get_thread_context(thread_id)20             context64.Dr7 |= 1 << (available * 2)21             if available == 0:22                 context64.Dr0 = address 23             elif available == 1:24                 context64.Dr1 = address25             elif available == 2:26                 context64.Dr2 = address27             elif available == 3:28                 context64.Dr3 = address29             #set condition30             context64.Dr7 |= condition << ((available * 4) + 16)31             #set length32             context64.Dr7 |= length << ((available * 4) + 18)33             #update context34             h_thread = self.open_thread(thread_id)35             if not kernel32.SetThreadContext(h_thread, byref(context64)):36                 print '[*] Set thread context error.'37  38         #update breakpoint list39         self.hardware_breakpoints[available] = (address, length, condition)40         return True41  

 

 

 

移除硬件斷點就只需要將調試寄存器改回來就可以了,下面只提供代碼:

 1     def bp_del_hw(self, slot): 2         for thread_id in self.enumerate_threads(): 3             context = self.get_thread_context(thread_id) 4             context.Dr7 &= ~(1 <<  (slot * 2)) 5             if slot == 0: 6                 context.Dr0 = 0x00000000 7             elif slot == 1: 8                 context.Dr1 = 0x00000000 9             elif slot == 2:10                 context.Dr2 = 0x0000000011             elif slot == 3:12                 context.Dr3 = 0x0000000013             #condition14             context.Dr7 &= ~(3 << ((slot * 4) + 16))15             #length16             context.Dr7 &= ~(3 << ((slot * 4) + 18))17              18             h_thread = self.open_thread(thread_id)19             kernel32.SetThreadContext(h_thread,byref(context))20         del self.hardware_breakpoints[slot] 21         return True22  

 

內存斷點

內存斷點是最特殊的一類斷點,它實際上并不是被設計來作為斷點使用的,不過我們可以利用它能產生中斷的特性當成斷點來使用。

內存在操作系統中是分頁管理的,每個內存頁都有讀和寫的權限,如果試圖對一個內存頁做權限之外的事情就會觸發一個異常導致中斷,因此打內存斷點就是修改該內存所在內存頁的權限。

我們可以調用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),]   

 

權限常量:

 1 # Memory page permissions, used by VirtualProtect() 2 PAGE_NOACCESS                  = 0x00000001 3 PAGE_READONLY                  = 0x00000002 4 PAGE_READWRITE                 = 0x00000004 5 PAGE_WRITECOPY                 = 0x00000008 6 PAGE_EXECUTE                   = 0x00000010 7 PAGE_EXECUTE_READ              = 0x00000020 8 PAGE_EXECUTE_READWRITE         = 0x00000040 9 PAGE_EXECUTE_WRITECOPY         = 0x0000008010 PAGE_GUARD                     = 0x0000010011 PAGE_NOCACHE                   = 0x0000020012 PAGE_WRITECOMBINE              = 0x00000400

 

斷點代碼:

 1     def bp_set_mem(self, address, size): 2         mbi = MEMORY_BASIC_INFORMATION() 3          4         if kernel32.VirtualQueryEx(self.h_process, address, byref(mbi), sizeof(mbi)) < sizeof(mbi): 5             return False 6          7         current_page = mbi.BaseAddress 8          9         while current_page <= address + size:10             self.guarded_pages.append(current_page)11             old_protection = c_ulong(0)12             if not kernel32.VirtualProtectEx(self.h_process, current_page,size,mbi.Protect | PAGE_GUARD,byref(old_protection)):13                 return False14             current_page += self.page_size15         self.memory_breakpoints[address] = (address, size, mbi)16         return True17  

 

至此,我們實現了調試器打斷點的需求,接下來我們要監聽異常,在斷點觸發的時候中斷,并調用相應例程進行處理。

 

斷點例程

我們最開始寫的get_debug_event只是在監聽到調試事件后簡單的讓目標進程繼續運行,并沒有做任何事情,現在我們修改這個函數如下:

 1     def get_debug_event(self): 2          3         debug_event = DEBUG_EVENT() 4         continue_status = DBG_CONTINUE 5         bpflag = False 6          7         if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE): 8              9             self.thread_id = debug_event.dwThreadId10             self.h_thread = self.open_thread(self.thread_id)11             self.context = self.get_thread_context(self.thread_id)12             13             print 'Event code: %s Thread ID: %d' % (EVENTCODE_MAP[debug_event.dwDebugEventCode],14                                                     debug_event.dwThreadId)15             16             if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:17                 18                 self.exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode19                 self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress20                21                 if self.exception == EXCEPTION_ACCESS_VIOLATION:22                     print 'Access Violation Detected.'23                 24                 elif self.exception == EXCEPTION_BREAKPOINT:25                     print 'EXCEPTION_BREAKPOINT'26                     bpflag = not self.first_breakpoint27                     continue_status = self.exception_handler_breakpoint()28                     29                 30                 elif self.exception == EXCEPTION_GUARD_PAGE:31                     print 'Guard Page Access Detected.'32                     continue_status == self.exception_handler_guard_page()33                 34                 elif self.exception == EXCEPTION_SINGLE_STEP:35                     print 'Single Stepping.'36                     continue_status = self.exception_handler_single_step()37                 38             kernel32.ContinueDebugEvent(debug_event.dwProcessId,debug_event.dwThreadId, continue_status )39            40             #if it is int3 breakpoint 41             if bpflag == True:42                 self.write_process_memory(self.exception_address,'/xCC')

 

值得一提的是,《Gray hat python》在int3斷點中斷后是直接將‘/xCC’修改回原字節,并沒有在恢復目標進程執行后重新將int3

斷點打回去,因此斷點只生效一次,筆者的代碼則做了相應處理,使斷點能夠繼續生效。

相應斷點例程如下:

 1              2  3     #deal with memory breakpoint exception 4     def exception_handler_guard_page(self): 5         print '[*] Hit the memory breakpoint.' 6         print '[**] Exception address: 0x%08x' % self.exception_address 7         return DBG_CONTINUE 8      9     #deal with breakpoint exception10     def exception_handler_breakpoint(self):11         print '[*] Inside the int3 breakpoint handler'12         print '[**] Exception address: 0x%08x' % self.exception_address13         if self.first_breakpoint == True:14             self.first_breakpoint = False15             print '[**] Hit the first breakpoint.'16         else:17             print '[**] Hit the user defined breakpoint.'18             # put the original byte back 19             self.write_process_memory(self.exception_address,20                                       self.breakpoints[self.exception_address]21                                       )22         return DBG_CONTINUE23     24     #deal with single step exception25     def exception_handler_single_step(self):26         27         if self.context == False:28             print '[*] Exception_handler_single_step get context error.'29         else:30             if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(0):31                 slot = 032             elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_key(1):33                 slot = 134             elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_key(2):35                 slot = 236             elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_key(3):37                 slot = 338             else:39                 continue_status = DBG_EXCEPTION_NOT_HANDLED40             # remove this hardware breakpoint41             if self.bp_del_hw(slot):42                 continue_status = DBG_CONTINUE43                 print '[*] Hardware breakpoint removed.'44             else :45                 print '[*] Hardware breakpoint remove failed.'46             #raw_input('[*] Press any key to continue.')47             return continue_status48  

 

總結

本文對《Gray hat python》一書中第三章做了歸納,并且修改了源代碼為64位版本,目前在筆者的電腦上運行沒有任何問題。筆者初學PythonWindows內核編程,在StackOverflow查了很多問題都是由于書中的代碼在64位環境下不兼容導致的,又沒在網上找到一份64位版本的代碼,因此寫了本文。在學會編寫調試器后,對一些像hook和進程注入這樣的底層黑客技術也有了自己的思路,將來找時間將其實現。本文也是作為筆者的心得體會而寫的,如果有大神發現代碼中的問題歡迎指正,也可以與我交流討論,我的QQ83488773


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品久久国产精品99gif| 久久精品中文字幕免费mv| 国产一区视频在线播放| 韩国精品美女www爽爽爽视频| 川上优av一区二区线观看| 亚洲精选一区二区| 久久成人综合视频| 麻豆乱码国产一区二区三区| 国内外成人免费激情在线视频网站| 91国产中文字幕| 亚洲天堂色网站| 亚洲毛片在线看| 成人免费观看a| 菠萝蜜影院一区二区免费| 国产精品九九九| 一区二区三区 在线观看视| 久久久噜久噜久久综合| 自拍偷拍亚洲欧美| 国产国产精品人在线视| 久久久久久久97| 久久精品一偷一偷国产| 日韩三级影视基地| 国产精品自拍偷拍视频| 2023亚洲男人天堂| 日韩中文字幕在线视频| 久久精品一本久久99精品| 国产91在线播放精品91| 岛国av一区二区在线在线观看| 中文字幕不卡av| 麻豆国产精品va在线观看不卡| 日韩欧美高清视频| 国产精品情侣自拍| 久久91亚洲精品中文字幕| 欧美性xxxxx极品娇小| 久久天天躁狠狠躁老女人| 国产成人精品在线视频| 国内精品中文字幕| 久久男人av资源网站| 欧美精品免费看| 精品动漫一区二区三区| 久久久精品久久久久| 久久久天堂国产精品女人| 欧美激情一区二区三区在线视频观看| 欧美日韩黄色大片| 亚洲丝袜在线视频| 亚洲欧美在线播放| 亚洲三级黄色在线观看| 欧美性猛交xxxx免费看| 国产成人高清激情视频在线观看| 亚洲人成77777在线观看网| 97精品国产91久久久久久| 不卡伊人av在线播放| 亚洲精品之草原avav久久| 日韩网站免费观看| 97免费中文视频在线观看| 97精品一区二区三区| 久久免费视频在线| 欧美日韩一区二区精品| 国产精品午夜一区二区欲梦| 久久免费在线观看| 亚洲精品综合精品自拍| 2019中文字幕全在线观看| 欧美性jizz18性欧美| 亚洲精品视频网上网址在线观看| 久久精品人人爽| 欧美国产日韩一区二区| 亚洲国产一区二区三区在线观看| 亚洲激情视频在线| 欧美一区二区三区四区在线| 欧美一区二三区| 亚洲国产日韩欧美在线99| 亚洲激情 国产| 欧美亚洲成人精品| 欧美自拍大量在线观看| 97色在线视频| 日韩一区二区在线视频| 日韩电影第一页| 日韩视频在线免费观看| 日韩在线视频中文字幕| 成人97在线观看视频| 九九久久综合网站| 欧美大片免费看| 久久艹在线视频| 成人信息集中地欧美| 欧美在线免费观看| 日韩欧美精品中文字幕| 在线播放国产一区中文字幕剧情欧美| 国产午夜精品全部视频播放| 国产成人精品久久二区二区| 欧美黄色www| 欧美夫妻性视频| 亚洲tv在线观看| 成人免费观看49www在线观看| 美女久久久久久久| 宅男66日本亚洲欧美视频| 欧美丰满老妇厨房牲生活| 18一19gay欧美视频网站| 欧美极品xxxx| 国产综合久久久久久| 欧美激情xxxx性bbbb| 久久久精品国产亚洲| 日产日韩在线亚洲欧美| www.日韩欧美| 日韩视频精品在线| 日韩在线免费视频观看| 久久亚洲综合国产精品99麻豆精品福利| 久久久成人av| www.日韩av.com| 国产欧美精品一区二区三区-老狼| 国产精品久久久久久久久久小说| 亚洲小视频在线| 成人免费淫片aa视频免费| 国产成人免费av电影| 亚洲变态欧美另类捆绑| 91精品国产高清自在线看超| 欧美日本啪啪无遮挡网站| 亚洲一区二区自拍| 91亚洲精品久久久| 成人做爰www免费看视频网站| 亚洲国产成人一区| 国产精品网址在线| 欧美成人在线免费视频| 国产日韩欧美一二三区| 亚洲第一精品自拍| 最近2019年中文视频免费在线观看| 国产精品亚洲激情| 最好看的2019年中文视频| 国产精品欧美激情| 亚洲精品一区中文字幕乱码| 国产+人+亚洲| 2019国产精品自在线拍国产不卡| 日韩一区二区欧美| 亚洲视频在线看| 亚洲香蕉伊综合在人在线视看| 久久久噜噜噜久噜久久| 成人免费视频xnxx.com| 日韩电视剧在线观看免费网站| 欧美日韩另类字幕中文| 久久精品国产亚洲精品| 日韩欧美极品在线观看| 国产精品一区二区久久久久| xxxx性欧美| 日韩高清免费观看| 日韩中文字幕在线播放| 久久免费精品视频| 欧美xxxx综合视频| 亚洲精品在线观看www| 国产69精品99久久久久久宅男| 欧美激情伊人电影| 欧美激情小视频| 国产经典一区二区| 中文字幕亚洲欧美日韩在线不卡| 日韩精品在线免费观看视频| 亚洲精品欧美极品| 亚洲少妇中文在线| 日韩av在线导航| 亚洲欧美激情四射在线日| 精品女同一区二区三区在线播放| 成人网在线视频| www.色综合| 97精品国产97久久久久久免费| 尤物九九久久国产精品的特点| 久久九九热免费视频| 久久综合伊人77777尤物|