Пишем упаковщик по шагам. Шаг четвертый. Запускаем.
Предыдущий шаг: здесь.
Появилась новая версия библиотеки для работы с 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/
|
Предыдущий шаг: здесь.Появилась новая версия библиотеки для работы с PE-файлами (0.1.4). Перекачайте и пересоберите ее.Итак, из прошлых шагов мы имеем работающий упаковщик и базовый распаковщик, |
РэдЛайн, создание сайта, заказать сайт, разработка сайтов, реклама в Интернете, продвижение, маркетинговые исследования, дизайн студия, веб дизайн, раскрутка сайта, создать сайт компании, сделать сайт, создание сайтов, изготовление сайта, обслуживание сайтов, изготовление сайтов, заказать интернет сайт, создать сайт, изготовить сайт, разработка сайта, web студия, создание веб сайта, поддержка сайта, сайт на заказ, сопровождение сайта, дизайн сайта, сайт под ключ, заказ сайта, реклама сайта, хостинг, регистрация доменов, хабаровск, краснодар, москва, комсомольск |
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2025-12-02 » Когда ошибка молчит: как бессмысленные сообщения ломают пользовательский опыт
- 2025-12-02 » 9 лучших бесплатных фотостоков
- 2025-12-02 » UTM-метки: ключевой инструмент аналитики для маркетолога
- 2025-12-02 » ПромоСтраницы Яндекса: Что такое и для чего служит
- 2025-12-02 » Метатеги для сайта: исчерпывающее руководство по Title, Description, Canonical, Robots и другим тегам
- 2025-11-26 » Оценка эффективности контента: превращаем информационный балласт в рабочий актив
- 2025-11-26 » 10 причин высокого показателя отказов на сайте
- 2025-11-26 » Когда и зачем обновлять структуру сайта
- 2025-11-26 » Скрытые демотиваторы: как мелочи разрушают эффективность команды
- 2025-11-26 » Зачем запускать MVP и как сделать это грамотно?
- 2025-11-20 » Половина российских компаний сократит расходы на транспорт и маркетинг в 2026 году
- 2025-11-20 » Перенос сайта с большим количеством ссылок
- 2025-11-20 » Перелинковка сайта: Что такое и как ее использовать
- 2025-11-20 » Критерии выбора SEO-специалиста и подрядчика для продвижения сайта
- 2025-11-20 » Применение искусственного интеллекта в рекламных агентствах: комплексное исследование трендов 2025 года
- 2025-11-19 » Геозапросы по-новому: как покорить локальное SEO с помощью ИИ
- 2025-11-14 » Консалтинг: сущность и ключевые направления
- 2025-11-14 » Онлайн-формы: универсальный инструмент для сбора обратной связи
- 2025-11-14 » Факторы конверсии органического трафика
- 2025-11-14 » Планирование рекламного бюджета: самостоятельный подход
- 2025-11-14 » Авторизация на сайте: как выбрать решение для удержания клиентов и сохранения продаж
- 2025-11-13 » Эффективные методы стимулирования клиентов к оставлению положительных отзывов
- 2025-11-13 » Налоговая реформа — 2026: грядущие изменения для предпринимателей
- 2025-11-13 » Альтернативы мессенджерам: что выбрать вместо Telegram и WhatsApp
- 2025-11-13 » Маркировка рекламы для начинающих: полное руководство по требованиям ЕРИР
- 2025-11-13 » ИИ не отберет вашу работу — её займет специалист, владеющий искусственным интеллектом
- 2025-10-29 » Как оценить эффективность работы SEO-специалиста: практическое руководство для маркетологов и владельцев бизнеса
- 2025-10-29 » Киберспорт как маркетинговый инструмент: стратегии привлечения геймеров
- 2025-10-29 » Как говорить с аудиторией о сложном
- 2025-10-29 » Что такое доказательства с нулевым разглашением (ZKP) и их роль в блокчейне
Секрет быть несчастным: иметь время занудствовать на тему, счастлив ты или нет Шоу Джордж Бернард - (1856-1950) - английский писатель. В своем творчестве ниспровергал догматизм и предвзятость, традиционность представлений |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.














Мы создаем практически любые сайты от продающих страниц до сложных, высоконагруженных и нестандартных веб приложений! Наши сайты это надежные маркетинговые инструменты для успеха Вашего бизнеса и увеличения вашей прибыли! Мы делаем красивые и максимально эффектные сайты по доступным ценам уже много лет!
Комплексный подход это не просто продвижение сайта, это целый комплекс мероприятий, который определяется целями и задачами поставленными перед сайтом и организацией, которая за этим стоит. Время однобоких методов в продвижении сайтов уже прошло, конкуренция слишком высока, чтобы была возможность расслабиться и получать \ удерживать клиентов из Интернета, просто сделав сайт и не занимаясь им...
Мы оказываем полный комплекс услуг по сопровождению сайта: информационному и техническому обслуживанию и развитию Интернет сайтов.
Контекстная реклама - это эффективный инструмент в интернет маркетинге, целью которого является увеличение продаж. Главный плюс контекстной рекламы заключается в том, что она работает избирательно.