Python 灰帽子筆記之偵錯程式
前言:近日閱讀《Python灰帽子-黑客與逆向工程師的Python程式設計之道》看的我是雲裡霧裡,單單這個偵錯程式就各種Google資料,現在也是懂了點點,感覺做下筆記,以便後續繼續學習
手寫一個偵錯程式有助於我們理解hook、程序注入等底層黑客技術具體實現,在編寫過程中需要涉及大量Windows核心程式設計知識,因此手寫偵錯程式也可以作為啟發式學習核心程式設計的任務驅動。要想除錯一個程式, 就需要與目標程式建立某種聯絡, 為此, 我們的偵錯程式應該具備兩種基本能力:
1. 開啟一個程式, 並使它以自身子程序的形式執行起來
2. 附加到一個正在進去的程式上
1.建立子程序
BOOL CreateProcess (
LPCTSTR lpApplicationName, // 程式路徑
LPTSTR lpCommandLine, // 命令列引數
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags, // 建立的標誌
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo, // 指向一個用於決定新程序的主窗體如何顯示的 STARTUPINFO 結構體
LPPROCESS_INFORMATION lpProcessInformation // 指向一個用來接收新程序的識別資訊的 PROCESS_INFORMATION 結構體
);
基本上, 我們只要關心 lpApplicationName、lpCommandLine、dwCreationFlags、lpStartupInfo 和 lpProcessInformation 這五個引數, 其它的設為 None即可.
2.偵錯程式專案簡介
偵錯程式包括三個py檔案,my_debugger.py 、my_debugger_defines.py 、my_test.py
my_debugger_defines.py : 儲存的是程式常量及結構定義的地方
my_debugger.py : 是核心程式碼實現的地方
my_test.py : 執行檔案
直接上示例程式碼:
my_debugger_defines.py
# coding=utf-8
from ctypes import *
# 為ctype變數建立符合匈牙利命名風格的匿名,這樣可以使程式碼更接近Win32的風格
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte)
LPTSTR = POINTER(c_char)
HANDLE = c_void_p
# 常值定義
DEBUG_RPOCESS = 0x00000001
CREATE_NEW_CONSOLE = 0x00000010
# 定義函式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),
]
my_debugger.py
# coding=utf-8
from ctypes import *
from my_debugger_defines import *
kernel32 = windll.kernel32
class Debugger():
def __init__(self):
pass
def load(self,path_to_exe):
# 引數dwCreationFlags中的標誌位控制著程序的建立方式。你若希望新建立的程序獨佔一個新的控制檯視窗,而不是與父程序
# 共用同一個控制檯,你可以加上標誌位CREATE_NEW_CONSOLE creation_flags = DEBUG_PROCESS
creation_flags = CREATE_NEW_CONSOLE # DEBUG_RPOCESS
# 例項化之前定義的結構體
startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION()
# 在以下兩個成員變數的共同作用下,新建程序將在一個單獨的窗體中被顯示,你可以通過改變結構體STARTUPINFO中的各成員
# 變數的值來控制debugee程序的行為。
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0
# 設定結構體STARTUPINFO中的成員變數
# cb的值,用以表示結構體本身的大小
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 lauched the process!"
print "[*] PID: %d" % process_information.dwProcessId
else:
print "[*] Error: 0x%08x." % kernel32.GetLastError()
my_test.py
import my_debugger
debugger = my_debugger.Debugger()
debugger.load("C:\\WINDOWS\\system32\\calc.exe")
一個簡單的偵錯程式就已經構建完成了,執行my_test.py指令碼後,會看到下圖所示:
3.偵錯程式附加到現有程序
my_debugger_defines.py
# -*- coding: utf-8 -*-
from ctypes import *
# 統一命名風格
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte)
LPSTR = c_char_p
LPWSTR = c_wchar_p
HANDLE = c_void_p
PVOID = c_void_p
UINT_PTR = c_ulong
DEBUG_PROCESS = 0x00000001 # 與父程序共用一個控制檯(以子程序的方式執行)
CREATE_NEW_CONSOLE = 0x00000010 # 獨佔一個新控制檯(以單獨的程序執行)
PROCESS_ALL_ACCESS = 0x001F0FFF
INFINITE = 0xFFFFFFFF
DBG_CONTINUE = 0x00010002
# 定義 CreateProcessA() 所需的結構體
class STARTUPINFOA(Structure):
_fields_ = [
('cb', DWORD),
('lpReserved', LPSTR),
('lpDesktop', LPSTR),
('lpTitle', LPSTR),
('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_INFORMATIONA(Structure):
_fields_ = [
('hProcess', HANDLE),
('hThread', HANDLE),
('dwProcessId', DWORD),
('dwThreadId', DWORD),
]
########################################################################
class EXCEPTION_RECORD(Structure):
pass
EXCEPTION_RECORD._fields_ = [ # 這裡之所以要這麼設計, 是因為 ExceptionRecord 呼叫了 EXCEPTION_RECORD, 所以要提前宣告
('ExceptionCode', DWORD),
('ExceptionFlags', DWORD),
('ExceptionRecord', POINTER(EXCEPTION_RECORD)),
('ExceptionAddress', PVOID),
('NumberParameters', DWORD),
('ExceptionInformation', UINT_PTR * 15),
]
class EXCEPTION_DEBUG_INFO(Structure):
_fields_ = [
('ExceptionRecord', EXCEPTION_RECORD),
('dwFirstChance', DWORD),
]
class U_DEBUG_EVENT(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', U_DEBUG_EVENT),
]
my_debugger.py
# -*- coding: utf-8 -*-
from ctypes import *
from my_debugger_defines import *
kernel32 = windll.kernel32
class Debugger():
def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False # 用來控制迴圈的開關
def load(self, path_to_exe):
creation_flags = DEBUG_PROCESS
startupinfo = STARTUPINFOA()
process_information = PROCESS_INFORMATIONA()
startupinfo.cb = sizeof(startupinfo)
# 下面的設定讓新程序在一個單獨窗體中被顯示
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0
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)
else:
print 'Error: 0x%08x' % kernel32.GetLastError()
def open_process(self, pid):
h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
return h_process
def attach(self, pid):
self.h_process = self.open_process(pid)
if kernel32.DebugActiveProcess(pid):
self.debugger_active = True
self.pid = int(pid)
self.run()
else:
print 'Unable to attach to the process.'
def run(self):
while self.debugger_active == True:
self.get_debug_event()
def get_debug_event(self):
debug_event = DEBUG_EVENT()
continue_status = DBG_CONTINUE
if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE):
# 這裡沒有對除錯事件做任何處理, 只是簡單的返回到目標程式
raw_input('press a key to continue ...')
self.debugger_active = False
kernel32.ContinueDebugEvent(debug_event.dwProcessId,
debug_event.dwThreadId,
continue_status)
def detach(self):
if kernel32.DebugActiveProcessStop(self.pid):
print 'Finished debugging. Exiting ...'
return True
else:
print 'There was an error'
return False
my_test.py
# -*- coding: utf-8 -*-
import my_debugger
debugger = my_debugger.Debugger()
pid = raw_input('Enter the pid: ')
debugger.attach(int(pid))
debugger.detach()
執行my_test.py結果如下圖所示:
總結:
書籍《Python灰帽子-黑客與逆向工程師的Python程式設計之道》是本好書,可惜初看時很難去理解,偵錯程式原理和用途,今後的學習還在偵錯程式的基礎上去學習,應該會慢慢的去理解它,將其拿之為我所用
陽臺測試: 239547991(群號)