Разработка приложения, способного защищать исполняемые файлы, страница 8

Если по каким либо причинам её создание невозможно, выдаётся сообщение об этом и обработка файла прекращается. Затем создаём мэппинг с правом записи и размером, превышающим размер файла на величину File Alignment. Дело в том, что File Alignment не может быть меньше 512 байт, а для кода нашей секции этого достаточно. К защищаемому файлу дописывается содержимое файла PEGUARD.BIN (код секции расшифровки). Замечено, что линкеры, которые включают импортную информацию в секцию кода, помещают IAT не после Import Directory Table, а в самое начало первой секции кода. Такие файлы отличаются тем, что значение в поле RVA в Data Directory по имени IAT совпадает со значением BaseOfCode в Optional Header. Если программа обнаруживает этот признак, она начинает шифрование не с начала секции кода, а сразу после окончания IAT. Далее выполняется шифрование по уже описанному алгоритму. Оно продолжается до тех пор, пока не будет зашифровано 4 килобайта, или не будет достигнута Import Directory Table (если она находится в секции кода). Параллельно с шифрованием выполняется вычисление псевдо-контрольной суммы. Вычисление контрольной суммы не зашифрованного кода не имеет смысла, так как если защищаемая программа будет загружена не по «желаемому» адресу, после обработки загрузчиком релокейшенов контрольная сумма кода изменится.

Поэтому просто суммируются все байты, прибавляемые к байтам кода, а по окончании шифрования к этой сумме прибавляется ещё длина пароля. В секции расшифровки сохраняется виртуальный адрес начала зашифрованного кода, его длина, и псевдо-контрольная сумма. В Section Table для зашифрованной секции устанавливаются флаги, разрешающие запись в неё. После этого создаётся запись в Section Table для секции расшифровки (её имя — Peguard). В заголовке программы делаем пометки о том, что увеличилось количество секций, увеличился объем программы в памяти. В секции расшифровки сохраняем старую точку входа в программу, а вместо неё в заголовок прописываем входную точку нашей секции.

Сохранить нужно также адреса названий первых двух функций, импортируемых из KERNEL32.DLL. Вместо них в IAT (или в ILT, если она присутствует) заносятся имена нужных нам функций. Сохраняем также адреса строк в IAT в которые нужно будет занести адреса самих функций, после их определения с помощью GetProcAddress. В последнюю очередь выполняется большое количество корректировок адресов в коде расшифровывающей секции в соответствии с её виртуальным адресом. Пользователь получает сообщение об успешном завершении процесса и происходит закрытие мэппингов и файлов. Процесс снятия защиты происходит в обратном порядке.

Алгоритм работы секции расшифровки

В самом начале работы программа сохраняет в стеке значения регистров. Затем определяет текущее значение EIP: команда CALL вызывает следующую за ней строку, а затем команда POP выталкивает из стека адрес этой (то есть предыдущей) строки.

Вычитая из полученного адреса адрес, который был бы в EIP, если бы программа была загружена по «желаемому» адресу, получаем разницу между реальным и «желаемым» адресом. После этого выполняется проверка, не выполнена ли уже расшифровка. Это необходимо на случай, если управление на Entry Point передаётся неоднократно (так происходит в DLL). Если программа ещё не расшифрована, в теле секции сохраняются адреса функций GetModuleHandle и GetProcAddress (при шифровке в код подставляются адреса первых двух строк IAT для KERNEL32.DLL). Вслед за этим определяется и сохраняется handle для KERNEL32.DLL.

Далее наша программа находит адреса двух функций, которые были в импортной таблице до шифрования (адреса имён этих функций также подставлены в секцию при защите файла) и заносит их в IAT, на их законные места. После этого, для использования секцией расшифровки определяются адреса функций LoadLibrary, FreeLibrary и ExitProcess. Используя функцию LoadLibrary, загружаем библиотеку .DLL. Затем находим адрес функции расшифровки в этой DLL. Этой функции передаются начальный адрес, длина зашифрованного кода, и псевдо-контрольная сумма. Если функция расшифровки возвращает не нулевое значение, происходит выход из программы.

Алгоритм работы функции расшифровки в DLL