Python构建Windows调试器

一、Windows API

1、进程

创建进程
参考文档:CreateProcess

注意:本章内容所有使用的函数都是Windows的内核函数对编码要求比较严格,因为python使用的是kernel32.dll动态链接库里导出的函数,在kernel32.dll里没有CreateProcess()这个函数,只有CreateProcessA()跟CreateProcessW()。

/***ANSI版本***/
BOOL WINAPI CreateProcessA(
     LPCSTR lpApplicationName,
     LPSTR lpCommandLine,
     LPSECURITY_ATTRIBUTES lpProcessAttributes,
     LPSECURITY_ATTRIBUTES lpThreadAttributes,
     BOOL blnheritHandles,
     DWORD dwCreationFlags,
     LPVOID lpEnvironment,
     LPCSTR lpCurrentDirectory,
     LPSTARTUPINFOA lpStartupInfo,    //ANSI版本
     LPPROCESS_INFORMATION lpProcessInformation
);
/***Unicode版本***/
BOOL WINAPI CreateProcessW(
     LPCWSTR lpApplicationName,
     LPWSTR lpCommandLine,
     LPSECURITY_ATTRIBUTES lpProcessAttributes
     LPSECURITY_ATTRIBUTES lpThreadAttributes,
     BOOL bInheritHandles,
     DWORD dwCreationFlags,
     LPVOID lpEvironment,
     LPCWSTR lpCurrentDirectory
     LPSTARTUPINFOW lpStartupInfo,   //Unicode版本
     LPPROCESS_INFORMATION lpProcessInformation
);

窗口状态
参考文档:STARTUPINFO

typedef struct _STARTUPINFO {
//ANSI版本:STARTUPINFOA
//Unicode版本:STARTUPINFOW
     DWORD  cb;
     LPTSTR lpReserved; //ANSI版本;Unicode版本 LPTWSTR lpReserved
     LPTSTR lpDesktop;  //ANSI版本;Unicode版本 LPTWSTR lpDesktop
     LPTSTR lpTitle;  //ANSI版本;Unicode版本 LPTWSTR lpTitle
     DWORD  dwX;
     DWORD  dwY;
     DWORD  dwXSize;
     DWORD  dwYSize;
     DWORD  dwXCountChars;
     DWORD  dwYCountChars;
     DWORD  dwFillAttribute;
     DWORD  dwFlags;
     WORD   wShowWindow;
     WORD   cbReserved2;
     LPBYTE lpReserved2;
     HANDLE hStdInput;
     HANDLE hStdOutput;
     HANDLE hStdError;
}STARTUPINFO, *LPSTARTUPINFO;

进程信息
参考文档:PROCESS_INFORMATION

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;
  HANDLE hThread;
  DWORD  dwProcessId;
  DWORD  dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

获取进程句柄
参考文档:OpenProcess

HANDLE WINAPI OpenProcess(
     DWORD dwDesiredAccess,  //该参数用于表示向目标进程对象索要的访问权限
     BOOL bInheritHadnle,
     DWORD dwProcessId,
);

附加进程
参考文档:DebugActiveProcess

BOOL WINAPI DebugActiveProcess(
     DWORD dwProcessId  //进程的PID
);

调试事件
参考文档:WaitForDebugEvent

BOOL WINAPI WaitForDebugEvent(
     LPDEBUG_EVENT lpDebugEvent,
     DWORD         dwMilliseconds
);

恢复进程
参考文档:ContinueDebugEvent

BOOL WINAPI ContinueDebugEvent(
    DWORD dwProcessId,
    DWORD dwThreadId,
    DWORD dwContinueStatus
);

分离进程
参考文档:DebugActiveProcessStop

BOOL WINAPI DebugActiveProcessStop(
     DWORD dwProcessId
);

调试事件的信息
参考文档:DEBUG_EVENT

typedef struct _DEBUG_EVENT {
     DWORD dwDebugEventCode;  //事件码
     DWORD dwProcessId;
     DWORD dwThreadId;
     union {
        EXCEPTION_DEBUG_INFO      Exception;
        CREATE_THREAD_DEBUG_INFO  CreateThread;
        CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
        EXIT_THREAD_DEBUG_INFO    ExitThread;
        EXIT_PROCESS_DEBUG_INFO   ExitProcess;
        LOAD_DLL_DEBUG_INFO       LoadDll;
        UNLOAD_DLL_DEBUG_INFO     UnloadDll;
        OUTPUT_DEBUG_STRING_INFO  DebugString;
        RIP_INFO                  RipInfo;
  } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

2、异常
参考文档:EXCEPTION_DEBUG_INFO

typedef struct _EXCEPTION_DEBUG_INFO {
       EXCEPTION_RECORD ExceptionRecord;
       DWORD            dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;

参考文档:EXCEPTION_RECORD

typedef struct _EXCEPTION_RECORD {
      DWORD                    ExceptionCode;   //异常状态码,参考GetExceptionCode函数
      DWORD                    ExceptionFlags;
      struct _EXCEPTION_RECORD  *ExceptionRecord;
      PVOID                    ExceptionAddress;
      DWORD                    NumberParameters;
      ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

3、线程
获取线程
参考文档:OpenThread

HANDLE WINAPI OpenThread(
     DWORD dwDesiredAccess,  //设置线程访问权限
     BOOL  bInheritHandle,
     DWORD dwThreadId
);

枚举线程
参考文档:CreateToolhelp32Snapshot

HANDLE WINAPI CreateToolhelp32Snapshot(
     DWORD dwFlags,  //获取信息的确切类型(进程类型,线程类型,模块类型,堆类型)
     DWORD th32ProcessID
);

线程筛选
参考文档:Thread32First

BOOL WINAPI Thread32First(
     HANDLE          hSnapshot,
     LPTHREADENTRY32 lpte  //THREADENTRY32结构体
);

参考文档:Thread32Next

BOOL WINAPI Thread32Next(
    HANDLE          hSnapshot,
    LPTHREADENTRY32 lpte  //THREADENTRY32结构体
);

参考文档:THREADENTRY32

typedef struct tagTHREADENTRY32 {
    DWORD dwSize;
    DWORD cntUsage;
    DWORD th32ThreadID;
    DWORD th32OwnerProcessID;
    LONG  tpBasePri;
    LONG  tpDeltaPri;
    DWORD dwFlags;
} THREADENTRY32, *PTHREADENTRY32;

4、CONTEXT
参考文档:CONTEXT

//CONTEXT
//由于不同处理器的CONTEXT是不同的,MSDN官方没有给出具体的结构。
//有安装VC,或者VisualStudio的可以转到定义。查看具体结构。
//x86处理器的CONTEXT结构体
typedef struct _CONTEXT {
    DWORD ContextFlags;
    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;
    FLOATING_SAVE_AREA FloatSave;
    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;
    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;
    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;              
    DWORD   EFlags;          
    DWORD   Esp;
    DWORD   SegSs;
    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; //MAXIMUM_SUPPORTED_EXTENSION = 512
} CONTEXT;
//ContextFlags
CONTEXT_CONTROL = 0x00010001  //SS:SP, CS:IP, FLAGS, BP
CONTEXT_INTEGER = 0x00010002  //AX, BX, CX, DX, SI, DI
CONTEXT_SEGMENTS = 0x00010004  //DS, ES, FS, GS
CONTEXT_FLOATING_POINT = 0x00010008
CONTEXT_DEBUG_REGISTERS = 0x00010010  //DB 0-3,6,7
CONTEXT_EXTENDED_REGISTERS = 0x00010020
CONTEXT_FULL = 0x00010007 //CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)

获取线程的CONTEXT
参考文档:GetThreadContext

BOOL WINAPI GetThreadContext(
    HANDLE    hThread,
    LPCONTEXT lpContext
);

设置线程的CONTEXT
参考文档:SetThreadContext

BOOL WINAPI SetThreadContext(
    HANDLE  hThread,
    const CONTEXT *lpContext
);

5、内存操作
参考文档:ReadProcessMemory

BOOL WINAPI ReadProcessMemory(
    HANDLE  hProcess,
    LPCVOID lpBaseAddress,
    LPVOID  lpBuffer,
    SIZE_T  nSize,
    SIZE_T  *lpNumberOfBytesRead
);

参考文档:WriteProcessMemory

BOOL WINAPI WriteProcessMemory(
    HANDLE  hProcess,
    LPVOID  lpBaseAddress,
    LPCVOID lpBuffer,
    SIZE_T  nSize,
    SIZE_T  *lpNumberOfBytesWritten
);

获取函数虚拟内存地址
参考文档:GetProcAddress

FARPROC WINAPI GetProcAddress(
    HMODULE hModule,
    LPCSTR  lpProcName
);

获取函数所在模块(.dll或.exe文件)
参考文档:[GetModuleHandle][1] [CloseHandle][2]
[1]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms683199(v=vs.85).aspx
[2]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms724211(v=vs.85).aspx

/***ANSI版本***/
HMODULE WINAPI GetModuleHandleA(
    LPCSTR lpModuleName
);
/***Unicode版本***/
HMODULE WINAPI GetModuleHandleW(
    LPCWSTR lpModuleName
);
//使用完该函数后记得调用CloseHandle()关闭句柄,避免内核对象泄漏(句柄泄漏)
BOOL WINAPI CloseHandle(
    HANDLE hObject
);

获取系统信息
参考文档:[GetSystemInfo][3] [SYSTEM_INFO][4]
[3]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms724381(v=vs.85).aspx
[4]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms724958(v=vs.85).aspx

void WINAPI GetSystemInfo(
   LPSYSTEM_INFO lpSystemInfo  //SYSTEM_INFO结构体
);
typedef struct _SYSTEM_INFO {
  union {
    DWORD  dwOemId;
    struct {
      WORD wProcessorArchitecture;
      WORD wReserved;
    };
  };
  DWORD     dwPageSize;  //系统默认内存页大小
  LPVOID    lpMinimumApplicationAddress;
  LPVOID    lpMaximumApplicationAddress;
  DWORD_PTR dwActiveProcessorMask;
  DWORD     dwNumberOfProcessors;
  DWORD     dwProcessorType;
  DWORD     dwAllocationGranularity;
  WORD      wProcessorLevel;
  WORD      wProcessorRevision;
} SYSTEM_INFO;

获取指定进程内存页信息
参考文档:[VirtualQueryEx][5] [MEMORY_BASIC_INFORMATION][6] [VirtualProtectEx][7]
[5]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366907(v=vs.85).aspx
[6]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366775(v=vs.85).aspx
[7]: https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366899(v=vs.85).aspx

SIZE_T WINAPI VirtualQueryEx(
  HANDLE                    hProcess,
  LPCVOID                   lpAddress,
  PMEMORY_BASIC_INFORMATION lpBuffer,
  SIZE_T                    dwLength
);
typedef struct _MEMORY_BASIC_INFORMATION {
  PVOID  BaseAddress;
  PVOID  AllocationBase;
  DWORD  AllocationProtect;
  SIZE_T RegionSize;
  DWORD  State;
  DWORD  Protect; 
  DWORD  Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
BOOL WINAPI VirtualProtectEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flNewProtect,  //Memory Protection Constants
  PDWORD lpflOldProtect
);

二、各种系统宏定义

1、进程权限
参考文档:Process Access Rights

//Process Access Rights
DELETE = 0x00010000
READ_CONTROL = 0x00020000
WRITE_OWNER = 0x00080000
SYNCHRONIZE = 0x00100000

PROCESS_CREATE_PROCESS = 0x0080
PROCESS_CREATE_THREAD = 0x0002
PROCESS_DUP_HANDLE = 0x0040
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
PROCESS_SET_INFORMATION = 0x0200
PROCESS_SET_QUOTA = 0x0100
PROCESS_SUSPEND_RESUME = 0x0800
PROCESS_TERMINATE = 0x0001
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020

PROCESS_ALL_ACCESS = 0x001F0FFF //该值表示目标进程的所有访问权限,其值为以上权限的其他值取位或运算得来。

2、线程权限
参考文档:Thread Access Rights

//Thread Access Rights
DELETE = 0x00010000
READ_CONTROL = 0x00020000
WRITE_DAC = 0x00040000
WRITE_OWNER = 0x00080000
SYNCHRONIZE = 0x00100000

THREAD_DIRECT_IMPERSONATION = 0x0200
THREAD_GET_CONTEXT = 0x0008
THREAD_IMPERSONATE = 0x0100
THREAD_QUERY_INFORMATION = 0x0040
THREAD_QUERY_LIMITED_INFORMATION = 0x0800
THREAD_SET_CONTEXT = 0x0010
THREAD_SET_INFORMATION = 0x0020
THREAD_SET_LIMITED_INFORMATION = 0x0400
THREAD_SET_THREAD_TOKEN = 0x0080
THREAD_SUSPEND_RESUME = 0x0002
THREAD_TERMINATE = 0x0001

THREAD_ALL_ACCESS = 0X001F03FF //包括以上所有访问权限。

3、调试状态

//ContinueStatus
DBG_CONTINUE = 0x00010002
DBG_EXCEPTION_NOT_HANDLE = 0x80010001

4、DEBUG_EVENT事件码

//参考DEBUG_EVENT结构体
EXCEPTION_DEBUG_EVENT = 0x1
CREATE_THREAD_DEBUG_EVENT = 0x2
CREATE_PROCESS_DEBUG_EVENT = 0x3
EXIT_THREAD_DEBUG_EVENT = 0x4
EXIT_PROCESS_DEBUG_EVENT = 0x5
LOAD_DLL_DEBUG_EVENT = 0x6
UNLOAD_DLL_DEBUG_EVENT = 0x7
OUTPUT_DEBUG_STRING_EVENT = 0x8
RIP_EVENT = 0x9

5、信息类型

//参考API函数CreateToolhelp32Snapshot
TH32CS_INHERIT = 0x80000000
TH32CS_SNAPHEAPLIST = 0x00000001
TH32CS_SNAPMODULE = 0x00000008
TH32CS_SNAPMODULE32 = 0x00000010 
TH32CS_SNAPPROCESS = 0x00000002
TH32CS_SNAPTHREAD = 0x00000004

TH32CS_SNAPALL = 0x0000000F //包含进程类型、线程类型、模块类型、堆类型。

6、内存保护页状态
参考文档: Momery Protection Constants

三、异常事件处理

四、断点设置

1、软断点

当调试器被告知需要在某一个内存地址上设置一个断点时,调试器将首先读取位于这个内存地址上的第一个操作码字节并将器存储在断点列表中,接着调试器将字节0xCC写入那个内存地址取代值。当cpu试图执行操作码0xCC时,将触发一个断点事件(INT3事件),二调试器将最终捕获这个事件。接着调试器将检查指令指针是否正指向一个此前被我们设置了断点的内存地址,如果这个地址在调试器内部的断点列表中被查找到,那么调试器会将之前存储的字节数据写回此内存地址中,这样当次进程恢复执行后,正确的指令操作码将被执行。

//本文使用Python3.5版本
//myDebugger.py
//读取内存
  def read_process_memory(self, address, length):
        data = b""  //初始化字节码
        read_buf = create_string_buffer(length)
        count = c_ulong(0)
        if not kernel32.ReadProcessMemory(self.h_process, address, read_buf, length, byref(count)):
            print("read_process_memory fail.")
            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”
  def bp_set_sw(self, address):
        if not address in self.software_breakpoints.keys():
            try:
                original_byte = self.read_process_memory(str(address), 2) //为了方便比较这里设置读取2个字节
                print("read process memory %s" %original_byte)
                //写入一个INT3中断指令,操作码值为"\xCC"
                res = self.write_process_memory(str(address), b"\xCC")  //第二个参数传递的是字节类型
                if res:
                    print("Write success.")
                    print("New memory %s"%self.read_process_memory(str(address),2))  //为了方便比较这里设置读取2个字节
                else:
                    print(kernel32.GetLastError())
                    print("Write fail.")
                //将设置的断点记录进断点列表中
                self.software_breakpoints[address] = (str(address), original_byte)
            except:
                print("bp_set fail.")
                print(kernel32.GetLastError())  //返回异常代码值
                return False
        return True

2、硬件断点

通过使用位于cpu上的调试寄存器来实现。一般cpu上有8个调试寄存器(DR0到DR7)。DR0到DR3用于存储所设硬件断点的内存地址。任何时刻最多只能使用4个硬件断点。DR4和DR5保留使用。DR6为调试状态寄存器,记录上一次断点触发所产生的调试事件类型信息。DR7是硬件断点的激活开关,同时存储各个断点触发条件信息。通过设置DR7特定标记位,可以设置断点的触发条件:

  • 当位于一个特定内存地址上的指令被执行时触发断点(00 ——执行断点,断点长度标记1字节)
  • 当数据被写入一个特定内存地址是触发断点(01——数据写入断点,断点长度标志2字节 WORD)
  • 当数据被读出或写入(不包括执行)一个特定非可执行内存地址是触发断点。(11——数据读写(但非执行)断点,断点长度标记4字节 DWORD)

硬件断点使用1号中断(INT1),INT1事件被用于硬件断点和单步事件。
在cpu试图执行一条指令前,首先检查当前指令所在的地址是否被设置了有效的硬件断点,除此之外,cpu还会检查当前指令包含的操作数是否位于设置了硬件断点的内存地址上。如果以上的内存地址被存储在DR0-DR3中任意的一个调试寄存器中,并且满足之前所设定的读、写或者执行条件,那么cpu暂停并触发一个INT1事件。如果相关的内存地址并没有存储在任何一个调试寄存器中,cpu在执行完当前的指令后将继续执行下一条指令并且同样进行断点检测,依次往复。

//myDebugger.py
//设置硬件断点
  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):
            //这里的condition其实就是调试寄存器DR7的标志位,
            //HW_ACCESS = 0x00000003, HW_EXECUTE = 0x00000000, HW_WRITE = 0x00000001
            return False
        //检查是否存在空置的调试寄存器
        if not 0 in self.hardware_breakpoints.keys():  
           //Python3.x 的版本没有has_keys(),本文所有用到该函数的都改用in代替
           //Python2.x 的写法:if not self.hardware_breakpoints.has_keys(0): 
            available = 0
        elif not 1 in self.hardware_breakpoints.keys():
            available =1
        elif not 2 in self.hardware_breakpoints.keys():
            available = 2
        elif not 3 in self.hardware_breakpoints.keys():
            available = 3
        else:
            return  False
        //在每个线程环境下设置调试寄存器
        for thread_id in self.enumerate_threads():
            context = self.get_thread_context(thread_id=thread_id)
            context.Dr7 |= 1 << (available * 2)  //设置DR7中相应的标志位,来激活断点
            //在空置的寄存器下写入断点地址
            if available == 0:
                context.Dr0 = address
            elif available == 1:
                context.Dr1 = address
            elif available == 2:
                context.Dr2 = address
            elif available == 3:
                context.Dr3 = address
            //设置硬件断点触发条件
            context.Dr7 |= condition << ((available * 4) + 16)
            //设置硬件断点长度
            context.Dr7 |= length << ((available * 4) + 18)
            //提交改动后线程上下文环境信息
            h_thread = self.open_thread(thread_id)
            kernel32.SetThreadContext(h_thread, byref(context))
        //更新内部硬件断点列表
        self.hardware_breakpoints[available] = (str(address), length, condition)
        return  True
//执行单步事件处理
  def exception_handler_single_step(self):
        //摘自PyDbg源码中的一段注释:
        //判断该单步事件是否有一个硬件断点所触发,若是则捕获这个断点事件,根据Intel给出的文档
        //应当能够通过检测调试寄存器Dr6上的BS标志位来判断出该单步事件的触发原因,然而Windows
        //系统似乎并没有正确地将这个标志位传递过来。
        if self.context.Dr6 &0x01 and 0 in self.hardware_breakpoints.keys():
            slot = 0
        elif self.context.Dr6 &0x02 and 1 in self.hardware_breakpoints.keys():
            slot = 1
        elif self.context.Dr6 &0x04 and 2 in self.hardware_breakpoints.keys():
            slot = 2
        elif self.context.Dr6 &0x08 and 3 in self.hardware_breakpoints.keys():
            slot = 3
        else:
            //此次INT1中断不是由硬件断点触发
            continue_status = DBG_EXCEPTION_NOT_HANDLED
            return continue_status
        //从列表中移除断点
        if self.bp_del_hw(slot):
            continue_status = DBG_CONTINUE
            print("[*] Hardware breakpoint removed.")
            return continue_status
//删除硬件断点
  def bp_del_hw(self, slot):
        //移除所有线程的硬件断点
        for thread_id in self.enumerate_threads():
            context = self.get_thread_context(thread_id=thread_id)
            //重新设置DR7调试标志位
            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
            else:
                return False
            //清空断点触发条件标志位
            context.Dr7 &= ~(3 << ((slot * 4) + 16))
            //清空断点长度标志位
            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

3、内存断点

当一个调试器设置一个内存断点时,调试器实质上所做的是改变一个内存区域或一个内存页的访问权限。当cpu视图访问者一内存区域时,便会触发GUARD_PAGE_EXCEPTION异常事件。

//myDebugger.py
//设置内存断点
  def bp_set_mem(self, address, size):
        mbi = MEMORY_BASIC_INFORMATION()
        //判断是否能获取一个完整的MEMORY_BASIC_INFORMATION结构体,否则返回false
        if kernel32.VirtualQueryEx(self.h_process, address, byref(mbi), sizeof(mbi)) < sizeof(mbi):
            return False
        current_page = mbi.BaseAddress
       //对整个内存断点区域所覆盖的所有内存页进行设置访问权限
        while current_page <= address + size:
            //将这个内存页记录在列表中,以便于将这些保护页与操作系统或debuge进程自设的保护页区别开来
            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

五、功能整合

//myDebugDefines.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
LPVOID = c_void_p
UINT_PTR = c_ulong
SIZE_T = c_ulong

DEBUG_PROCESS = 0x00000001
CREATE_NEW_CONSOLE = 0x00000010
PROCESS_ALL_ACCESS = 0x001F0FFF
INFINITE = 0xFFFFFFFF
DBG_CONTINUE = 0x00010002
DBG_EXCEPTION_NOT_HANDLED = 0x80010001

EXCEPTION_DEBUG_EVENT = 0x01
CREATE_THREAD_DEBUG_EVENT = 0x02
CREATE_PROCESS_DEBUG_EVENT = 0x03
EXIT_THREAD_DEBUG_EVENT = 0x04
EXIT_PROCESS_DEBUG_EVENT = 0x05
LOAD_DLL_DEBUG_EVENT = 0x06
UNLOAD_DLL_DEBUG_EVENT = 0x07
OUTPUT_DEBUG_STRING_EVENT = 0x08
RIP_EVENT = 0x09

EXCEPTION_ACCESS_VIOLATION = 0xC0000005
EXCEPTION_BREAKPOINT = 0x80000003
EXCEPTION_GUARD_PAGE = 0x80000001
EXCEPTION_SINGLE_STEP = 0x80000004

TH32CS_SNAPTHREAD = 0x00000004

THREAD_ALL_ACCESS = 0x001F03FF

CONTEXT_FULL = 0x00010007
CONTEXT_DEBUG_REGISTERS = 0x00010010

HW_ACCESS = 0x00000003
HW_EXECUTE = 0x00000000
HW_WRITE = 0x00000001

PAGE_GUARD = 0x00000100

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", LPTSTR),
        ("hStdInput", HANDLE),
        ("hStdOutput", HANDLE),
        ("hStdError", HANDLE),
    ]
class PROCESS_INFORMATION(Structure):
    _fields_ = [
        ("hProcess", HANDLE),
        ("hThread", HANDLE),
        ("dwProcessId", DWORD),
        ("dwThreadId", 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),
]
class EXCEPTION_DEBUG_INFO(Structure):
    _fields_ = [
        ("ExceptionRecord", EXCEPTION_RECORD),
        ("dwFirstChance", DWORD),
    ]
class DEBUG_EVENT_UNION(Union):
    _fields_ = [
        ("Exception", EXCEPTION_DEBUG_INFO),
    ##  ("CreateThread", CREATE_THREAD_DEBUG_INFO),
    ]
class DEBUG_EVENT(Structure):
    _fields_ = [
        ("dwDebugEventCode", DWORD),
        ("dwProcessId", DWORD),
        ("dwThreadId", DWORD),
        ("u", DEBUG_EVENT_UNION),
    ]
class THREADENTRY32(Structure):
    _fields_ = [
        ("dwSize", DWORD),
        ("cntUsage", DWORD),
        ("th32ThreadID", DWORD),
        ("th32OwnerProcessID", DWORD),
        ("tpBasePri", DWORD),
        ("tpDeltaPri", DWORD),
        ("dwFlags", DWORD)
    ]
class FLOATING_SAVE_AREA(Structure):
    _fields_ = [
        ("ControlWord", DWORD),
        ("StatusWord", DWORD),
        ("TagWord", DWORD),
        ("ErrorOffset", DWORD),
        ("ErrorSelector", DWORD),
        ("DataOffset", DWORD),
        ("DataSelector", DWORD),
        ("RegisterArea", BYTE * 80),
        ("Cr0NpxState", DWORD),
    ]
class CONTEXT(Structure):
    _fields_ = [
        ("ContextFlags", DWORD),
        ("Dr0", DWORD),
        ("Dr1", DWORD),
        ("Dr2", DWORD),
        ("Dr3", DWORD),
        ("Dr6", DWORD),
        ("Dr7", DWORD),
        ("FloatSave", FLOATING_SAVE_AREA),
        ("SegGs", DWORD),
        ("SegFs", DWORD),
        ("SegEs", DWORD),
        ("SegDs", DWORD),
        ("Edi", DWORD),
        ("Esi", DWORD),
        ("Ebx", DWORD),
        ("Edx", DWORD),
        ("Ecx", DWORD),
        ("Eax", DWORD),
        ("Ebp", DWORD),
        ("Eip", DWORD),
        ("SegCs", DWORD),
        ("EFlags", DWORD),
        ("Esp", DWORD),
        ("SegSs", DWORD),
        ("ExtendedRegisters", BYTE * 512),
    ]
class PROC_STRUCT(Structure):
    _fields_ = [
        ("wProcessorArchitecture", WORD),
        ("wReserved", WORD),
    ]
class SYSTEM_INFO_UNION(Union):
    _fields_ = [
        ("dsOemId", DWORD),
        ("sProcStruc", PROC_STRUCT),
    ]
class SYSTEM_INFO(Structure):
    _fields_ = [
        ("uSysInfo", SYSTEM_INFO_UNION),
        ("dwPageSize", DWORD),
        ("lpMinimumApplicationAddress", LPVOID),
        ("lpMaximumApplicationAddress", LPVOID),
        ("dwActiveProcessMask", DWORD),
        ("dwNumberOfProcessors", DWORD),
        ("dwProcessorType", DWORD),
        ("dwAllocationGranularity", DWORD),
        ("wProcessorLevel", WORD),
        ("wProcessorRevision", WORD),
    ]
class MEMORY_BASIC_INFORMATION(Structure):
    _fields_ = [
        ("BaseAddress", PVOID),
        ("AllocationBase", PVOID),
        ("AllocationProtect", DWORD),
        ("RegionSize",SIZE_T),
        ("State", DWORD),
        ("Protect", DWORD),
        ("Type", DWORD),
    ]
//myDebugger.py
//完整代码
//定义debugger类
class debugger():
    def __init__(self):
        self.h_process = None
        self.h_thread = None
        self.pid = None
        self.debugger_active = False
        self.context = None
        self.exception = None
        self.exception_address = None
        self.software_breakpoints = {}
        self.hardware_breakpoints = {}
        self.memory_breakpoints = {}
        self.first_breakpoints = True
        system_info = SYSTEM_INFO()
        kernel32.GetSystemInfo(byref(system_info))
        self.page_size = system_info.dwPageSize
        self.guarded_pages = []

    def load(self, path_to_exe):
        //参数dwCreationFlags中的标志位控制着进程的创建方式。
        //以下参数初始化可以参考CreateProcess函数
        creation_flags = DEBUG_PROCESS
        //实例化之前定义的结构体
        startupinfo = STARTUPINFO()
        process_information = PROCESS_INFORMATION()
        //以下两个成员变量的共同作用下,新建进程将在一个单独的窗体中被显示,也可以通过改变
        //结构体STARTUPINFO中的各成员变量的值来控制debugee进程的行为。
        startupinfo.dwFlags = 0x1
        startupinfo.wShowWindow = 0x0
        //设置结构体STARTUPINFO中的成员变量cb的值,用以表示结构体本身的大小
        startupinfo.cb = sizeof(startupinfo)

        if  windll.kernel32.CreateProcessW(path_to_exe,
                                    None, None, None, None,
                                    creation_flags,
                                    None, None,
                                    byref(startupinfo),
                                    byref(process_information)):
            //注意print的规范,python3.x是带有小括号的,python2.x没有小括号。
            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())
    //打开指定pid的进程
    def open_process(self, pid):
        h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
        print(type(h_process))
        return h_process
    //附加进程
    def attach(self, pid):
        self.h_process = self.open_process(pid)
        //试图附加到目标进程,若附加操作失败,则输出提示信息后返回
        print("attach after h_process: 0x%08x." %self.h_process)
        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):
        //等待发生在debugee进程中的调试事件
        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):
            ##input("press a key to continue...")
            ##self.debugger_active = False
            //获取相关线程的句柄并提取其CONTEXT
            self.h_thread = self.open_thread(debug_event.dwThreadId)
            self.context = self.get_thread_context(debug_event.dwThreadId)
            ##self.context = self.get_thread_context(self.h_thread)
            print("Event code: %d Thread ID: %d" %(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:
                continue_status = self.exception_handler_breakpoint()
            elif self.exception == EXCEPTION_GUARD_PAGE:
                print("Guard Page Access Detected.")
            elif self.exception == EXCEPTION_SINGLE_STEP:
                self.exception_handler_single_step()
                print("Single Stepping.")
            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("[*] Detach fail.")
            return False
    //打开线程
    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("[*] Could not obtain a valid thread handle.")
            return False
    //枚举线程
    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:
            print("enumerate_thread fail.")
            return False
    //获取线程的CONTEXT
    def get_thread_context(self, thread_id):
        context = CONTEXT()
        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
        h_thread = self.open_thread(thread_id)
        if kernel32.GetThreadContext(h_thread, byref(context)):
            kernel32.CloseHandle(h_thread)
            return context
        else:
            print("get thread context fail.")
            return False
    //断点处理
    def exception_handler_breakpoint(self):
        print("[*] Inside the breakpoint handler.")
        print("Exception address: 0x%08x" %self.exception_address)
        print(kernel32.GetLastError())
        if not self.exception_address in self.hardware_breakpoints.keys():
            if self.first_breakpoints == True:
                self.first_breakpoints = False
                print("[*] Hit the first breakpoint.")
        else:
            print("[*] Hit user defined breakpoint.")
        return DBG_CONTINUE
    //从内核模块提取目标函数虚拟内存地址
    def func_resolve(self, dll, function):
        handle = kernel32.GetModuleHandleW(dll)
        print("func_resolve handle 0x%08x" %handle)
        address = kernel32.GetProcAddress(handle, function)
        print(kernel32.GetLastError())
        print("func_resolve address 0x%08x" %address)
        kernel32.CloseHandle(handle)
        return address
    //完整代码,参考软断点
    def read_process_memory(self, address, length):   
    def write_process_memory(self, address, data):
    def bp_set_sw(self, address):
    //完整代码,参考硬件断点
    def bp_set_hw(self, address, length, condition):    
    def exception_handler_single_step(self):    
    def bp_del_hw(self, slot):
    //完整代码,参考内存断点
    def bp_set_mem(self, address, size):
//test.py
import myDebugger
from myDebuggerDefines import *
debugger = myDebugger.debugger()
##debugger.load("C:\\windows\\system32\\calc.exe")
##print("h_process:0x%08x"%debugger.h_process)

pid = input("Enter the PID of the process to attach to : ")
debugger.attach(int(pid))

'''
//枚举线程存入列表中
list = debugger.enumerate_threads()
//从列表中读取每个线程的CONTEXT信息
for thread in list:
    thread_context = debugger.get_thread_context(thread)
    print("[*] Dumping registers for thread ID : 0x%08x" %thread)
    print("[**]EIP: 0x%08x"%thread_context.Eip)
    print("[**]ESP: 0x%08x"%thread_context.Esp)
    print("[**]EBP: 0x%08x"%thread_context.Ebp)
    print("[**]EAX: 0x%08x"%thread_context.Eax)
    print("[**]EBX: 0x%08x"%thread_context.Ebx)
    print("[**]ECX: 0x%08x"%thread_context.Ecx)
    print("[**]EDX: 0x%08x"%thread_context.Edx)
    print("[*]END DUMP")
'''
//获取msvcrt.dll模块里的printf函数的虚拟地址
printf_address = debugger.func_resolve("msvcrt.dll", b"wprintf")  //第二个参数需要转化成字节编码
print("[*] Address of printf: 0x%08x" %printf_address)
##print("pid:%d"%debugger.pid)
//测试软断点
##debugger.bp_set_sw(str(printf_address))
//测试硬件断点
##debugger.bp_set_hw(printf_address, 1, HW_EXECUTE)
//测试内存断点
debugger.bp_set_mem(printf_address, 0x2000)
debugger.run()
##debugger.detach()
//用于测试
//测试步骤:先运行printf_loop.py,然后到系统任务管理器找到该进程的PID,再运行test.py,提示输入PID。
//printf_loop.py
from ctype import *
import time

msvcrt = cdll.msvcrt
counter = 0
while 1:
     //这里使用printf打印,测试时就获取printf函数的虚拟内存地址
     //如果使用wprintf打印,测试时就获取wprintf函数的虚拟内存地址
     msvcrt.printf("Loop interation %d!\n" %counter) 
     time.sleep(1)
     counter += 1

六、兼容问题

1、python2.x跟python3.x部分规范问题。
2、ANSI跟Unicode编码问题。python3.x用的是Unicode编码,如果需要使用到ANSI编码,只需在字符串前加b,例:b“string”

结束语

本文只是根据调试器的基本原理构建一个简单的调试器。

你可能感兴趣的:(Python构建Windows调试器)