Файл: Изучение методов детектирования ВПО, методов противодействия детектированию ВПО и создание программы для анализа эффективности антивирусов, применяющих те или иные подходы к обнаружению угроз.pdf
Добавлен: 30.06.2023
Просмотров: 82
Скачиваний: 2
pe_base::import_rebuilder_settings settings;
//Original import address table нам не нужна (пояснения ниже)
settings.build_original_iat(false);
//Будем переписывать IAT именно по тому адресу,
//которому указали (load_library_address_rva)
settings.save_iat_and_original_iat_rvas(true, true);
//Расположим импорты прямо за концом упакованных данных
settings.set_offset_from_section_start(added_section.get_raw_data().size());
//Пересоберем импорты
image.rebuild_imports(imports, added_section, settings);
}
{
//Новая секция
pe_base::section unpacker_section;
//Имя - exsite
unpacker_section.set_name("exsite");
//Доступна на чтение и исполнение
unpacker_section.readable(true).executable(true);
{
//Получаем ссылку на данные секции распаковщика
std::string& unpacker_section_data = unpacker_section.get_raw_data();
//Записываем туда код распаковщика
//Этот код хранится в авто файле
//unpacker.h, который мы подключили в main.cpp
unpacker_section_data = std::string(reinterpret_cast<const char*>(unpacker_data), sizeof(unpacker_data));
//Записываем по нужным смещениям адрес
//загрузки образа
*reinterpret_cast<DWORD*>(&unpacker_section_data[original_image_base_offset]) = image.get_image_base_32();
//и виртуальный адрес самой первой секции упакованного файла,
//в которой лежат данные для распаковки и информация о них
//В самом начале это секции, как вы помните, лежит
//структура packed_file_info
*reinterpret_cast<DWORD*>(&unpacker_section_data[rva_of_first_section_offset]) = image.get_image_sections().at(0).get_virtual_address();
}
//Добавляем и эту секцию
const pe_base::section& unpacker_added_section = image.add_section(unpacker_section);
//Выставляем новую точку входа - теперь она указывает
//на распаковщик, на самое его начало
image.set_ep(image.rva_from_section_offset(unpacker_added_section, 0));
}
//Удалим все часто используемые директории
image.remove_directory(IMAGE_DIRECTORY_ENTRY_BASERELOC);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_EXPORT);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_IAT);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_RESOURCE);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_SECURITY);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_TLS);
image.remove_directory(IMAGE_DIRECTORY_ENTRY_DEBUG);
//Урезаем таблицу директорий, удаляя все нулевые
//Урезаем не полностью, а минимум до 12 элементов, так как в оригинальном
//файле могут присутствовать первые 12 и использоваться
image.strip_data_directories(16 - 4);
//Удаляем стаб из заголовка, если какой-то был
image.strip_stub_overlay();
//Создаем новый PE-файл
//Вычислим имя переданного нам файла без директории
std::string base_file_name(argv[1]);
std::string dir_name;
std::string::size_type slash_pos;
if((slash_pos = base_file_name.find_last_of("/\\")) !=
std::string::npos)
{
dir_name = base_file_name.substr(0, slash_pos + 1); //Директория исходного файла
base_file_name = base_file_name.substr(slash_pos + 1); //Имя исходного файла
}
base_file_name = dir_name + "packed_" + base_file_name;
std::ofstream new_pe_file(base_file_name.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
if(!new_pe_file)
{
//Если не удалось создать файл - выведем ошибку
std::cout << "Cannot create " << base_file_name << std::endl;
return -1;
}
//Пересобираем PE-образ
//Урезаем DOS-заголовок, накладывая на него NT-заголовки
//(за это отвечает второй параметр true)
//Не пересчитываем SizeOfHeaders - за это отвечает третий параметр
image.rebuild_pe(new_pe_file, true, false);
std::cout << "Packed image was saved to " << base_file_name << std::endl;
}
catch(const pe_exception& e)
{
//Если по какой-то причине открыть его не удалось
std::cout << e.what() << std::endl;
return -1;
}
return 0;
}
Unpacker: unpacker.cpp
//Подключаем файл со структурами из проекта упаковщика
#include "../simple_pe_packer/structs.h"
//Алгоритм распаковки
#include "lzo_conf.h"
/* decompression */
LZO_EXTERN(int)
lzo1z_decompress ( const lzo_bytep src, lzo_uint src_len,
lzo_bytep dst, lzo_uintp dst_len,
lzo_voidp wrkmem /* NOT USED */ );
//Создадим функцию без пролога и эпилога
extern "C" void __declspec(naked) unpacker_main()
{
//Пролог вручную
__asm
{
push ebp;
mov ebp, esp;
sub esp, 256;
}
//Адрес загрузки образа
unsigned int original_image_base;
//Относительный адрес первой секции,
//в которую упаковщик кладет информацию для
//распаковщика и сами упакованные данные
unsigned int rva_of_first_section;
//Эти инструкции нужны только для того, чтобы
//заменить в сборщике распаковщика адреса на реальные__asm
{
mov original_image_base, 0x11111111;
mov rva_of_first_section, 0x22222222;
}
//Получаем указатель на структуру с информацией,
//которую для нас приготовил упаковщик
const packed_file_info* info;
//Она находится в самом начале
//первой секции упакованного файла
info = reinterpret_cast<const packed_file_info*>(original_image_base + rva_of_first_section);
//Два тайпдефа прототипов функций LoadLibraryA и GetProcAddress
typedef HMODULE (__stdcall* load_library_a_func)(const char* library_name);
typedef INT_PTR (__stdcall* get_proc_address_func)(HMODULE dll, const char* func_name);
//Считаем их адреса из структуры packed_file_info
//Их нам туда подложил загрузчик
load_library_a_func load_library_a;
get_proc_address_func get_proc_address;
load_library_a = reinterpret_cast<load_library_a_func>(info->load_library_a);
get_proc_address = reinterpret_cast<get_proc_address_func>(info->get_proc_address);
//Создаем буфер на стеке
char buf[32];
//kernel32.dll
*reinterpret_cast<DWORD*>(&buf[0]) = 'nrek';
*reinterpret_cast<DWORD*>(&buf[4]) = '23le';
*reinterpret_cast<DWORD*>(&buf[8]) = 'lld.';
*reinterpret_cast<DWORD*>(&buf[12]) = 0;
//Загружаем библиотеку kernel32.dll
HMODULE kernel32_dll;
kernel32_dll = load_library_a(buf);
//Тайпдеф прототипа функции VirtualAlloc
typedef LPVOID (__stdcall* virtual_alloc_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
//Тайпдеф прототипа функции VirtualProtect
typedef LPVOID (__stdcall* virtual_protect_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
//Тайпдеф прототипа функции VirtualFree
typedef LPVOID (__stdcall* virtual_free_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
//VirtualAlloc
*reinterpret_cast<DWORD*>(&buf[0]) = 'triV';
*reinterpret_cast<DWORD*>(&buf[4]) = 'Alau';
*reinterpret_cast<DWORD*>(&buf[8]) = 'coll';
*reinterpret_cast<DWORD*>(&buf[12]) = 0;
//Получаем адрес функции VirtualAlloc
virtual_alloc_func virtual_alloc;
virtual_alloc = reinterpret_cast<virtual_alloc_func>(get_proc_address(kernel32_dll, buf));
//VirtualProtect
*reinterpret_cast<DWORD*>(&buf[0]) = 'triV';
*reinterpret_cast<DWORD*>(&buf[4]) = 'Plau';
*reinterpret_cast<DWORD*>(&buf[8]) = 'etor';
*reinterpret_cast<DWORD*>(&buf[12]) = 'tc';
//Получаем адрес функции VirtualProtect
virtual_protect_func virtual_protect;
virtual_protect = reinterpret_cast<virtual_protect_func>(get_proc_address(kernel32_dll, buf));
//VirtualFree
*reinterpret_cast<DWORD*>(&buf[0]) = 'triV';
*reinterpret_cast<DWORD*>(&buf[4]) = 'Flau';
*reinterpret_cast<DWORD*>(&buf[8]) = 'eer';
//Получаем адрес функции VirtualFree
virtual_free_func virtual_free;
virtual_free = reinterpret_cast<virtual_free_func>(get_proc_address(kernel32_dll, buf));
//Относительный виртуальный адрес директории импорта
DWORD original_import_directory_rva;
//Виртуальный размер директории импорта
DWORD original_import_directory_size;
//Оригинальная точка входа
DWORD original_entry_point;
//Общий размер всех секций файла
DWORD total_virtual_size_of_sections;
//Количество секций в оригинальном файле
BYTE number_of_sections;
//Копируем эти значения из структуры packed_file_info,
//которую для нас записал упаковщик
original_import_directory_rva = info->original_import_directory_rva;
original_import_directory_size = info->original_import_directory_size;
original_entry_point = info->original_entry_point;
total_virtual_size_of_sections = info->total_virtual_size_of_sections;
number_of_sections = info->number_of_sections;
//Указатель на память, в которую
//мы запишем распакованные данные
LPVOID unpacked_mem;
//Выделяем память
unpacked_mem = virtual_alloc(
0,
info->size_of_unpacked_data,
MEM_COMMIT,
PAGE_READWRITE);
//Выходной размер распакованных данных
lzo_uint out_len;
out_len = 0;
//Производим распаковку алгоритмом LZO
lzo1z_decompress(
reinterpret_cast<const unsigned char*>(reinterpret_cast<DWORD>(info) + sizeof(packed_file_info)),
info->size_of_packed_data,
reinterpret_cast<unsigned char*>(unpacked_mem),
&out_len,
0);
//Указатель на DOS-заголовок файла
const IMAGE_DOS_HEADER* dos_header;
//Указатель на файловый заголовок
IMAGE_FILE_HEADER* file_header;
//Виртуальный адрес начала заголовков секций
DWORD offset_to_section_headers;
//Просчитываем этот адрес
dos_header = reinterpret_cast<const IMAGE_DOS_HEADER*>(original_image_base);
file_header = reinterpret_cast<IMAGE_FILE_HEADER*>(original_image_base + dos_header->e_lfanew + sizeof(DWORD));
//Вот по такой формуле
offset_to_section_headers = original_image_base + dos_header->e_lfanew + file_header->SizeOfOptionalHeader
+ sizeof(IMAGE_FILE_HEADER) + sizeof(DWORD) /* Signature */;
//Обнулим всю память первой секции
//эта область соответствует области памяти, которую
//в оригинальном файле занимают все секции
memset(
reinterpret_cast<void*>(original_image_base + rva_of_first_section),
0,
total_virtual_size_of_sections - rva_of_first_section);
//Изменим атрибуты блока памяти, в котором
//расположены заголовки PE-файла и секций
//Нам необходим доступ на запись
DWORD old_protect;
virtual_protect(reinterpret_cast<LPVOID>(offset_to_section_headers),
number_of_sections * sizeof(IMAGE_SECTION_HEADER),
PAGE_READWRITE, &old_protect);
//Теперь изменим количество секций
//в заголовке PE-файла на оригинальное
file_header->NumberOfSections = number_of_sections;
//Виртуальный адрес структуры заголовка секции
DWORD current_section_structure_pos;
current_section_structure_pos = offset_to_section_headers;
//Перечислим все секции
for(int i = 0; i != number_of_sections; ++i)
{
//Создаем структуру заголовка секции
IMAGE_SECTION_HEADER section_header;
//Обнуляем структуру
memset(§ion_header, 0, sizeof(section_header));
//Заполняем важные поля:
//Характеристики
section_header.Characteristics = (reinterpret_cast<packed_section*>(unpacked_mem) + i)->characteristics;
//Смещение файловых данных
section_header.PointerToRawData = (reinterpret_cast<packed_section*>(unpacked_mem) + i)->pointer_to_raw_data;
//Размер файловых данных
section_header.SizeOfRawData = (reinterpret_cast<packed_section*>(unpacked_mem) + i)->size_of_raw_data;
//Относительный виртуальный адрес секции
section_header.VirtualAddress = (reinterpret_cast<packed_section*>(unpacked_mem) + i)->virtual_address;
//Виртуальный размер секции
section_header.Misc.VirtualSize = (reinterpret_cast<packed_section*>(unpacked_mem) + i)->virtual_size;
//Копируем оригинальное имя секции
memcpy(section_header.Name, (reinterpret_cast<packed_section*>(unpacked_mem) + i)->name, sizeof(section_header.Name));
//Копируем заполненный заголовок
//в память, где находятся заголовки секций
memcpy(reinterpret_cast<void*>(current_section_structure_pos), §ion_header, sizeof(section_header));
//Перемещаем указатель на следующий заголовок секции
current_section_structure_pos += sizeof(section_header);
}
//Указатель на сырые данные секции
//Необходим для восстановления сжатых данных секций
//и расположения их по нужным местам
DWORD current_raw_data_ptr;
current_raw_data_ptr = 0;
//Восстановим указатель на заголовки секций
current_section_structure_pos = offset_to_section_headers;
//Снова перечисляем все секции
for(int i = 0; i != number_of_sections; ++i)
{
//Заголовок секции, который мы только что сами записали
const IMAGE_SECTION_HEADER* section_header = reinterpret_cast<const IMAGE_SECTION_HEADER*>(current_section_structure_pos);
//Копируем данные секции в то место памяти,
//где они должны располагаться
memcpy(reinterpret_cast<void*>(original_image_base + section_header->VirtualAddress),
reinterpret_cast<char*>(unpacked_mem) + number_of_sections * sizeof(packed_section) + current_raw_data_ptr,
section_header->SizeOfRawData);
//Перемещаем указатель на данные секции
//в распакованном блоке данных
current_raw_data_ptr += section_header->SizeOfRawData;
//Переходим к следующему заголовку секции
current_section_structure_pos += sizeof(IMAGE_SECTION_HEADER);
import_dir = reinterpret_cast<IMAGE_DATA_DIRECTORY*>(offset_to_directories + sizeof(IMAGE_DATA_DIRECTORY) * IMAGE_DIRECTORY_ENTRY_IMPORT);
//Записываем значения размера и виртуального адреса в соответствующие поля
import_dir->Size = original_import_directory_size;
import_dir->VirtualAddress = original_import_directory_rva;
//Если у файла имеются импорты
if(original_import_directory_rva)
{
//Виртуальный адрес первого дескриптора
IMAGE_IMPORT_DESCRIPTOR* descr;
descr = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(original_import_directory_rva + original_image_base);
//Перечисляем все дескрипторы
//Последний - нулевой
while(descr->Name)
{
//Загружаем необходимую DLL
HMODULE dll;
dll = load_library_a(reinterpret_cast<char*>(descr->Name + original_image_base));
//Указатели на таблицу адресов и lookup-таблицу
DWORD* lookup, *address;
//Учтем, что lookup-таблицы может и не быть
lookup = reinterpret_cast<DWORD*>(original_image_base + (descr->OriginalFirstThunk ? descr->OriginalFirstThunk : descr->FirstThunk));
address = reinterpret_cast<DWORD*>(descr->FirstThunk + original_image_base);
//Перечисляем все импорты в дескрипторе
while(true)
{
//До первого нулевого элемента в лукап-таблице
DWORD lookup_value = *lookup;
if(!lookup_value)
break;
//Проверим, импортируется ли функция по ординалу
if(IMAGE_SNAP_BY_ORDINAL32(lookup_value))
*address = static_cast<DWORD>(get_proc_address(dll, reinterpret_cast<const char*>(lookup_value & ~IMAGE_ORDINAL_FLAG32)));
else
*address = static_cast<DWORD>(get_proc_address(dll, reinterpret_cast<const char*>(lookup_value + original_image_base + sizeof(WORD))));
//Переходим к следующему элементу
++lookup;
++address;
}
//Переходим к следующему дескриптору
++descr;
}
}
//Вернем атрибуты памяти заголовков, как было изначально
virtual_protect(reinterpret_cast<LPVOID>(offset_to_section_headers), number_of_sections * sizeof(IMAGE_SECTION_HEADER), old_protect, &old_protect);
//Эпилог вручную
_asm
{
//Переходим на оригинальную точку входа
mov eax, original_entry_point;
add eax, original_image_base;
leave;
//Вот так
jmp eax;
}
}
Converter: main.cpp
#include <pe_32_64.h> //Директивы для линкования с собранной библиотекой PE
#ifndef _M_X64
#ifdef _DEBUG
#pragma comment(lib, "../../Debug/pe_lib.lib")
#else
#pragma comment(lib, "../../Release/pe_lib.lib")
#endif
#else
#ifdef _DEBUG
#pragma comment(lib, "../../x64/Debug/pe_lib.lib")
#else
#pragma comment(lib, "../../x64/Release/pe_lib.lib")
#endif
#endif
int main(int argc, char* argv[])
{
//Подсказка по использованию
if(argc != 3)
{
std::cout << "Usage: unpacker_converter.exe unpacker.exe output.h" << std::endl;
return 0;
}
//Открываем файл
//Открываем файл unpacker.exe - его имя
//и путь к нему хранятся в массиве argv по индексу 1
std::ifstream file(argv[1], std::ios::in | std::ios::binary);
if(!file)
{
//Если открыть файл не удалось - сообщим и выйдем с ошибкой
std::cout << "Cannot open " << argv[1] << std::endl;