
Содержание
- Что такое паковщик?: Введение в назначение паковщиков и руководство по тому, что необходимо для их разработки.
- Предварительные требования: Инструменты, необходимые для работы с этим руководством.
- Знакомство: Демонстрация того, что вы создадите с помощью этого руководства.
- Проектируем наш CMake-проект: Мини-урок по CMake о создании системы сборки для несколько сложных потребностей паковщика.
- Упаковка бинарных файлов в вашу заглушку: Урок о том, как написать часть паковщика из связки паковщик/заглушка, а также введение в формат исполняемых файлов Windows.
- Управление ресурсами
- Разбор PE-файла
- Изменение PE-файла
- Имитация загрузчика: Урок о том, как создать минимальную исполняемую заглушку для распаковки и загрузки целевого исполняемого файла, а также введение в более продвинутые манипуляции с исполняемыми файлами Windows.
- Чтение нашего PE из памяти
- Загрузка нашего PE для выполнения
- Разрешение API-импортов
- Разрешение адресов
- Передача управления
- Дальнейшие упражнения: Некоторые упражнения для дальнейшего расширения ваших знаний о разработке паковщиков.
Выглядит как пугающее чтиво? Попробуйте версию в виде презентации, в которой кратко изложен этот readme. Видео на YouTube скоро появится!
Что такое паковщик?
Паковщик — это программа, которая распаковывает и запускает другую программу в своём адресном пространстве (или иногда в адресном пространстве другого процесса). Иногда он известен как вектор атак на среду анализа, такие как отладчики и виртуальные песочницы. В основном он используется для нескольких вещей:
- Сжатие: Паковщики часто применяются для сжатия кода данного бинарного файла. Это одно из немногих легитимных применений. См. UPX как пример сжимающего паковщика.
- Обфускация: Паковщики также применяются при попытке обфусцировать или иным образом защитить программу от реверс-инжиниринга. См. паковщик packman от Riot Games как пример анти-реверс паковщика.
- Обход защиты: Вредоносное ПО часто использует различные паковщики для обхода антивирусов и даже EDR. См. этот анализ паковщика SmokeLoader как пример обходящего защиту паковщика.
По сути, паковщик выполняет лишь несколько основных шагов:
- Этап сжатия: Здесь исходный исполняемый файл либо сжимается, либо обфусцируется, либо и то и другое, в новый бинарный файл.
- Этап распаковки: Здесь упакованный исполняемый файл распаковывает или деобфусцирует свой исходный исполняемый файл для загрузки.
- Этап загрузки: Здесь паковщик имитирует различные шаги, похожие на загрузчик исполняемых файлов хост-платформы. Из-за сложности исполняемых бинарных файлов это самый сложный шаг, в зависимости от того, насколько глубоко вы хотите эмулировать.
- Этап выполнения: Здесь управление передаётся от исполняемой заглушки хоста в только что загруженный (т.е. распакованный) код.
Так же просто, паковщик состоит всего из нескольких частей:
- Паковщик: Эта часть отвечает за подготовку так называемой исполняемой заглушки и предоставление ей данных, необходимых для распаковки данного бинарного файла. Это, по сути, этап сжатия.
- Заглушка: Этот бинарный файл является частью кода, отвечающей за распаковку, загрузку и выполнение исходного бинарного файла. Как видите, исполняемая заглушка выполняет большую часть тяжёлой работы для паковщика.
Создание паковщика может быть непростым делом, поскольку заглушку нужно как-то собрать и импортировать в исполняемый файл паковщика. Для Windows изучение системы сборки Visual Studio за пределами простой компиляции может быть утомительным занятием. К счастью, CMake предоставляет простую кроссплатформенную систему сборки, поддерживающую Visual Studio, и она хорошо настраивается!
Это руководство ставит целью научить следующему:
- Как разделить и интегрировать две основные части паковщика в целостную, тестируемую среду сборки.
- Как манипулировать исполняемым файлом Windows, чтобы добавить дополнительные загружаемые данные.
- Как перемещаться по исполняемому файлу Windows, чтобы извлекать и загружать произвольные данные.
- Как эмулировать части загрузчика Windows для загрузки и выполнения исполняемого файла Windows.
Если вы уже знакомы с C++ и CMake, можете сразу перейти к разделу об упаковке. В противном случае читайте дальше!
Предварительные требования
- Знание C++: Это руководство бесполезно, если вы не знаете C++, поскольку мы будем много работать с арифметикой указателей.
- Visual Studio: Visual Studio содержит полнофункциональный компилятор C++ для Windows. Этот проект протестирован с Visual Studio 2019, но более новые версии должны подойти.
- CMake: CMake — это система сборки, которую мы используем для инструментирования наших сборок под компилятор Visual Studio.
Знакомство
Сначала давайте как-то докажем, что эта система сборки работает и создаёт правильно упакованный исполняемый файл. После установки CMake и Visual Studio перейдите в корневой каталог этого репозитория с помощью вашего любимого терминала и выполните следующее:```
$ mkdir build
$ cd build
$ cmake ../
Это создаст необходимые файлы проекта для сборки кода из руководства по Packer. Затем выполните:```
$ cmake --build ./ --config Release
Это соберёт проект packer в режиме Release. Затем вы можете запустить следующий тест:```
$ ctest -C Release ./
Если всё пройдёт хорошо, test\_pack и test_unpack должны выполниться успешно. Если хотите сами увидеть результаты упаковки, вы увидите следующее:```
$ ./packed.exe
I'm just a little guy!
Давайте поговорим обо всех составных частях, которые сделали это возможным.
Набрасываем наш CMake-проект
Итак, мы знаем, каковы основные компоненты упаковщика, но как мы можем протестировать наш упаковщик сразу в рамках цикла разработки? Нам нужно добавить третий бинарный файл в наш общий проект, чтобы должным образом протестировать процесс упаковки и распаковки нашего бинарного файла. Итак, всего нам понадобится три проекта:
- упаковщик
- заглушка
- тестовый исполняемый файл для упаковки
Нам также понадобится библиотека сжатия, чтобы гарантировать, что бинарный файл сжимается в исполняемый файл заглушки. Для этого отлично подойдёт zlib.
Иерархия нашего проекта на первых порах должна выглядеть так:```
packer/
+---+ CMakeLists.txt
+ dummy/
| |
| +---+ CMakeLists.txt
| + src/
| |
| +---+ main.cpp
|
+ stub/
| |
| +---+ CMakeLists.txt
| + src/
| |
| +---+ main.cpp
|
+ src/
| |
| +---+ main.cpp
|
+ zlib-1.2.13/
|
+---+ CMakeLists.txt
+ ...
Наш main.cpp в каждой папке пока может просто выглядеть так:```cpp
#include <iostream>