DotFix :: Портал разработки и защиты программ
Главная
Программы
Статьи
Разное
Форум
Контакты
Автор: GPcH. Дата публикации: 29.08.2005

Дизассемблер своими руками

Как часто ты слышал про дизассемблеры? Думаю не стоит объяснять что это такое и для чего это нужно. Ты наверняка не раз отлаживал свою (или чужую :) программу под IDA или в Olly Debugger’е а также несомненно знаком с hiew’ом. Все эти проги довольно шустро раскладывают скомпилированный EXE файл на ассемблерный код который ты можешь изучать и изменять. В этой статье я опишу как можно уже сейчас замутить простенький дизассемблер под свои нужды.

Введение
Для начала думаю нелишне напомнить тебе, для чего все же тебе может пригодиться самодельный дизассемблер. Знаком ли ты с EXE упаковщиками? Это такие проги которые способны пожать твою прогу в 2 или более раза, при этом оставив ее работоспособной. Из бесплатных представителей таких прог ты наверняка помнишь опенсорсный UPX. А задумывался ли ты как пишут распаковщики для таких прог? Вряд ли. А зря! Ядро распаковщиков, особенно статических (которые распаковывают программу без запуска) основано именно на дизассемблере, который в совокупности с анализатором кода позволяет понять код распаковываемой программы и распаковать ее используя нужный алгоритм упаковщика. Если ты когда-нибудь решишь написать такой распаковщик - тебе без самопального дизассемблера не обойтись. А задумывался ли ты как работают все современные защиты программ? Чтобы извратить код программы до неузнаваемости, дабы его не смог понять взломщик, они дизассемблируют твою прогу команда за командой, делая код метаморфным, попутно внедряя мусорные инструкции. Я уже не говорю про виртуальные машины, которые вообще представляют собой кодер-декодер ассемблерных команд в псевдокод и наоборот. Надеюсь я убедил тебя в том, что без дизассемблера никуда, особенно если тебя увлекает все, что связано с PE форматом программ.

Дизассемблирование
Ты наверняка задаешься вопросом: "А как это ваще работает и почему бы не написать дизассемблер с нуля?". Как бы это не было тривиально - ответ на него ты сможешь получить только осознав, насколько именно тебе сложно написать дизассемблер используя только Intel’овские мануалы на архитектуру процессора. Я лишь кратко рассмотрю принципы кодирования ассемблерных команд, чтобы принцип дизассемблирования был более прозрачным. Каждый байт секции кода твоей программы участвует в формировании той или иной машинной инструкции. Чтобы правильно определить начало следующей команды нужно правильно (как бы это сделал процессор) дизассемблировать предыдущую. Для этого необходимо четко представлять себе формат команд ассемблера. Команды процессора Intel кодируются следующим образом:



При этом единственный обязательный параметр это "код операции", остальные используются в зависимости от сложности и навороченности той или иной команды. Например "преффикс" используется довольно редко, зато префиксы могут поистине творить чудеса над командами, к примеру префикс 66h меняет размерности регистров и адресов при этом в 16 битной программе этот префикс позволяет юзать 32 битные регистры, а в 32 битной - 16 битные. Поля modR/M позволяют определить формат данных, с которыми оперирует программа, будь то регистры, адреса и прочее. Поле SIB расширяет возможности адресации 32 битного режима. Процессор узнает о присутствии этого поля по битам 100b в поле R/M. Далее идут непосредственно смешения и операнды, описанные в структуре modR/M+SIB.
Расшифровкой именно этих команд и занимается дизассемблер. Чтобы его написать самому с нуля потребуется море сил и времени. Хотя есть два пути. Наиболее простой из них - составить таблицу опкодов и используя нее дизассемблировать команды (именно этот принцип используется в большинстве дизассемблеров длин и в дельфевом декомпилере DeDe). Второй путь - самый сложный. Он подразумевает полное отсутствие таблиц и использование для дизассемблирования только тех данных что описывают мануалы от авторов процессора.
Какой путь выберешь ты - решать только тебе. Хотя предлагаю тебе не париться и использовать уже готовые решения. Об одном из таких решений читай ниже.

Выбираем компонент
Как ни странно, если очень сильно постараться, можно найти целых два бесплатных дизассемблера, которые можно внедрить в свою прогу на Delphi. Первый можно выдрать из свободно распространяющегося исходника декомпилятора Delphi - DeDe. При желании этот исходник ты можешь взять на www.wasm.ru и самостоятельно его изучить. Мы же рассмотрим второй дизассемблер, поставляющийся в виде компонента для Delphi и бесплатный для некоммерческого использования. Называется данный компонент madDisAsm и входит в состав большой библиотеки компонентов называющейся madCollection. Взять эту коллекцию можно отсюда: http://madshi.bei.t-online.de/madCollection.exe. Теперь когда ты скачал все что нужно давай разберемся, что мы имеем. А имеем мы 8 мощных компонентов, среди которых даже есть madBasic (бешеный барсик :)). Как ты понимаешь, из данного мощного пакета нам потребуется только madDisAsm. Его и рассмотрим.

madDisAsm
Данный компонент практически недокументирован. Документированы только прототипы функций и структур. Примеров же использования нет ни одного, отсюда следует что разбираться придется самим. Чтобы использовать данный компонент в нашем проекте подключи его в разделе Uses модуля так:
uses madDisAsm;
Теперь давай рассмотрим функции данного рульного компонента. А основных функции всего две. Первая позволяет дизассемблировать код по команде. При этом все надстройки над кодом ты можешь реализовывать сам в ходе декомпилирования. Прототип функции имеет вид:
function ParseCode (code: pointer; var disAsm: string) : TCodeInfo; overload;
code - pointer на бинарный код программы, который мы хотим дизассемблировать;
disAsm - переменная в которую будет занесена первая дизассемблированная строчка.
TCodeInfo - структура дизассемблированной строчки. одним из элементов этой структуры является ссылка на следующую строчку, которую мы можем использовать для цикличного вызова функции. Вот полный прототип данной структуры:

CODE NOW!
TCodeInfo = record
IsValid : boolean; // определяет валидность pointer’а на код
Opcode : word; // Опкод, один ($00xx) или два ($0fxx) байта
ModRm : byte; // ModRm байт (о нем я уже писал), если присутствует, иначе 0
Call : boolean; // эта инструкция call?
Jmp : boolean; // эта инструкция jmp?
RelTarget : boolean; // адрес относительный (или абсолютный) ?
Target : pointer; // абсолютный адрес
PTarget : pointer; // pointer на информацию в коде
PPTarget : TPPointer; // pointer на pointer с информацией
TargetSize : integer; // размер информации в байтах (1/2/4)
Enlargeable : boolean; // может ли размер опкода быть расширенным?
This : pointer; // адрес начала инструкции
Next : pointer; // адрес следующей инструкции
end;



Также для нас будет интересная еще одна функция. Ее особенностью является то, что она способна дизассемблировать всю функцию целиком, автоматически находя конец функции по команде retn. Вот ее прототип:

CODE NOW!
function ParseFunction (func: pointer; var disAsm: string) : TFunctionInfo; overload;



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

CODE NOW!
TFunctionInfo = record
IsValid : boolean;
EntryPoint : pointer;
CodeBegin : pointer;
CodeLen : integer;
LastErrorAddr : pointer;
LastErrorNo : cardinal;
LastErrorStr : string;
CodeAreas : array of record
AreaBegin : pointer;
AreaEnd : pointer;
CaseBlock : boolean;
OnExceptBlock : boolean;
CalledFrom : pointer;
Registers : array [0..7] of pointer;
end;
FarCalls : array of record
Call : boolean; // это CALL или JMP?
CodeAddr1 : pointer; // начало инструкции call
CodeAddr2 : pointer; // начало следующей инструкции
Target : pointer;
RelTarget : boolean;
PTarget : pointer;
PPTarget : TPPointer;
end;
UnknownTargets : array of record
Call : boolean;
CodeAddr1 : pointer;
CodeAddr2 : pointer;
end;
Interceptable : boolean;
Copy : record
IsValid : boolean;
BufferLen : integer;
LastErrorAddr : pointer;
LastErrorNo : cardinal;
LastErrorStr : string;
end;
end;



Есть и еще одна третья функция, которая вообще недокументированна:

CODE NOW!
function ParseFunctionEx (func: pointer; var disAsm: string, exceptAddr: Pointer; maxLines: Integer; autoDelimiters: Boolean);



Насколько я понял эта функция не возвращает структуры, зато дизасмит весь код нужной нам функции и кладет его в переменную disAsm. exceptAddr насколько я понял - это адрес конца дизассемблируемой функции (указывать необязательно), maxLines - число дизассемблируемых строк (если 0, то все), autoDelimiters - точно не могу сказать, но ориентировочно это - завершать ли функцию первым ret’ом или нет.

Кодим
Теперь, когда мы разобрались с работой компонента давай писать дизассемблер! Открывай Delphi, создавай новый проект, помещай на форму пару Edit’ов, Memo и два CommandButton’а. В результате этих несложных манипуляций мы получим что-то похожее на интерфейс проги (смотри на рисунке).



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

CODE NOW!
function TfrmMain.GetCode(strFileName: string; strOffset: string): pointer;
var
hFile: integer;
read_bytes: cardinal;
EP_code: array[1..64000] of byte;
begin
//открываем файл
hFile:=CreateFileA(pchar(strFileName), GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, NIL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
//если файл открыт успешно
if hFile<>-1 then begin
//устанавливаем файловый указатель на начало дизассемблируемого кода
SetFilePointer(hFile,StrToInt(strOffset),NIL,FILE_BEGIN);
//считываем 64000 байт кода
ReadFile(hFile,EP_Code,64000,read_bytes,NIL);
//закрываем файл
CloseHandle(hFile);
//возвращаем pointer на считанный код
result:=@EP_Code;
end else begin
//если не смогли открыть файл - выходим
exit;
end;
end;



В данной функции я использовал исключительно Win32 API. Это позволяет добиться максимальной скорости кода и простоты переносимости на другие языки программирования. Теперь напишем функцию, которая будет покомандно дизассемблировать код, pointer на который будет в нее передаваться:

CODE NOW!
function TfrmMain.Disasm(strAsm: pointer): string;
var
strDisAsm, strdasm: string;
retval: TCodeInfo;
begin
//получим в strDisAsm первую строчку кода, а в retval - структуру, в которой имеется pointer на следующую ассемблерную команду
retval:=madDisAsm.ParseCode(strAsm,strDisAsm);
//в переменной strdasm мы будем хранить весь дизассемблированный листинг
strdasm:=strDisAsm;
//перебераем циклом команды до тех пор пока не встретим ret
while strpos(pchar(strDisAsm),’ret’)= nil do begin
//дизассемблируем очередную команду
retval:=madDisAsm.ParseCode(retval.Next,strDisAsm);
//добавляем ее в конец дизассемблированного листинга
strdasm:=strdasm + #13#10 + strDisAsm;
end;
//возвращаем дизассемблированный код
result:=strdasm;
end;



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

CODE NOW!
procedure TfrmMain.cmdDisasmClick(Sender: TObject);
begin
txtDisasm.Text:=Disasm(GetCode(txtFileName.Text, txtOffset.Text));
end;
Код кнопка Dasasm function будет выглядеть так:
procedure TfrmMain.cmdDisAsmFunctionClick(Sender: TObject);
var
strDisAsm: string;
begin
madDisasm.ParseFunctionEx(GetCode(txtFileName.Text, txtOffset.Text),strDisAsm,nil,0,true);
txtDisasm.Text:=strDisAsm;
end;



Теперь, когда дизассемблер готов напишем тестовый проект для его проверки.

Пишем тестовый проект
Раз мы написали дизассемблер, то самое логичное писать тестовый проект на асме. Самым удобным на мой взгляд редактором и компилятором ассемблера является Fasm, который плюс к своему удобству и простоте очень часто обновляется и версия к версии становится все стабильнее и лучше. Взять его можно на www.wasm.ru. Уже скачал? Тогда запускай его и набирай следующий код:

CODE NOW!
include ’C:\ASm\fasm\INCLUDE\win32ax.inc’
.data
;создаем переменные с данными
Serial db ’Some program’,0
_MsgCaption db ’Disasm this’,0
.code
start:
;вывод сообщения на экран, что может быть проще
invoke MessageBox,0,Serial,_MsgCaption,MB_OK
;выход из программы
invoke ExitProcess,0
;установим конец процедуры, чтобы наш дизассемблер не завис
retn
.end start



Компиляем, получаем EXE файл, размером 2 килобайта. Даа, дельфям до ассемблера далеко.



Тестируем
Запускай скорее только что написанный дизассемблер. Вводи в одно текстовое поле путь к тестовому проекту а во второе адрес точки входа - 1024 (400h). Для простеньких ассемблерных прог адрес точки входа часто равен смещению секции кода, которое часто равно именно 400h. Жми теперь любую из кнопок декомпилера - ты должен увидеть в Memo код, напоминающий только что написанный тобой, но в более строгом виде:

CODE NOW!
0011fb5c push 0
0011fb5e push $40100d
0011fb63 push $401000
0011fb68 push 0
0011fb6a call dword ptr [$40307a]
0011fb70 push 0
0011fb72 call dword ptr [$40305c]
0011fb78 ret



как видишь все прекрасно работает.



Заключение
Надеюсь ты найдешь применение написанному нами дизассемблеру.


Комментарии
отсутствуют

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

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

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

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

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



04.09.2011 Долгожданный релиз VB Decompiler. Масса улучшений декомпиляции Native Code. Значительно расширенна и обновлена справочная система на русском и английском языках.
20.12.2010 DotFix Software поздравляет наших клиентов и посетителей сайта с наступающим Новым Годом и рождеством! Желаем приятно провести праздники и успехов в новом году!
28.11.2010 Выпущена новая версия защиты DotFix NiceProtect. Основные изменения коснулись обфускатора Delphi программ. Теперь имеется полная поддержка Tab и Page контролов на формах, что обеспечивает максимальную совместимость обфускации с Delphi XE программами.
21.10.2010 Обновлен декомпилятор Visual Basic программ до версии 8.1. Декомпиляция P-Code программ становится все более идеальной, также проделана большая работа по улучшению анализа Native Code и .NET приложений.
16.09.2009 Полностью обновлен движок сайта! Теперь все ссылки имеют читаемый понятный вид, разного рода глюки на страницах убраны. И теперь сайт полноценно работает на второй версии нашего движка.
Архив новостей
Яндекс цитирования

Движок сайта: DotFix Engine v0.2
Администрация сайта:
Выгодные условия - лобовое стекло, ваше авто попадет в умелые руки. . Торт в подарок для мужчины - купить подарок для мужчины. Подарки из циркония.