Загрузка...

База Reverse-Engineering

Тема в разделе Статьи создана пользователем Sasaeete 25 июн 2025. (поднята 2 июл 2025) 682 просмотра

Опрос

Какую следующую тему вы бы хотели увидеть?

Можно выбрать сразу несколько вариантов.
  1. Как деобфусцировать программы написанные на C# (ConfuserEx, Eazfuscator...)

    6
    75%
  2. Трактат про PE, и их уязвимости в том числе с примерами (Malware-dev)

    2
    25%
  3. Напишу в комментариях

    2
    25%
  1. Sasaeete
    Sasaeete Автор темы 25 июн 2025 Тема Python/Reverse - https://lolz.live/threads/8597182/ 154 23 мар 2020
    Я не гуру и не гений. Статья написана, чтобы собрать в кучу свои некоторые знания и, может быть, заинтересовать кого-то ещё вступить в ряды реверс-инженеров. А ещё у меня ужасные навыки форматирования, так что в некоторых местах будет казаться что здесь только белый, белый текст.
    Приветствую вас! В этой статье разберем некоторые основные темы реверса. После прочтения статьи у вас примерно сложится понимание о том как происходит этот процесс, что конкретно ищем, что мы можем сделать, и так далее.
    Для начала начнём с базы и будем углубляться. PE (Portable Executable) - универсальный формат для систем на базе Win32. Скажем так - PE-файл это скомпилированный код, подключенные библиотеки и ресурсы, к примеру ИКОНКИ. Чаще всего, с PE мы встречаемся когда видим расширение .exe, но он предназначен не только для .exe, а также для .dll, драйверов ядра (.sys).

    Инструменты для анализа PE:
    - PE-bear - база. Или если вам нужен обычный HEX-редактор - HxD

    Предположим, это нам понятно, дальше разберемся как система "читает" эти файлы. У PE файлов четкая структура:
    - MZ Заголовок(IMAGE_DOS_HEADER)
    - DOS-Заглушка(DOS_STUB)
    - PE заголовок(IMAGE_NT_HEADERS)
    - Таблица секций(IMAGE_SECTION_HEADER)
    - И сами секции

    структуру PE-файла представим так:
    [IMG]
    [IMG]

    Разберем каждый.

    1. MZ Заголовок
    Он занимает 64 байта, без него файл не запустится. Это флаг обратной совместимости для старенькой MS-DOS, чем больше вы будете углубляться в Шindows, тем больше вы поймете что они навалили огромный снежный ком, и тянут за собой падший груз. Грубо говоря, мы можем представить MZ (или же 4D 5A - В HexEditor) как флаг что это файл исполняемый для DOS.

    MZ заголовок содержит два поля:
    'e_magic' - магические число, и это и есть тот самый 4D 5A (MZ).
    'e_lfanew' - по смещению 0x3C, это указатель на грубо говоря "современный(aka главный) заголовок".
    [IMG]

    2. DOS-заглушка
    Обратная совместимость. Снова. Не будем углубляться, но если вы попробуете прямо сейчас запустить PE файл на системе MS-DOS, вас встретит сообщение This program cannot be run in DOS mode.

    А что дальше? Если бы мы хотели получить указатель на основной заголовок PE:

    C
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBase;
    PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)pFileBase + pDosHeader->e_lfanew);
    Кратко - мы просто берем адрес начала файла, прибавляем к нему смещение из e_lfanew и получаем указатель на "современный или же главный" PE заголовок.

    Разберем вторую строчку кода. pNtHeaders — это наша переменная. Ее тип PIMAGE_NT_HEADERS дословно означает "Pointer to IMAGE_NT_HEADERS" (Указатель на структуру IMAGE_NT_HEADERS). таким образом мы делаем так, что наша переменная pNtHeaders теперь указывает прямо на главный PE-заголовок.

    Здесь 3 части:
    Сигнатура - 50 45 00 00 или же PE\0\0. Это верификация что мы работаем с PE.
    File Header (заголовок файла) - сейчас разберем.
    Ну и optional header, но интересно другое - он обязательный (хотя optional сбивает с толку).
    Он содержит самую важную инфу для загрузки программы в память.

    File header (заголовок файла):
    Здесь мы видем что то аля такое:
    Machine - тип процессора для кого скомпилирован файл. 0x014C - для 32-бит, 0x8664 (x86_64) для 64 битных систем. Это еще одна верификация.
    NumberOfSections - Число секций
    SizeOfOptionalHeader - размер опционального заголовка
    Characteristics - флаги описывающие сам файл. .exe ли это, или .dll, поддерживает ли 32-битные адреса.

    Optional Header - говорим системе как правильно загрузить и запустить программу.
    Magic - еще одно магическое число - 0x10B для 32 бит и 0x20B для 64 битных.
    Дальше нас встречает самое важное:
    AddressOfEntryPoint - в отладчике x64dbg он так и записан, это точка вход в сам код.
    ImageBase - куда хочет загрузиться программа. Если адрес занят - система загрузит программу в другое место. А там уже релокация, но об этом позже.

    SectionAlignment и FileAlignment:
    FileAlignment это выравнивание секций на диске (512 байт (0x200))
    А SectionAlignment - в памяти. Часто 4КБ (0x1000) что равно размеру страницы памяти.
    SizeOfHeaders - суммарный размер всех заголовков.
    Subsystem - указывает для какой подсистемы предназначено, CUI - консольное, GUI графический интерфейс.
    DataDirectory, это уже массив из 16 элементов, дальше идут такие страшные вещи как таблица импорта, экспорта, ресурсы, информация для отладки. Для нас как реверс инженеров может интересовать таблица импорта и экспорта, к примеру программа с простейшим паролем импортирует MessageBoxA, или экспортирует свою DLL.
    Ссылка на диаграмму(всё не вместится) - ССЫЛКА

    Переходим к заключительному - где находится сам код. А лежит оно в секциях.

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

    Сама таблица - массив структур IMAGE_SECTION_HEADER. Мы уже разобрали что в File Header находится NumberOfSections, и как раз таки оно и говорит сколько элементов в этом массиве.

    Сама таблица:
    Name: Имя секции, .text/.data, но вы можете назвать ее хоть .lolz
    PointerToRawdata: Смещение (Raw-data) от начала на диске до этой секции. Это мы увидим в HEX-редакторе.
    VirtualAddress - Относительный виртуальный адрес (RVA) - по которому секция окажется в памяти после загрузки (Вспомните ImageBase)
    SizeOfRawData: Размер секции на диске. Оно должно быть кратное значение для FileAlignment.
    VirtualSize: размер секции в памяти.
    Characteristics: права доступа. Здесь может быть для нас интерес, так как по факту мы можем понять некоторые вещи:
    1. Может ли система исполнять код из этой секции (IMAGE_SCN_MEM_EXECUTE)
    2. Читать из неё (READ) или писать (WRITE)

    Таблица секций диаграмма


    Переходим к самим секциям.
    .text Здесь весь код программы. Сюда указывает AOEP (AddressOfEntryPoint)
    .data: здесь уже существующие переменные в коде.
    .rdata данные для чтения
    .rsrc здесь хранятся иконки
    .idata таблица импорта - та самая таблица импорта, с помощью таблицы импорта отладчик x64dbg (как пример) может узнать что программа импортирует.
    .reloc Таблица релокаций. Как я уже упомянул, если программа не смогла попасть по нужному ей адресу(ImageBase), и система ее куда то сдвинула - .reloc выходит на боевую задачу. По факту, все адреса сбились, и что делать? .reloc содержит список всех мест в коде где надо поправить адреса. ну а система берет и вычитает(или добавляет) разность между старым и новым. Кратко - она исправляет все жестко прописанные адреса в коде.

    Ссылка на диаграмму


    RAW (File offset) - смещение от начала файла на диске.
    RVA - адрес в памяти, относительно ImageBase
    VA(VirtualAddress) - точный адрес в памяти, который видит x64dbg. VA это ImageBase + RVA.
    Дабы найти на диске то, что видете в отладчике, обратно конвертируем из RVA в RAW. Ищете RVA, и делаем магию:
    RAW = RVA - VirtualAddress + PointerToRawData. И получаем Raw. Ну или наоборот:
    RVA = RAW - PointerToRawData + VirtualAddress

    Конвертация адресов
    Предположим, вам нужно конвертировать адрес. Вот у нас есть ImageBase = 0x400000(RVA), и секция .text в таблице секций VirtualAddress = 0x1000 и PointerToRawData(RAW) = 0x400
    Дальше ищем в файле - вычисляем:
    RVA = VA - ImageBase = 0x401050 - 0x400000 = 0x1050
    Это получается 0x1050 (RVA) - 0x1000 (rva начала секции) = 0x50.
    RAW = PointerToRawData + смещение в секции = 0x400 + 0x50 = 0x450 от начала файла на диске.
    [IMG]
    А ещё у PE много уязвимых мест. К примеру - замена AddressOfEntryPoint. Мы заменяем этот адрес, а потом возвращаем контроль обычной точке входа. Так работают пакеры, ну и вирусы.
    Также добавить свою секцию, либо ее расширить, либо найти пещеру(aka code caves)(.text) - пустые места в секциях забитые нулями. Из минусов - размер секции ограничен, и если инструкции довольно емкие - вы можете попробовать. Из плюсов - обнаружить такое тяжелее. Но как это сделать?

    Нам - достаточно изменить Virtual Size и SizeOfRawData.

    Способов уйма, так что кто знает что хранится в вашем .exe файле. Чисто теоретически таким образом можно сделать что-то уж очень интересное. Но это уже тема отдельная для создания вирусни.

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

    Хранение данных

    Регистры - давай представим что это просто его карманы, и используется оно для временного хранения данных с которыми он работает.
    EAX - Возврат результата из функции (чаще всего)
    ECX - часто счетчик в циклах.
    EDI/ESI - Указатели на источник (ESI) и назначения (EDI) при работе с массивами, грубо говоря это просто указатели для копирования данных.

    Стек (PUSH, POP)
    Стек это область памяти организованная по принципу LIFO (Кто последним пришел - первым ушел.). Есть потрясающее объяснение в виде тарелок:
    У вас стопка тарелок. Чтобы положить что-то в стек, вам прийдется ложить тарелку наверх. И по тому же принципу, брать ее сверху.
    На вершину стопки указывает регистр ESP или RSP
    Запомните - если в начале:
    E - это означает что 32 бит
    R - 64 бит
    ESP - 32 бит
    RSP - 64 бит.
    [IMG]
    Выглядит страшно? Попробуем по другому:
    [IMG]
    [IMG]
    [IMG]
    [IMG]
    [IMG]
    Дальше команды процессора. Самая популярная - MOV. Что то копировать из одного места в другое.
    Дальше идет вся арифметика - ADD, SUB, INC, DEC.
    ADD - сложить
    SUB - вычесть
    INC - прибавить 1
    DEC - уменьшить на 1
    Умножение - MUL / IMUL и деления DIV / IDIV существуют отдельные инструкции.

    Дальше, переходим к сравнению. Наверно одно из самых важных, это условия и решения условий.
    CMP (Compare) - сравнивает два значения, при этом не трогая их. Она устанавливает специальные флаги в процессоре, сообщая результат - равно - больше или меньше.
    JE Прыгнуть если значения равны
    JNE Прыгнуть если не равны
    JG прыгнуть если первое значение больше второго
    JL - прыгнуть если оно меньше
    Наверно вы догадываетесь что этими инструкциями мы и можем манипулировать. К примеру изменить JE на JNE, и тогда при вводе неправильного пароля он будет правильным, а правильного - неправильный. По приколу. Ну или правильнее будет заменить JE на JMP, дабы сразу "прыгнуть" в правильный вариант.

    CALL - вызов функций. Принцип простой - кладет в стек адрес инструкции, следующей за call, и передает управление вызываемой функции
    RET - забирает адрес возврата из стека и переходит по нему, откуда была вызвана функция.

    Дальше - Calling Conventions или же соглашение о вызовах.
    cdecl: Аргументы передаются через стек справа налево. Вызывающая функция чистит стек
    stdcall - похоже на cdecl но сама вызываемая функция чистит стек. Вы ее увидите в WinAPI
    Ну и также в 64 битных системах первые четыре аргумента передаются через регистры rcx, rdx, r8, r9.
    Установите себе IDA PRO(думаю не стоит говорить откуда ее брать). Если необходимо - компилятор Visual Studio. Или возьмите простейший crackme:
    CRACKME (не реклама)
    Давайте разберем простейший пример. Мы взяли вот этот код:
    C
    #include <stdio.h>

    #include <string.h>

    #include <windows.h>



    int main() {

    char name[100];

    SetConsoleOutputCP(1251);

    printf("Введите имя: ");

    scanf_s("%99s", name, (unsigned)_countof(name));



    if (strcmp(name, "DEV") == 0) {

    printf("Добро пожаловать!\n");

    }

    else {

    printf("Вас нету в списке\n");

    }



    return 0;

    }
    Введите имя: 123123
    Вас нету в списке

    C:\Users\Пользователь\source\repos\ConsoleApplication3\x64\Debug>ConsoleApplication3.exe
    Введите имя: test
    Вас нету в списке

    В дизассемблере вы увидите страшные вещи, просто найдите main.
    [IMG]
    Вот тут мы и ищем main, а также наблюдаем наши функции.

    Дальше попадаем мы в саму функцию Main. Мы можем нажать на Space(в Ida) и попасть в графовый вид - он довольно удобен.

    Обратите внимание на комментарии, я специально описал самые нужные для вас вещи.
    [IMG]
    Важно уточнить, что в моем комментарии было написано "Если ZF = 1, это означает что операция не была равна нулю" - ЛОЖЬ, я подразумевал что если ZF = 1 то операция была равна нулю(test eax, eax). Ну да ладно.

    Уже можно заметить что все довольно просто после этих комментариев - а логика ясна. Но вот вам еще диаграмма:
    [IMG]
    Наверно всё это кажется тяжелым, непонятным, но это нормально. Если собрать в единую - большая часть информации вам просто не нужна. Все эти RTC_CheckStackVars, restore rbp, rdi - вам это просто не нужно. Основное что нужно понять - strcmp(name,"DEV"). Он просто сравнивает значение.
    Заглянем под капот Str2:
    [IMG]
    Str2 = 'DEV', это и есть наш пароль(пользователь).

    Сравним IDA PRO для наглядности с Binary Ninja.
    [IMG]
    Мы с вами видем Pseudo C - IDA Pro тоже на эта способна, и Binary Ninja неплохо справилась. Чтобы увидеть Pseudocode в Ida - нажмите F5
    [IMG]

    Чтобы перевести из Hex в число - нажмите H.

    Перейдем к патчингу.

    [IMG]
    Чтобы попасть в патчинг грубо говоря, вам либо надо будет найти его в Edit → Patch Program → Assemble и тут нам очевидно достаточно поменять это на JZ (Если введём правильный пароль(DEV) = неправильный пароль, если неправильный = правильный.)
    [IMG]
    Получилось как то так. Теперь применим патчи, заходите туда же как и Assemble, но жмете Apply Patches to input file.

    [IMG]
    Вот и всё. Программа ныне работает не так как задумано - мы изменили её логику и получаем другой результат.

    И хотя я разобрал довольно малую часть, и наверно все было не сильно подробно (надо было написать книгу про то как работает стек и 1000 диаграмм к нему.

    А что дальше? Как научиться реверс-инжинирингу?

    Правильного ответа нету. Его конечно же не существует, на самом деле чтобы чему-то научиться вам нужно этого захотеть, это раз. Во вторых, вам нужно подобрать свой стиль обучения - кому то легче понять концепцию и пойти реверсить, кому-то легче выучить C и сравнивать его с ассемблерным видом, и так далее. Но без практики вы далеко не уйдете, и один из хороших способов - решать crackme. Вы будете сталкиваться с проблемами, ошибками, неудачами, но ключ к знаниям через преодоление непонимания. В любом случае я подвержен простой концепции:
     
    Этот материал оказался полезным?
    Вы можете отблагодарить автора темы путем перевода средств на баланс
    Отблагодарить автора
    25 июн 2025 Изменено
  2. krutyshkin
    krutyshkin 25 июн 2025 148 1162 6 фев 2019
    ну скопипастил молодец теперь можешь гордится
     
    25 июн 2025 Изменено
    1. Sasaeete Автор темы
      krutyshkin, А можно доказательств что это копипаст? Или это всего лишь предположение? Статья написана лично мною, да для технической точности я использовал материалы дабы не обманывать потенциального читателя, но весь написанный текст - моя работа.
  3. morphosed
    morphosed 26 июн 2025 Заблокирован(а) 3620 1 ноя 2023
    Прикольный непонятно для чего расписанный копипаст и решение крякми 1.1 jnz patch production. Статья коих в интернете миллион
     
    26 июн 2025 Изменено
    1. Sasaeete Автор темы
      morphosed, Спасибо за комментарий! Статья была написана полностью мною, расписал статью специально дабы было всё понятно новичкам, ведь именно им предназначалась статья. Ну и я люблю детали. Вы правы, подобных статей очень много, но самое главное отличие из всех этих статей - уникальность подачи текста.
  4. TW1KER22
    TW1KER22 1 июл 2025 0 9 июн 2025
    а что если запускаю ida.exe крякнутую про версию 9.1, у меня грузит, появляется процесс а окна нет:finger_down:
     
    1. morphosed
      TW1KER22, Не быть реверс инженером
Загрузка...
Top