前言:近日阅读《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()
总结:
书籍《Python灰帽子-黑客与逆向工程师的Python编程之道》是本好书,可惜初看时很难去理解,调试器原理和用途,今后的学习还在调试器的基础上去学习,应该会慢慢的去理解它,将其拿之为我所用
本人利用Bootstrap + EasyUI + Django开发网站:http://www.xuyangting.com/ 欢迎来访
阳台测试: 239547991(群号)
本人博客:http://xuyangting.sinaapp.com/