понедельник, 2 декабря 2013 г.

О расширении секций PE приложений

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

Проблемы расширения


Начнём пожалуй с того, что взглянем на проблемы которые стоят на нашем пути. На самом деле проблема только одна, это связи. Их можно поделить на 2 типа:

1. Смещения (анг. offset), т.е. относительные адреса, которые могут указывать на данные из других секций.
2. Виртуальные адреса(анг. virtual address), которые обычно находятся в кодосекции (напр. mov eax, dword ptr[00404264])

Весь PE заголовок напичкан смещениями, которые ссылаются на различные данные и структуры расположенные в различных секциях, которые тоже в свою очередь могут ссылатся на данные из других секций. Кодосекция и секция данных содержат виртуальные адреса, так же указывающие в на разные секции. Если мы просто изменим размер произвольной секции, то с большой вероятностью, это нарушит связи и наше PE приложение окажется нерабочим. Поэтому очевидно что при таких модификациях, важно сохранять целостность связей.


Рис. 1. Связи внутри PE приложения

Однако если взять и поменять смещения в некоторых структурах так чтобы они соответствовали изменённым адресам, это проблему не решит. Например машинный код программы по прежнему будет ссылатся на данные из других секций и тоже самое касается инициализированных данных, которые так же могут ссылатся куда угодно.

Но решения в данном случае есть, покрейней мере найдено два. Одно из них базирутеся на релоках, а другое на изменении последней секции. Оба сейчас мы и разберём.

Решение 1. Расширение/добавление хвостовой секции


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


Рис. 2. Добавление крайней секции.

На данном способе я не буду останавливатся, вот небольшой демонстрационный пример техники:

Добавление крайней секции в PE приложение
Расширение крайней секции в PE приложении

Кстати расширение последней секции в отличии от добавления, не всегда является возможным, поскольку последняя секция может хранить не инициализированные данные, тогда её вообще не будет соответствовать физическая файловая память. Так же последняя секция может иметь флаг IMAGE_SCN_MEM_DISCARDABLE(например в .relocs), который следует убрать от греха подальше.

Решение 2. Расширение секций на базе релоков


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

И так, давайте разберём идею. Если связи из PE заголовков изменить достаточно просто, пройдя по всем возможным структурам и перенаправив смещения в нужное русло, то связи из кода и данных(т.е. виртуальные адреса) к примеру перенаправить уже проблемотичнее, хотя бы потому что нужно знать места где собственно и находятся такие адреса. Вот для примера команда mov со статически вписанным адресом:

Address   Hex dump      Command
009E6473 |> A1 68B59E00 MOV EAX,DWORD PTR DS:[9EB568]


Понятно что найти такие команды достаточно сложно, даже дизассемблирование не всегда может пройти корректно и не всегда можно однозначно определить является ли данное значение адресом или каким-то обычным значением, что особенно касается данных. Но разрулить ситуациию помогают релоки. Дело в том, что суть релоков описать все такие виртуальные адреса, чтобы загрузчик при проецированнии модуля не по базовому адресу, мог перенаправить эти адреса на нужный участок памяти, куда и осуществляется проецирование. А почему бы и нам не воспользоватся данным методом?)

Для реализации этого метода, мы переберём все релоки и через них поправим все адреса которые нуждаются в нормализации.


Рис. 3. Расширения произвольной секции в два этапа.


Вот демонстрационный пример данный техники:

Расширение произвольно секций на базе релоков

Однако тут есть кое какие подводные камни, в частности для архитектуры x64 относительно секций кода. Дело в том что модель адресации в x64 отличается от х86, поэтому нормализовать смещения в секции кода не всегда получится.

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

Комментариев нет:

Отправить комментарий