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脚本后,会看到下图所示:
Python 灰帽子笔记之调试器_第1张图片

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 灰帽子笔记之调试器_第2张图片

总结:
书籍《Python灰帽子-黑客与逆向工程师的Python编程之道》是本好书,可惜初看时很难去理解,调试器原理和用途,今后的学习还在调试器的基础上去学习,应该会慢慢的去理解它,将其拿之为我所用

本人利用Bootstrap + EasyUI + Django开发网站:http://www.xuyangting.com/ 欢迎来访

阳台测试: 239547991(群号)

本人博客:http://xuyangting.sinaapp.com/

你可能感兴趣的:(python,灰帽子,调试器)