Пишем упаковщик по шагам. Шаг четвертый. Запускаем.
Предыдущий шаг: здесь.
Появилась новая версия библиотеки для работы с PE-файлами (0.1.4). Перекачайте и пересоберите ее.
Итак, из прошлых шагов мы имеем работающий упаковщик и базовый распаковщик, который пока что ничего не делает. В этом шаге мы добьемся запуска простых упакованных программ (которые не имеют ничего, кроме таблицы импорта и, возможно, релокаций). Первое, что нужно сделать в распаковщике помимо разархивирования данных - это поправить таблицу импорта оригинального файла. Обычно это делает загрузчик, но сейчас для сжатого файла роль загрузчика играем мы.
Добавим несколько полей в нашу структуру packed_file_info:
//Структура, хранящая информацию об упакованном файлеstruct packed_file_info { BYTE number_of_sections;//Количество секций в оригинальном файле DWORD size_of_packed_data;//Размер упакованных данных DWORD size_of_unpacked_data;//Размер оригинальных данных DWORD total_virtual_size_of_sections;//Полный виртуальный размер всех секций оригинального файла DWORD original_import_directory_rva;//Относительный адрес оригинальной таблицы импорта DWORD original_import_directory_size;//Размер оригинальной таблицы импорта DWORD original_entry_point;//Оригинальная точка входа DWORD load_library_a;//Адрес процедуры LoadLibraryA из kernel32.dll DWORD get_proc_address;//Адрес процедуры GetProcAddress из kernel32.dll DWORD end_of_import_address_table;//Конец IAT}; |
Мы добавили 4 поля, которые нам пригодятся в распаковщике. Теперь необходимо их заполнить в коде упаковщика:
//...//Структура базовой информации о 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(); |
Здесь все просто. Во втором уроке, если вы помните, я вручную считал общий виртуальный размер всех секций исходного файла и пояснял, что он эквивалентен значению, возвращаемому функцией get_size_of_image. Здесь мы этим воспользовались. С упаковщиком на этом все. Переходим к распаковщику (проект unpacker). Нам необходимо вкомпилировать в него алгоритм разархивирования LZO1Z. Я сделал просто и по-тупому - перенес в проект unpacker все файлы, необходимые для компиляции функции lzo1z_decompress (а именно, lzo1z_d1.c, lzo1x_d.ch, config1z.h, config1x.h, lzo_conf.h, lzo_ptr.h, lzo1_d.ch, miniacc.h). Кроме того, я прописал дополнительную include-директорию в проект: ../../lzo-2.06/include. Далее пришлось еще поковыряться с настройками проекта. Visual C++ при использовании функций memset, memcpy и подобных (а мы их использовать будем не раз) может по своему желанию встроить в получившийся exe-файл целую CRT, которая для нас совершенно лишняя. Пришлось отключить intrinsic (внутренние) функции (C/C++ - Optimization - Enable Intrinsic Functions - No) и полную оптимизацию (C/C++ - Optimization - Whole Program Optimization - No), на всякий случай добавить libcmt.lib в список игнорируемых библиотек (Linker - Input - Ignore Specific Default Libraries - libcmt.lib) и отключить генерацию кода на этапе линкования (Linker - Optimization - Link Time Code Generation - Default). А раз мы отключили все внутренние функции (среди них memset и memcpy), нам теперь нужна их собственная имплементация. Добавим два файла к проекту: memcpy.c и memset.c. В эти файлы я скопировал исходный код одноименных функций из CRT:
void* __cdecl memset(void*dst, int val, unsignedint count ){void*start = dst; while(count--){*(char*)dst =(char)val; dst =(char*)dst +1;} return(start);} |
void* __cdecl memcpy(void* dst, constvoid* src, unsignedint count ){void* ret = dst; /* * copy from lower addresses to higher addresses */while(count--){*(char*)dst =*(char*)src; dst =(char*)dst +1; src =(char*)src +1;} return(ret);} |
Нас поджидает еще одна проблема. У нас в коде теперь аж четыре модуля (четыре файла с исходным кодом, .c и .cpp), то после компиляции мы будем иметь четыре объектных (obj) файла. Далее линкер должен все это как-то слепить в единый exe-файл, и он это сделает. Но он расположит эти модули в exe-файле в одному ему известном порядке. Нам же необходимо, чтобы функция unpacker_main располагалась в самом начале кода распаковщика. Мы ведь ее в упаковщике патчим, помните? Эта проблема легко решается. Создадим текстовый файл с таким содержанием:
unpacker_main@0 lzo1z_decompress memset memcpy |
Назовем его link_order.txt и расположим его в папке с исходниками проекта unpacker. Этот файл скажет линкеру, в каком порядке должны располагаться функции в результирующем файле. Укажем этот файл в настройках проекта: Linker - Optimization - Function Order - link_order.txt. Все, с настройками покончено, начинаем писать код распаковщика!
Во-первых, я увеличил количество данных, выделяемых на стеке до 256 байтов (sub esp, 256). Переменных локальных много, поэтому перестрахуемся, а то вдруг 128 не хватит.
Пропишем прототип функции распаковки в начало файла unpacker.cpp:
//Алгоритм распаковки#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 */); |
Теперь мы сможем ее использовать в коде. Далее нам понадобятся функции VirtualAlloc (для выделения памяти), VirtualProtect (для изменения атрибутов страниц памяти) и VirtualFree (для освобождения выделенной памяти). Давайте импортируем их из kernel32.dll:
//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); //Тайпдеф прототипа функции VirtualAlloctypedef LPVOID (__stdcall* virtual_alloc_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);//Тайпдеф прототипа функции VirtualProtecttypedef LPVOID (__stdcall* virtual_protect_func)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);//Тайпдеф прототипа функции VirtualFreetypedef 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)); |
Этот кусок кода аналогичен коду в шаге 3, где мы загружали user32.dll и получали в ней адрес функции MessageBoxA, так что пояснять не буду. Далее следует перенести в локальную область видимости необходимые переменные, которые для нас запас упаковщик:
//Относительный виртуальный адрес директории импорта 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; |
Мы это сделали потому, что скоро структура packed_file_info, находящаяся в самом начале первой секции упакованного файла, будет затерта реальными распакованными данными. Теперь выделим память и распакуем в нее упакованный блок данных:
//Указатель на память, в которую//мы запишем распакованные данные 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<constunsignedchar*>(reinterpret_cast<DWORD>(info)+sizeof(packed_file_info)), info->size_of_packed_data, reinterpret_cast<unsignedchar*>(unpacked_mem), &out_len, 0); |
Инициализировать алгоритм LZO перед распаковкой не нужно, для распаковки достаточно вызвать единственную функцию, что мы и сделали. Далее вычислим виртуальный адрес заголовка первой секции.
//Указатель на 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);} //Освобождаем память с распакованными данными,//она нам больше не нужна virtual_free(unpacked_mem, 0, MEM_RELEASE); |
И, почти все готово. Чтобы упакованный файл запустился, остается лишь пофиксить его таблицу импорта, снова выступив в роли PE-загрузчика. Для начала пофиксим виртуальный адрес и размер таблицы импорта в PE-заголовке:
//Вычислим относительный виртуальный адрес//начала таблицы директорий DWORD offset_to_directories; offset_to_directories = original_image_base + dos_header->e_lfanew +sizeof(IMAGE_NT_HEADERS32)-sizeof(IMAGE_DATA_DIRECTORY)* IMAGE_NUMBEROF_DIRECTORY_ENTRIES; //Указатель на директорию импорта IMAGE_DATA_DIRECTORY* 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<constchar*>(lookup_value & ~IMAGE_ORDINAL_FLAG32)));else*address =static_cast<DWORD>(get_proc_address(dll, reinterpret_cast<constchar*>(lookup_value + original_image_base +sizeof(WORD)))); //Переходим к следующему элементу++lookup;++address;} //Переходим к следующему дескриптору++descr;}} |
Вот и все, мы, как PE-загрузчик, заполнили PE-файлу таблицу импорта. Осталась пара мелочей:
//Вернем атрибуты памяти заголовков, как было изначально 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;} |
Теперь вы поняли, зачем нам нужны были собственные пролог и эпилог функции на ассемблере. Вместо инструкции ret, которая раньше располагалась в самом конце кода распаковщика, мы поставили инструкцию jmp eax, осуществляющую переход на код оригинального файла.
Итак, распаковщик теперь сможет запустить простейший PE-файл, имеющий только таблицу импорта. Любой файл с ресурсами, TLS, экспортами работать не будет, и этим мы займемся в следующих шагах. Но мы уже можем запаковать сами себя и запустить запакованный вариант!
Как видно, мы запаковали сами себя, получив бинарник packed_simple_pe_packer.exe, и он работает!
Полный солюшен со всеми проектами для данного шага: Own PE Packer Step 4
Также рекомендую почитать
Обсудить на форуме
Источник: http://feedproxy.google.com/~r/kaimi/dev/~3/hJHKtytjSyU/


Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2025-03-14 » SPF-запись
- 2025-03-07 » SEO на маркетплейсах: как оптимизировать карточку товара для поисковой выдачи
- 2025-02-18 » Топ-10 бесплатных нейросетей для генерации изображений: лучшие ии генераторы 2024 года
- 2025-02-11 » Критическая уязвимость в 1С-Битрикс
- 2025-02-11 » Google Search Console: руководство для начинающих вебмастеров
- 2025-02-11 » Методы измерения результативности рекламных кампаний: плюсы и минусы
- 2025-02-11 » Тренды SEO в 2025 году
- 2025-02-10 » Свой Google в локалке. Ищем иголку в стоге сена
- 2025-01-29 » SEO — это комплексная работа. Шесть главных факторов ранжирования сайтов
- 2025-01-29 » Гайд для главной страницы e-commerce сайта: как оформить, чтобы повысить конверсию
- 2025-01-20 » Krea AI выпустила бесплатную функцию преобразования изображений в 3D-объекты — их можно вращать и вписывать в фотографии
- 2025-01-19 » Отзывы на Яндекс Картах: как пройти модерацию
- 2025-01-15 » Топ-6 лучших российских нейросетей, в которых можно генерировать тексты и изображения бесплатно и без VPN
- 2025-01-14 » 15 бесплатных способов узнать, чем интересуется ваша аудитория
- 2025-01-11 » Бездепозитные бонусы в казино за регистрацию с выводом: особенности и возможности получения
- 2025-01-09 » Новая модель LAM способна выполнять задачи в Word
- 2024-12-26 » Универсальный промпт для нейросети: как выжать максимум из ChatGPT, YandexGPT, Gemini, Claude в 2025
- 2024-11-26 » Капитан грузового судна, или Как начать использовать Docker в своих проектах
- 2024-11-26 » Обеспечение безопасности ваших веб-приложений с помощью PHP OOP и PDO
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
Дураки ставят вопросы чаще, чем пытливые люди Горький Максим - (1868-1936) - русский писатель, литературный критик и публицист, общественный деятель |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.
Или напишите нам в WhatsApp
Или напишите нам в WhatsApp