Файл: Изучение методов детектирования ВПО, методов противодействия детектированию ВПО и создание программы для анализа эффективности антивирусов, применяющих те или иные подходы к обнаружению угроз.pdf
Добавлен: 30.06.2023
Просмотров: 89
Скачиваний: 2
Из этих полей мы получаем адреса функций LoadLibraryA и GetProcAddress. Затем необходимо решить проблему со вставкой кода распаковщика в упакованный файл. Решение следующее - открываем получающийся после компиляции распаковщика файл, считываем данные из его единственной секции (по сути - код) и преобразовываем его в h-файл, на который мы сможем ссылаться в проекте с упаковщиком. Решение было взято уже реализованным, поэтому останавливаться на нем мы не будем. Дело осталось за малым, дописать тело распаковщика.
Итак, распаковщик настраивается и добавляется в упакованный файл. На этом разработка упаковщика закончена. Для проверки работоспособности достаточно добавить полезную нагрузку, например, в виде MessageBox’a в код распаковщика и запаковать, например, сам упаковщик. Однако окончание разработки не означает, что мы сможем пользоваться этим упаковщиком в дальнейшем. Он слишком прост и больше подходит под определение «работающего прототипа». Однако при помощи уже разработанных решений, можно доработать этот упаковщик. Именно доработанным упаковщиком мы и будем пользоваться в дальнейшем.
2.2. Тестирование
Для проверки работоспособности нашего упаковщика, достаточно добавить полезную нагрузку в код распаковщика в виде простенького MessageBox’a как уже было предложено выше, упаковать сам упаковщик и запустить упакованный файл. В итоге результатом запуска упакованного приложения является окошко с надписью: «Hello!». (рис.2.6)
Рисунок
ЗАКЛЮЧЕНИЕ
Результатом проведённой работы стало создание программы упаковщика, с помощью которой были произведены модификации нескольких образцов ВПО с целью тестирования работоспособности АС.
В ходе работы стало известно, что в современных условиях необходимо своевременное обнаружение ВПО и применение комплексных мер, направленных на предотвращение последствий их работы. Именно этим и занимаются Антивирусы.
Изучив состояние вопроса на данный момент, становится очевидным необходимость создания ПО для анализа уязвимостей АС. Примером такого ПО является разработанный в данной работе упаковщик. Принцип и результаты работы разработанной программы наглядно и подробно продемонстрированы.
СПИСОК ИСПОЛЬЗУЕМЫХ ИСТОЧНИКОВ
- Касперски К. Техника отладки программ без исходных текстов / К. Касперски. – Санкт-Петербург: БХВ-Петербург, 2005. – 823 с.
- Подпружников Ю. В. Классификация методов обнаружения неизвестного вредоносного программного обеспечения / Современные тенденции технических наук: материалы Междунар. науч. конф. (г. Уфа, октябрь 2011 г.). — Уфа: Лето, 2011. — С. 22-25. — URL https://moluch.ru/conf/tech/archive/5/1133/ (дата обращения: 13.01.2019).
- . Касперский Е. Компьютерное zловредство / Е. Касперский. – Санкт-Петербург: Питер, 2007. – 208 с.
- Денисов Т.В. «Антивирусная защита» // Мой Компьютер - №4 1999 г.
- Защита информации. Конфидент. - 1998. - №1. - 96 с.
- Peering Inside the PE: A Tour of the Win32 Portable Executable File Format — URL https://msdn.microsoft.com/en-us/library/ms809762.aspx (дата обращения: 22.01.2019).
- Касперски К. Компьютерные вирусы изнутри и снаружи / К.Касперски. – Санкт-Петербург: Питер, 2006. – 526 с.
- Касперски К. Записки исследователя компьютерных вирусов / Касперски К. – Санкт-Петербург: Питер, 2005. – 316 с.
- Ташков П.А. Защита компьютера. Сбои, ошибки и вирусы / Ташков П.А. – Санкт-Петербург: Питер, 2010. – 288 с.
- PE (Portable Executable): На странных берегах — URL https://habr.com/post/266831/ (дата обращения: 11.01.2019).
- Донцов Д. Как защитить компьютер от ошибок, вирусов, хакеров / Донцов Д. – Санкт-Петербург: Питер, 2008. – 160 с.
ПРИЛОЖЕНИЕ 1: ЛИСТИНГ РЕАЛИЗОВАННОГО ПРИЛОЖЕНИЯ
Packer: main.cpp
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <boost/scoped_array.hpp>
//Заголовочный файл библиотеки для работы с PE-файлами
#include <pe_32_64.h>
//Заголовочный файл алгоритма LZO1Z999
#include "../../lzo-2.06/include/lzo/lzo1z.h"
//Заголовочный файл со структурами
#include "structs.h"
//Заголовочный файл с параметрами распаковщика
#include "../unpacker/parameters.h"
//Тело распаковщика (авто)
#include "unpacker.h"
//Директивы для линкования с собранными библиотеками PE и LZO
#ifndef _M_X64
#ifdef _DEBUG
#pragma comment(lib, "../../Debug/pe_lib.lib")
#pragma comment(lib, "../Debug/lzo-2.06.lib")
#else
#pragma comment(lib, "../../Release/pe_lib.lib")
#pragma comment(lib, "../Release/lzo-2.06.lib")
#endif
#else
#ifdef _DEBUG
#pragma comment(lib, "../../x64/Debug/pe_lib.lib")
#pragma comment(lib, "../x64/Debug/lzo-2.06.lib")
#else
#pragma comment(lib, "../../x64/Release/pe_lib.lib")
#pragma comment(lib, "../x64/Release/lzo-2.06.lib")
#endif
#endif
int main(int argc, char* argv[])
{
//имя файла, который мы хотим упаковать
if(argc != 2)
{
std::cout << "Usage: simple_pe_packer.exe PE_FILE" << std::endl;
return 0;
}
//Открываем файл - его имя хранится в массиве argv по индексу 1
std::ifstream file(argv[1], std::ios::in | std::ios::binary);
if(!file)
{
//Если открыть файл не удалось - сообщим и выйдем с ошибкой
std::cout << "Cannot open " << argv[1] << std::endl;
return -1;
}
try
{
//Пытаемся открыть файл как 32-битный PE-файл
pe32 image(file, false, false);
//Проверим, не .NET ли
if(image.is_dotnet())
{
std::cout << ".NEt image cannot be packed!" << std::endl;
return -1;
}
//Просчитаем энтропию секций файла, чтобы убедиться, что файл не упакован
{
std::cout << "Entropy of sections: ";
double entropy = image.calculate_entropy();
std::cout << entropy << std::endl;
if(entropy > 6.8)
{
std::cout << "File has already been packed!" << std::endl;
return -1;
}
}
//Инициализируем библиотеку сжатия LZO
if(lzo_init() != LZO_E_OK)
{
std::cout << "Error initializing LZO library" << std::endl;
return -1;
}
std::cout << "Reading sections..." << std::endl;
//Получаем список секций PE-файла
const pe_base::section_list& sections = image.get_image_sections();
if(sections.empty())
{
//Если у файла нет ни одной секции, нам нечего упаковывать
std::cout << "File has no sections!" << std::endl;
return -1;
}
//Структура базовой информации о PE-файле
packed_file_info basic_info = {0};
//Получаем и сохраняем изначальное количество секций
basic_info.number_of_sections = sections.size();
//Запоминаем относительный адрес и размер
//оригинальной таблицы импорта упаковываемого файла
pe32 image(file, false, false);
//Проверим, не .NET ли
if(image.is_dotnet())
{
std::cout << ".NEt image cannot be packed!" << std::endl;
return -1;
}
//Просчитаем энтропию секций файла, чтобы убедиться, что файл не упакован
{
std::cout << "Entropy of sections: ";
double entropy = image.calculate_entropy();
std::cout << entropy << std::endl;
if(entropy > 6.8)
{
std::cout << "File has already been packed!" << std::endl;
return -1;
}
}
//Инициализируем библиотеку сжатия LZO
if(lzo_init() != LZO_E_OK)
{
std::cout << "Error initializing LZO library" << std::endl;
return -1;
}
std::cout << "Reading sections..." << std::endl;
//Получаем список секций PE-файла
const pe_base::section_list& sections = image.get_image_sections();
if(sections.empty())
{
//Если у файла нет ни одной секции, нам нечего упаковывать
std::cout << "File has no sections!" << std::endl;
return -1;
}
//Структура базовой информации о PE-файле
packed_file_info basic_info = {0};
//Получаем и сохраняем изначальное количество секций
basic_info.number_of_sections = sections.size();
//Запоминаем относительный адрес и размер
//оригинальной таблицы импорта упаковываемого файла
basic_info.original_import_directory_rva = image.get_directory_rva(IMAGE_DIRECTORY_ENTRY_IMPORT);
basic_info.original_import_directory_size = image.get_directory_size(IMAGE_DIRECTORY_ENTRY_IMPORT);
//Запоминаем его точку входа
basic_info.original_entry_point = image.get_ep();
//Запоминаем общий виртуальный размер всех секций
//упаковываемого файла
basic_info.total_virtual_size_of_sections = image.get_size_of_image();
//Строка, которая будет хранить последовательно Приложение 1
//структуры packed_section для каждой секции
std::string packed_sections_info;
{
//Выделим в строке необходимое количество памяти для этих стркуткр
packed_sections_info.resize(sections.size() * sizeof(packed_section));
//"Сырые" данные всех секций, считанные из файла и слепленные воедино
std::string raw_section_data;
//Индекс текущей секции
unsigned long current_section = 0;
//Перечисляем все секции
for(pe_base::section_list::const_iterator it = sections.begin(); it != sections.end(); ++it, ++current_section)
{
//Ссылка на очередную секцию
const pe_base::section& s = *it;
{
//Создаем структуру информации
//о секции в строке и заполняем ее
packed_section& info
= reinterpret_cast<packed_section&>(packed_sections_info[current_section * sizeof(packed_section)]);
//Характеристики секции
info.characteristics = s.get_characteristics();
//Указатель на файловые данные
info.pointer_to_raw_data = s.get_pointer_to_raw_data();
//Размер файловых данных
info.size_of_raw_data = s.get_size_of_raw_data();
//Относительный виртуальный адрес секции
info.virtual_address = s.get_virtual_address();
//Виртуальный размер секции
info.virtual_size = s.get_virtual_size();
//Копируем имя секции (оно максимально 8 символов)
memset(info.name, 0, sizeof(info.name));
memcpy(info.name, s.get_name().c_str(), s.get_name().length());
}
//Если секция пустая, переходим к следующей
if(s.get_raw_data().empty())
continue;
//А если не пустая - копируем ее данные в строку
//с данными всех секций
raw_section_data += s.get_raw_data();
}
//Если все секции оказались пустыми, то паковать нечего!
if(raw_section_data.empty())
std::cout << "All sections of PE file are empty!" << std::endl;
return -1;
}
packed_sections_info += raw_section_data;
}
//Новая секция
pe_base::section new_section;
new_section.set_name(".rsrc");
//Доступна на чтение, запись, исполнение
new_section.readable(true).writeable(true).executable(true);
//Ссылка на сырые данные секции
std::string& out_buf = new_section.get_raw_data();
//Мы используем тип lzo_align_t для того, чтобы
//память была выровняна как надо
//(из документации к LZO)
boost::scoped_array<lzo_align_t> work_memory(new lzo_align_t[LZO1Z_999_MEM_COMPRESS]);
//Длина неупакованных данных
lzo_uint src_length = packed_sections_info.size();
//Сохраним ее в нашу структуру информации о файле
basic_info.size_of_unpacked_data = src_length;
//Длина упакованных данных
//(пока нам неизвестна)
lzo_uint out_length = 0;
//Необходимый буфер для сжатых данных
//(длина исходя из документации к LZO)
out_buf.resize(src_length + src_length / 16 + 64 + 3);
//Производим сжатие данных
std::cout << "Packing data..." << std::endl;
if(LZO_E_OK !=
lzo1z_999_compress(reinterpret_cast<const unsigned char*>(packed_sections_info.data()),
src_length,
reinterpret_cast<unsigned char*>(&out_buf[0]),
&out_length,
work_memory.get())
)
{
//Если что-то не так, выйдем
std::cout << "Error compressing data!" << std::endl;
return -1;
}
std::cout << "Packing complete..." << std::endl;
//Сохраним длину упакованных данных в нашу структуру
basic_info.size_of_packed_data = out_length;
out_buf.resize(out_length);
//Собираем буфер воедино, это и будут
//финальные данные нашей новой секции
out_buf =
//Данные структуры basic_info
std::string(reinterpret_cast<const char*>(&basic_info), sizeof(basic_info))
//Выходной буфер
+ out_buf;
//Проверим, что файл реально стал меньше
if(out_buf.size() >= src_length)
{
std::cout << "File is incompressible!" << std::endl;
return -1;
}
{
//Сначала получим ссылку на самую первую
//существующую секцию PE-файла
const pe_base::section& first_section = image.get_image_sections().front();
//Установим виртуальный адрес для добавляемой секции (читай ниже)
new_section.set_virtual_address(first_section.get_virtual_address());
//Теперь получим ссылку на самю последнюю
//существующую секцию PE-файла
const pe_base::section& last_section = image.get_image_sections().back();
//Посчитаем общий размер виртуальных данных
DWORD total_virtual_size =
//Виртуальный адрес последней секции
last_section.get_virtual_address()
//Выровненный виртуальный размер последней секции
+ pe_base::align_up(last_section.get_virtual_size(), image.get_section_alignment())
//Минус виртуальный размер первой секции
- first_section.get_virtual_address();
//Удаляем все секции PE-файла
image.get_image_sections().clear();
//Изменяем файловое выравнивание, если вдруг оно было
//больше, чем 0x200 - это минимально допустимое
//для выровненных PE-файлов
image.realign_file(0x200);
//Добавляем нашу секцию и получаем ссылку на
//уже добавленную секцию с пересчитанными адресами и размерами
pe_base::section& added_section = image.add_section(new_section);
//Устанавливаем для нее необходимый виртуальный размер
image.set_section_virtual_size(added_section, total_virtual_size);
std::cout << "Creating imports..." << std::endl;
//Создаем импорты из библиотеки kernel32.dll
pe_base::import_library kernel32;
kernel32.set_name("kernel32.dll"); //Выставили имя библиотеки
//Создаем импортируемую функцию
pe_base::imported_function func;
func.set_name("LoadLibraryA"); //Ее имя
kernel32.add_import(func); //Добавляем ее к библиотеке
//И вторую функцию
func.set_name("GetProcAddress");
kernel32.add_import(func); //Тоже добавляем
//Получаем относительный адрес (RVA) поля load_library_a
//нашей структуры packed_file_info, которую мы расположили в самом
//начале добавленной секции
DWORD load_library_address_rva = pe_base::rva_from_section_offset(added_section,
offsetof(packed_file_info, load_library_a));
//Устанавливаем этот адрес как адрес
//таблицы адресов импорта (import address table)
kernel32.set_rva_to_iat(load_library_address_rva);
//Создаем список импортируемых библиотек
pe_base::imported_functions_list imports;
//Добавляем к списку нашу библиотеку
imports.push_back(kernel32);
//Настроим пересборщик импортов