x64dbg, отладка для новичков

Доброго времени суток, друзья! Эта статья базовая и обзорная по возможностям современного отладчика x64dbg для новичков. Данное приложение свободное и доступное вы его можете скачать с официального сайта.

Содержание

Коротко о регистрах и флагах
Знакомство с интерфейсом x64dbg
Вкладка CPU
Вкладка «Граф» (Graph)
Вкладка «Журнал»
Вкладка «Заметки»
Вкладка «Точки останова» («Breakpoints»)
Вкладка «Карта памяти» («Memory map»)
Вкладка «Стек вызовов»
Вкладка «SEH» (Structured Exception Handler)
Вкладка «Сценарии»
Вкладка «Отладочные символы»
Вкладка «Ссылки» («Refs»)
Вкладка «Потоки» («Threads»)
Вкладка «Дескрипторы»
Написание приложения требующего ввода «секретной последовательности»
Получение правильного ключа

Визуализируем логику работы программы
Выводы

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

Однако подобного рода сообщения все же не способны пролить свет на функционирование приложения. Вы уверенны в том что приложение не соединяется с вредоносными узлами и не получает от них команды? Нет? — Тогда эта статья для вас.

Коротко о регистрах и флагах

Отладчик это словно скальпель в руках хирурга. Данный инструмент вскрывает для вас тело программы. Прыгая между последовательностью команд в реальном времени, модифицируя содержимое регистров и флагов вы меняете ход работы приложения. Регистры (регистровая память) это область где хранятся данные с которыми процессор в данный момент имеет дело. Изначально регистры были размером в 16 бит, например, ax — регистр аккамулятор (используется для складывания/вычитания величин), cx — регистр счетчик (используется для указания длинны цепочек данных), dx — регистр данных и так далее. Флаги это словно светофоры в реальной жизни. Флаг имеет два состояния 0 и 1. Флаги с которыми вы сталкиваетесь в повседневной жизни: флаг нуля zf, флаг переполнения of, флаг парности pf.

Также есть специальные флаги, например, когда ваш компьютер от начала своего включения работает в реальном режиме и не может воспользоватся всей оперативной памятью. Для того что бы это стало возможным выполняется комманда, которая устанавливает флаг PE (protected enabled) в 1. Процессорные флаги нельзя модифицировать явно. Для этого используются комманды. Не следует путать процессорные флаги и флаговые переменные в программах написанных на языках высокого уровня. Их в отличии от процессорных флагов можно модифицировать явно, например:

#include <iostream>
using namespace std;
// целочисленный тип состоить из 4 байтов.
// итого в одой целочисленной переменной 32 разных флага
// чтобы было видно наглядно продемонстрируем флаги в двоичном и шестнадцатичном
// виде:
// 00000000 00000000 00000000 00000001 — 0x1 первый флаг установлен в 1
// 00000000 00000000 00000000 00000010 — 0x2 второй флаг задан
// 00000000 00000000 00000000 00000100 — 0x4 третий флаг установлен в 1
// 00000000 00000000 00000000 00001000 — 0x8 четвертый флаг установлен в 1
// 00000000 00000000 00000000 00010000 — 0x10 пятый флаг установлен в 1
// ……………………………………………………………..
// 10000000 00000000 00000000 00000000 — 0x100000 32-ой флаг установлен в 1
int main() // главная функция консольного приложения
{
int state=0; // Пусть в начале не один флаг не задан
// Давайте установим второй и четвертый флаг
// Для этого выполним булевую операцию «ИЛИ»
// над нашей флаговой переменной state
state = state | 10; // 00000000 00000000 00000000 00001010 — 0xA
// Чтобы проверить установлен 4-ой флаг в единицу или нет
if(state & 8 == 1) cout<<«The 4th flag is set!»<<endl;
else cout<<«The 4th flag is not set!»<<endl;
// Чтобы выключить флаг необходимо проделать обратные операции
// «И» + «НЕТ». Выключим четвертый флаг
state = state & ( !8 );
// «И» — & (символ амперсанд), «ИЛИ» — | (вертикальная черта), «НЕТ» — !
// «ИСКЛЮЧАЮЩЕЕ ИЛИ» (другие названия: XOR, сумма по модулю 2) — ^
// Эти операторы языка программирования — называются функциями булевой алгебры.
// Элементов «И», «ИЛИ», «НЕ» достаточно чтобы построить компьютер.
// Физически данные элементы можно представить в виде электрических схем.
// Собственно процессор и есть множество транзисторов.
return 0; // ноль возвращается всегда в случае успеха
}
Пример обращения с флаговыми переменными в языке С++

Итак флаговая концепция очень удобна для комбинирования множества свойств объекта в одной целочисленной переменной.

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

Битность регистров приведена ниже:
al,ah,bl,bh,dl,dh,cl,ch — 8 битные регистровые пары формируют 16 битные ниже;
ax,bx,dx,cx — 16 бит
eax,ebx,edx,ecx — 32 бита
rax,rbx,rdx,rcx — 64 бита

Не трудно заметить, что в название 16-битных регистров добавили букву, но предназначение у них то же, по аналогии с ax,bx,cx. Если вы думаете что время 16-битных процессоров прошло — вы ошибаетесь. Их используют как правило для встраиваемых систем не требующих много ресурсов. Для примера, зачем для контроля освещения в комнате использовать мощьный процессор, если это можно собрать дешевле на менее мощьных чипах. И конечно не обязательно на архитектуре x86.

Знакомство с интерфейсом x64dbg

Не смотря на x64 в названии, в нем также присутствует и версия для 32 битных систем. Отладчик портабильный его можно записать на съемный носитель и перенести на другую машину. Кроме того существует возможность расширить возможности и удобство использования за счет плагинов.

Стандартный же вид состоит из панели инструментов сверху. Предназначена она для быстрого открытия файлов, манипуляций с ходом работы программы, вставки закладок (очень удобно если программа запутана множественным ветвлением). Одним из самых интересных, на мой взгляд, инструментов есть поиск строк и межмодульных вызовов.

Вкладка CPU

CPU tab
x64dbg

Под панелью инструментов мы можем увидеть вкладки. Главная из которых — CPU. В этой вкладке вы можете увидеть состояние работы приложения. Регистр RIP — для 64-битных ОС (или регистр eip для 32-битных) визуально представлен стрелочкой, что указывает на текущий адрес в котором содержится комманда. Адреса в оперативной памяти куда загружается программа даются операционной системой. Большинство пользовательских ОС используют защищенный режим. В защищенном режиме для каждой программы выделяются страницы памяти. Каждая страница обладает своими атрибутами (доступ, чтение, запись). Если вы попробуете записать что-либо на место в памяти доступное только для чтения, то получите ошибку memory access violation. Смотреть: Управления оперативной памятью в ОС .

Ниже мы видим серию вкладок dump 1-5. Можно просматривать до 5 разных фрагментов памяти не прыгая по разным адресам. Удобней переключатся между часто просматриваемыми дампами памяти. Также в серии вкладок есть «просмотр», «локальные переменные», «структура». Если программа включает отладочные символы эти вкладки очень пригодятся. Отладочные символы включаются в проект на начальном этапе разработки. При диагностирование неисправности приложения, удобней видеть не числовой адрес а соответствующую ему переменную. Однако, из конечного продукта отладочные символы убирают.

Справа сверху во вкладке CPU мы видим регистры процессора. При этом видны также специализованные регистры FPU (юнит для работы с числами с плавающей точкой). Содержимое регистров и флаги можно менять.

Вкладка «Граф» (Graph)

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

application graph
Граф ветвления приложения

Это не блок-схема в классическом понимании, но все же очень удобная вещь. Вершины графа подписаны адресами (например 4A15A8).

Вкладка «Журнал», или «Log» в англоязычной версии

Log
Лог работы отладчика

Данная вкладка отражает журнал отладки приложения, загрузки и выгрузки ресурсов приложения. Также полезна тем что вы можете увидеть: адреса входа в программу, все установленные точки останова, аргументы с которыми запущенно приложение, состояние инициализации приложения.

Вкладка «Заметки» («Notes»)

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

Вкладка «Точки останова» («Breakpoints»)

breakpoints
Точки останова

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

Вкладка «Карта памяти» («Memory map»)

Показывает как программа и сопутствующие модули раскладываются памяти на секции:

«.text» — секция испольняемого кода
«.rdata» — данные для инициализации (константы только для чтения)
«.data» — собственно переменные
«.pdata», «.xdata» — информация об исключениях
«.bss» — зарезервированное место для неинициализованных данных
«.idata» — таблица импорта функций
«.tls» — временное локальное хранилище для потока (в том случае если программа многопоточная)
«.rsrc» — секция ресурсов приложения (иконки, картинки, аудио и прочее)

sections of our app
Карта памяти исследуемого процесса

Если кликнуть правой кнопкой по секции можно сделать следующее:

  • перейти к дизассемблированному коду (переход на вкладку CPU)
  • перейти к дампу, рассмотреть секцию приложения или модуля более детально
  • направить дамп памяти в файл
  • оставить комментарий над секцией
  • поиск шаблона (поиск ascii, или юникод последовательностей)
  • найти ссылки на данные в данной секции
  • также можно выделить/освободить память под определенную секцию
  • перейти на необходимый адрес
  • задать права доступа
  • установить точку останова, чтобы наглядно видеть когда происходят обращения к секции
  • скопировать строку из таблицы, или же отдельную ячейку

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

Вкладка «Стек вызовов»

В данной вкладке мы видим таблицу вызовов функций. При вызове функции, например командой call, следующий адресс после нее (адресс возврата) помещается в стек. Если в функции будет еще функция то аналогичное происходит и с ней. По умолчанию показывается состояние активного кадра вызовов на данный момент.

Контекстное меню в данной вкладке позволяет проследовать по адресу возврата и осмотреть дизассеблированный код. Также можно скопировать адрес возврата в буфер.

Вкладка «SEH» (Structured Exception Handler)

Данная вкладка отображает структуру исключений. Код приложения должен покрывать все возможные варианты поведения пользователя и программы. Нам пока данный функционал не нужен.

Вкладка «Сценарии»

automation
Сценарий

Модификация множества регистров вручную не всегда удобна. Для этих целей вы можете воспользоватся контекстным меню во вкладке сценарии, открыв файл или введя одну команду. Попробуйте ввести комманду inc rax (для x64) или inc eax (для x32). После выполнения соответствующей команды вы увидите что содержимое регистра увеличилось на 1.

Вкладка «Отладочные символы»

Смотреть на безликие адреса памяти неудобно. Для удобства при начальном этапе разработки включают отладочные символы. Это позволяет понять где совершена ошибка и быстро её исправить.

Отладочные символы

Как вы можете заметить кроме приложения здесь содержатся ещё библиотеки. Библиотека kernel32.dll одна из главных (Да, kernel32.dll присутствует на 64 битной машине). В ней содержатся функции создания и отладки процессов, управления консолью и прочие. msvcrt.dll — это runtime библиотека, стандартных с-функций функций. Справку по этим функциям можно просмотреть в msdn.

Вкладка «Ссылки» («Refs»)

В этой вкладке можно посмотреть все обращения к области в памяти. Из контекстного меню можно поставить точку останова на выбранную область, перейти к просмотру дампа, установить закладку, скопировать адрес и/или дизассеблированный код.

string references
Поиск ссылок на строки

Ссылки на строки можно искать как в отдельном модуле/приложении или во всех сразу.

Вкладка «Потоки» («Threads»)

Предоставляет информацию о наличии потоков. Позволяет перейти на первую инструкцию в потоке. Разделение приложения на потоки должно увеличить скорость его работы. Каждый поток имеет свой приоритет. Этот приоритет можно переопределить клацнув в контекстном меню на потоке. Также потоки можно терминировать.

Вкладка «Дескрипторы»

input output descriptors
Дескрипторы

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

Написание приложения требующего ввода «секретной последовательности»

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

Я установил себе IDE Code Blocks. Он поставляется в нескольких вариантах, качайте тот что включает в себя компилятор mingw gcc для вашей архитектуры (чтоб не настраивать все вручную). Данная IDE содержит шестнадцатичный редактор. Примеры использования 16-ичного редактора вы можете найти в статье: Представление информации: язык программирования Python и кодировки .

Далее я создал проект обычного консольного приложения. В Settings->Compiler->Compiler settings включил опцию -static. Код приложения ниже:

#include <iostream>
using namespace std;
int main()
{
string some_secret_var=»sequence that is valid»;
string key=»»;
while (true)
{
cout << «Enter valid Key: «<< endl;
getline(cin,key);// Если напишете cin>>key, то занесете только первое слово
if(some_secret_var==key){break;}
else cout << «Key is wrong!»<<endl;
}
cout<<«Your key is valid!»<<endl;
system(«pause»);
return 0;
}
Простое приложение которое требует ключь

Как видите, приложение просто до невозможности. Переменная some_secret_var содержит последовательность что проходит валидацию. Цыкл while безконечен пока в переменную key не будет введен правильный код. В случае ввода неправильного кода получим «Key is wrong!», в противном случае «Your key is valid».

Получение правильного ключа

Для начала нам нужно протестировать приложение (можно без отладчика). В общем виде алгоритм тестирования выглядит следующим образом:

  1. Запускаем приложение
  2. В зависимости от того что оно нам предлагает делаем действие
  3. Смотрим на реакцию на наше действие (в нашем случае пользовательский ввод)

Визуально мы видим цыкл который снова и снова заставляет нас ввести правильную последовательность, в случае неуспеха. Но мы же крепкие ребята))). Один из самых простых способов попытатся найти ключ — это осуществить поиск уже известных нам переменных или констант. Правой кнопкой клацаем во вкладке CPU и выбираем «Поиск в»->»Все модули»->»Ссылки на строки». В строку поиска вбиваем знакомую нам строку «key is» и видим следующе:

Адрес=00000000004A1562
Дизассемблированный код=lea rdx,qword ptr ds:[4A5054]
Строка=»Key is wrong!»

По адрессу 4A5062 у нас содержится искомая строка. Если перейдете во вкладку «Карта памяти» вы увидите что указанная строка находится в секции приложения «.rdata»

Адрес=00000000004A5000
Размер=000000000000F000
Информация о странице=».rdata»
Содержимое секции=Инициализированные данные только для чтения
Тип выделения=IMG
Текущие права доступа=-R—
Защита при выделении=ERWC-

Поскольку переменная string some_secret_var не меняет своего значения она занесена компилятором в секцию где хранятся константы (независимо от того объявите вы константу, или просто создадите переменную — компилятор сделает это за вас). Правой кнопкой в этой же вкладке клацаем «Перейти к дампу». Мы перешли к дампу и видим все текстовые константы.

00000000004A5000 basic_string::_M_construct null not valid.sequence that is valid
00000000004A5040 ..Enter valid Key: .Key is wrong!.Your key is valid!.pause……

Пробуем константу «sequence is valid». Это то то что нам надо. Мы нашли ключь.

Визуализируем логику работы программы

Теперь давайте снова перейдем на тот фрагмент что отвечает за вывод «Key is wrong», адрес 4A5000 в нашей программе. Нажимаем кнопку «G» и наслаждаемся визуализацией работы программы. Мы видим цыкл который начинается с адреса 4A15A8.

code branching
Функция memcmp

Программа в ассемлерных инструкциях выглядит длиннее не правда ли? Нажав кнопку «о» вы увидите несколько сжатый вид. А теперь вопрос где же происходит сравнение и какой функцией?

call <JMP.&memcmp>
test eax,eax
jne crack_my_sequence.4A1555 ; если ключь не правильный переходим по этому адресу

Ассемблерная команда test eax,eax эквивалентна команде cmp eax,0 (но работает немного быстрее потому что обращение к регистровой памяти быстрее чем к оперативной). Команда «cmp eax,0», в свою очередь, работает как команда sub с той разницей что не меняется содержимое одного из регистров. По сути это сравнение с нулем результата функции memcmp. Команды test/cmp/sub модифицируют флаг zf. Команда jne переходит по указанному адресу если флаг не равен нулю (то есть когда мы ввели неправильное значение).

Выводы

Итак подведем итоги. Сегодня мы с вами научились следующему:

1) Использованию процессором регистров и флагов
2) Познакомились с флаговой концепцией в высокоуровневых языках
3) Познакомились с интерфейсом x64dbg
4) Можем выявить приложение использующее операции ввода-вывода с файлами, сокетами, потоками
5) Написали простое приложение на языке с++ и захардкодили «секретную строку»
6) Провели анализ работы приложения и нашли текстовые константы
7) Визуализировали логику работы программы

Оставьте комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *