+ Reply to Thread
Results 1 to 11 of 11

Thread: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

  1. #1
    Prosper-H's Avatar

    Default Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    Перейти к содержанию

    Intro

    Теперь, когда мы рассмотрели основы, пришло время применить знания, полученные в предыдущих главах книги. Когда Microsoft разработала Windows, она добавила поразительное множество отладочных функций, что бы помочь разработчикам и специалистам по обеспечению качества. В этой главе, мы будем интенсивно использовать эти функции, для создания собственного отладчика на Python’e. Важно отметить здесь и то, что мы, по сути, совершаем углубленное изучение PyDbg (Педрама Амини), поскольку, на данный момент, это наиболее чистый Windows-отладчик, написанный на Python (прим. пер. есть еще один стремительно развивающийся WinAppDbg).


    3.1 Debuggee, Where Art Thou?

    Для того что бы выполнить отладку процесса, у вас должна быть возможность ассоциировать отладчик с отлаживаемым процессом. Следовательно, наш отладчик должен иметь возможность либо открыть исполняемый файл и запустить его, либо присоединиться к уже запущенному процессу. Windows debugging API – предоставляет простой способ сделать и то и другое.

    Существуют некоторые различия между открытием процесса и присоединением к нему. Преимуществом открытия процесса есть то, что у вас есть возможность получить контроль над процессом еще до того, как он сможет запустить какой бы то ни было код. Это может быть удобно при анализе вредоносных программ или других типов вредоносного кода. При присоединении вы вклиниваетесь в уже работающий процесс, что позволяет пропускать часть кода и анализировать специфические области, в которых вы заинтересованы. В зависимости от того, что отлаживается и то какой анализ нужно проделать - вы выбираете, какой подход вам использовать.

    Первый способ, получения процесса выполняющегося под отладчиком – это запустить исполняемый файл испод самого отладчика. Для создания процесса в Windows, вам нужно вызвать функцию CreateProcessA() [1]. Установка конкретных флагов, которые передаются в эту функцию автоматически, включает процесс отладки. Вызов CreateProcessA() выглядит так:

    Code:
    BOOL WINAPI CreateProcessA(
        LPCSTR lpApplicationName,
        LPTSTR lpCommandLine,
        LPSECURITY_ATTRIBUTES lpProcessAttributes,
        LPSECURITY_ATTRIBUTES lpThreadAttributes,
        BOOL bInheritHandles,
        DWORD dwCreationFlags,
        LPVOID lpEnvironment,
        LPCTSTR lpCurrentDirectory,
        LPSTARTUPINFO lpStartupInfo,
        LPPROCESS_INFORMATION lpProcessInformation
    );
    На первый взгляд это вызов может показаться сложным, но, как и в реверс инжиниринге, мы должны всегда разбивать вещи на более мелкие части, что бы понять общую картину. Мы будем иметь дело только с параметрами, которые важны для создания процесса испод отладчика, а именно: lpApplicationName, lpCommandLine, dwCreationFlags, lpStartupInfo и lpProcessInformation. Остальные параметры могут быть установлены в NULL. Для полного ознакомления с вызовом этой функции, обратитесь к записи в Microsoft Developer Network (MSDN). Первые два параметра используются для установки пути к исполняемому файлу, который мы хотим запустить и установки аргументов командной строки, которые он (исполняемый файл) принимает. Параметр dwCreationFlags занимает особое значение, которое указывает на то, что процесс должен быть запущен в качестве отлаживаемого процесса. Последние два параметра являются указателями на структуры STARTUPINFO [2] и PROCESS_INFORMATION [3] соответственно, которые определяют, как процесс должен быть запущен, а так же предоставляют важную информацию, касаемо самого процесса, после того как тот был успешно запущен.

    Создайте два новых файла: my_debugger.py и my_debugger_defines.py. Затем мы начнем создавать родительский класс debugger(), в который мы будем постепенно добавлять функциональность отладчика кусок за куском. В дополнение, мы поместим все: структуры, объединения и константы в файл my_debugger_defines.py, для удобства в последующего сопровождения кода.

    Example 1: my_debugger_defines.py
    Code:
    from ctypes import *
    
    # Let's map the Microsoft types to ctypes for clarity
    WORD = c_ushort
    DWORD = c_ulong
    LPBYTE = POINTER(c_ubyte)
    LPTSTR = POINTER(c_char)
    HANDLE = c_void_p
    
    # Constants
    DEBUG_PROCESS = 0x00000001
    CREATE_NEW_CONSOLE = 0x00000010
    
    # Structures for CreateProcessA() function
    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),
            ]
    Example 1: my_debugger.py
    Code:
    from ctypes import *
    from my_debugger_defines import *
    
    import sys
    import time
    kernel32 = windll.kernel32
    
    class debugger():
    
        def __init__(self):
            pass
                    
                    
        def load(self,path_to_exe):
            
            # dwCreation flag determines how to create the process
            # set creation_flags = CREATE_NEW_CONSOLE if you want
            # to see the calculator GUI
            creation_flags = DEBUG_PROCESS
        
            # instantiate the structs
            startupinfo         = STARTUPINFO()
            process_information = PROCESS_INFORMATION()
            
            # The following two options allow the started process
            # to be shown as a separate window. This also illustrates
            # how different settings in the STARTUPINFO struct can affect
            # the debuggee.
            startupinfo.dwFlags     = 0x1
            startupinfo.wShowWindow = 0x0
            
            # We then initialize the cb variable in the STARTUPINFO struct
            # which is just the size of the struct itself
            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 launched the process!"
                print "[*] The Process ID I have is: %d" % process_information.dwProcessId
              
            else:    
                print "[*] Error with error code %d." % kernel32.GetLastError()
    Сейчас мы напишем небольшой тест, что бы убедиться, что все работает так, как было запланировано. Назовите этот файл my_test.py и убедитесь в том, что он находится в той же папке, что и остальные файлы.

    Example 1: my_test.py
    Code:
    import my_debugger
    
    debugger = my_debugger.debugger()
    
    debugger.load("C:\\WINDOWS\\system32\\calc.exe")
    Если вы выполните этот файл на Python’e, либо с помощью командной строки, либо с помощью вашей IDE, то произойдет запуск заданного процесса (calc.exe), затем будет выведен отчет, с указанием идентификатора запущенного процесса (PID), после чего последует завершение работы скрипта. Если вы используете вышеуказанный пример, то вы не увидите графической оболочки калькулятора. Причина, по которой вы не увидите графический интерфейс, заключается в том, что процесс не прорисовывается на экране, поскольку он ожидает команды от отладчика для продолжения своего выполнения. У нас еще не создана логика для этого, но она скоро появится! Сейчас вы знаете, как запустить процесс готовый для отладки. Пришло время сварганить какой-нибудь код, который присоединит (attach) отладчик к запущенному процессу.

    Для того, что бы подготовить процесс, к присоединению, нужно получить дескриптор процесса. Большинство функций, которые мы будем использовать, требуют действительного дескриптора процесса, помимо этого это позволяет нам узнать, можем ли мы получить доступ к процессу, прежде чем мы попытаемся его отладить. Это делается с помощью фукнции OpenProcess() [4], которая экспортируется из kernel32.dll и имеет следующий прототип:

    Code:
    HANDLE WINAPI OpenProcess(
        DWORD dwDesiredAccess,
        BOOL bInheritHandle
        DWORD dwProcessId
    );
    Параметр dwDesiredAccess указывает, какой тип прав доступа, мы запрашиваем для объекта процесса, на который хотим получить дескриптор. Для того что бы выполнить отладку, нам нужно установить его в PROCESS_ALL_ACCESS. Параметр bInheritHandle всегда, в нашем случае, устанавливается в False, а в параметре dwProcessId задается PID процесса, на который мы хотим получить дескриптор. Если функция выполняется успешно, то она возвращает дескриптор объекта процесса.

    Для присоединения к процессу, используем функцию DebugActiveProcess() [5]. Ее прототип выглядит следующим образом:

    Code:
    BOOL WINAPI DebugActiveProcess(
        DWORD dwProcessId
    );
    Мы просто передаем ей PID процесса, к которому хотим присоединиться. После того, как система определяет, что у нас есть соответствующие права доступа к процессу, целевой процесс предполагает, что присоединенный процесс (к отладчику) готов обрабатывать отладочные события, после чего отказывается от управления отладчиком (прим. пер. другими словами отладчик передает управление контролируемому процессу, т.е. процесс продолжается выполняться, до возникновения отладочных событий). Отладчик ловит эти отладочные события, вызывая функцию WaitForDebugEvent() [6] в цикле. Функция выглядит следующим образом:

    Code:
    BOOL WINAPI WaitForDebugEvent(
        LPDEBUG_EVENT lpDebugEvent,
        DWORD dwMilliseconds
    );
    Первый параметр это указатель на структуру DEBUG_EVENT [7]. Эта структура описывает события отладки. Второй параметр, мы установим в INFINITE, поэтому вызов WaitForDebugEvent() не будет возвращаться до возникновения события.

    Для каждого события, которое отладчик перехватывает, существуют связанные обработчики событий, которые выполняют некоторые типы действий прежде, чем позволить процессу продолжить свое выполнение. После того, как обработчики закончили свою работу, мы наверняка захотим продолжить выполнение процесса. Это осуществляется с помощью функции ContinueDebugEvent() [8], которая выглядит следующим образом:

    Code:
    BOOL WINAPI ContinueDebugEvent(
        DWORD dwProcessId,
        DWORD dwThreadId,
        DWORD dwContinueStatus
    );
    Параметры dwProcessId и dwThreadId – это параметры полей в структуре DEBUG_EVENT, которая инициализируется, когда отладчик перехватывает событие отладки. Параметр dwContinueStatus сигнализирует процессу либо продолжать выполнение (DBG_CONTINUE), либо продолжать обработку исключений (DBG_EXCEPTION_NOT_HANDLED).

    Единственное, что нам остается реализовать – это отсоединение (detach) от процесса. Делается это с помощью вызова функции DebugActiveProcessStop() [9], которая принимает PID процесса, от которого вы хотите отсоединиться, в качестве единственного параметра.

    Давайте соберем все выше перечисленное в месте и расширим наш класс, в файле my_debugger, предоставляя ему возможность открывать и получать дескриптор процесса. Заключительной деталью реализации будет создание основного цикла для обработки отладочных событий. Откройте my_debugger.py и введите следующий код.
    ВНИМАНИЕ: Все необходимые структуры, объединения и константы были определены в файле my_debugger_defines.py, исходный код которого доступен по адресу http://www.nostarch.com/ghpython.htm. Скачайте этот файл сейчас и перезапишите вашу текущую копию. В дальнейшем мы не будем рассматривать структуры, объединения и константы, поскольку вы должны хорошо себя чувствовать с ними уже сейчас.
    Example 2: my_debugger.py
    Code:
    from ctypes import *
    from my_debugger_defines import *
    
    import sys
    import time
    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):
            ...
            print "[*] We have successfully launched the process!"
            print "[*] The Process ID I have is: %d" % process_information.dwProcessId
            
            # Obtain a valid handle to the newly created process
            # and store it for future access
            self.h_process = self.open_process(process_information.dwProcessId)
    
        ...
    
        def open_process(self,pid):
    
            h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,pid,False)
            return h_process
    
        def attach(self,pid):
    
            self.h_process = self.open_process(pid)
    
            # We attempt to attach to the process
            # if this fails we exit the call
    
            if kernel32.DebugActiveProcess(pid):
                self.debugger_active = True
                self.pid = int(pid)
            else:
                print "[*] Unable to attach to the process."
    
        def detach(self):
    
            if kernel32.DebugActiveProcessStop(self.pid):
                print "[*] Finished debugging. Exiting..."
                return True
            else:
                print "There was an error"
                return False
    Теперь давайте, модифицируем наш тестовый файл, добавив в него тестирование новой функциональности, которую мы реализовали выше.

    Example 2: my_test.py
    Code:
    import my_debugger
    
    debugger = my_debugger.debugger()
    
    pid = raw_input("Enter the PID of the process to attach to: ")
    
    debugger.attach(int(pid))
    
    debugger.detach()
    Для проверки работоспособности этого теста выполните следующие шаги:
    1. Запустите калькулятор. Для этого перейдите в "Пуск => Выполнить => Все программы => Стандартные => Калькулятор".
    2. Клацните правой кнопкой мыши на панели инструментов Windows и выберите Диспетчер задач из контекстного меню.
    3. Выберите вкладку Процессы.
    4. Если вы не видите колонки PID на дисплее, выберите "Вид => Выбрать столбцы".
    5. Убедитесь, что установлен флажок напротив Идентификатор процесса (PID) и нажмите OK.
    6. Найдите PID соответствующий calc.exe.
    7. Выполните скрипт my_test.py и введите PID, который вы нашли на предыдущем шаге.
    8. Сценарий выведет сообщение, после чего завершит свою работу.
    9. Теперь вы в состоянии взаимодействовать с запущенным калькулятором.

    Сейчас, когда мы объяснили основы получения дескриптора процесса, создание отлаживаемого процесса и присоединение к запущенному процессу, мы готовы погрузиться в более сложные особенности, которые наш отладчик будет поддерживать.


    3.2 Получение состояния регистров процессора

    Отладчик должен уметь захватывать состояние регистров процессора в любое время и в любой заданной точке. Это дает нам возможность определить состояние стека, при возникновении исключения, местонахождение указателя инструкций и другую полезную информацию. Для этого, в начале, мы должны получить дескриптор выполняемого в данный момент потока, в отлаживаемой программе, что достигается за счет использования функции OpenThread() [10]. Она выглядит следующим образом.

    Code:
    HANDLE WINAPI OpenThread(
        DWORD dwDesiredAccess,
        BOOL bInheritHandle,
        DWORD dwThreadId
    );
    Она выглядит так же, как и функция OpenProcess(), за тем лишь исключением, что вместо идентификатора процесса (PID) передается идентификатор потока (TID).

    Нам нужно получить список всех потоков, которые выполняются внутри процесса, затем выбрать интересующий нас и получить правильный дескриптор на него с помощью OpenThread(). Давайте рассмотрим, как перечислить потоки в системе.

    3.2.1 Перечисление потоков

    Для того что бы получить состояние регистра из процесса, мы должны уметь перечислять все работающие потоки внутри процесса. Потоки – это то, что фактически выполняется в процессе. Даже, если приложение не многопоточное, оно содержит, по крайней мере, один поток – главный поток. Мы можем перечислять потоки, используя очень мощную функцию CreateToolhelp32Snapshot() [11], которая экспортируется из kernel32.dll. Эта функция позволяет получать: список процессов, потоков и загруженных модулей (DLLs), в нутрии процессов, а так же “кучу” (heap list), которую имеет процесс. Прототип функции выглядит следующим образом:

    Code:
    HANDLE WINAPI CreateToolhelp32Snapshot(
        DWORD dwFlags,
        DWORD th32ProcessID
    );
    Параметр dwFlags указывает, какой тип информации должна собрать функция (потоки, процессы, модули или кучи). Мы установим его в TH32CS_SNAPTHREAD, равный 0x00000004, что означает, что мы хотим собрать все потоки, зарегистрированные в текущем снимке (snaphot). Параметр th32ProcessID это просто PID процесса, для которого мы хотим сделать снимок, но он используется только для TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, TH32CS_SNAPHEAPLIST и TH32CS_SNAPALL режимов. Поэтому, определение того, принадлежит ли поток нашему процессу или нет – зависит от нас… Когда CreateToolhelp32Snapshot() выполняется успешно, она возвращает дескриптор на объект снимка, который мы используем в последующих вызовах, для сбора дополнительной информации.

    Как только у нас есть список потоков из снимка, мы можем начать перечислять их. Для того, что бы начать перечисление, используем функцию Thread32First() [12], которая выглядит следующим образом:

    Code:
    BOOL WINAPI Thread32First(
        HANDLE hSnapshot,
        LPTHREADENTRY32 lpte
    );
    Параметр hSnapshot принимает открытый дескриптор, возвращенный из CreateToolhelp32Snapshot(), а параметр lpte является указателем на структуру THREADENTRY32 [13]. Эта структура заполняется после успешного вызова функции Thread32First() и содержит важную информацию для первого найденного потока. Структура определяется следующим образом:

    Code:
    typedef struct THREADENTRY32{
        DWORD dwSize;
        DWORD cntUsage;
        DWORD th32ThreadID;
        DWORD th32OwnerProcessID;
        LONG tpBasePri;
        LONG tpDeltaPri;
        DWORD dwFlags;
    };
    В этой структуре есть три поля, которые нам интересны: dwSize, th32ThreadID и th32OwnerProcessID. Поле dwSize должно быть инициализировано до вызова функции Thread32First(). Для этого просто установите его значение – равное размеру самой структуры. Поле th32ThreadID это TID для потока, который уже рассматривался. Мы можем использовать этот идентификатор в качестве параметра dwThreadId, обсуждавшегося ранее в функции OpenThread(). Поле th32OwnerProcessID это PID который идентифицирует процесс, под которым был запущен поток. Для того что определить все потоки, принадлежащие процессу, нам нужно сравнить значение каждого th32OwnerProcessID с PID процесса, к которому мы присоединились (attach) или создали и если есть совпадение – мы знаем, что этим потоком владеет наша отлаживаемая программа. Как только мы собрали информацию о первом потоке, нам нужно перейти к следующему потоку в снимке (snaphot) вызвав функцию Thread32Next(). Она принимает те же параметры, что и Thread32First(), которую мы уже рассмотрели. Все что нам нужно сделать это продолжать вызывать Thread32Next() в цикле, до тех пор, пока в снимке не останется ни одного потока.

    3.2.2 Собираем все вместе

    Теперь, когда мы можем получить дескриптор потока, остается последний шаг, который заключается в получение всех регистров. Это можно сделать с помощью вызова GetThreadContext() [14]. Помимо этого, мы можем использовать функцию SetThreadContext() [15], что бы изменить значения, сразу после того, как мы получили допустимую запись контекста.

    Code:
    BOOL WINAPI GetThreadContext(
        HANDLE hThread,
        LPCONTEXT lpContext
    );
    
    BOOL WINAPI SetThreadContext(
        HANDLE hThread,
        LPCONTEXT lpContext
    );
    Параметр hThread – это дескриптор, возвращенный из функции OpenThread(), а параметр lpContext – это указатель на структуру CONTEXT, которая содержит значения всех регистров. Структура CONTEXT важна для понимания. Она определяется следующим образом:

    Code:
    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];
    };
    Как вы можете видеть, в этот список включены все регистры, включая отладочные и сегментные регистры. Мы будем полагаться на эту структуру на протяжении оставшейся части нашего упражнения, посвященного созданию отладчика, поэтому убедитесь, что вы знакомы с ней.

    Давайте вернемся к нашему старому другу my_debugger.py и немного расширим его, добавив перечисление потоков и извлечение регистров.

    Example 3: my_debugger.py
    Code:
    class debugger():
    
        ...
        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:
                # You have to set the size of the struct
                # or the call will fail
                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:
                return False
    
        def get_thread_context (self, thread_id=None,h_thread=None):
    
            context = CONTEXT()
            context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
    
            # Obtain a handle to the thread
            if h_thread is None:
                self.h_thread = self.open_thread(thread_id) 
    
            if kernel32.GetThreadContext(h_thread, byref(context)):
                kernel32.CloseHandle(h_thread)
                return context
            else:
                return False
    Теперь, когда мы расширили дебаггер еще больше, давайте обновим тестовый скрипт, что бы опробовать новые возможности.

    Example 3: my_test.py
    Code:
    import my_debugger
    
    debugger = my_debugger.debugger()
    
    pid = raw_input("Enter the PID of the process to attach to: ")
    
    debugger.attach(int(pid))
    
    list = debugger.enumerate_threads()
    
    # For each thread in the list we want to
    # grab the value of each of the registers
    
    for thread in list:
        thread_context = debugger.get_thread_context(thread)
    
        # Now let's output the contents of some of the registers
        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"
    
    debugger.detach()
    Когда, на этот раз, вы запустите тест, вы увидеть следующий вывод, показанный в Листинге 3-1.

    Листинг 3-1: Значение регистров процессора для каждого выполняющегося потока
    Code:
    Enter the PID of the process to attach to: 4028
    [>] Dumping registers for thread ID: 0x00000550
    [**] EIP: 0x7c90eb94
    [**] ESP: 0x0007fde0
    [**] EBP: 0x0007fdfc
    [**] EAX: 0x006ee208
    [**] EBX: 0x00000000
    [**] ECX: 0x0007fdd8
    [**] EDX: 0x7c90eb94
    [>] END DUMP
    [>] Dumping registers for thread ID: 0x000005c0
    [**] EIP: 0x7c95077b
    [**] ESP: 0x0094fff8
    [**] EBP: 0x00000000
    [**] EAX: 0x00000000
    [**] EBX: 0x00000001
    [**] ECX: 0x00000002
    [**] EDX: 0x00000003
    [>] END DUMP
    [>] Finished debugging. Exiting...
    Не плохо, правда? Сейчас у нас есть возможность запрашивать состояние всех регистров процессора, всякий раз, когда мы этого пожелаем. Опробуйте скрипт на нескольких процессах и посмотрите, какие результаты вы получите! На данный момент, у нас реализовано ядро нашего отладчика, поэтому, пришло время добавить несколько базовых отладочных обработчиков и точек останова (breakpoints).


    3.3 Реализация отладочных обработчиков событий

    Для того, что бы наш отладчик принимал, определенные события, нужно установить обработчики, для каждого отладочного события, которое может возникнуть. Если вернемся к функции WaitForDebugEvent(), то мы знаем, что она возвращает заполненную структуру DEBUG_EVENT, всякий раз, когда происходить отладочное событие. Мы будем использовать информацию, содержащуюся в этой структуре, что бы определить, как обрабатывать отладочные события. Структура DEBUG_EVENT определяется следующим образом:

    Code:
    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;
    };
    В этой структуре много полезной информации. Поле dwDebugEventCode особенно интересно, так как оно сообщает тип события перехваченного в функции WaitForDebugEvent(). Помимо этого оно сообщает тип и значение для объединения (union) “u”. Различные отладочные события и их коды показаны в Таблице 3-1.


    Таблица 3-1: Отладочные события

    Проверяя значение поля dwDebugEventCode, можно сопоставить его с полученной структурой, определив значение, хранящееся в нем (поле), как определенное значение, хранящееся в объединении "u". Давайте изменим наш отладочный цикл, что бы отобразить сработавшие события отладки. Используя эту информацию, мы можем видеть события, в общем потоке, после создания или присоединения к процессу. Обновим наши скрипты my_debugger.py и my_test.py.

    Example 4: my_debugger.py
    Code:
    ...
    class debugger():
    
        def __init__(self):
            self.h_process = None
            self.pid = None
            self.debugger_active = False
            self.h_thread = None
            self.context = None
        ...
    
        def run(self):
            
            # Now we have to poll the debuggee for 
            # debugging events           
            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):
    
                # Let's obtain the thread and context information
                self.h_thread = self.open_thread(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)
    
                kernel32.ContinueDebugEvent( debug_event.dwProcessId, debug_event.dwThreadId, continue_status )
    Example 4: my_test.py
    Code:
    import my_debugger
    
    debugger = my_debugger.debugger()
    
    pid = raw_input("Enter the PID of the process to attach to: ")
    
    debugger.attach(int(pid))
    
    debugger.run()
    
    debugger.detach()
    Если мы снова используем нашего старого друга calc.exe, то вывод тестового скрипта будет выглядеть следующим образом Листинг 3-2.

    Листинг 3-2: Коды событий при присоединении к процессу calc.exe
    Code:
    Enter the PID of the process to attach to: 2700
    Event Code: 3 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 6 Thread ID: 3976
    Event Code: 2 Thread ID: 3912
    Event Code: 1 Thread ID: 3912
    Event Code: 4 Thread ID: 3912
    Основываясь на выводе нашего скрипта – видно, что событие CREATE_PROCESS_EVENT (0x3) появилось первым, затем последовало довольно много событий LOAD_DLL_DEBUG_EVENT (0x6), после чего сработало событие CREATE_THREAD_DEBUG_EVENT (0x2). Затем появилось следующее событие EXCEPTION_DEBUG_EVENT (0x1), которое является управляемой точкой останова Windows и позволяет отладчику, перед возобновлением выполнения, изучить состояние процесса. Последний вызов EXIT_THREAD_DEBUG_EVENT (0x4) завершает выполнение потока, с TID 3912.

    События исключений особенно интересны, так как подобные исключения могут включать: точки останова; нарушения прав доступа или неправильного разруливания доступа к памяти (т.е. попытка записать данные в память, предназначенную только для чтения). Все эти события важны для нас, но давайте начнем с перехвата первой управляющей точки останова Windows. Откройте my_debugger.py и вставьте следующий код.

    Example 5: my_debugger.py
    Code:
    ...
    class debugger():
    
        def __init__(self):
            self.h_process = None
            self.pid = None
            self.debugger_active = False
            self.h_thread = None
            self.context = None
            self.exception = None
            self.exception_address = None
    
            ...
    
        def get_debug_event(self):
    
            debug_event = DEBUG_EVENT()
            continue_status= DBG_CONTINUE
    
            if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):
                # Let's obtain the thread and context information
                self.h_thread = self.open_thread(debug_event.dwThreadId)
    
                self.context = self.get_thread_context(h_thread=self.h_thread)
    
                print "Event Code: %d Thread ID: %d" % (debug_event.dwDebugEventCode, debug_event.dwThreadId)
    
                # If the event code is an exception, we want to
                # examine it further.
                if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
    
                    # Obtain the exception code
                    exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode
                    self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress
    
                    if exception == EXCEPTION_ACCESS_VIOLATION:
                        print "Access Violation Detected."
    
                        # If a breakpoint is detected, we call an internal
                        # handler.
                    elif exception == EXCEPTION_BREAKPOINT:
                        continue_status = self.exception_handler_breakpoint()
                    elif exception == EXCEPTION_GUARD_PAGE:
                        print "Guard Page Access Detected."
                    elif exception == EXCEPTION_SINGLE_STEP:
                        print "Single Stepping."
    
                kernel32.ContinueDebugEvent( debug_event.dwProcessId, debug_event.dwThreadId, continue_status )
        ...
    
        def exception_handler_breakpoint(self):
    
            print "[*] Inside the breakpoint handler."
            print "Exception Address: 0x%08x" % self.exception_address
    
            return DBG_CONTINUE
    Если теперь перезапустить тестовый скрипт, то в его выводе вы увидите обработку программных точек останова, вызванных исключениями. Помимо этого мы создали заглушки для аппаратных точек останова (EXCEPTION_SINGLE_STEP) и точек останова на память (EXCEPTION_GUARD_PAGE). Вооружившись нашими новыми знаниями, мы можем реализовать три разных типа точек останова, с корректными обработчиками для каждой.


    3.4 Всемогущий брейкпойнт

    Теперь когда у нас есть основной функционал отладчика, пришло время добавить точки останова (breakpoints). Используя информацию из Главы 2, мы реализуем: программные точки останова, аппаратные точки останова и точки останова на память. Так же мы разработаем специальные обработчики для каждого типа брейкпойнтов и покажем, как корректно возобновить работу процесса после останова.

    3.4.1 Программные брейкпойнты

    Для того, что бы установить программный брейкпойнт (soft breakpoint), мы должны уметь читать и писать в память процесса. Делается это с помощью функций ReadProcessMemory() [16] и WriteProcessMemory() [17]. Они имею схожие прототипы:

    Code:
    BOOL WINAPI ReadProcessMemory(
        HANDLE hProcess,
        LPCVOID lpBaseAddress,
        LPVOID lpBuffer,
        SIZE_T nSize,
        SIZE_T* lpNumberOfBytesRead
    );
    
    BOOL WINAPI WriteProcessMemory(
        HANDLE hProcess,
        LPCVOID lpBaseAddress,
        LPCVOID lpBuffer,
        SIZE_T nSize,
        SIZE_T* lpNumberOfBytesWritten
    );
    Обе функции позволяют отладчику просматривать и изменять память отлаживаемой программы. Параметр lpBaseAddress – это адрес, откуда вы хотите начать читать или куда вы хотите начать писать данные. Параметр lpBuffer – это указатель на данные, которые содержат либо прочитанные данные, либо данные для записи. Параметр nSize – это общее количество байтов, которое вы хотите прочитать или записать.

    Использую две эти функции, мы можем дать возможность нашему отладчику, довольно легко, использовать программные точки останова (soft breakpoints). Давайте, модифицируем наш основной отладочный класс, для поддержки установки и обработки программных брейкпойнтов.

    Example 6: my_debugger.py
    Code:
    ...
    class debugger():
    
        def __init__(self):
            self.h_process = None
            self.pid = None
            self.debugger_active = False
            self.h_thread = None
            self.context = None
            self.breakpoints = {}
        ...
    
        def read_process_memory(self,address,length):
            data = ""
            read_buf = create_string_buffer(length)
            count = c_ulong(0)
    
            if not kernel32.ReadProcessMemory(self.h_process,
                                                            address,
                                                            read_buf,
                                                            length,
                                                            byref(count)):
                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
    
        def bp_set(self,address):
    
            if not self.breakpoints.has_key(address):
                try:
                    # store the original byte
                    original_byte = self.read_process_memory(address, 1)
    
                    # write the INT3 opcode
                    self.write_process_memory(address, "\xCC")
    
                    # register the breakpoint in our internal list
                    self.breakpoints[address] = (original_byte)
                except:
                    return False
            return True
    Теперь, когда у нас есть поддержка программных брейкпойнтов, нам нужно найти подходящее место для их установки. Вообще, брекпойнты устанавливаются на вызовы функций любого типа; для демонстрационного примера возьмем функцию printf() и попытаемся ее перехватить. Windows debugging API – предоставляет нам прозрачный метод определения виртуального адреса, с помощью функции GetProcAddress() [18], которая экспортируется из kernel32.dll. Единственным, основным требованием, этой функции является дескриптор модуля (на .dll или .exe файл), который содержит интересующую нас функцию и который можно получить с помощью функции GetModuleHandle() [19]. Прототипы функций GetProcAddress() и GetModuleHandle() выглядят следующим образом:

    Code:
    FARPROC WINAPI GetProcAddress(
        HMODULE hModule,
        LPCSTR lpProcName
    );
    
    HMODULE WINAPI GetModuleHandle(
        LPCSTR lpModuleName
    );
    Делается это довольно просто: получаем дескриптор модуля, а затем ищем адрес экспортируемой функции, которая нам нужна. Давайте добавим вспомогательную функцию, в наш отладчик, что бы реализовать это. Снова вернемся скрипту my_debugger.py и добавим следующие строки:

    Example 6: my_debugger.py
    Code:
    ...
    class debugger():
        ...
        def func_resolve(self,dll,function):
    
            handle = kernel32.GetModuleHandleA(dll)
            address = kernel32.GetProcAddress(handle, function)
    
            kernel32.CloseHandle(handle)
    
            return address
    Теперь давайте создадим второй тестовый скрипт, который будет использовать printf() в цикле. Мы определим адрес функции, а затем установим программный брейкпойнт на нее. После попадания на брейкпойнт, мы увидим некоторые данные в консоли, после чего, процесс, продолжит выполнение своего цикла. Создайте новый скрипт, назвав его printf_loop.py и поместите в него следующий код.

    Example 6: printf_loop.py
    Code:
    from ctypes import *
    import time
    
    msvcrt = cdll.msvcrt
    counter = 0
    
    while 1:
        msvcrt.printf("Loop iteration %d!\n" % counter)
        time.sleep(2)
        counter += 1
    Теперь давайте обновим наш первый тестовый файл, что бы присоединиться к этому процессу и установим брейкпойнт на printf().

    Example 6: my_test.py
    Code:
    import my_debugger
    
    debugger = my_debugger.debugger()
    
    pid = raw_input("Enter the PID of the process to attach to: ")
    
    debugger.attach(int(pid))
    
    printf_address = debugger.func_resolve("msvcrt.dll","printf")
    
    print "[*] Address of printf: 0x%08x" % printf_address
    
    debugger.bp_set(printf_address)
    
    debugger.run()
    Для того, что бы протестировать его, запустите printf_loop.py. Запишите PID (можно посмотреть с помощью диспетчера задач) для процесса python.exe. Далее, с консоли, запустите скрипт my_test.py и введите записанный PID. После чего вы должны увидеть следующий вывод, показанный в Листинге 3-3.

    Листинг 3-3: Порядок отладочных событий для программного брейкпойнта
    Code:
    Enter the PID of the process to attach to: 4048
    [>] Address of printf: 0x77c4186a
    [>] Setting breakpoint at: 0x77c4186a
    Event Code: 3 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 6 Thread ID: 3148
    Event Code: 2 Thread ID: 3620
    Event Code: 1 Thread ID: 3620
    [>] Exception address: 0x7c901230
    [>] Hit the first breakpoint.
    Event Code: 4 Thread ID: 3620
    Event Code: 1 Thread ID: 3148
    [>] Exception address: 0x77c4186a
    [>] Hit user defined breakpoint.
    Мы видим, что функция printf() определена по адресу 0x77c4186a, поэтому устанавливаем брейкпойнт на этот адрес. Первое исключение, которое поймано, является управляющей точкой останова Windows, но когда приходит второе исключение – видно, что адрес исключения равен 0x77c4186a, что соответствует адресу функции printf(). После обработки брейкпойнта, процесс должен возобновить свою работу. Наш отладчик теперь поддерживает программные точки останова, поэтому давайте перейдем к аппаратным брейкпойнтам.

    3.4.2 Аппаратные брейкпойнты

    Второй тип точек останова – это аппаратные брэйкпоинты, которые предполагают установку определенных битов в отладочных регистрах процессора. Мы подробно рассмотрели их в Главе 2, поэтому давайте перейдем к деталям реализации. Самое важное, при взаимодействии с аппаратными брэйкпоинтами, это отслеживание какой из четырех доступных отладочных регистров свободен для использования, а какой уже занят и используется. Мы должны быть уверены, что всегда используем свободный слот, иначе у нас могут возникнуть проблемы, когда брейкпойнт не сработает, в то время когда мы ожидаем этого.

    Давайте начнем с перечисления всех потоков в процессе, затем получим запись контекста для каждого из них. Используя полученную запись контекста, модифицируем один из регистров, между DR0 и DR3 (в зависимости от того какой из них свободен) для того, что бы поместить адрес требуемого брейкпойнта. Затем зеркально отразим соответствующие биты в регистре DR7, для включения брейкпойнта, а так же установки его типа и длины.

    После того, как мы создали подпрограмму, что бы установить брейкпойнт, следующее, что нам нужно это изменить главный цикл обработки отладочных событий, чтобы мы могли правильно обработать исключение, которые выскочит на аппаратном брейкпойнте. Мы знаем, аппаратная точка останова срабатывает на INT 1, поэтому мы просто добавим еще один обработчик исключений в наш отладочный цикл. Давайте начнем с установки брейкпойнта.

    Example 7: my_debugger.py
    Code:
    ...
    class debugger():
        def __init__(self):
            self.h_process = None
            self.pid = None
            self.debugger_active = False
            self.h_thread = None
            self.context = None
            self.breakpoints = {}
            self.first_breakpoint= True
            self.hardware_breakpoints = {}
        ...
        def bp_set_hw(self, address, length, condition):
    
            # Check for a valid length value
            if length not in (1, 2, 4):
                return False
            else:
                length -= 1
    
            # Check for a valid condition
            if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE):
                return False
    
            # Check for available slots
            if not self.hardware_breakpoints.has_key(0):
                available = 0
            elif not self.hardware_breakpoints.has_key(1):
                available = 1
            elif not self.hardware_breakpoints.has_key(2):
                available = 2
            elif not self.hardware_breakpoints.has_key(3):
                available = 3
            else:
                return False
    
            # We want to set the debug register in every thread
            for thread_id in self.enumerate_threads():
                context = self.get_thread_context(thread_id=thread_id)
    
                # Enable the appropriate flag in the DR7
                # register to set the breakpoint
                context.Dr7 |= 1 << (available * 2)
    
                # Save the address of the breakpoint in the
                # free register that we found
                if available == 0:
                    context.Dr0 = address
                elif available == 1:
                    context.Dr1 = address
                elif available == 2:
                    context.Dr2 = address
                elif available == 3:
                    context.Dr3 = address
    
                # Set the breakpoint condition
                    context.Dr7 |= condition << ((available * 4) + 16)
    
                # Set the length
                    context.Dr7 |= length << ((available * 4) + 18)
    
                # Set thread context with the break set
                h_thread = self.open_thread(thread_id)
                kernel32.SetThreadContext(h_thread,byref(context))
    
            # update the internal hardware breakpoint array at the used
            # slot index.
            self.hardware_breakpoints[available] = (address,length,condition)
    
            return True
    Вы видите, что мы выбираем свободный слот, для хранения брейкпойнта, проверяя глобальный словарь hardware_breakpoints. После того, как мы получили свободный слот, нам нужно установить адрес брейкпойнта в слот и обновить в регистре DR7 соответствующие флаги, которые позволяют включить этот самый брейкпойнт. Теперь, когда у нас есть механизм поддержки, для установки аппаратных брейкпойнтов, давайте обновим цикл обработки отладочных событий и добавим обработчик исключений поддерживающий прерывание INT 1.

    Example 7: my_debugger.py
    Code:
    ...
    class debugger():
    ...
        def get_debug_event(self):
    
            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()
            ...
        def exception_handler_single_step(self):
    
            # Comment from PyDbg:
            # determine if this single step event occurred in reaction to a
            # hardware breakpoint and grab the hit breakpoint.
            # according to the Intel docs, we should be able to check for
            # the BS flag in Dr6. but it appears that Windows
            # isn't properly propagating that flag down to us.
            if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(0):
                slot = 0
            elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_key(1):
                slot = 1
            elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_key(2):
                slot = 2
            elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_key(3):
                slot = 3
            else:
                # This wasn't an INT1 generated by a hw breakpoint
                continue_status = DBG_EXCEPTION_NOT_HANDLED
    
            # Now let's remove the breakpoint from the list
            if self.bp_del_hw(slot):
                continue_status = DBG_CONTINUE
    
            print "[*] Hardware breakpoint removed."
            return continue_status
    
        def bp_del_hw(self,slot):
    
            # Disable the breakpoint for all active threads
            for thread_id in self.enumerate_threads():
    
                context = self.get_thread_context(thread_id=thread_id)
    
                # Reset the flags to remove the breakpoint
                context.Dr7 &= ~(1 << (slot * 2))
    
                # Zero out the address
                if slot == 0:
                    context.Dr0 = 0x00000000
                elif slot == 1:
                    context.Dr1 = 0x00000000
                elif slot == 2:
                    context.Dr2 = 0x00000000
                elif slot == 3:
                    context.Dr3 = 0x00000000
    
                # Remove the condition flag
                context.Dr7 &= ~(3 << ((slot * 4) + 16))
    
                # Remove the length flag
                context.Dr7 &= ~(3 << ((slot * 4) + 18))
    
                # Reset the thread's context with the breakpoint removed
                h_thread = self.open_thread(thread_id)
                kernel32.SetThreadContext(h_thread,byref(context))
    
            # remove the breakpoint from the internal list.
            del self.hardware_breakpoints[slot]
            return True
    Тут все довольно просто: когда происходи прерывание INT 1 мы проверяем, устанавливался ли какой-нибудь отладочный регистр с аппаратным брейкпойнтом. И если отладчик обнаруживает установленную аппаратную точку останова, соответствующую адресу исключения, он обнуляет флаги в регистре DR7 и сбрасывает регистр отладки, который содержит адрес брейкпойнта. Давайте посмотрим на этот процесс в действии. Модифицируйте скрипт my_test.py, для использования аппаратных брейкпойнтов. В качестве хомячка будем использовать нашу функцию printf().

    Example 7: my_test.py
    Code:
    import my_debugger
    from my_debugger_defines import *
    
    debugger = my_debugger.debugger()
    
    pid = raw_input("Enter the PID of the process to attach to: ")
    
    debugger.attach(int(pid))
    
    printf = debugger.func_resolve("msvcrt.dll","printf")
    print "[*] Address of printf: 0x%08x" % printf
    
    debugger.bp_set_hw(printf,1,HW_EXECUTE)
    debugger.run()
    В этом тесте мы просто устанавливаем брейкпойнт на функцию printf() всякий раз, как она выполняется. Длина брейкпойнта всего один байт. В данном тесте импортировали файл my_debugger_defines.py. Это было сделано для того, что бы у нас был доступ к константе HW_EXECUTE, которая придает немного ясности коду.

    Когда запустите скрипт – увидите вывод, представленный в Листинг 3-4.

    Листинг 3-4: Порядок событий для обработки аппаратных брейкпойнтов
    Code:
    Enter the PID of the process to attach to: 2504
    [>] Address of printf: 0x77c4186a
    Event Code: 3 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 6 Thread ID: 3704
    Event Code: 2 Thread ID: 2228
    Event Code: 1 Thread ID: 2228
    [>] Exception address: 0x7c901230
    [>] Hit the first breakpoint.
    Event Code: 4 Thread ID: 2228
    Event Code: 1 Thread ID: 3704
    [>] Hardware breakpoint removed.
    Основываясь на выводе скрипта – можно видеть, что после срабатывания исключения обработчик удаляет брейкпойнт. Цикл продолжит выполняться после того, как отработает обработчик. Теперь у нас есть поддержка программных и аппаратных брейкпойнтов, давайте завершим создание нашего отладчика, добавив поддержку брейкпойнтов на память.

    3.4.3 Брейкпоинты на память

    Заключительная функцией, которую мы реализуем, будет брейкпойнт на память. В начале, мы просто запросим информацию о разделе памяти, что бы определить, где находится ее базовый адрес (где в виртуальной памяти начинается страница). После того, как мы определили размер страницы, нам нужно установить права доступа на эту страницу, что бы обеспечить ее охрану. Когда процессор попытается получить доступ к этой памяти, сработает исключение GUARD_PAGE_EXCEPTION. Используя специфический обработчик, для данного исключения, мы вернем оригинальный права доступа для страницы и продолжим выполнение.

    Для того, что бы правильно рассчитать размер страницы, которой мы собираемся манипулировать, нужно вначале обратиться с запросом к операционной системе, что бы получить размер страницы по умолчанию. Это делается с помощью функции GetSystemInfo() [20], которая заполняет структуру SYSTEM_INFO [21]. Эта структура содержит поле dwPageSize, в котором находится правильный, для системы, размер страницы. Мы реализуем этот шаг, во время инициализации первого экземпляра класса debugger().

    Example 8: my_debugger.py
    Code:
    ...
    class debugger():
    
        def __init__(self):
            self.h_process = None
            self.pid = None
            self.debugger_active = False
            self.h_thread = None
            self.context = None
            self.breakpoints = {}
            self.first_breakpoint= True
            self.hardware_breakpoints = {}
    
            # Here let's determine and store
            # the default page size for the system
            system_info = SYSTEM_INFO()
            kernel32.GetSystemInfo(byref(system_info))
            self.page_size = system_info.dwPageSize
        ...
    Теперь, когда получен размер страницы по умолчанию, мы готовы обращаться к странице и манипулировать ее правами. Первый шаг, заключается в запросе страницы, на адрес которой мы хотели бы установить брейкпойнт. Это делается с помощью функции VirtualQueryEx() [22], которая заполняет структуру MEMORY_BASIC_INFORMATION [23] характерными значениями для страницы, которую мы запрашиваем. Ниже приводятся определения для функции и структуры:

    Code:
    SIZE_T WINAPI VirtualQuery(
        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;
    }
    Как только структура была заполнена, будем использовать значение в поле BaseAddress, как начальную точку для установки прав доступа на страницу. Для установки правд доступа используется функция VirtualProtectEx() [24], которая имеет следующий прототип:

    Code:
    BOOL WINAPI VirtualProtectEx(
        HANDLE hProcess,
        LPVOID lpAddress,
        SIZE_T dwSize,
        DWORD flNewProtect,
        PDWORD lpflOldProtect
    );
    Итак, давайте перейдем к коду. Мы собираемся создать глобальный список защищенных страниц, которые нам нужно явно установить, так же как и глобальный список брейкпойнтов, на адреса в памяти, которые наш обработчик исключений будет использовать, когда произойдет исключение GUARD_PAGE_EXCEPTION. Затем установим права доступа на адрес и окружающие его страницы памяти (если адрес находится между двумя или более страницами памяти).

    Example 8: my_debugger.py
    Code:
    ...
    class debugger():
    
        def __init__(self):
            ...
            self.guarded_pages = []
            self.memory_breakpoints = {}
        ...
    
        def bp_set_mem (self, address, size):
            mbi = MEMORY_BASIC_INFORMATION()
    
            # If our VirtualQueryEx() call doesn’t return
            # a full-sized MEMORY_BASIC_INFORMATION
            # then return False
    
            if kernel32.VirtualQueryEx(self.h_process,
                                               address,
                                               byref(mbi),
                                               sizeof(mbi)) < sizeof(mbi):
                return False
    
            current_page = mbi.BaseAddress
    
            # We will set the permissions on all pages that are
            # affected by our memory breakpoint.
            while current_page <= address + size:
    
                # Add the page to the list; this will
                # differentiate our guarded pages from those
                # that were set by the OS or the debuggee process
                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
    
                # Increase our range by the size of the
                # default system memory page size
                current_page += self.page_size
    
            # Add the memory breakpoint to our global list
            self.memory_breakpoints[address] = (address, size, mbi)
    
            return True
    Теперь у вас есть возможность устанавливать брэйкпоинты на память. Если вы опробуете их, в текущем состоянии, используя нашу функцию printf() зацикленную в цикле, вы получите вывод, который будет просто говорить "Guard Page Access Detected". Хорошо то, что когда к защищенной странице получают доступ и срабатывает исключение, операционная система на самом деле удаляет защиту, установленную на страницу памяти, и позволяет вам продолжить выполнение. Подобное действие избавляет вас от создания специфического обработчика решающего эту задачу. Однако вы могли бы создать дополнительную логику, в существующем отладочном цикле, для выполнения определенных действий, когда срабатывает брейкпойнт. Например, вы могли бы осуществить такие действия, как: восстановление брейкпойнта, чтение памяти из места, где был установлен брэйкпоинт, приготовление свежего кофе, почесывание бороды – в общем, все что угодно.


    3.5 Заключение

    На этом мы завершаем разработку простого отладчика под Windows. На данный момент, вы имеете не только надежные знания для создания отладчика, но также получили некоторые очень важные навыки, которые будут полезны вам не зависимо от того занимаетесь ли вы отладкой или нет! При использовании другого отладочного инструмента, вы сможете понять, что тот делает на низком уровне. Помимо этого вы будете знать как, при необходимости, наилучшим образом, модифицировать его, конкретно под ваши нужды.

    Следующие шаги будут состоять в том, что бы продемонстрировать некоторые возможности, продвинутого использования, двух сформировавшихся и стабильных отладочных платформ под Windows: PyDbg и Immunity Debugger. Вы получили большое количество информации о том, как PyDbg работает "под капотом", поэтому будете чувствовать себя комфортно, переходя к нему. Синтаксис Immunity Debugger немного отличается, но он предлагает существенно отличающийся набор функций. Понимание того, как использовать и тот и другой отладчик, для специфических задач отладки, очень важно для вас, поскольку позволит вам автоматизировать отладку. Дорогу осилит лишь тот, кто шагает! Поэтому вперед, вперед и только в перед! Переходим к PyDbg…


    3.6 Дополнительные материалы от переводчика

    Для самых ленивых я разбил исходные коды, приведенные в книге, на Example (Примеры). Все это добро вы можете скачать тут. Успехов =)


    © Translated by Prosper-H from r0 Crew
    Last edited by root; 13-05-2012 at 02:33. Reason: Исправление опечатки
    Дорогу осилит идущий. (К. Касперски)

    Двери есть везде. Просто нужно знать, как в них войти. ("Хроники Амбера", персонаж: Корвин)

  2. 6 пользователя(ей) сказали cпасибо:
    Dark Koder (16-12-2013) Heroin (15-11-2011) Markus (15-11-2011) Rectifier (13-11-2011) coldfire (14-11-2011) ximera (14-11-2011)
  3. #2
    ximera's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    Народ у меня тут поползла ошибка не могу понять что еще передать в качестве параметра:(

    Code:
    Enter the PID of the process to attach to: 4032
    Traceback (most recent call last):
      File "C:\Documents and Settings\Администратор\workspace\myFirstDebbuger\src\my_test.py", line 21, in <module>
        thread_context = debugger.get_thread_context(thread)
    AttributeError: debugger instance has no attribute 'get_thread_context'
    Сам исходник:

    Code:
    '''
    Created on 11.05.2012
    
    @author: aspidites
    '''
    
    import my_debugger
    
    debugger = my_debugger.debugger()
    
    pid = raw_input("Enter the PID of the process to attach to: ")
    
    debugger.attach(int(pid))
    
    list = debugger.enumerate_threads()
    
    # For each thread in the list we want to
    # grab the value of each of the registers
    
    for thread in list:
         thread_context = debugger.get_thread_context(thread)
    
        # Now let's output the contents of some of the registers
        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"
    
    debugger.detach()
    Все работает на виртуалке WinXP SP3 чистая, код пишется в Eclipse с PyDev.

    Почти разобрался, у меня ошибка была в my_debugger.py, только не понял пока где:(
    Last edited by ximera; 13-05-2012 at 02:19.
    Чтобы избегать ошибок, надо набираться опыта; чтобы набираться опыта, надо делать ошибки. © Лоренс Питер

    Неизбежное прими достойно. © Сенека Луций Анней

    Господи... храни сумасшедших. © Сумасшедший Фрэнки

  4. #3
    root's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    В этом фрагменте:

    Example 3: my_debugger.py
    Code:
    class debugger():
    
        ...
        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:
                # You have to set the size of the struct
                # or the call will fail
                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:
                return False
    
    def get_thread_context (self, thread_id=None,h_thread=None):
    
        context = CONTEXT()
        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
    
        # Obtain a handle to the thread
        if h_thread is None:
            self.h_thread = self.open_thread(thread_id) 
    
        if kernel32.GetThreadContext(h_thread, byref(context)):
            kernel32.CloseHandle(h_thread)
            return context
        else:
            return False
    Была опечатка с отступами (в книге), которую я забыл исправить на правильный вариант:

    Code:
    class debugger():
    
        ...
        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:
                # You have to set the size of the struct
                # or the call will fail
                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:
                return False
    
        def get_thread_context (self, thread_id=None,h_thread=None):
    
            context = CONTEXT()
            context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
    
            # Obtain a handle to the thread
            if h_thread is None:
                self.h_thread = self.open_thread(thread_id) 
    
            if kernel32.GetThreadContext(h_thread, byref(context)):
                kernel32.CloseHandle(h_thread)
                return context
            else:
                return False
    Должно работать.
    Успех – это путь от провала до провала без потери энтузиазма. (В. Черчиль)

    Не бойся идти медленно, бойся остановиться. (Китайская пословица)

    When you lose fun and start doing things only for the payback, you're dead. (c) TCLH (Phrack 65, Intro)

  5. Пользователь сказал cпасибо:
    ximera (13-05-2012)
  6. #4
    ximera's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    да, спасибо я уже сам разобрался:) Вот за что я люблю C подобный синтаксис, так за фигурные скобки, все просто и ясно, где какой блок. Но и к этому я думаю привыкну)
    Чтобы избегать ошибок, надо набираться опыта; чтобы набираться опыта, надо делать ошибки. © Лоренс Питер

    Неизбежное прими достойно. © Сенека Луций Анней

    Господи... храни сумасшедших. © Сумасшедший Фрэнки

  7. Пользователь сказал cпасибо:
    Dark Koder (16-12-2013)
  8. #5

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    В Win7 64 bits все регистры пустые.
    Пока не разбирался плотно, но похоже, что надо пользоваться WOW64 функциями для получения контекста и не только.

  9. #6
    root's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    В Win7 64 bits все регистры пустые.
    Пока не разбирался плотно, но похоже, что надо пользоваться WOW64 функциями для получения контекста и не только.
    Ты бы еще на Линуксе затестил. Эта книга расчитана только на Windows XP.

    Ну, а вообще, если исходить из того, что поддержка Windows XP заканчивается 8 апреля 2014, то самый комфортный переход с WinXP на ГуаноN (т.е Windows N) будет с 2015-2016 года. Но переходить надо буде не на Windows 7, а скорее всего на какую-нибудь Windows 9. Ну или как минимум на Windows 8 SPN.

    А пока мой совет: все что можно запустить на Windows XP нужно запускать на Windows XP (в плане реверсинга, конечно).
    Last edited by root; 03-11-2012 at 15:12.
    Успех – это путь от провала до провала без потери энтузиазма. (В. Черчиль)

    Не бойся идти медленно, бойся остановиться. (Китайская пословица)

    When you lose fun and start doing things only for the payback, you're dead. (c) TCLH (Phrack 65, Intro)

  10. #7
    AbreC's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    Quote Originally Posted by root View Post
    [...]
    Ну, а вообще, если исходить из того, что поддержка Windows XP заканчивается 8 апреля 2014, то самый комфортный переход с WinXP на ГуаноN (т.е Windows N) будет с 2015-2016 года. Но переходить надо буде не на Windows 7, а скорее всего на какую-нибудь Windows 9. Ну или как минимум на Windows 8 SPN.
    [...]
    Надеюсь, что с XP будем все-таки переходить на ReactOS Beta, а не на очередное блудотворение убийц негров Африки.
    А если не на ReactOS, то лучше уж хотя бы на полностью свободный дистриб GNU/Linux (безо всего проприетарного. Да-да, даже так)
    You may stop this individual, but you can't stop us all... after all, we're all alike. © The Mentor (Phrack 7, File 3)

  11. #8

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    Это понятно. У самого на работе одни редхаты. Но сейчас почти все ноуты мало мальски производительные идут с 7-кой и почти всегда 64 бита ибо 4гб - уже стандарт. Так что осваивать wow64 все равно придется, как ни крути, ведь реверсить хочется реальные программки, а они уже на win 7 64 ориентируются.

    Но конечно понятно, что книжка рассчитана на win xp 32, а тренироваться надо на кошках (с) :)
    Пошел грызть бинарный код дальше.
    Спасибо большое за перевод книги. Помогает жить.

    P.S. Я сделал epub для ebook-а свеого. Epub читать на небольшом экране удобнее. Если кому надо - пишите, вышлю.
    Last edited by Melchizedek; 04-11-2012 at 20:20.

  12. Пользователь сказал cпасибо:
    root (04-11-2012)
  13. #9
    root's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    переходить на ReactOS Beta
    Та ты что, сплюнь. ReactOS если и будет кому-то интересен, то только с академической точки зрения. Надо быть мазохистом, чтобы им пользоваться. Бум надеяться, что к тому времени, когда XP отойдет в мир иной и мы начнем переход на WinN, появятся 16x ядерные процессоры и мега-адские размеры планок ОЗУ (хотя мне и сейчас моих 24Гб с головой хватает). Вот тогда будет шик. Я вообще давно хочу купить себе серверный ящик, а лучше стоечку с несколькими серверами, с мощными процессорами и кучей гигабайтов ОЗУ на борту. Я бы тогда был самым счастливым человеком на земле. Правда пока цены кусаются, но к 2016-му я думаю уже обзаведусь этаким чудом техники.

    А если не на ReactOS, то лучше уж хотя бы на полностью свободный дистриб GNU/Linux
    Я уже давно перешел ))) И хотя на основной системе у меня установлен исключительно Debian - это сложно назвать полным переходом, поскольку я все время сижу в XP, но только из под ВМвари. Должен сказать, что благодаря мощности современных процессоров чувствую себя очень даже комфортно, еще лучше чем было до перехода (правда тогда у меня и компик был другой, но сути это особой не меняет).

    Сейчас у меня в среднем запущено 3-4 виртуалки, в особо напряженные моменты 5-6 - и я просто в восторге. В одной я "кодю", в другой лажу в инет по доверенным сайтам и читаю книги, в третей лажу в инет по всем сайтам, четвертая у меня green-зона для работы c web-манями и прочим, в пятой я перевожу статьи для форума, в шестой дебажу чистые программки, в седьмой дебажу всякую дребедень по типу малвари, в восьмой у меня web-мастерская (для тестов каких-то web-скриптов). Как я жил без этого раньше - ума не приложу. И что самое ценное, любую виртуалку можно всегда за саспендидь (suspend) и вернуть ее в боевую готовность в течении нескольких секунд, с полностью рабочим окружением. Ну, а если вдруг с одной из виртуалкой случись что (окромя девелоперской), так всегда можно восстановить из резервной (чистой) копии, заблаговременно для этого подготовленной (благо HDD в наше время очень дешевые и +-пару сотен гигабайт не играют ни какой роли). А если понадобилась еще одна виртуалка, так это вообще плевое дело - поставил на клонирование, из чистой копии, и через 2-5 минуты у тебя уже полностью готова новая боевая среда. В общем сказка ^,^

    (безо всего проприетарного. Да-да, даже так)
    Это уже фанатизм. Работа за ПК должна быть комфортной и если этот комфорт обеспечивает проприетарное ПО, то я без всякого угрызения совести пользую его.

    Это понятно. У самого на работе одни редхаты. Но сейчас почти все ноуты мало мальски производительные идут с 7кой и почти все 64 бита ибо 4гб уже стандарт. Так что осваивать wow64 все равно придется, как ни крути, ведь реверсит хочется реальные программки, а они уже не win 7 64 bits ориентируются.
    Никогда не брал ноуты с предустановленной виндой. Зря выкинутые деньги на ветер. Не знаю насчет всех, но большинство ноутов идут в двух комплектация с Win7, DOS'ом или Linux'ом на борту, либо вообще только с DOS'ом или Linux'ом (учитывая пост-советские реалии). ИМХО, единственное оправдание обладателей семерки - это игры. Во всем же остальном можно обойтись поднятой по верх линукса виртуалкой с Windows XP. Хотя, не спорю, для некоторых случаев, таких как "мего-девелоперские проекты" или "дизайн ориентированные среды" все же подходит только чистая Win7 или MacOS. Но как показывает практика, дизайнеры сидят только под MacOS, а мего-девелоперские проекты дальше офисов своей разработки так и не выходят. Поэтому проблем использовать Линукс, кроме как проблем с "поиграть в какой-то новый пожиратель времени" не вижу. Тем более с учетом современных "мало мальски производительных ноутов". У меня на одном моем старом ноуте с забыченым intel dual core процессором и 2Гб оперативки, даже под WinXP вполне себе нормально уживалось 2 две виртуалки WinXP. Я уже не говорю о махинах которые выпускают сейчас.

    В общем, будущее за Линуксом, по крайней я так для себя решил. Тем более учитывая анти-пиратскую политику* и те "клеточные" интерфейсы (в смысле, интерфейсы для одноклеточных организмов), которые толкает Майкрософт.

    *Анти-пиратская политика Майкрософт - это вообще отдельный разговор... Недавно покупал знакомому ноут и ставил на него Win7. Пока разобрался во всех способах активации этого Гуано-поделия, чуть было дубом не рухнул. Если мелко мягкие будут двигаться в этом же духе дальше, то я не удивлюсь, если скоро все начнут переходить с Масдаевских Пираток под Линукс. Для успеха Линуксу в основном не хватает Гейм-дейв'a, правильного маркетинга и грамотно составленных дистров. Я не знаю, что там курят в Canonical (те что с Убунты), но будь у меня финансовая поддержка я бы за несколько лет взорвал рынок настольных ОС. Тут как говорится, было бы желание, грамотный маркетинг, деньги и идеи.

    В общем, как то так.

    P.S. Я сделал epub для ebook-а свеого. Epub читать на небольшом экране удобнее. Если кому надо - пишите, вышлю.
    Скинь мне в ЛС или запость в эту тему я прикреплю к основному посту.
    Last edited by root; 04-11-2012 at 21:28.
    Успех – это путь от провала до провала без потери энтузиазма. (В. Черчиль)

    Не бойся идти медленно, бойся остановиться. (Китайская пословица)

    When you lose fun and start doing things only for the payback, you're dead. (c) TCLH (Phrack 65, Intro)

  14. Пользователь сказал cпасибо:
    Dark Koder (16-12-2013)
  15. #10

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    Развеем миф о том, что вы ничего не сможете создать например под Windows7 64 bit на python3.
    Описывать и комментить мне влом(не все я сам понимаю). Вся суть сводится к тому, что вам необходимо взять контекст для 64 битной версии(там и регистры слегка отличаются) и добавить пару новых типов данных, а старую структуру Context временно закомментить, ну и слегка переписать метод получения контекста нити, и само собой вызов. Разобраться в изменениях вы сможете, если уж добрались сюда. Короче:
    В my_debugger_defines.py:
    Закомментировать вот это:
    Code:
    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),
    ]
    Вместо него вставить вот это:
    Code:
    class M128A(Structure):
        _fields_ = [("Low",DWORD64), ("High",DWORD64)]
    
    class XMM_SAVE_AREA32(Structure):
        _pack_ = 1
        _fields_ = [
                    ('ControlWord', WORD),
                    ('StatusWord', WORD),
                    ('TagWord', BYTE),
                    ('Reserved1', BYTE),
                    ('ErrorOpcode', WORD),
                    ('ErrorOffset', DWORD),
                    ('ErrorSelector', WORD),
                    ('Reserved2', WORD),
                    ('DataOffset', DWORD),
                    ('DataSelector', WORD),
                    ('Reserved3', WORD),
                    ('MxCsr', DWORD),
                    ('MxCsr_Mask', DWORD),
                    ('FloatRegisters', M128A * 8),
                    ('XmmRegisters', M128A * 16),
                    ('Reserved4', BYTE * 96)
                    ]
    
    class DUMMYSTRUCTNAME(Structure):
        _fields_=[
                  ("Header", M128A * 2),
                  ("Legacy", M128A * 8),
                  ("Xmm0", M128A),
                  ("Xmm1", M128A),
                  ("Xmm2", M128A),
                  ("Xmm3", M128A),
                  ("Xmm4", M128A),
                  ("Xmm5", M128A),
                  ("Xmm6", M128A),
                  ("Xmm7", M128A),
                  ("Xmm8", M128A),
                  ("Xmm9", M128A),
                  ("Xmm10", M128A),
                  ("Xmm11", M128A),
                  ("Xmm12", M128A),
                  ("Xmm13", M128A),
                  ("Xmm14", M128A),
                  ("Xmm15", M128A)
                  ]
    
    
    class DUMMYUNIONNAME(Union):
        _fields_=[
                  ("FltSave", XMM_SAVE_AREA32),
                  ("DummyStruct", DUMMYSTRUCTNAME)
                  ]
    
    class CONTEXT64(Structure):
        _pack_ = 16
        _fields_ = [
                    ("P1Home", DWORD64),
                    ("P2Home", DWORD64),
                    ("P3Home", DWORD64),
                    ("P4Home", DWORD64),
                    ("P5Home", DWORD64),
                    ("P6Home", DWORD64),
                    ("ContextFlags", DWORD),
                    ("MxCsr", DWORD),
                    ("SegCs", WORD),
                    ("SegDs", WORD),
                    ("SegEs", WORD),
                    ("SegFs", WORD),
                    ("SegGs", WORD),
                    ("SegSs", WORD),
                    ("EFlags", DWORD),
                    ("Dr0", DWORD64),
                    ("Dr1", DWORD64),
                    ("Dr2", DWORD64),
                    ("Dr3", DWORD64),
                    ("Dr6", DWORD64),
                    ("Dr7", DWORD64),
                    ("Rax", DWORD64),
                    ("Rcx", DWORD64),
                    ("Rdx", DWORD64),
                    ("Rbx", DWORD64),
                    ("Rsp", DWORD64),
                    ("Rbp", DWORD64),
                    ("Rsi", DWORD64),
                    ("Rdi", DWORD64),
                    ("R8", DWORD64),
                    ("R9", DWORD64),
                    ("R10", DWORD64),
                    ("R11", DWORD64),
                    ("R12", DWORD64),
                    ("R13", DWORD64),
                    ("R14", DWORD64),
                    ("R15", DWORD64),
                    ("Rip", DWORD64),
                    ("DebugControl", DWORD64),
                    ("LastBranchToRip", DWORD64),
                    ("LastBranchFromRip", DWORD64),
                    ("LastExceptionToRip", DWORD64),
                    ("LastExceptionFromRip", DWORD64),
                    ("DUMMYUNIONNAME", DUMMYUNIONNAME),
                    ("VectorRegister", M128A * 26),
                    ("VectorControl", DWORD64)
    ]
    В константы(т.е. сюда):
    Code:
    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
    Добавить новую константу:
    Code:
    DWORD64=c_uint64
    В my_debugger.py:
    Вот этот метод:
    Code:
    def get_thread_context (self, thread_id=None,h_thread=None):
    
            context = CONTEXT()
            context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
    
            # Obtain a handle to the thread
            if h_thread is None:
                self.h_thread = self.open_thread(thread_id) 
    
            if kernel32.GetThreadContext(h_thread, byref(context)):
                kernel32.CloseHandle(h_thread)
                return context
            else:
                return False
    Заменить на этот:
    Code:
    def get_thread_context(self,thread_id=None, h_thread=None):
            context=CONTEXT64()
            context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
            if h_thread is None:
                self.h_thread = self.open_thread(thread_id)
            kernel32.SuspendThread(self.h_thread)
    
            if kernel32.GetThreadContext(self.h_thread, byref(context)):
                kernel32.ResumeThread(self.h_thread)
                return context
            else:
                kernel32.ResumeThread(self.h_thread)
                return False
    Ну и в my_test.py:
    Заменить в вызове вот это:
    Code:
    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
    Вот на это:
    Code:
            print ("[**] RIP: 0x%08x" %thread_context.Rip)
            print ("[**] RSP: 0x%08x" %thread_context.Rsp)
            print ("[**] RBP: 0x%08x" %thread_context.Rbp)
            print ("[**] RAX: 0x%08x" %thread_context.Rax)
            print ("[**] RBX: 0x%08x" %thread_context.Rbx)
            print ("[**] RCX: 0x%08x" %thread_context.Rcx)
            print ("[**] RDX: 0x%08x" %thread_context.Rdx)
    Весь код рабочий. Работает как на Python3, так и на python 2.5+, только для Python 2.5+ необходимо заменить конструкции print('Бла-Бла'), на print 'Бла-бла'.

  16. 4 пользователя(ей) сказали cпасибо:
    Dark Koder (16-12-2013) gavz (09-02-2016) root (16-12-2013) ximera (16-12-2013)
  17. #11
    root's Avatar

    Default Re: Gray Hat Python: Глава 3 - Создание отладчика под Windows (Перевод: Prosper-H)

    Развеем миф о том, что вы ничего не сможете создать например под Windows7 64 bit на python3.
    Так нигде же про миф не говорилось =) К 2015-2016 году всё портируют под Win7 и мы без лишней тарабарщины перейдем, как минимум, на Win7+.

    Вся суть сводится к тому, что вам необходимо взять контекст для 64 битной версии(там и регистры слегка отличаются) и добавить пару новых типов данных, а старую структуру Context временно закомментить, ну и слегка переписать метод получения контекста нити, и само собой вызов.
    До этого еще додуматься надо было ;)

    Весь код рабочий. Работает как на Python3, так и на python 2.5+, только для Python 2.5+ необходимо заменить конструкции print('Бла-Бла'), на print 'Бла-бла'.
    Чтобы не править print'ы нужно подключить фичу:

    Code:
    from __future__ import print_function
    Правда работать будет только начиная с Python 2.6+, но ведь 2.5 уже прошлый век? Так что не страшно. ИМХО.
    Успех – это путь от провала до провала без потери энтузиазма. (В. Черчиль)

    Не бойся идти медленно, бойся остановиться. (Китайская пословица)

    When you lose fun and start doing things only for the payback, you're dead. (c) TCLH (Phrack 65, Intro)

  18. Пользователь сказал cпасибо:
    ximera (17-12-2013)
+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:35
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org