Обратный инжиниринг с помощью FASM и x64dbg

Узнайте, как использовать FASM для создания нашей первой программы на ассемблере и отладки исполняемого файла с помощью x64dbg, в этой статье Реджинальда Вонга, ведущего исследователя по борьбе с вредоносным ПО в Vipre Security, компании J2 Global, охватывающей различные технологии безопасности, ориентированные на атаки и вредоносное ПО.

Основное знание, которое требуется заранее для любого обратного инженера, — это язык ассемблера. Понимание языка ассемблера похоже на изучение азбуки реверсирования. Сначала это может показаться трудным, но со временем это станет похоже на мышечную память. Язык ассемблера — это язык, который используется для связи с машиной. Исходный код программы может быть понят человеком, но не машиной. Исходный код должен быть скомпилирован в виде кода на языке ассемблера, чтобы машина могла его понять.

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

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

ФАСМ

FASM, или Flat Assembler, похож на MASM и NASM. Как и MASM, он имеет собственный редактор исходного кода. Как и в NASM, разделы легко идентифицировать и настроить, а программное обеспечение поставляется в вариантах как для Windows, так и для Linux:

1.png

FASM можно скачать с В нашем программировании на ассемблере мы будем использовать FASM, так как мы можем использовать его редактор как в Windows, так и в Linux.

x64dbg

Этот отладчик наиболее рекомендуется, так как разработчики поддерживают его в актуальном состоянии, работая с сообществом. Он также поддерживает как 64-, так и 32-разрядные платформы Windows с множеством полезных плагинов. Он имеет аналогичный интерфейс, как Ollydebug.

2.png

x64dbg можно скачать с https://x64dbg.com/.

Привет, мир

Мы собираемся использовать FASM для создания нашей первой программы на ассемблере, и мы будем отлаживать исполняемый файл, используя x64dbg.

Установка ФАСМ

Используя нашу установку Windows, загрузите FASM с http://flatassembler.net/, затем распакуйте FASM в папку по вашему выбору:

Запустите FASMW.EXE, чтобы вызвать графический интерфейс FASM.

Оно работает!

В текстовом редакторе запишите следующий код, или вы можете просто сделать Git-клон данных по адресу https://github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch3/fasmhello.asm.

format PE CONSOLE
entry start

include '%include%\win32a.inc' 

section '.data' data readable writeable 
  message db 'Hello World!',0
  msgformat db '%s',0

section '.code' code readable executable 
  start:
    push message
    push msgformat
    call [printf]
    push 0
    call [ExitProcess]

section '.idata' import data readable writeable 
  library kernel32, 'kernel32.dll', \
          msvcrt, 'msvcrt.dll'
  import kernel32, ExitProcess, 'ExitProcess'
  import msvcrt, printf, 'printf'

Сохраните его, нажав File->Save as…, затем нажмите Run->Compile:

3.png

Исполняемый файл будет расположен там, где был сохранен исходный код:

4.png

Если «Привет, мир!» не появился, следует отметить, что это консольная программа. Вам нужно будет открыть командный терминал и запустить оттуда исполняемый файл:

5.png

Работа с распространенными ошибками при построении

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

Недействительным аргумент – Проверьте синтаксис в указанной строке. Могут отсутствовать параметры определения или объявления.

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

Анализ программы

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

Программа в основном состоит из раздела кода и раздела данных. Раздел кода, как следует из его названия, — это место, где размещаются программные коды. С другой стороны, раздел данных — это место, где находятся данные, такие как текстовые строки, используемые программным кодом. Существуют требования, прежде чем программа может быть скомпилирована. Эти требования определяют, как будет построена программа. Например, мы можем указать компилятору построить эту программу как исполняемый файл Windows, а не как исполняемый файл Linux. Мы также можем указать компилятору, с какой строки кода программа должна начать работу. Пример структуры программы приведен здесь:

6.png

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

Имея базовую картину того, как устроена программа, давайте посмотрим, как была написана наша программа. Первая строка, формат PE CONSOLE, указывает, что программа будет скомпилирована как исполняемый файл Windows PE и построена для запуска на консоли, более известной в Windows как командная строка.

Следующая строка, запись start, означает, что программа начнет выполнение кода, расположенного на стартовой метке. Имя метки может быть изменено по желанию программиста. Следующая строка, включающая ‘%include%\win32a.inc’, добавит объявления из библиотечного файла FASM win32a.inc. Ожидаемые объявленные функции предназначены для вызова функций API printf и ExitProcess, которые обсуждались в разделе idata.

В эту программу встроено три раздела: разделы данных, кода и idata. Имена разделов здесь помечены как .data, .code и .idata. Разрешения для каждого раздела также указаны как доступные для чтения, записи и выполнения. В разделе данных размещаются и перечисляются целые числа и текстовые строки с помощью инструкции определения байта (db). Раздел кода — это место, где выполняются строки кода инструкции. В разделе idata объявляются импортированные функции API.

В следующей строке мы видим, что раздел данных определен как доступный для записи:

section '.data' data readable writeable

Раздел .data программы содержит две постоянные переменные, message и msgformat. Обе текстовые строки являются строками ASCIIZ (ASCII-Zero), что означает, что они заканчиваются нулевым (0) байтом. Эти переменные определяются с помощью инструкции db:

message db 'Hello World!',0
 msgformat db '%s',0

Следующая строка определяет раздел кода. Он определяется с разрешениями на чтение и выполнение:

section '.code' code readable executable

Именно в разделе .code находится метка start: и наш код. Имена меток начинаются с двоеточия.

В программировании на C функция printf обычно используется для вывода сообщений на консоль с использованием синтаксиса C, как показано ниже:
int printf (const char * формат, …);

Первый параметр — это сообщение, содержащее спецификаторы формата. Второй параметр содержит фактические данные, которые заполняют спецификаторы формата. С точки зрения языка ассемблера функция printf — это функция API, которая находится в библиотеке msvcrt. Функция API настраивается путем помещения аргументов в пространство стека памяти перед вызовом функции. Если ваша программа написана на C, функция, требующая 3 параметра (например, myfunction(arg1, arg2, arg3)) будет иметь следующий эквивалент на ассемблере:

push <arg3>
push <arg2>
push <arg1>
call myfunction

Для 32-битного адресного пространства инструкция push используется для записи данных типа DWORD (32 бита) на вершину стека. Адрес вершины стека хранится в регистре ESP. При выполнении команды push ESP уменьшается на 4. Если аргументом является текстовая строка или буфер данных, адрес помещается в стек. Если аргумент является числовым значением, значение напрямую помещается в стек.

Следуя той же структуре вызовов API с двумя аргументами, наша программа вызывала printf следующим образом:

push message
 push msgformat
 call [printf]

В разделе данных адреса, помеченные как message и msgformat, помещаются в стек в качестве настройки перед вызовом функции printf. Адреса обычно заключаются в квадратные скобки, []. Как обсуждалось ранее, вместо этого используется значение по адресу. На самом деле printf — это метка, которая является локальным адресом в программе, объявленной в разделе .idata. [printf] значит, мы используем адрес функции API printf из библиотеки msvcrt. Таким образом, вызов [printf] выполнит функцию printf из библиотеки msvcrt.

То же самое касается ExitProcess. ExitProcess — это функция ядра 32, которая завершает запущенный процесс. Для этого требуется один параметр, который является кодом выхода. Код выхода 0 означает, что программа завершится без каких-либо ошибок:

push 0 
 call [ExitProcess]

В синтаксисе C этот код эквивалентен ExitProcess(0), который завершает программу с результатом успеха, определенным с нулем.

Раздел программы .idata содержит внешние функции и настроен на чтение и запись:

section '.idata' import data readable writeable

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

library kernel32, 'kernel32.dll', \
           msvcrt, 'msvcrt.dll'

После объявления библиотек конкретные функции API указываются с помощью команды импорта. Синтаксис: import <имя библиотеки>, <имя функции>, <имя функции в файле библиотеки>. Здесь импортируются две внешние функции API: ExitProcess ядра32 и printf msvcrt:

import kernel32, ExitProcess, 'ExitProcess'
 import msvcrt, printf, 'printf'

Аннотированную версию программы можно найти по адресу

Библиотеку функций API можно найти в библиотека MSDN, у которого также есть автономная версия, упакованная в установщик Visual Studio. Он содержит подробную информацию о том, для чего предназначена функция API и как ее использовать. Интернет-версия выглядит следующим образом:

7.png

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

Похожие записи

Добавить комментарий

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