Lecture

Lecture

noname

Лучше поздно чем никогда, именно эта мысль посетила меня и я решил структурировать то что старательно кусками пытаюсь запихнуть тебе в голову последние пару недель.


Начну пожалуй с постановки задачи - так как изначально она была поставлена некорректно, впоследствие несколько раз менялась и за этими изменениями исчезла полнся картина того, что надо было сделать. Начнем с краткого описания (или юзерстори как любит выражаться один мой знакомый): "Необходимо реализовать программу, которая умеет загружать и запускать из памяти DLL-файл в контексте дочернего процесса".


За довольно лаконичной формулировкой скрывается большое количество вопросов, которые придется решить в ходе выполнения задачи, поэтому сначала детализируем ее и озвучим эти вопросы.

1. Запуск DLL-файла из памяти

   - Что за DLL-файлы? Как они используются и как они создаются? Что содержится внутри этих файлов?

   - Как операционная система позволяет использовать программам эти файлы? Какой существует в системе механизм загрузки DLL-файлов?

   - Какова структура DLL-файла?

   - Какие способы запуска DLL-файла существуют? Чем они отличаются?

2. Запуск в контексте дочернего процесса:

   - Что такое процесс в ОС Windows?

   - Что такое дочерний процесс?

   - Что такое контекст процесса в ОС Windows?

   - Как меняется контекст?

   - Какие бывают процессы? Как характеристики процессов влияют на загрузку DLL-файлов в него?


Что за DLL-файлы? Как они используются и как они создаются? Что содержится внутри этих файлов?

DLL (Dynamic Link Library) - в ОС Windows динамическая библиотека, позволяющая многократное одновременное использование одного программного модуля разными программами. Из этого определения сразу же вытекает немного сторонний вопрос "А что такое тогда статическая библиотека?" - это библиотека, код и данные, которой в момент компиляции сохраняются непосредственно в файл программы (.EXE).

Изначально DLL-файлы проектировались как механизм, который поможет сохранить место на диске и систематизировать его использование (это было особенно важно во времена, когда компьютеры были большими, а память маленькой). Также при проектирование важным плюсом таких программных модулей указывалась в удобстве для разработки и наращивания функционала отдельных программ (ведь заменить одну DLL в системе намного проще чем переустановить весь программный комплекс). В дальнейшем идея подобной организации модулей породило такую технологию как COM (Component Object Model), на которой и по сей день работают 99% продуктов майкрософта. Однако как бы не была хороша идея - в реальности люди получили проблему с красноречивым названием DLL-hell (ситуация когда разные программы имеют в зависимостях разные версии одной и той же DLL который в свою очередь несовместимы друг с другом). Решили эту проблему впоследствии тем, что начали хранить в системе несколько разных версий одной и той же DLL, чем и убили основной плюс задуманной архитектуры.

Создаются DLL-файлы как это не банально, но разработчиками ПО на большом множестве языков программирования. Содержимое этих файлов при общей схожести формата (о нем мы поговорим дальше) может отличаться. Изначально в DLL-файлах содержалось тоже что и в EXE-файлах - код и данные, затем появились специальные DLL, у которых например внутри были только ресурсы (иконки, конфиги, текст и т.д.) - и в некоторых случаях для таких DLL даже стали менять расширения (что потом привело к большим проблемам с безопасностью). Поэтому не стоит удивляться увидев DLL-файл размеро в 60 Мб, ведь он вполне может оказаться просто хранилкой для иконок или наоборот увидев что какое-то приложение использует модули с каким-то странным расширением (вроде OCX для технологии ActiveX) - внутри это все старые добрые DLL-файлы.

Таким образом, динамические библиотеки хранятся отдельно от программ, которые их используют, статические существуют непосредственно внутри EXE-файла. Содержимое DLL-файла может разнится начиная от кода, заканчивая до описания шрифтов.

Как операционная система позволяет использовать программам эти файлы? Какой существует в системе механизм загрузки DLL-файлов?

Собственно как мы поняли из предыдущего ответа - DLL файлы создавались для более удобного способа расширения функционала приложений. Соответственно в системе должен быть предусмотрен способ как программа сможет использовать эту возможность. Итак в ОС Windows существует два способа как программа может сообщить системе о том, что намерена использовать функционал библиотеки:

  1. Статически - добавить в специальный подраздел самой программы (EXE-файла) указание о том, что для работы ей требуются определенные DLL-файлы.
  2. Динамически - во время работы, программа может обратиться к специальной системной функции (в Windows этот набор функций называется WinAPI) с красноречивым названием LoadLibrary, которая в качестве параметра принимает путь до библиотеки. В ответ на этот вызов, система проверяет наличие требуемой библиотеки и в случае успеха, загружает ее.

Еще раз обращаю внимание что это способы загрузки библиотеки или по-другому импортирования, а не линковки. И та и другая бывает двух видов (вообще если вдаваться в подробности, то у линковки есть еще третий вид, но это сейчас к делу не относится) но это абсолютно разные процессы.

Какова структура DLL-файла?

Вот мы вплотную и подошли к первому важному вопросу. В современных версиях ОС Windows, все исполняемые файлы (exe, dll, sys, etc...) имеют формат PE (Portable Executable), но это не всегда было так - до этого в более старых версиях Windows были такие форматы как COM, MZ, NE. Стоит отметить, что часть PE формата содержит куски из предыдущих форматов (сделано это было для поддержания обратной совместимости). Итак, что же такое формат PE - это формат исполняемых файлов в ОС Windows, который является доработаной и измененной версией более общего формата исполняемых файлов COFF (Common Object File Format). Интересный факт в том, что COFF изначально появился в UNIX-системах, но потом там его выжил ELF, а в Windows так и осталась модифицированная версия, которую назвали PE.

Структурными единицами PE-файла являются такие сущности как заголовки, секции и директории.

Заголовки в PE-файле - носят больше описательный характер и содержат сервисную информацию для загрузчика, например такую как объем памяти необходимый для секции, адрес с которого она начинается, права доступа на область памяти в которой будет находиться определенная секция и другие.

Секции же в свою очередь являются носителями информации. Они содержат непосредственно код, данные программы, ресурсы (иконки, конфиги и т.д.). Стоит обратить внимание, что секции неважно что будет храниться в ней. Для того чтобы охарактеризовать информацию внутри секции существуют директории. Это области внутри секции, которые дают понять, что это за информация и как с ней надо обращаться.

Общий принцип работы с PE-файлом можно описать двумя действия:

  1. Получи данные о секции из заголовка (размер, адрес начала, права доступа)
  2. Выдели необходимую память и сохрани туда саму секцию
  3. Перебери все директории в секции и используй информацию в них в соответствие с описанием директории.

Взглянув на схему ниже, станет понятно что весь PE-формат легко представить в виде набора ссылающихся друг на друга структур языка С.

Важный момент заключается в том, что PE-файл в оперативной памяти и на жестком диске выглядят совершенно по-разному. Это объясняется тем, что когда файл компилируется его надо сохранить на жесткий диск и для этого размеры всех секций файла выравниваются до размера кластера на жестком диске (который обычно 512 байт, а точное значение хранится в поле OptionalHeader.FileAlignment). В случае же когда файл загружается в оперативную память, он попадает на страницу (обычно размером 4 Кб) и вновь выравнивается до нужного объема нулями. Точная величина этого выравнивания может быть найдена в поле OptionalHeader.SectionAlignment.



Обозначив эти вопросы можно прийти к следующей детализации задачи:

1. Выяснив алгоритм работы системного загрузчика DLL-файлов в контекст процесса, реализовать собственную его версию.

2. Породить дочерний процесс и реализовать взаимодействие с ним для загрузки DLL-файла.



Report Page