Создание собственного упаковщика исполняемых файлов

автор | 20 Декабрь, 2008
рубрики Статьи Комментарии к записи Создание собственного упаковщика исполняемых файлов отключены

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

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

Для написания своего упаковщика тебе понадобится не так много инструментов, как ты, наверно, думаешь. Прежде всего нужен хороший отладчик для анализа и модифицирования кода. Тут выбор небольшой — я предложу использовать не SoftIce, а OllyDbg, который гораздо проще для освоения новичкам. Для написания оболочки пакера, понадобится знание какого-нибудь языка программирования. Подойдет даже «народный» Delphi. Но я предпочитаю C++. Однако, сам код распаковщика будем писать на ассемблере, так что особо обольщаться не стоит. И понадобится еще PE Editor из пакета утилиты PE Tools, чтоб смотреть — что ты натворишь с ехе файлом.

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

— распаковать в памяти запакованные секции ЕХЕ файла;

— разузнать адреса функций таблицы импорта и записать их в памяти.

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

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

Мы должны учесть, что импорт может находиться в упаковываемой на секции. Следовательно, мы должны его обрабатывать. Мы подсунем в упаковываемый файл свой импорт (он лишь будет использоваться нашим депакером, а не оригинальной программой). А оригинальный импорт мы должны настроить, то есть найти вручную адреса функций библиотек. Для этого существует хорошая системная функция GetProcAddress. В «фэйковую» таблицу импорта мы включим такие функции, как:

— LoadLibrary (подключение требуемых библиотек)

— GetProcAddress (возвращает адрес функции в библиотеке)

— GlobalAlloc (выделяем память для нашей цели)

— GlobalFree (освобождаем эту память)

Загрузчик ОС сам найдет адреса функций из библиотек для «фэйкового» импорта.

В депакере каким-либо способом надо узнать адрес загрузки EXE (ImageBase). Он узнается весьма просто. В тело депакера нужно сохранить относительный виртуальный адрес (RVA) в секции с нашим депакером. В самом начале депакера требуется узнать виртуальный адрес текущего исполняемого кода (тот, что в регистре eip). Далее от этого адреса вычитаются RVA этой секции. Однако в полученном адресе младший байт нужно обнулить. Так как ImageBase — всегда круглое число. Помимо этих значений в тело депакера потребуется сохранить RVA таблицы импорта и оставить место для временных переменных (хотя можно использовать стековые переменные, но это — на любителя).

В конце действий депакер должен передать управление на оригинальную точку кода (OEP). И эту OEP мы тоже должны сохранить в теле депакера, она будет равна точке входа в программу до упаковки.

Подведём общий итог принципа работы депакера:

1) Узнаём дельта-смещение и сохраняем егот в регистр ebp для быстрого доступа к нему из любого места депакера. На всем протяжении работы депакера он нам пригодится.

2) Узнаём ImageBase и сохраняем его в переменную, заранее предназначенную для него.

3) Узнаём из PE заголовка RVA «фэйковой» таблицы импорта (она хранится в директории импорта).

4) Установив адреса системных функций эмпирическим путем (для оперативности), сохраним их в заранее предназначенные места в теле депакера.

5) Обрабатываем оригинальную таблицу импорта, обнаруживая адреса функций в библиотеках.

6) Передаём управление оригинальной программе.

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

Поскольку мы не можем одновременно распаковывать секцию и тут же ее записывать на это же место в памяти, нужен буфер размера этой незапакованной секции (где мы его ранее сохранили в теле депакера). Для этого и нужны GlobalAlloc и Globalfree. Распаковав секцию в этот буфер, затем копируем ее на место сжатой.

Где-то в депакере нужно сохранить виртуальный адрес и размер первой секции (которую упаковываем), дабы мы имели возможность из депакера передать эти параметры в функцию распаковки Aplib. Данные операции легко проделываются системной API CopyMemory. Все основные структуры/функции для работы с PE файлами уже давно созданы. Так что «заморачиваться» с этим не следует, и вполне допустимо их использование.

Самое интересное — это написать депакер. Необходимо учитывать множество факторов, ведь компилятор Delphi создаст относительные адреса в этой процедуре с депакером такие, какие они в самом пакере. Но мы не знаем, по какому конкретному адресу будет находиться депакер. Для решения данного вопроса есть такое модное понятие, как дельта-смещение. Дельта-смещение – это значение, показывающее, насколько изменилось положение текущего кода относительно того, которое задал компилятор Delphi. Тем самым, чтобы оперировать с указателями на код в депакере, используется формула (дельта-смещение + метка на нужный код). Компилятор превратит метку в виртуальный адрес. При исполнении этого кода указанная сумма даст правильный адрес на метку, даже если код будет исполняться в любом месте памяти.

Оценить эту тему:
1 звезда2 звезды3 звезды4 звезды5 звезд (183 голосов, средний: 1,14 из 5)
Loading...Loading...
Популярность: 6 418 просмотров
Вы можете следить за любыми ответами на эту запись через RSS 2.0 feed. Комментарии в настоящее время закрыты.