Пишем упаковщик по шагам. Шаг пятый. Ресурсы.
Предыдущий шаг здесь.
Пора усовершенствовать наш упаковщик. Он уже способен упаковывать и запускать самые простые бинарники, имеющие лишь таблицу импорта. Бинарники с экспортами, ресурсами, 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
Новые статьи и публикации
- 2024-04-17 » 23 сервиса для эффективного экспресс-аудита любого сайта
- 2024-04-08 » Яндекс переходит на новую версию Wordstat
- 2024-04-08 » Яндекс интегрировал в свой облачный сервис эмпатичную нейросеть
- 2024-04-08 » Новая версия нейросети Claude превзошла по мощности аналоги Google и OpenAI
- 2024-04-08 » Как пользоваться GPT 4 и Claude бесплатно и без VPN
- 2024-03-13 » Стратегии SEO на 2024 год
- 2024-03-13 » Как использовать анимацию с помощью JavaScript-библиотеки GSAP
- 2024-03-13 » Использование GSAP 3 для веб-анимации
- 2024-03-13 » Cогласование топографической съёмки с эксплуатирующими организациями
- 2024-02-19 » Теряются лиды? Как настроить сквозную аналитику
- 2024-02-17 » Мерч и IT: на что обратить внимание в 2024 году
- 2024-02-16 » Копируем с RSync: основные примеры синхронизации файлов
- 2024-02-15 » Лучшие noCode AI платформы для создания диалоговых ботов
- 2024-02-14 » Факторы ранжирования Google 2024 — исследование Semrush
- 2024-02-12 » Перенос сайта на другой хостинг
- 2024-02-05 » В России сформирован реестр хостинг-провайдеров
- 2024-02-04 » Использование SSH для подключения к удаленному серверу Ubuntu
- 2024-02-03 » Подключаемся к серверу за NAT при помощи туннеля SSH. Простая и понятная инструкция
- 2024-02-02 » Настройка CI/CD для Gitlab-репозитория: схемы и гайд по шагам
- 2024-02-01 » GitLab CI Pipeline. Запуск сценария через SSH на удаленном сервере
- 2024-01-29 » Introduction to GitLab’s CI/CD for Continuous Deployments
- 2024-01-26 » Настройка GitLab CI/CD
- 2024-01-25 » Установка shell gitlab runner
- 2024-01-25 » Установка и регистрация gitlab-runner в docker контейнере
- 2024-01-25 » Переменные Gitlab-Ci
- 2024-01-25 » Настройка CI/CD в GitLab для синхронизации проекта с веб-серверами
- 2024-01-25 » Копирование файлов scp
- 2024-01-21 » Бездепозитные бонусы от казино: обзор условий и правил использования
- 2024-01-18 » Современная обработка ошибок в PHP
- 2024-01-18 » Пример шаблона проектирования MVC в PHP
"В будущем на рынке останется два вида компаний: те, кто в Интернет и те, кто вышел из бизнеса." |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.