Секреты покорения эльфов Крис Касперски ака мыщъх Хакер, номер #083, стр. 083-106-2 Аббревиатура ELF расшифровывается как Execution & Linkable Format (формат исполнения и компоновки). Он состоит в определенном родстве с win32 PE, поэтому у них много общего. В начале ELF-файла расположен служебный заголовок (ELF-header), описывающий основные характеристики файла - тип (исполнения или линковки), архитектура ЦП, виртуальный адрес точки входа, размеры и смещения остальных заголовков. За ELF-header'ом следует таблица сегментов (program header table), перечисляющая имеющиеся сегменты и их атрибуты. В формате линковки она необязательна. Линкер не обращает внимания на сегменты, так как работает исключительно на уровне секций. Напротив, системный загрузчик, загружающий исполняемый ELF-файл в память, игнорирует секции и оперирует целыми сегментами. Сегменты и секции - что это такое? Сегмент - это непрерывная область адресного пространства со своими атрибутами доступа. В частности, сегмент кода имеет атрибут исполнения, а сегмент данных - атрибуты чтения и записи. Не стоит путать ELF-сегменты с сегментами x86 процессора! В защищенном режиме 386+ никаких «сегментов» в изначальном смысле этого слова уже нет, а есть только селекторы, и все сегменты ELF-файла загружаются в единый 4 Гбайтовый x86-сегмент! В зависимости от типа сегмента величина выравнивания в памяти может варьировать от 4h до 1000h байт (размер страницы на x86). В самом ELF-файле они хранятся в невыровненном виде, плотно прижатые друг к другу. Так что со свободным пространством для внедрения сплошные напряги. Ближайший аналог ELF-сегментов - PE-секции, но в PE-файлах секция - это наименьшая структурная единица, а вот в ELF-файлах сегмент может быть разбит на один или несколько фрагментов - секций. В частности, типичный кодовый сегмент состоит из секций .init (процедуры инициализации), .plt (секция связок), .text (основой код программы) и .finit (процедуры финализации). Секции нужны линкеру для комбинирования, чтобы он мог отобрать секции с похожими атрибутами и оптимальным образом растасовать их по сегментам при сборке файла, то есть «скомбинировать». Несмотря на то, что системный загрузчик игнорирует таблицу секций, линкер все-таки помещает ее копию в исполняемый файл. Место тратится совсем немного, зато отладчикам и дизассемблерам так приятнее. По не совсем понятным причинам gdb и многие другие программы отказываются загружать файл с поврежденной или отсутствующей таблицей секций, чем часто пользуются для защиты программ от постороннего вмешательства. Структуру и назначение полей служебных заголовков здесь разбирать не будем. Этим займется hex-редактор, и нам эти подробности не понадобятся. Интересующиеся могут обратиться к файлу /usr/include/elf.h - там все подробно расписано. Лучше сосредоточимся на загрузке файла в память. По умолчанию ELF-заголовок находится по адресу 8048000h. Это и есть базовый адрес загрузки. На стадии линковки он может быть свободно изменен на другой, но большинство программистов оставляют его «как есть». Все сегменты проецируются в память в соответствии с виртуальными адресами, прописанными в таблице сегментов, причем, виртуальная проекция образа всегда непрерывна, и между сегментами не должно быть незаполненных «дыр». |