Пишем упаковщик по шагам. Шаг пятый. Ресурсы.
Предыдущий шаг здесь.
Пора усовершенствовать наш упаковщик. Он уже способен упаковывать и запускать самые простые бинарники, имеющие лишь таблицу импорта. Бинарники с экспортами, ресурсами, TLS, DLL с релокациями ему пока что не под силу. Нужно над этим работать. Для начала сделаем обработку второй по важности вещи после импортов - директории ресурсов.
Сначала добавим пару полей в структуру packed_file_info:
//... DWORD original_resource_directory_rva;//Относительный адрес оригинальной директории ресурсов DWORD original_resource_directory_size;//Размер оригинальной директории ресурсов//... |
Эти поля будут хранить информацию об оригинальной директории ресурсов. В распаковщике мы их будем записывать в заголовок PE-файла после распаковки данных секций. Но это еще не все. Ведь мы упаковываем данные всех секций, слепляя их в один большой блок, в том числе и ресурсы. У файла пропадет иконка и информация о версии, если они имелись. Он может вообще не запуститься, если у него имелись какие-то специфические манифесты в ресурсах, определяющие права или библиотеки, требуемые файлу для запуска. Нам необходимо расположить главную иконку приложения, информацию о версии и манифест не упаковывая рядом со сжатыми данными в новой директории ресурсов, выставив на нее указатель в PE-заголовке.
Сначала займемся распаковщиком (проект unpacker), тем более, изменения будут минимальными. Добавим строки аналогично коду для директории импорта:
//...//Относительный виртуальный адрес директории ресурсов DWORD original_resource_directory_rva;//Виртуальный размер директории ресурсов DWORD original_resource_directory_size; //... original_resource_directory_rva = info->original_resource_directory_rva; original_resource_directory_size = info->original_resource_directory_size; //... //Указатель на директорию ресурсов IMAGE_DATA_DIRECTORY* resource_dir; resource_dir =reinterpret_cast<IMAGE_DATA_DIRECTORY*>(offset_to_directories +sizeof(IMAGE_DATA_DIRECTORY)* IMAGE_DIRECTORY_ENTRY_RESOURCE);//Записываем значения размера и виртуального адреса в соответствующие поля resource_dir->Size = original_resource_directory_size; resource_dir->VirtualAddress = original_resource_directory_rva; |
С распаковщиком все. Переходим к упаковщику (проект simple_pe_packer). Добавим, опять-таки, аналогичные для директории импорта строки для директории ресурсов:
//...//Запоминаем относительный адрес и размер//оригинальной директории ресурсов упаковываемого файла basic_info.original_resource_directory_rva= image.get_directory_rva(IMAGE_DIRECTORY_ENTRY_RESOURCE); basic_info.original_resource_directory_size= image.get_directory_size(IMAGE_DIRECTORY_ENTRY_RESOURCE);//... |
В принципе, этого уже должно быть достаточно, чтобы файл с ресурсами (формами, например) мог запуститься. Проблемы возникнут, если у файла есть xp manifest или что-то подобное. Поэтому нам теперь необходимо пересобрать директорию ресурсов в упаковщике, как я описывал выше. В начало файла main.cpp добавим новый #include <pe_resource_manager.h>. Он необходим для доступа к вспомогательным классам для работы с ресурсами. Код будем добавлять в то место упаковщика, где удаляются секции исходного файла (прямо перед этим действием):
//Новая пустая корневая директория ресурсов pe_base::resource_directory new_root_dir; if(image.has_resources()){ std::cout<<"Repacking resources..."<< std::endl; //Получим ресурсы исходного файла (корневую директорию) pe_base::resource_directory root_dir = image.get_resources();//Оборачиваем оригинальную и новую директорию ресурсов//во вспомогательные классы pe_resource_viewer res(root_dir); pe_resource_manager new_res(new_root_dir); try{//Перечислим все именованные группы иконок//и группы иконок, имеющие ID pe_resource_viewer::resource_id_list icon_id_list(res.list_resource_ids(pe_resource_viewer::resource_icon_group)); pe_resource_viewer::resource_name_list icon_name_list(res.list_resource_names(pe_resource_viewer::resource_icon_group));//Сначала всегда располагаются именованные ресурсы, поэтому проверим, есть ли ониif(!icon_name_list.empty()){//Получим самую первую иконку для самого первого языка (по индексу 0)//Если надо было бы перечислить языки для заданной иконки, можно было вызвать list_resource_languages//Если надо было бы получить иконку для конкретного языка, можно было вызвать get_icon_by_name (перегрузка с указанием языка)//Добавим группу иконок в новую директорию ресурсов new_res.add_icon( res.get_icon_by_name(icon_name_list[0]), icon_name_list[0], res.list_resource_languages(pe_resource_viewer::resource_icon_group, icon_name_list[0]).at(0));}elseif(!icon_id_list.empty())//Если нет именованных групп иконок, но есть группы с ID{//Получим самую первую иконку для самого первого языка (по индексу 0)//Если надо было бы перечислить языки для заданной иконки, можно было вызвать list_resource_languages//Если надо было бы получить иконку для конкретного языка, можно было вызвать get_icon_by_id_lang//Добавим группу иконок в новую директорию ресурсов new_res.add_icon( res.get_icon_by_id(icon_id_list[0]), icon_id_list[0], res.list_resource_languages(pe_resource_viewer::resource_icon_group, icon_id_list[0]).at(0));}}catch(const pe_exception&){//Если какая-то ошибка с ресурсами, например, иконок нет,//то ничего не делаем} try{//Получим список манифестов, имеющих ID pe_resource_viewer::resource_id_list manifest_id_list(res.list_resource_ids(pe_resource_viewer::resource_manifest));if(!manifest_id_list.empty())//Если манифест есть{//Получим самый первый манифест для самого первого языка (по индексу 0)//Добавим манифест в новую директорию ресурсов new_res.add_resource( res.get_resource_data_by_id(pe_resource_viewer::resource_manifest, manifest_id_list[0]).get_data(), pe_resource_viewer::resource_manifest, manifest_id_list[0], res.list_resource_languages(pe_resource_viewer::resource_manifest, manifest_id_list[0]).at(0));}}catch(const pe_exception&){//Если какая-то ошибка с ресурсами,//то ничего не делаем} try{//Получим список структур информаций о версии, имеющих ID pe_resource_viewer::resource_id_list version_info_id_list(res.list_resource_ids(pe_resource_viewer::resource_version));if(!version_info_id_list.empty())//Если информация о версии есть{//Получим самую первую структуру информации о версии для самого первого языка (по индексу 0)//Добавим информацию о версии в новую директорию ресурсов new_res.add_resource( res.get_resource_data_by_id(pe_resource_viewer::resource_version, version_info_id_list[0]).get_data(), pe_resource_viewer::resource_version, version_info_id_list[0], res.list_resource_languages(pe_resource_viewer::resource_version, version_info_id_list[0]).at(0));}}catch(const pe_exception&){//Если какая-то ошибка с ресурсами,//то ничего не делаем}} |
Итак, что же происходит в этом куске кода? Если вкратце, то мы получаем ресурсы оригинального файла и ищем в них:
1. Иконки
2. Манифест
3. Информацию о версии
Эти три вида ресурсов необходимо в неупакованном виде положить в новую директорию ресурсов. Иконку (точнее, группу иконок) из оригинального файла мы возьмем самую первую, именно ее использует Windows как основную иконку файла. Манифест всегда у файла один, и в нем сказано, какие права нужны файлу для запуска и какие DLL-файлы используются. Там может храниться и другая информация. Наконец, информация о версии - это непосредственно информация о файле, которую также можно просмотреть в проводнике Windows.
Я до этого нигде не описывал, как устроена директория ресурсов, поэтому для общего развития приведу эту информацию сейчас. Посмотрим на ресурсы какого-нибудь exe-файла в CFF Explorer'е:
Как видно, ресурсы организованы в виде дерева. В корневой директории сначала располагаются записи, указывающие на тип ресурса. В них вложены директории с записями, содержащими имя или ID ресурса, а в них, в свою очередь, вложены директории с записями, указывающими язык ресурса. Последние содержат директорию данных, которая указывает уже на данные ресурса.
Так вот, в коде выше мы перечисляем все иконки, имеющие ID или имя. Так как в PE-файлах все ресурсы сортируются, причем сначала идут именованные ресурсы, а потом только ресурсы с ID, исходя из этого, мы и ищем самую первую иконку. Она и будет иконкой приложения, и ее мы добавляем в новую директорию ресурсов, сохраняя ее имя/ID и язык. Аналогично поступаем с манифестом и информацией о версии - у всех файлов не больше одной такой записи, причем всегда неименованные, т.е. имеющие ID, поэтому берем и сохраняем первую запись манифеста и первую запись информации о версии.
Новую директорию ресурсов мы создали, теперь надо ее сохранить в создаваемом нами файле. Сначала добавим пару строк перед строкой, производящей сборку импортов:
//Если у нас есть ресурсы для сборки,//отключим автоматическое урезание секции после//добавления в нее импортовif(!new_root_dir.get_entry_list().empty()) settings.enable_auto_strip_last_section(false); //Пересоберем импорты image.rebuild_imports(imports, added_section, settings); |
Дело в том, что все пересборщики в моей библиотеке для работы с PE автоматически уберут все нулевые байты с конца секции, в которую пересобирают импорты/экспорты/ресурсы и т.д., при условии, что секция последняя в файле. Это совершенно адекватное поведение, позволяющее уменьшить размер файла в пределах файлового выравнивания, так как загрузчик все равно нулевые байты восстановит, но уже в памяти, в количестве [выровненный виртуальный размер секции] минус [физический размер секции]. Таким образом, если у нас какие-то структуры внутри секции заканчиваются нулевым элементом (например, завершающий дескриптор таблицы импортов), физически в файл он записан не будет, если эта таблица импортов собирается в последней секции PE-файла. С этим, кстати, связаны интересные баги в таких редакторах, как CFF Explorer, он опирается только на физические данные файла, а не на виртуальные. Поэтому он может криво отобразить ту же таблицу импорта с отрезанными нулевыми байтами в конце.
Сейчас же нам необходимо, чтобы нулевые байты в конце секции, относящиеся к таблице импорта, не обрезались, так как мы сразу после импортов разместим ресурсы. Если мы обрежем эти нулевые байты, то директория ресурсов наслоится на директорию импортов, и получится полная хрень.
Идем дальше - пересобираем ресурсы:
//Пересоберем ресурсы, если есть, что пересобиратьif(!new_root_dir.get_entry_list().empty()) image.rebuild_resources(new_root_dir, added_section, added_section.get_raw_data().size()); |
Здесь все ясно - если какие-то ресурсы есть, мы их записываем в новый PE-файл. Располагаем мы их в самом конце добавленной секции, сразу за импортами. Так как секция последняя на момент пересборки ресурсов, она расширится автоматически.
Осталось убрать одну строчку, которая убирает директорию ресурсов:
image.remove_directory(IMAGE_DIRECTORY_ENTRY_RESOURCE); |
Все готово! Можно проверить упаковщик на любом exe-файле, имеющем импорты, ресурсы и даже релокации (хотя мы их пока что не обрабатываем). Давайте возьмем какой-нибудь проект на MSVC++ с MFC и посмотрим с помощью CFF Explorer'а, какие ресурсы в нем были изначально:
После упаковки имеем:
Итак, осталась одна группа иконок с несколькими иконками в ней (иконки приложения), манифест (configuration files) и информация о версии. Разумеется, упакованный файл успешно запускается!
Полный солюшен для этого шага: Own PE Packer step 5
Также рекомендую почитать
Обсудить на форуме
Источник: http://feedproxy.google.com/~r/kaimi/dev/~3/sv1MqHn6llo/


Дайджест новых статей по интернет-маркетингу на ваш 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 » Интеграция с Яндекс Еда
Гораздо больше людей сдавшихся, чем побежденных. |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.
Или напишите нам в WhatsApp
Или напишите нам в WhatsApp