• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Разработка вредоносного ПО. Часть 8 - инъекция COFF и выполнение в памяти

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Введение

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

На этот раз мы реализуем загрузчик объектного файла COFF, который похож на функцию BOF (объектный файл маячка) в Cobalt Strike https://www.cobaltstrike.com/help-beacon-object-files .

TrustedSec также решила эту проблему https://www.trustedsec.com/blog/coffloader-building-your-own-in-memory-loader-or-how-to-run-bofs/

Код находится в моем репозитории GitHub https://github.com/0xpat/COFFInjector .

Примечание. Здесь мы работаем с 64-битным кодом.

Компиляция кода C

Создание исполняемого файла из исходного кода C/C ++ - это трехэтапный процесс:

1. Предобработка - интерпретация директив прекомпилятора (объединение #include файлов, замена #define идентификаторов). Прекомпилятор в основном заменяет текст в исходном коде для создания единицы перевода.

2. Компиляция (которую мы подробно рассматривали в части 6). Компилятор генерирует ассемблерный код из исходного кода и создает объектный файл.

3. Линковка - объединение объектных файлов и необходимых библиотек в окончательный исполняемый файл (который также может быть DLL).


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

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

Объектные файлы COFF

Общий формат объектного файла - это формат исполняемого кода, созданный в Unix. На основе этого Microsoft создала свой вариант формата COFF и PE. [Документация Microsoft содержит множество информации о форматах файлов COFF и PE.

Объектные файлы, созданные компилятором Visual Studio, используют формат COFF. Такой объектный файл (с расширением .obj) содержит:

- заголовок (с информацией об архитектуре, отметкой времени, количеством секций и символов и др.),

- секции (с ассемблерным кодом, отладочной информацией, директивами компоновщика, информацией об исключениях, статическими данными и т. д.),

- таблица символов (например, функций и переменных) с информацией об их расположении.


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

Нам нужно просмотреть содержимое файла COFF и извлечь код вместе с данными перемещения и выполнить перемещения. Окончательный код (с примененными перемещениями) можно выполнить, просто вызвав его как функцию (((void (*) ()) (code)) ()) или с помощью (например, CreateThread).

Пример объектного файла

Рассмотрим очень простое консольное приложение:

C:
int main()
{
    MessageBoxA(NULL, "Content", "Title", NULL);
    return 0;
}

Функция MessageBoxA находится в user32.dll - нам нужно намекнуть об этом компоновщику.

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

Это можно сделать, изменив параметры проекта в Visual Studio или используя следующую директиву:

#pragma comment (lib, "user32.lib")

Я отключил оптимизацию компилятора (/Od) для этого фрагмента кода. Включение оптимизации привело к различному расположению данных в объектном файле и вызвало проблемы с моим загрузчиком COFF. Потребуются дальнейшие испытания.

Компиляция с использованием компилятора MSVC (cl.exe) создает объектный файл (с расширением .obj). Мы можем проанализировать его содержимое с помощью инструмента dumpbin, поставляемого с MSVC. Давайте посмотрим на результат работы инструмента.

Директивы (секции .drectve)

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

36.png


Данные только для чтения (раздел .rdata)

Это статически инициализированные данные, например строковые литералы.

37.png


Исполняемый код (раздел .text)

Это собственно ассемблерный код. В моем примере компилятор MSVC назвал этот раздел .text$mn.

38.png


Здесь все становится интереснее, давайте дизассемблируем этот код:

39.png


Здесь мы видим, как аргументы MessageBoxA (int 0, char * "Content", char * "Title", int 0) передаются в соответствии с соглашением о вызовах x64 https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention .

Давайте посмотрим на третью инструкцию (расположенную по смещению 0x07 . При дизассемблирование с использованием, например, ShellNoob (https://github.com/reyammer/shellnoob:), инструкция использует смещение 0x00000000

40.png


Итак, как дизассемблер dumpbin знает, что функция должна ссылаться на символ? Вот где в игру вступает релокация. Под необработанными данными раздела .text мы можем увидеть информацию о перемещении. Например, первая запись таблицы перемещений говорит, что 0x00000000 байтов по смещению 0x0A (который является вторым операндом инструкции lea) следует заменить фактическим (относительно RIP) адресом символа №. 8.

То же самое касается инструкции вызова по смещению 0x17 вместе с соответствующей записью релокации и символом. Однако здесь перемещение касается относительного адреса функции. Это верно - относительный адрес (операнд вызова) разыменовывается и вызывается сохраненное в нем значение (фактический адрес MessageBoxA).

Когда код загружается в системную память, загрузчик анализирует данные о перемещении и помещает функции и адреса данных в нужные места. Однако это происходит во время загрузки исполняемого файла PE. Мы хотим загрузить объектный файл COFF, поэтому нам нужно проанализировать его и выполнить перемещения в памяти.

Таблица символов

Эта таблица содержит символы, такие как статические переменные или внешние функции.

41.png


Таблицу символов немного сложно читать и понимать. Однако обычно поле "Значение" указывает смещение символа внутри раздела (описывается полем "SectionNumber ").

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

Загрузчик объектных файлов

Чтобы внедрить и выполнить простой файл COFF, нам нужно прочитать раздел .text и заполнить все нули относительными адресами внешних функций и статических данных (т.е. переместить символы, указанные в этом разделе). Конечно, нам также нужно разместить эти символы где-нибудь в памяти, например, после кода.

Чтобы найти внешние функции, нам нужно будет просмотреть библиотеки, указанные в директивах компоновщика. Мы можем использовать функции LoadLibrary/GetModuleHandle/GetProcAddress или, например, просматривать PEB и InMemoryOrderModuleList (смотри Часть 4).

Схема ниже иллюстрирует эту концепцию:

42.png


Я использовал библиотеку COFFI https://github.com/serge1/COFFI для парсинга файлов COFF. Это отличная библиотека C++ только для заголовков, в которой есть все функции, необходимые для чтения данных из объектных файлов. COFFI использует некоторые структуры данных стандартной библиотеки C++, такие как строки, векторы и т.д. , как и мой код.

Мой алгоритм выглядит так:

1. Получите указатели на раздел .text и релокации, директивы, статические данные и таблицу символов.

2. Вычислите память, необходимую для кода + статических данных + указателей внешних функций (путем итерации всех перемещений .text).

3. Скопируйте код в память RW (X).

4. Скопируйте статические символы сразу после кода (размер каждого символа рассчитывается путем проверки смещения следующего в данном разделе).

5. При копировании статических символов выполнить перемещения (заменить нули в коде относительными адресами).

6. Разрешите все статические функции, просмотрев библиотеки, указанные в директивах компоновщика (LoadLibrary, но не файлы библиотеки DLL), поместите адреса в память (сразу после статических данных; используйте GetProcAddress) и выполните перемещения. Имена функций WinAPI имеют префикс __imp_ в таблице символов COFF.

7. Вызовите место где начинается код (убедитесь, что память исполняемая - при необходимости используйте VirtualProtect).

Определение дополнительных API


BOF Cobalt Strike реализует набор функций, которые могут быть вызваны из кода внедренного объектного файла (также известного как Beacon API).Мы тоже можем это сделать.

В загруженном объектном файле может быть определена внутренняя функция, например:

C:
void COFF_API_Print(char* string)
{
    printf(string);
}

и добавлен как импорт в код объектного файла:

__declspec (dllimport) void COFF_API_Print (char * string);

Затем она должна быть обработана, как импорт WinAPI во время загрузки.

Возврат значения

При вызове внедренной основной функции из объектного файла мы можем получить доступ к возвращаемому значению от вызывающей стороны:

int returnValue = ((int (*) ()) code) ();

Предостережения

- Этот код PoC предполагает, что объектный файл содержит только одну функцию (основную) и не будет работать, если есть другие подпрограммы.

- Объектный файл скомпилирован без среды выполнения C, и в нем нет функций инициализации среды выполнения - точка входа - main. Также отключена оптимизация кода компилятором.

Резюме

Мы получили представление о формате объектного файла COFF, создаваемом компилятором MSVC. Поскольку эти файлы содержат всю информацию, необходимую для выполнения кода, они также могут быть инжектированы и выполнены в памяти, например, по каналу C&C. Это мощный метод, который, несомненно, создает проблемы для обнаружения вредоносного кода.

Код находится в моем репозитории на GitHub https://github.com/0xpat/COFFInjector



Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://0xpat.github.io/Malware_development_part_8/
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх