Пишем упаковщик по шагам. Шаг восьмой. DLL и экспорты.
Предыдущий шаг здесь.
Наш упаковщик уже умеет все, кроме одной вещи - упаковки бинарников, имеющих экспорты. Это, в частности, абсолютное большинство DLL-файлов и OCX-компоненты. Некоторые exe-файлы также имеют экспорты. Наш упаковщик должен пересобрать таблицу экспортов и расположить ее в доступном месте, чтобы загрузчик мог ею воспользоваться.
Пока что можно немного расслабиться - в упаковщике кода добавится совсем немного (в распаковщике, в общем-то, тоже, но он будет на ассемблере).
Займемся сначала упаковщиком (проект simple_pe_packer). Если у файла есть экспорты, нужно их считать, поэтому сразу после строк
//... tls.reset(new pe_base::tls_info(image.get_tls_info()));} |
напишем:
//Если файл имеет экспорты, получим информацию о них//и их список pe_base::exported_functions_list exports; pe_base::export_info exports_info;if(image.has_exports()){ std::cout<<"Reading exports..."<< std::endl; exports = image.get_exported_functions(exports_info);} |
Тут библиотека для работы с PE-файлами сильно упрощает нам жизнь, поэтому вдаваться в подробности, как устроены структуры экспортов, не нужно. Далее заменим строки
//Наконец, обрежем уже ненужные нулевые байты с конца секцииif(!image.has_reloc()) pe_base::strip_nullbytes(unpacker_added_section.get_raw_data()); |
на
//Наконец, обрежем уже ненужные нулевые байты с конца секцииif(!image.has_reloc()&&!image.has_exports()) pe_base::strip_nullbytes(unpacker_added_section.get_raw_data()); |
Так как после распаковщика и TLS у нас будут идти либо экспорты, либо релокации, либо и то и другое, и необходимо, чтобы они не налезли на TLS или распаковщик. Кроме того, нужно перенести строки
//Изменим размер данных секции распаковщика ровно//по количеству байтов в теле распаковщика//(на случай, если нулевые байты с конца были обрезаны//библиотекой для работы с PE) data.resize(sizeof(unpacker_data)); |
выше, так как следует изменять размер данных точно по количеству байтов в распаковщике непосредственно после его записи туда в случае, если у файла есть TLS либо релокации либо экспорты. Расположим этот кусок после строк
//...//Добавляем и эту секцию pe_base::section& unpacker_added_section = image.add_section(unpacker_section); if(tls.get()|| image.has_exports()|| image.has_reloc()){//Изменим размер данных секции распаковщика ровно//по количеству байтов в теле распаковщика//(на случай, если нулевые байты с конца были обрезаны//библиотекой для работы с PE) unpacker_added_section.get_raw_data().resize(sizeof(unpacker_data));} |
Это, кстати, нужно было сделать еще в прошлом шаге, когда мы научили упаковщик обрабатывать релокации. Теперь меняем строки
//Пересобираем релокации, располагая их в конце//секции с кодом распаковщика image.rebuild_relocations(reloc_tables, unpacker_section, unpacker_section.get_raw_data().size()); |
на
//Пересобираем релокации, располагая их в конце//секции с кодом распаковщика image.rebuild_relocations(reloc_tables, unpacker_section, unpacker_section.get_raw_data().size(), true, !image.has_exports()); |
по все той же причине - чтобы экспорты не налезли на релокации.
Пришло время обработать экспорты, пересобрав их директорию и расположив ее во второй добавленной нами секции ("kaimi.ru"):
if(image.has_exports()){ std::cout<<"Repacking exports..."<< std::endl; pe_base::section& unpacker_section = image.get_image_sections().at(1); //Пересобираем экспорты и располагаем их в секции "kaimi.ru" image.rebuild_exports(exports_info, exports, unpacker_section, unpacker_section.get_raw_data().size());} |
И вновь библиотека для работы с PE сильно упростила на жизнь. Теперь просто уберем строку, которую добавляли раньше:
image.remove_directory(IMAGE_DIRECTORY_ENTRY_EXPORT); |
Теперь переходим к распаковщику. Казалось бы - что в нем править? Мы пересобрали директорию экспортов, что еще нужно? Есть одна проблема. В отличие от exe-файла, у DLL точка входа может быть вызвана загрузчиком больше одного раза. Например, при создании нового потока, или когда процесс завершается. А по адресу точки входа у нас тело распаковщика, который уже выполнил свою работу и все распаковал. Если его дернуть второй раз, то просто-напросто все упадет. Поэтому в распаковщик нужно добавить проверку, был ли файл уже распакован, и если был, то управление следует передать на оригинальную точку входа распакованного файла. Я воспользовался хитростью, которую применял в своем предыдущем распаковщике. Мы прямо внутри кода распаковщика разместим переменную размером 4 байта, заполненную нулями. После распаковки в нее мы запишем адрес оригинальной точки входа. Перед распаковкой мы проверим, нулевая ли это переменная, и если нет - это значит, что файл уже был распакован, и мы просто передадим управление по адресу, содержащемуся в этой переменной. Для начала создадим саму переменную и добавим проверку на ноль:
//... __asm {mov original_image_base,0x11111111;mov rva_of_first_section,0x22222222;mov original_image_base_no_fixup,0x33333333;} //Адрес переменной, говорящей о том,//был ли код уже распакован DWORD* was_unpacked; __asm {//Хитрость с получением адреса //следующей за call инструкции call next2;addbyte ptr [eax],al;addbyte ptr [eax],al; next2://В eax- адрес первой инструкции //addbyte ptr [eax],alpopeax; //Сохраним этот адрес mov was_unpacked,eax; //Посмотрим, что по нему лежит moveax,[eax]; //Если там ноль, то перейдем //на распаковщик testeax,eax;jz next3; //Если не ноль, то завершим распаковщик //и перейдем на оригинальную точку входа leave;jmpeax; next3:} |
Поясню, что делает этот код. Чтобы создать переменную внутри кода (прямо посередине), мы в MASM32 могли воспользоваться директивой dd или db или какой-то еще подобной. В инлайновом ассемблере MSVC++ такие директивы не разрешены. Но нам нужно как-то сделать переменную из 4-х байтов, содержащих нули! Я сделал это так: команда ассемблера "add byte ptr [eax], al" занимает ровно два байта и имеет опкод 00 00. Таким образом, вписав две такие команды подряд, получаем четыре подряд идущих нулевых байта - это то, что нам нужно. Осталось как-то получить адрес первой инструкции с учетом того, что располагаться она может по любому виртуальному адресу - код-то у нас базонезависимый. Это делается с помощью инструкции call next2, которая пропускает наши левые команды и заодно заталкивает в стек адрес возврата, равный адресу команды, следующей за call. За call идут как раз наши инструкции. Теперь мы имеем их адрес. Далее проверяем, что по нему лежит (mov eax, [eax]), изначально там будет ноль, и тело распаковщика начнет выполняться, так как инструкция jz next3 произведет переход на метку. Если же распаковщик уже выполнился, мы запишем в переменную по адресу was_unpacked (который указывает на наши левые команды) адрес оригинальной точки входа, и проверка jz next3 не пройдет. Произойдет завершение тела распаковщика и переход на оригинальную точку входа исходного файла. Нам остается собственно записать по адресу was_unpacked адрес точки входа:
//... info = reinterpret_cast<const packed_file_info*>(original_image_base + rva_of_first_section); //Получим адрес оригинальной точки входа DWORD original_ep; original_ep = info->original_entry_point + original_image_base; __asm {//Запишем его по адресу, содержащемуся в переменной //was_unpacked movedx, was_unpacked;moveax, original_ep;mov[edx],eax;} |
На этом все, и мы можем собрать и протестировать наш упаковщик. Для теста я сделал новый солюшен с несколькими проектами: двумя DLL-файлами и одним EXE. Одну библиотеку EXE-файл грузит статически, другую динамически, после чего вызывает у этих библиотек несколько функций. Статически загружаемая библиотека и сам DLL-файл содержат статический TLS. (В динамически загружаемых библиотеках статического TLS быть не должно, так как он не будет инициализирован). Этот солюшен я приложил в архив в конце статьи. Упаковав и обе DLL-ки, и exe-файл (дав потом обоим упакованным DLL-файлам оригинальные имена), я убедился, что все работает без изменений, как и оригинальные файлы.
Полный солюшен для этого шага: Own PE Packer Step 8
Также рекомендую почитать
Обсудить на форуме
Источник: http://feedproxy.google.com/~r/kaimi/dev/~3/8cCGriHfe9g/
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 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
- 2024-01-18 » Мифический человеко-DevOps
- 2023-12-28 » Google подвел итоги 2023 года в поиске
- 2023-12-28 » 5 ошибок отдела продаж, из-за которых вы теряете клиентов
- 2023-12-28 » Американский суд признал монополию Google на рынках дистрибуции Android-приложений
- 2023-12-28 » Хостинг-провайдер GoDaddy перестанет оказывать услуги пользователям из России
Чтобы вырастить плодоносящий сайт - его полезно регулярно поливать и удобрять с помощью рекламы и оптимизации Компания "RedLine" |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.