• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Как превратить отладчик в хакерский комбайн

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Автоматизация x64dbg позволит создавать собственные инструменты для типовых задач отладки, таких как распаковка файлов, поиск сигнатур, перехват или подмена на лету результатов работы функций и даже фаззинг в оперативной памяти! Давай посмотрим, какие есть способы сделать это и как ими пользоваться.

Встроенный интерпретатор​

Для быстрого решения повседневных задач в отладчик встроен простой скриптовый язык. Давай рассмотрим скрипт на нем, который будет распаковывать файл, накрытый UPX — самым распространенным упаковщиком.

Код:
//start
msg "upx (3.91) unpacker"
msg "make sure you're at the entry point of the program before you continue"
pause

//clear breakpoints
bc
bphwc

//script start
step
bphws csp,r
erun
bphwc

//find oep jump
find cip,"80E9" //some pattern
cmp $result,0
je error

//go to OEP
bp $result+1
erun
bc
sti

//finish script
ret

error:
msg "didn't find oep jump"
ret

Синтаксис говорит сам за себя: имя команды, за которым следует разделяемый запятыми набор аргументов. Комментарий идет после двух слешей. Строчки обрамляются двойными кавычками. Цифровые константы интерпретируются как шестнадцатеричные числа, то есть число 100 на самом деле значит 256.


Одна и та же команда может иметь несколько имен, в длинной и короткой форме. Например, команда SetBPX, устанавливающая точку останова, может быть записана как bp. Короткая запись удобна для управления отладчиком из встроенной консоли, расположенной снизу.

Результат команды msg
Результат команды msg

Готовый скрипт можно загрузить из текстового файла, а затем отдать на исполнение или запустить пошаговую отладку скрипта, нажимая Tab для перехода на следующую строку.

Исполняем по одной команде за раз
Исполняем по одной команде за раз

Разберем, что делает скрипт, переписав команды в длинной форме:

Код:
pause

//clear breakpoints
DeleteBPX
DeleteHardwareBreakpoint

//script start
StepOver
SetHardwareBreakpoint csp,r
erun
DeleteHardwareBreakpoint

//find oep jump
find cip,"80E9" //some pattern
cmp $result,0
je error

//go to OEP
SetBPX $result+1
erun
DeleteBPX
StepInto

//finish script
ret

error:
msg "didn't find oep jump"
ret
www

Для просмотра ссылки Войди или Зарегистрируйся
Первая команда — pause — останавливает выполнение скрипта, после чего его необходимо запустить вручную, исполнение продолжится со следующей команды. Это удобно, когда от пользователя требуется выполнять действие, например ставить EIP на точку входа.

Далее DeleteBPX удаляет все программные точки останова, а DeleteHardwareBreakpoint очищает отладочные регистры процессора. Строка StepOver говорит отладчику сделать один шаг в пошаговом режиме, то есть перейти в коде на следующую ассемблерную инструкцию, не заходя в CALL, а перепрыгивая на следующую за ним команду.

SetHardwareBreakpoint устанавливает хардварную точку останова. У нее один обязательный и два опциональных аргумента: адрес, на который устанавливается брейк‑пойнт, тип точки останова и размер «триггерной» области в байтах. Первым аргументом передается регистр CSP, это особый виртуальный регистр, который на x32 будет прочитан как ESP, а на x64 как RSP. Второй аргумент r обозначает тип readwrite, то есть отладочное прерывание сработает, когда байт по указанному адресу будет записан или прочитан.

Строка erun отпускает поток исполнения, ровно до того момента, как сработает любая точка останова. После чего через DeleteHardwareBreakpoint удаляются все хардварные точки останова. Регистр EIP в этот момент находится на первой инструкции, которая меняет байт на вершине стека. В момент исполнения скрипта это будет конец распаковки.

Инструкция find ищет шаблон на указанной странице памяти. Первый аргумент — адрес, с которого начинается поиск. Он берется из регистра CIP, то есть EIP в нашем файле x32. Вторым аргументом указан шаблон 80E9, это обычная подстрока. Команда поддерживает маски (whildcard), поэтому можно указывать неизвестную часть шаблона через вопросительные знаки, например EB0?90??8D.

Результат, то есть найденный адрес вхождения, помещается в глобальную переменную $result. Следующая команда, cmp (сокращение от compare), использует его для сравнения с константой 0, которая будет возвращена в find, если вхождения не найдено. Логика работы cmp повторяет версию для x86: результат сравнения записывается в глобальные переменные $_EZ_FLAG и $_BS_FLAG, это аналог флагов из процессорного регистра EFLAGS.

Окно со всеми переменными
Окно со всеми переменными

Управление внутри скрипта передается через команду je (сокращение от Jump if Equal). В данном случае скрипт передаст управление на метку error, если вхождение подстроки 80E9 не было найдено.

Дальше SetBPX устанавливает софтверную точку останова по адресу $result+1. Скрипты поддерживают Для просмотра ссылки Войди или Зарегистрируйся с множеством операций над переменными. К найденному адресу прибавляется единица, это адрес команды JMP OEP, которая передает управление на оригинальную точку входа (original entry point) сразу после распаковки файла. Следом идут знакомые нам erun и DeleteBPX.

Последняя значимая команда — StepInto, она просит сделать один шаг в пошаговом режиме. То есть исполнить найденную инструкцию JMP OEP, передав управление на первую инструкцию распакованного файла. После чего на команде ret исполнение скрипта завершается.


История вопроса​

Не могу не отметить, что идея подобных скриптов принадлежит старинному плагину для OllyDbg под названием Для просмотра ссылки Войди или Зарегистрируйся. Отладчик x64dbg — духовный наследник «Ольги» и взял лучшие практики от своего предка. Так выглядит скрипт под OllyScript для распаковки UPX:

Код:
var hwdBP
var softBP
sti
findop eip, #61#
mov hwdBP, $RESULT
bphws hwdBP, "x"
run
findop eip, #E9????????#
mov softBP, $RESULT
bp softBP
run
sti
cmt eip, "<<>>"
msg "OEP found, you can dump the file starting from this address"
ret

Как говорится, найди десять отличий.


Плагины​

Еще один способ автоматизировать отладку — написание плагинов для x64dbg. Плагин — это обычный DLL-файл, собранный с инклудами из Plugin SDK. При загрузке плагина отладчик ищет в экспорте DLL-функции pluginit и plugsetup, которые сообщают информацию о плагине и регистрируют новые команды.

Plugin SDK идет в комплекте при скачивании актуального релиза отладчика. Он представляет собой набор заголовочных и бинарных файлов, которые сообщают компилятору, какие функции можно вызывать из внешнего модуля.

Все новые функции определены в подключаемых файлах. Для них не существует нормальной документации, но все должно быть понятно из текста заголовочных файлов. Например, \pluginsdk\_scriptapi_debug.h:

Код:
namespace Script
{
    namespace Debug
    {
        SCRIPT_EXPORT void Wait();
        SCRIPT_EXPORT void Run();
        SCRIPT_EXPORT void Pause();
        SCRIPT_EXPORT void Stop();
        SCRIPT_EXPORT void StepIn();
        SCRIPT_EXPORT void StepOver();
        SCRIPT_EXPORT void StepOut();
        SCRIPT_EXPORT bool SetBreakpoint(duint address);
        // (...)
    };
};

Имена функций говорят сами за себя. Из‑за вложенных неймспейсов вызов функций выглядит как Script::Debug::Wait(). Это сделано из‑за того, что имена могут повторяться в разных контекстах.

Я решил не заморачиваться с настройкой проекта и просто Для просмотра ссылки Войди или Зарегистрируйся плагин, готовый к сборке. Он без проблем собирается в Visual Studio при условии, что в системе установлен Windows SDK и нужный компилятор. Осталось заменить пару функций, и новый плагин готов:

Код:
#include "pluginsdk/_scriptapi_debug.h"
#include "pluginsdk/_scriptapi_memory.h"
#include "pluginsdk/_scriptapi_register.h"
#include "pluginsdk/_scriptapi_pattern.h"
#include "pluginsdk/_scriptapi_comment.h"
#include "pluginsdk/_scriptapi_gui.h"
#include "pluginsdk/bridgemain.h"

#define PLUGIN_NAME "unpack_upx"

bool pluginInit(PLUG_INITSTRUCT* initStruct)
{
    if(!_plugin_registercommand(pluginHandle, PLUGIN_NAME, cbCommand, false))
        _plugin_logputs("[" PLUGIN_NAME "] Error registering the "" PLUGIN_NAME "" command!");
    return true;
}

static bool cbCommand(int argc, char* argv[])
{
    Script:ebug::Wait();
    auto cip = Script::Register::GetCIP();

    if (Script::Memory::ReadByte(cip) == 0x60)
    {
        DbgCmdExecDirect("bc");
        DbgCmdExecDirect("bphwc");
        auto found = Script:attern::FindMem(cip, 0x1000, "83 EC ?? E9");

        if (found)
        {
            Script:ebug::SetBreakpoint(found + 3);
            Script:ebug::Run();
            Script:ebug::StepIn();
            Script::Comment::Set(Script::Register::GetCIP(), "OEP");
            Script::Gui::Message("Reached OEP. Use Scylla to dump and restore imports!");
            DbgCmdExecDirect("scylla");
        }
        else
            Script::Gui::Message("Couldn't find OEP jump (83 EC ?? E9)...");
    }
    else
        Script::Gui::Message("Put EIP on a UPX entry point (0x60) to continue...");

    return true;
}

Разберемся, что делает код. Функция _plugin_registercommand в pluginInit регистрирует новую команду, ее можно будет вызывать по имени прямо из консоли. Колбэк cbCommand срабатывает в этом случае.

Сначала код проверяет, что байт по адресу EIP равен 0x60 — это простая сигнатура, первый опкод в UPX pushad, который бинарно представлен как 0x60.

Дальше вызываются DbgCmdExecDirect — это прямое исполнение текстовых команд, как строчка для интерпретатора. Через него вызываются знакомые нам команды для очищения списка брейк‑пойнтов. Далее Script::pattern::FindMem по шаблону 83 EC ?? E9 ищет адрес кода.

Код:
83EC 80        sub esp,FFFFFF80
E9 80F9F9FF    jmp loaddll_upx.OEP

Так выглядит искомый ассемблерный код, количество вычитаемых из ESP байтов заранее неизвестно, поэтому вместо 80 стоит ??. Это последняя инструкция после распаковки, за ней следует переход на оригинальную точку входа.

Когда получен нужный адрес, на него ставится точка останова. А поймав ее, код проходит на один шаг вперед. Здесь ставится комментарий на текущий EIP, после чего вызывается scylla — встроенный в x64dbg инструмент для дампинга (то есть «упаковки») распакованных в памяти процесса файлов обратно в EXE.

Настало время проверить плагин в деле. Собираю исходный код, меняю имя выходного файла на unpack_upx.dp32 и помещаю его в папку x64dbg\x32\plugins. Запускаю отладчик и вижу в логе:

Код:
[pluginload] unpack_upx
[PLUGIN, unpack_upx] Command "unpack_upx" registered!
[PLUGIN] unpack_upx v1 Loaded!

Значит, плагин загружен. Перехожу на точку входа в запакованном UPX файле и ввожу команду unpack_upx. Моментально вижу сообщение об успешной распаковке и запуск инструмента scylla. Все работает!


Автоматизация на Python​

Плагин Для просмотра ссылки Войди или Зарегистрируйся появился как проект стороннего разработчика, но сейчас его поддерживает mrexodia — один из авторов x64dbg. Как ты уже догадался, плагин добавляет поддержку скриптов на языке Python. Для работы плагина нужно установить в систему Python 2.7.10. Внешний (не встроенный в тело плагина) интерпретатор позволяет устанавливать сторонние библиотеки через pip, чем значительно расширяет возможности наших скриптов. Вдруг, например, по ходу отладки потребуется экзотическая криптография, парсер редкого формата или другой декомпилятор?

info​

Поддержка Python — признак крутого проекта. Надеюсь, в остальных инструментах она тоже однажды появится.
После установки плагина выбираем в меню Plugins → x64dbgpy → Open GUI Script и загружаем готовый скрипт с диска. А отдельные команды можно исполнять из консоли. Для этого справа от строки ввода в выпадающем списке меняем Default на Python.

Рассмотрим пример скрипта, реализующего распаковку UPX:

Код:
from x64dbgpy.pluginsdk import *
import sys

cip = register.GetCIP()
if memory.ReadByte(cip) != 0x60:
    gui.Message("Start at UPX entry point (1:[CIP]==0x60)")
    exit(0)

x64dbg.DbgCmdExecDirect("bc")
x64dbg.DbgCmdExecDirect("bphwc")
found = pattern.FindMem(cip, 0x1000, "83 EC ?? E9");
if found == 0:
    gui.Message("Could not find pattern!");
    exit(0)

debug.SetBreakpoint(found + 3)
debug.Run()
debug.StepIn()

cip = register.GetCIP()
comment.Set(cip, "OEP Found by Python!")
gui.Message("Reached OEP. Use Scylla to dump and restore imports!")
x64dbg.DbgCmdExec("scylla")

В глаза бросается схожесть происходящего с кодом плагина. И это неспроста — вызываемые из Python функции — это не более чем обертки над Plugin SDK API. Поэтому имена функций на Python практически полностью повторяют имена из инклудов для C++.

Нормальной документации к плагину нет, но есть неплохая Для просмотра ссылки Войди или Зарегистрируйся с примерами.


Внешняя автоматизация​

До этого момента управление отладчиком осуществлялось изнутри: он выполнял загружаемые скрипты. Это удобно для полуавтоматического анализа или распаковки конкретного файла. Но что, если таких файлов сотни? Тут пригодится внешний инструмент, который создает инстансы отладчика и управляет ими извне. Именно для этого был создан Для просмотра ссылки Войди или Зарегистрируйся.

Инструмент состоит из двух частей: библиотека на Python в роли сервера и плагин для x64dbg, выступающий в роли клиента. Для установки копируем плагин в папку отладчика и ставим управляющую плагином библиотеку через pip:

pip install x64dbg_automate --upgrade
В отличие от прошлого плагина, x64dbg-automate поддерживает актуальный Python третьей версии, что значительно расширяет список доступных нам библиотек.

Набор API здесь свой, но это все так же лишь удобная обертка для Plugin SDK. Названия методов API на этот раз не совпадают с Plugin SDK, но по ним есть полноценная Для просмотра ссылки Войди или Зарегистрируйся.

Давай рассмотрим вот такой скрипт:

Код:
from x64dbg_automate import X64DbgClient
client = X64DbgClient(x64dbg_path=r"C:\Soft\x64dbg\x64\x64dbg.exe")
client.start_session(r'c:\Windows\System32\winver.exe')

mem = client.virt_alloc()
client.write_memory(mem, 'Xakep.RU'.encode('utf-16le'))

client.set_breakpoint('ShellAboutW', singleshoot=True)
client.go() # Entrypoint breakpoint
client.wait_until_stopped()
client.go() # ShellAboutW
client.wait_until_stopped()

client.set_reg('rdx', mem)
client.go()

client.detach_session()

Сначала создается дебаг‑сессия — с указанием пути до отладчика и отлаживаемого файла. В этот момент запускается дебаггер. Дальше через внутренние API в памяти процесса выделяется блок памяти и в нее записывается строка в кодировке UTF-16. Мы ставим одноразовую точку останова на вход в ShellAboutW. Ждем, пока отладчик до нее доберется, и меняем регистр RDX. По соглашению о вызовах в x64-коде второй аргумент для WinAPI передается через RDX. Поэтому, подставив в него адрес строки, мы на лету подменяем заголовок окна.

Результат выполнения скрипта
Результат выполнения скрипта

Судя по постам автора плагина (darbonzo), x64dbg-automate создавался с прицелом на анализ малвари. Действительно, внешние скрипты позволяют анализировать тысячи семплов за раз. А управление отладчиком через Plugin SDK предоставляет возможность автоматизировать распаковку, обход антиотладки и даже сравнение сигнатур в памяти распакованного семпла при помощи такого движка, как YARA.


Выводы​

Автоматизация отладки позволяет делать с отладчиком буквально все то же самое, что с ним можно делать при ручном управлении. И это немало! Фактически мы получаем кастомизируемый инструмент для исследования исполняемых файлов и автоматизации любых операций над ними. В следующих статьях я покажу, как автоматизировать другие популярные инструменты, так что до скорого!
 
Activity
So far there's no one here
Сверху Снизу