1. 程式人生 > >Python 灰帽子筆記之偵錯程式

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(群號)