Современные решения

для защиты Windows приложений

и восстановления исходного кода
Автор: Сергей Чубченко. Дата публикации: 11.06.2006

Автоматизируем работу с EXE файлами


Еще со времен доса системщики привыкли автоматизировать свою работу. Кто-то пишет батники, кто-то забивает задачи в крон, а у кого-то свой софт под это дело. Это порой не раз выручает, что не говори. Но вот что делать реверсерам и кодерам? Об этом и пойдет речь в данной статье.

Введение

Как Вы уже поняли, рассматривать мы будем автоматизацию не простых операций, а анализ и разработку кода, причем применительно к EXE файлам, так как насчет автоматизации бэкапов и прочей мелкой но рутинной работы многие наверное для себя уже давно определились с инструментами. Речь у нас пойдет о скриптовом языке DotFix Script, входящим в состав протектора DotFix LiteProtect. Прежде чем углубляться в возможности сего языка, поговорим немного о том, что же от него требуется тем кто решил защитить свой код от взлома. Средств автоматизации для таких задач не много и все таят какие то сложности по изучению. Порой проще понять стандартный язык программирования вроде бейсика или дельфей и автоматизировать на нем, чем учить чей то скриптовый язык. Здесь все же во многом соглашусь - всегда проще реализовать задачу на первоисточнике. Но вот когда речь идет о работе с PE файлами, тут уже стандартные языки не столь дружелюбны. Простой скрамблер UPX или средство смены имен секций потребует немалых знаний в PE формате файлов, да еще если нет заголовков и прототипов нужных структур и функций - тут вообще беда. И простая по мерам программиста задача может затянуться на несколько дней. Но не все так плохо, как может показаться. С подобными проблемами сталкивался и автор данного материала и в свое время написал довольно мощный скриптовый движок о котором будет рассказано в этой статье. Данный скриптовый язык позволяет автоматизировать решение практически любых задач по работе с PE файлами. Многие пользователи протектора DotFix LiteProtect даже и не задумываются о том что в его состав входит такое мощное средство автоматизации. Остальные же не изучают по одной простой причине - практически нет готовых примеров, а следовательно и стимулов к изучению. В этой статье я постараюсь исправить это упущение и рассмотреть на конкретных примерах функционал данного языка.

Почему именно DotFix Script

У читателя, продвинутого в области скриптовых движков возникнет вопрос: почему именно DotFix Script, а не к примеру Inq Soft Sign of Mystery или не GPScript? Все дело в том, что данные среды хоть и предназначены для автоматизации задач с использованием скриптов, но специализированных команд для работы с PE файлами в них нет, отсюда они не упрощают, а только усложняют данного рода задачи. Предлагаю поподробнее ознакомиться с предметом разговора. Для этого прежде чем продолжить читать рекомендую установить DotFix LiteProtect, если он еще не установлен, так как без практики весь нижеследующий текст будет мало полезен. Ну что, раз все уже установлено - приступим!

Основные команды языка

Прежде чем что-то писать, необходимо знать матчасть. Это аксиома. С этим спорить не будем, лучше рассмотрим основной набор команд языка. А начнем мы с функций работы со строками как базовых.

set <value>, <accumulator>

Первый операнд команды set - числовое или символьное значение, второй - аккумулятор (переменная), в которую будет занесено это значение. По сути команда на понятном человеку языке выглядела бы так: accumulator=value. Соответственно для сложения, вычитания и других операций с числовыми данными используются следующие команды:

add <value 1>,<value 2>, <accumulator> sub <value 1>,<value 2>, <accumulator> mul <value 1>,<value 2>, <accumulator> div <value 1>,<value 2>, <accumulator>

Логично предположить, что value 1 и value 2 представляют собой первое и второе число, а accumulator - аккумулятор (переменная) для сохранения результата. Разумный вопрос, а как использовать переменные в value 1 и 2? Легко! Переменную указываем в макросовом виде. Пример: имеем переменную "a", в которой хранится число 10, и переменную "b", в которой хранится число 20, тогда конструкция:

add @a@@b@,150,c

выполнится так: "а" приложится к "b" и составит 1020 и к этому числу прибавится 150. В результате в переменной "c" будет 1020+150, то есть 1170. Думаю методика понятна.
Ниже представлю некоторые другие команды для работы со строками, прототипы которых описаны в справке к программе: concat, chrtohex, hextochr, substr, length, createstring, rndbyte, settoclipboard. Имена команд интуитивно понятны и легко запоминаются, потому проблемы думаю не возникнут. Если и возникнут - далее язык будет рассмотрен на примерах.

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

messagebox <contents>, <message code>, <title>, <accumulator>

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

inputbox <question>, <accumulator>

Форма запроса данных от пользователя. question - текст вопроса, accumulator - имя переменной, в которую будут занесены введенные пользователем данные.

console.load <text> - грузим консоль с заголовком text
console.unload - выгружаем консоль
console.print <text> - вывести text на консоль
console.get <accumulator> - запросить строку у пользователя. Как только он ее введет скрипт продолжит выполнение команд. Логично догадаться, что accumulator переменная будет содержать введенные данные.
console.color <forecolor>, <backcolor> - изменяет палитру, с которой будет выведена строка, командой console.print. Рекомендуется для украшения интерфейса
console.attributes <atributes> - меняет атрибуты консоли (сделано лишь для полной реализации консольных команд, а практически можно не использовать

Для работы с реестром используются команды:

registry.set <key>, <path>, <parameter>, <value>
registry.get <key>, <path>, <parameter>, <accumulator>

пригодятся если скрипт имеет настройки, которые нужно раз установить и больше не вводить.
Если же ini файл ближе, то можно использовать:

ini.set <key>, <subkey>, <value>
ini.get <key>, <subkey>, <accumulator>

Если затребуется приостановить выполнение команд на несколько секунд, рекомендуется использовать команду pause <milliseconds>, для воспроизвения музыки юзать стоит play <wav file>, хоть не mp3, но все же может понадобиться в работе. Для выхода из программы следует использовать exit. Если же Вы опытный программист, то будете рады узнать, что в скрипте поддреживаются API функции:

loadfunction <function name>, <path to dll>, <param1>, <param2>, <param3>, <accumulator>

Да, параметров только 3, но это максимум, который можно выжать из динамической загрузки, если не считать asm вставки.

Как и в любом нормальном языке можно использовать метки и делать на них переходы:

label <title> - создать метку title
goto <label> - перейти на метку label

Напоследок я оставил команды для работы непосрадственно с файлами PE формата и не только. Вот они:

delfile <filename> - удаляет файл с именем filename
copyfile <filename from>, <filename to> - копирует файл filename from в файл filename to
addlog <filename>, <string> - в файл filename добавлется строка string (рекомендуется использовать для ведения логов работы скрипта)
shell <filename> - запускает EXE файл
putcode <offset>, <HEX_Stub> - помещает по смещению offset (записанному в HEX виде) набор байт (также записанных в HEX виде)
getcode <offset>, <length>, <accumulator> считывает в аккумулятор последовательность байт длиной length по смещению offset
getfile <accumulator> - считывает все содержимое файла в аккумулятор
getfilelength <accumulator> - считывает длину файла в аккумулятор
getoem <accumulator> - считывает OEM информацию из DOS заголовка EXE файла
setoem <text> - записывает OEM информацию в dos_header (рекомендуется использовать это поле для своих записей в DOS заголовке типа "Protected by John Smith" в обрабатываемых PE файлах, все равно загрузчику Windows наплевать на это поле).
getoep <accumulator> - считать точку входа в программу в аккумулятор
setoep <hex string> - изменить точку входа в программе на ту, что записана в HEX виде в hex string
createsection <name>, <size>, accumulator (file offset)>, accumulator (virtual address)> - создает в файле новую секцию с именем name и размером size. При этом смещение секции в PE файле и виртуальный адрес сохраняются в соответствующих аккумуляторах.
setflag <hex string> - ставит флаг hex string на все секции. Полезно поставить флаг возможности записи на секцию чтобы не получить Acces Violation при работе записанного в файл кода
invert <hex string (8 bytes)>, <accumulator> - инвертирует байты (требуется для push’ей, call’ов и других инструкций. Пример: был адрес 00401011, в аккумуляторе после инвертирования будет 11104000. Перед этим числом ставим E8 и получаем call address, который можно записывать в файл.
getimagebase <accumulator> - получает Image Base
xorcode <offset>, <length>, <accumulator> - xor’им <length> байт по смещению <offset> и заносим код декодера в accumulator
createpath <path> - создаем путь из неограниченного числа каталогов или просто одну папку

Последнее что хотелось бы отметить, что после любой команды в самом конце можно поставить запятую и написать if <variable> = <some text>, где variable - имя переменной, а some text то чему переменная может быть равна (кстати знак "=" не единственный, также можно использовать ">" или "<"). При этом команда выполнится только при равенстве переменной тексту. Вот пример:

messagebox Do you wand to patch program?, 4, Patch, retval

если пользователь нажмет на кнопку Yes:

messagebox You select Yes, 16, Yes, retval1, if retval = 6

если пользователь нажмет на кнопку No:

messagebox You select No, 16, No, retval2, if retval = 7

Теперь когда с языком разобрались попробуем на примерах.

Маскируем EXE файл под Borland C++ 1999

Довольно интересно упаковать EXE файл скажем UPX’ом или ASPack’ом, да чтоб при этом PEiD и другие файловые анализаторы думали что это непакованный файл, скомпиленный в борландовом C++. Тут и крэкеры будут смущены немного, и эмулятор распаковщика сглючит эмуляции кода. Короче довольно полезная штука. Теперь определимся с планом работы. Именно с планом, а не алгоритмом. Для начала нам потребуется сформировать сигнатуру - это будут первые байты с OEP нормальной Borland C++ программы, затем выравнивание стэка и очистка регистров, если они менялись этим кодом и переход на оригинальную точку входа. Нашу сигнатуру сформированную по указанному выше плану необходимо вставить в последнюю секцию и поменять точку входа на нее. Примерный алгоритм реализации этого на DotFIx Script’е представлен ниже. Не правда ли просто?

;спросим у пользователя, нужно ли вносить изменения в код messagebox Do you want to protect this file?, 4, Protector, retval ;если не нужно - выходим goto exit , if retval = 7 ;узнаем Entry Point getoep oep, va_oep ;узнаем image base getimagebase imagebase ;объединяем add @va_oep@,@imagebase@,va_oep ;инвертируем этот адрес invert @va_oep@, va_oep ;узнаем длину генерируемой сигнатуры (сама сигнатура + прыжок на Original Entry Point) length EB1066623A432B2B484F4F4B90E900000000A100000000C1E002A3000000005290B8@va_oep@FFE0,len ;создаем секцию длиной с длину сигнатуры createsection cool,@len@,raw,va ;определяем рандомный адрес, чтобы смутить анализаторы нестатичностью сигнатуры add @va@,@imagebase@,address ;инвертируем этот адрес invert @address@, address ;вставляем сигну в созданную секцию putcode @raw@, EB1066623A432B2B484F4F4B90E900000000A1@address@C1E002A3@address@5290B8@va_oep@FFE0,len ;меняем EP на адрес новой секции setoep @va@ ;устанавливаем флаги секций в C0000020 setflag C0000020 ;выводим новую точку входа на экран messagebox New oep: @va@,16 label exit

Как видим, PEiD даже на normal scan’е наивно верит, что это C++

PEiD


Определяем тип компиляции Visual Basic проекта

Очень часто аналитикам в антивирусных лабораториях приходится иметь дело с VB вирусами. Кто с такими сталкивался, тот знает, как иногда полезно заранее знать, псевдокод там или нормальный ассемблерный листинг (да, бейсик умеет и в то и в то компилировать). Это бы упростило скорость подбора инструмента для исследования в несколько раз. Раскрою большой секрет - если иметь под рукой DotFix Script задача решается минут за 10. Сомневаетесь? Тогда давайте напишем такой скрипт и разберем как он работает:

getoep oep, va_oep getimagebase imagebase add @va_oep@,@imagebase@,va_oep add @oep@,1,oep getcode @oep@, 4, VBHeader invert @VBHeader@, VBHeader sub @VBHeader@,@imagebase@,VBHeader add @VBHeader@,30,VBHeader getcode @VBHeader@, 4, ProjectInfo invert @ProjectInfo@, ProjectInfo sub @ProjectInfo@,@imagebase@,ProjectInfo add @ProjectInfo@,20,ProjectInfo getcode @ProjectInfo@, 4, NativeCode messagebox This program compiled to P-Code,64,VB file sniffer,retval, if NativeCode = 00000000 messagebox This program compiled to Native Code,64,VB file sniffer,retval, if NativeCode > 00000000

Один щелчок мыши и мы уже имеем полное представление о типе команд VB файла. Пора грузить декомпилятор.

Visual Basic compilation detection using DotFix LiteProtect

Как видим скрипт мегапрост. Решить ту же задачу на любом из общепризнанных языков программирования потребовало бы втрое больше времени не говоря уже о том, что потребовалось бы позаботиться об открытии файла, встраивании менюшки и т.д. Все эти рутинные операции за нас выполняет скриптовый движок и сам DotFix LiteProtect, нам же остается только объяснить ему что делать с уже открытым файлом. Задача корректно закрыть файл и прочие также будут целиком и полностью ложиться на скрипт. Теперь рассмотрим только что написанный код. Суть его в том, что мы считываем сначала адрес VBHeader структуры, по этому адресу считываем саму структуру, а затем по смещению 30h относительно начала структуры считываем поле ProjectInfo. Оно в свою очередь указывает на соответствующую структуру ProjectInfo. По смещению 20h уже относительно начала ProjectInfo структуры считываем четырех байтное поле NativeCode. Если оно отлично от нуля, то мы имеем Native Code программу, иначе это пикод. Так как Dword поля расположены в EXE файле в формате справа на лево, то мы их все приводим к нормальному отображению командой invert. Затем эти адреса необходимо перевести в Offset, для этого мы отнимаем из них ImageBase. Признаюсь честно - для более корректного перевода надо скорректировать это число по Offset и VA адресам секции, в которой находится адрес, но в VB программах эти адреса у первой секции обычно всегда равны, поэтому я решил не усложнять скрипт лишними командами.

Пишем простенький протектор

Теперь когда мы немного разобрались с командами скриптового языка я думаю имеет смысл написать что-нибудь гораздо более серьезное, а именно предлагаю написать протектор PE файлов. Он будет криптовать первые 10 байт в EXE файле, начиная от точки входа, а точка входа будет меняться на последнюю секцию. В нее мы запишем дешифровщик того участка из 10 байт и соответственно переход на оригинальную точку входа в программу. В простейшем виде скрипт будет выглядеть так:

;определяем точку входа getoep oep, va_oep ;xor’им первые 10 байт точки входа и ;заносим код декриптовщика в переменную ’decoder’ xorcode @va_oep@,10,decoder ;определяем imagebase getimagebase imagebase ;складываем oep и imagebase add @va_oep@,@imagebase@,va_oep ;записываем байты в обратном порядке invert @va_oep@, va_oep ;заносим длину нашего кода в переменную ’len’ length 60@decoder@61B8@va_oep@FFE0,len ;создаем секцию с именем ’.text’ и длиной ’len’ ;функция занесет реальный и виртуальный адреса ;в переменные ’raw’ и ’va’. createsection .text,@len@,raw,va ;вставляем код в exe файл по адресу, ;который содержится в переменной ’raw’ putcode @raw@, 60@decoder@6168@va_oep@E800000000C3C3 ;изменим oep на адрес начала нашего кода ;в созданной секции setoep @va@ ;разблокируем возможность записи в секции setflag C0000020 ;выведем на экран сообщение messagebox New entry point: @va@,64

Как видите, то что пишут на стандартных алгоритмических языках целый день и отлаживают неделю мы в состоянии написать за полчаса, включая время отладки. Ладно, это мы отвлеклись. Как же работает скрипт? Все просто! Определяем точку входа и ксорим первые 10 байт, затем копируем декриптор в переменную decoder. Теперь создаем новую секцию и добавляем в нее функцию дешифровки и переход на OEP (оригинальную точку входа). Если Вас смутила запись "60@decoder@6168@va_oep@E800000000C3C3", объясняю:

60 - опкод команды ассемблера pushad
61 - опкод команды ассемблера popad
68@va_oep@ - push @va_oep@
E800000000 - push 0
C3 - ret

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

Использование скриптов

Ну вот мы написали скрипты, поставили последнюю команду и первая мысль - а куда это все сохранять для последующего исполнения? И ведь правда. О самом главном я так и не сказал. Все скрипты, необходимо сохранять в таком формате: "<имя скрипта>.fix". ".fix" это расширение, "имя скрипта" - имя, которое будет выводиться в программе. Теперь полученный нами скрипт копируем в папку Scripts. Она находится в той папке, в которую Вы установили DotFix LiteProtect. Теперь самое время запустить LiteProtect и выбрать любой EXE файл, затем в списке скриптов выбрать нужный и нажать кнопку Patch - при этом скрипт начнет выполняться. Рекомендую во всех скриптах вначале писать запрос у пользователя, не по ошибке ли он запустил скрипт. Это так сказать правило хорошего тона. Также рекомендуется в скриптах предусматривать бэкап исходного файла и возможность его восстановления в случае ошибки скрипта (рекомендую обратить внимание на команды copyfile и delfile). Также советую предусмотреть консольный интерфейс и возможность сохранения предпочтений пользователя в реестр. Только соблюдая эти советы можно написать полезный скрипт.

Пара слов об отладке

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

Дебаггер сразу нашел все ошибки в скрипте

DotFix Script Debugger

Хочу компилятор

Разобравшись со скриптовым языком Вы наверняка напишете что-нибудь полезное. Это что-то будет использовать и консоль, и настройки и другие возможности скриптового языка. Но тут возникнет вопрос: а нельзя ли этот скрипт превратить в нормальный EXE файл, который может работать с коммандной строкой или запрашивать путь к файлу при запуске и работает независимо от LiteProtect’а? Обрадую тебя, над этим вопросом автор уже работает и возможно в одной из ближайших версии DotFix LiteProtect’а появится компилятор скриптов в EXE файл. Так что можете уже начинать писать свой суперскрипт! А если не хватает команд - писать мне, буду расширять скриптовый язык нужными операторами и командами.

Вместо заключения

Я надеюсь, что прочитанные страницы не пропадут зря и Вы наверняка разработаете какой-нибудь мощный скрипт, автоматизирующий рутинные операции по работе с PE файлами. Удачи Вам!


Комментарии

отсутствуют

Добавление комментария


Ваше имя (на форуме):

Ваш пароль (на форуме):

Комментарии могут добавлять только пользователи,
зарегистрированные на форуме данного сайта. Если Вы не
зарегистрированы, то сначала зарегистрируйтесь тут

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