Главная / Блог / Скрипты и функции в x64dbg: автоматизация реверса и примеры

Скрипты и функции в x64dbg: автоматизация реверса и примеры

10 СЕНТЯБРЬ, 2025
Скрипты и функции в x64dbg: автоматизация реверса и примеры
Скрипты и функции в x64dbg: автоматизация реверса и примеры

Реверс чужих приложений отнимает много времени, поэтому сегодня все инструменты анализа приложений поддерживают скрипты (сценарии), которые позволяют автоматизировать процесс. В данном материале мы научимся создавать скрипты для наиболее распространённых из отладчиков под Windows – это пользовательский x64dbg, а так-же дебагер режима ядра WinDBG. Начнём, пожалуй, с первого и попробуем ответить на вопрос, зачем в принципе нужны скрипты, и какую выгоду мы сможем от них поиметь. Забегая вперёд скажу, что автор x64dbg «Дункан Огилви» со-своей командой постарались здесь на славу, и грех нам не использовать их труды в своей практике.

Вводная часть

В процессе реверса софта мы не можем предугадать, какие именно техники использовал разработчик в своём софте, а потому в любом случае требуется поверхностный анализ кода приложения. Например, о многом может сказать список импортируемых API-функций, после чего уже прорисовывается общая картина. Ну или проверка значения «Энтропии» секций в инструменте типа «PE-Anatomist», что позволит обнаружить сжатые данные.

Кстати, даже секция ресурсов .rsrc способна скрывать вредоносный код — мы подробно разбирали это в материале шелл-код в секции ресурсов PE-файла.

А вот дальше уже потребуется сбор сведений по определённой логике, который имеет смысл автоматизировать. В большинстве случаях для этого в отладчиках есть поддержка точек-останова «BreakPoint», но и их возможности в дефолте сильно ограничены. Вот здесь и приходят на помощь скрипты (на русском сценарии), импульс от сделки с которыми просто впечатляет. Скриптом мы можем запускать отлаживаемые процессы сразу на исполнение, и останавливать их по какому-либо условию, или налету распаковывать/расшифровывать информацию, и т.д. В общем делать всё, что душа пожелает, и именно в этом реальная сила современных инструментов отладки, с которыми многие из начинающих реверсеров просто не знакомы. Цель данной статьи – заполнить этот пробел.

Формат скриптов отладчика x64dbg

Скрипты здесь делятся на 2 группы – непосредственно «Сценарии» и более мощные «Функции». Не смотря на то, что грань между ними тонка, они всё-же отличаются как по содержимому, так и по возможностям. По формату это обычные текстовые файлы с любым расширением, хотя предпочтительно использовать рекомендуемое автором расширение *.txt. Функции считаются более производительными, т.к. представляют собой просто набор команд управления дебагером, где каждая команда указывается в скрипте с новой строки. Но у каждого из них есть своя зона ответственности, а обзор мы начнём именно со «Сценариев», как более простых для понимания.

Пример сценария

Эта крутая фишка позволяет просматривать блоки памяти в чётко структурированной форме. Например многие функции Win32-API возвращают массивы данных, сбрасывая их в заранее подготовленные нами структуры. Эти структуры имеют именованные поля, которые мы потом читаем. Но в реале API-функция просто записывает данные в память по указанному в аргументе адресу, где нет никаких именованных полей. Посмотрите на скрин, где по Ctrl+G я запросил у отладчика дамп системной «РЕВ» (Process Environment Block). Кстати у x64dbg в запазухе есть зарезервированные слова для запроса адресов, например peb(), teb(), и tid() (идентификатор потока).

PEB 1

Как видим, отладчик послушно вернул нам содержимое блока по адресу 0x000007ff`fffd8000, только что означают эти данные – сам чёрт не разберёт. Если мы хотим получить профит от вкладки «Структура», сначала нужно создать текстовый файл с подробным описанием полей (прототипы структур можно найти в сишных хидерах). Вот фрагмент такого файла для структуры РЕВ, который можно будет сохранить в папку \script отладчика:

//--------  Заголовок с дефайнами -----------------
//--------  заполняется 1 раз для каждого файла ---
//--------  и описывает размеры полей -------------
ClearTypes

AddType  uint64_t, QWORD
AddType  uint32_t, DWORD
AddType  uint16_t, WORD
AddType  uint8_t,  BYTE
AddType  wchar_t,  UNICODE
AddType  char,     ASCII
//--------  Конец описателя размеров полей --------

AddStruct PEB64        // create an РЕВ structure
  AddMember PEB64,BYTE,InheritedAddressSpace
  AddMember PEB64,BYTE,ReadImageFileExecOptions
  AddMember PEB64,BYTE,BeingDebugged
  AddMember PEB64,BYTE,BitField
  AddMember PEB64,DWORD,Reserved,0x01
  AddMember PEB64,DWORD64,Mutant
  AddMember PEB64,DWORD64,ImageBaseAddress
  AddMember PEB64,DWORD64,Ldr
  AddMember PEB64,DWORD64,ProcessParameters
  AddMember PEB64,DWORD64,SubSystemData
  AddMember PEB64,DWORD64,ProcessHeap
//......... продолжение следует

Теперь (1)переходим на вкладку «Сценарии» в верхнем окне отладчика, (2)правым кликом мыши выбираем в ней наш новоиспечённый файл скриптов выше, и (3)как он появится в окне, обязательно запустим его клавишей «Space» пробел. Если в ответ получим окно «ОК», значит всё сделали правильно, и теперь переходим на вкладку «Структура» в окне дампов памяти. Здесь аналогично правой клавишей выбираем «Тип посещения» и вводим «РЕВ64», где «РЕВ64» задаёт название структуры в нашем текстовом файле сценариев. По нажатию ОК появится ещё бокс, на этот раз с запросом адреса дампа в памяти (на который планируем наложить структуру) – отвечам зарезервированным словом peb(). Результатом этих телодвижений будет аккуратно оформленный дамп, где каждая строка в столбце «Имя» соотносится к столбцу «Значение». Теперь сравните, что мы получили на предыдущем скрине с сырым дампом, и на этом. Обратите внимание на стартовый адрес структуры в памяти 000007ff`fffd8000 – в обоих случаях он совпадает:

PEB 2

Поле «BeingDebugger» в структуре РЕВ является флагом, что процесс находится под отладкой. Этот байт принимает логическое значение True/False (соответственно да/нет), и как видим в данном случае он взведён в 1, а значит всё идёт по плану. Можно в меню активировать плагин «ScyllaHide», тогда этот байт сразу сбросится в нуль, ведь плаг и работает над тем, чтобы скрыть отладчик от исследуемых процессов.

А сами техники антиотладки и трюки с проверками вроде CMP/JE я подробно показывал в статье антиотладка и обход CMP/JE на практике.

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

Пример функций x64dbg

Функции поддерживаются встроенным в отладчик мощным скриптовым языком, который чем-то похож на плюсы С++, но по сути не является таковым, хотя-бы на уровне синтаксиса. Результат работы функций отображается в окне «Журнал», а сами функции – это лишь последовательность вынесенных в отдельный файл команд. Все команды скриптов можно вводить прямо в окно ком.строки (в подвале основного окна), а выхлоп проверять в окне журнала. Самих команд огромное кол-во, а потому перечислить их все в одной статье никак не получится. Кому интересно, можно посмотреть описание в прилагаемом к x64dbg файле-справки *.pdf (правда оформление его на редкость отвратительное).

Начнём, пожалуй, со сбора информации о загруженных в исследуемое приложение модулей DLL – вот синтаксис, который имеет смысл сначала отшлифовать через ком.строку, и если в окне «Журнал» получим желаемый результат, то сохранять уже во-внешний файл скрипта. Здесь пример для либы Kernel32.dll, но можно указать и любую, причём обязательно без квадратных скобок:

Module

Аналогично можно приручить функцию скрипта и к основным командам отладчика, и работой с точками-останова «BreakPoint»:

Debug Break

А вот для чтения/записи памяти адрес непременно должен быть прописан внутри квадратных скобок, причём вместо «byte» можно подставлять зарезервированные слова «word/dword/qword» (2,4,8 соответственно):

Memory

Числа и строки можно выводить только в системах счисления HEX и DEC, причём большинство аргументов совпадают со-спецификатороми API printf() из msvcrt.dll:

String

При необходимости можно задавать переменные командой «var», однако есть и зарезервированная для общего случая и команда «$result», что очень удобно использовать на практике:

Any

Ну команды в глобальном масштабе..

Функции поддерживают большинство инструкций ассемблера, например: пересылку данных mov, вызовы процедур call/ret, проверку с условными переходами cmp/jne, прыжки goto/run, все лог.инструкции and/or/xor, и многое другое. В документации всё описано, и остаётся просто проштудировать её несколько раз.

Но самым отличным на мой взгляд решением во-первых стала возможность устанавливать обратные вызовы «Callback» на точках останова, которая реализуется командой «SetBreakpointCommand», а во-вторых расширять регистры общего назначения с 32 до 64 бит просто префиксом(С), например CAX=EAX=RAX, CIP=EIP=RIP, и т.д. Как результат, один скрипт можно будет использовать как для 32, так и для 64-битных приложений. В общем вместо 1000 слов посмотрим на такой скрипт, по результат которого в лог сбрасывается основная инфа при срабатывании API выделения памяти VirtualAlloc(). Само приложение может выглядеть так (выделяет память и копирует туда строку):

Script

А вот скрипт для его обработки:

cls
bpc
bphc

bp VirtualAlloc
SetBreakpointCommand VirtualAlloc, "scriptcmd call cb_virtual_alloc"

$base  = mod.main()
$entry = mod.entry($base)

log
log "      Image Base:  {p:$base}"
log "    Base of Code:  {p:entry}  {a:cip}"
log "  Size in memory:  {d:mod.size($base)} byte"
log "     PEB address:  {p:peb()}"
log "     TEB address:  {p:teb()}"
log "       Thread ID:  {x:tid()}"
log "      Process ID:  {x:$pid}"
log "  Process Handle:  {x:$hp}"
log "     First bytes:  {mem;.16@cip}"
log "  First instruct:  {i:cip}  (len={dis.len(cip)} byte)"
log

refstr
i = 0
loop:
  addr = ref.addr(i)
  log "    String refer:  {d:i} = {p:addr} --> {i:addr}"
  i++
  cmp i, ref.count()
  jne loop
  log

goto main

cb_virtual_alloc:
    rtr
    log "  Memory alloc..:  {p:cax}"
    log "  Memory size...:  {d:arg.get(1)} byte"
    log "  Memory protect:  {d:arg.get(3)}.  (R=2, RW=4, RE=20, RWE=40)"
    log
    savedata "f:\dump.bin", 00402000, .128

main:
    run
ret

Немного комментов для этого скрипта..

Первые три строчки на входе очищают все софт/хард бряки, если таковые имелись. Далее устанавлиется точка на VirtualAlloc(), а «SetBreakpointCommand» определяет колбек, который сработает при останове. На сл.этапе печатается лог о текущем модуле, который предваряют 2 переменные «$base/entry». Выражение типа «ref.count()» считает количество ссылок, а «ref.addr(index)» получает адрес ссылки по индексу. Под занавес в хвосте прописана функция обратного вызова «Callback», внутри которого читаются аргументы API VirtualAlloc() именно на входе, посколько сам колбек начинается с команды «rtr», что подразумевает RunTrace. Если убрать эту «rtr», то получим аргументы уже на выходе из VirtualAlloc(), что собственно безсмысленно. И наконец команда с тремя аргументами «savedata» позволяет сохранить заданный участок памяти на диск, т.е. сдампить его.

Теперь загружаем приложение в x64Dbg, и на вкладке "Скрипты/Сценарии" жмём [ПКМ --> Загрузить скрипт] или просто Ctrl+O. Если всё ок, то текст скрипта увидим в окне, а запустить его можно клавишей "Пробел". Текстовый оригинал скрипта можно править снаружи, и не выгружая обновлять его в окне отладчика через Ctrl+R (или пкм --> перезагрузить скрипт). Результат будет отображён как в окне "Сценарии", так и в консольном окне логов "Журнал". В репозитории разраба: https://github.com/x64dbg/Scripts имеются сдесяток готовых скриптов. Заглянув в их содержимое можно ознакомиться с основными командами, и общей философией создания функций – очень выручает. Результат работы нашего тестового скрипта выглядит как на скрине ниже, хотя в соседнем окне «Сценарии» можно получить этот-же лог в более информативном виде:

x64dbg_script

Заключение

Понять структуру стриптов отладчика x64dbg можно только пощупав их руками, а потому эта статья была написана только с целью заинтересовать читателя. Если объяснять всё досканаль, то в тетради не хватит клеток, да и не нужно это, т.к. на сайте разработчика по указанному выше линку имеется масса примеров, например для поиска в памяти приложения сигнатур протекторов/упаковщиков, и многое другое. Просто откройте их, и всё станет намного прозрачней. Удачи в изучении этого интересного направляений!

Поделиться

0 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...