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

Виртуальная машина

Крекер скачал новую программу... как обидно, опять без регистрации все функции заблокированы... ну, не в первой, подумал крэкер и полез за дизассемлером. Минут 10 поисследовав программу он не нашел ни единой строчки понятного кода. Может программа упакована? Да вроде нет... код на точке входа стандартный, созданный компилятором. И тут крекер понял, что имеет дело с VM - виртуальной машиной.

Вступление
Что же такое виртуальная машина, спросишь ты. В простейшем случае - это интерпретатор команд, способный переводить команды, написанные на понятном только ему языке в машинный код, понятный процессору твоего компьютера. Вообще EXE файлы, создаваемые компиляторами содержат машинный код, иначе называемый native кодом, поддающийся дизассемблированию. Совсем немногие языки программирования способны создавать код, отличный от машинного. Оно и понятно - зачем городить огород, ведь в любом случае процессору понятен только машинный native код. Напротив, код выполняемый на виртуальной машине - это так называемый интерпретируемый код. Переходником между ним и процессором как раз и служит виртуальная машина, которую чаще всего называют интерпретатором. Первоначально основной целью идеи создания интерпретаторов было упрощение портирования программ под другие операционные системы. Но метод интерпретации стал популярен и немного в другой области. Его стали использовать авторы профессиональных защит для затруднения исследования и взлома программ крекерами. Думаешь, почему программы написанные на Visual Basic’е так сложно ломаются и кряки к ним выходят не сразу после появления этих программ в сети? Вся проблема в том, то Visual Basic умеет компилировать программы не только в машинный код, но и в интерпретируемый p-code, который исполняется виртуальной машиной MSVBVM60.DLL, без которой не запустится ни одна VB программа. С другими языками программирования до недавнего времени об использовании виртуальных машин не было и речи, но теперь есть софт и для перевода кода, созданного такими средами программирования как Delphi и C++ из машинного в интерпретируемый. Но обо всем по порядку. Ниже я рассмотрю способы перевода программ, написанных в самых известных средах программирования в интерпретируемый, трудновзламываемый код. Думаю после этого твои программы наконец-то перестанут ломать и настроение твое улучшится ;)

Виртуальная машина VB 6.0
Как я уже говорил Visual Basic имеет собственную виртуальную машину - так называемую Micro$oft Visual Basic Virtual Machine 6.0 (MSVBVM60.DLL). Движок интерпретатора содержится в секции Engine этой библиотеки и работает только в том случае, если вы компилируете программу в p-code. По умолчанию VB компилит проги в native код, поэтому чтобы включить компиляцию в интерпретируемый код нужно в среде программирования зайти в меню Project -> Properties. Далее на вкладке Compiler поставить радиокнопку в положение Compile to p-code и нажать OK. Теперь программа будет компилироваться в интерпретируемый код и взломать ее будет в разы сложней. Хотя если у крэкера есть декомпилятор p-code’а то он сможет увидеть чуть ли не исходник твоей проги. Но тут я тебя обрадую - бесплатных декомпиляторов, способных декомпилить прогу до исходника нет, а коммерческие стоят настолько дорого, что крекеру они будут не по карману, да и интереса у него не будет покупать этот декомпилятор, так что шаровар, скомпиленных в p-code в сети крайне мало. В общем, если ты пишешь программы на VB 6.0, не забывай об этой возможности. Я надеюсь тебе стало интересно, что же будет в EXE файле после компиляции? Так вот, точка входа в программу будет стандартно выглядеть так:

CODE NOW!
push offset VB_Header
call MSVBVM60.DLL::ThunRTMain



Отсюда следует, что любую VB программу запускает функция ThunRTMain. Единственный параметр, который ей передается - ссылка на VB_Header - структуру, полностью описывающую настройки программы, содержащиеся в ней функции и ссылки на таблицу переходников импорта, а также ссылки на массивы форм. В этих структурах активно используется COM технология описания объектов форм, понятная VB интерпретатору. Но самое главное, что нужно функции ThunRTMain из этих структур - во что откомпилирована программа. Если программа откомпилирована в p-code, то VB подключает к работе свой интерпретатор, иначе настраивает объекты и передает управление main функции программы. Перед интерпретированием движок настраивает также все объекты интерфейса, но main функцию интерпретирует сам, что чрезвычайно затрудняет отладку программы крэкером, так как по сути ему приходится отлаживать не программу а ее интерпретатор. К великой радости крэкера отладчик p-code’а существует и при всем при том бесплатен, но это несильно упрощает взлом программы, так как этот отладчик довольно глючный, а распакованные программы вообще отказывается отлаживать, потому не забудь перед релизом запаковать программу любым EXE упаковщиком, например FSG или nSpack’ом. Так что, как говорят в рекламе: "отсюда правило" - хочешь затруднить исследование программы - компилируй ее в p-code :)



Виртуальная машина VS .NET
В новой платформе .NET на каком бы ты языке не писал - твоя программа будет компилироваться в интерпретируемый код. Если в VB 6.0 библиотека интерпретатора, требуемая твоей программе после ее компиляции занимала 1.3 Mb, то здесь разработчики пошли дальше - .NET FrameWork занимает 20 Mb и если его нет на машине клиента, то программы, написанные в среде .NET работать не будут. Интерпретируемый язык сильно изменился и теперь называется IL. На данный момент существуют несколько декомпиляторов этого языка, которые без особого геморроя переведут EXE файл твоей программы в исходник, поэтому получается не защита программы, а как раз наоборот. Тут самое время тебя обрадовать - авторы компилятора просекли фишку и сделали две примочки:
1. обфускатор IL кода, способный убрать всю лишнюю информацию из EXE. После такой обработки исследовать программу будет крайне сложно даже под декомпилятором.
2. Есть также программа для конвертирования IL кода в машинный native код. Но по слухам эта примочка очень сильно глючит и то что твоя программа после конвертирования будет нормально работать зависит лишь от фазы луны :)
Обе эти программки входят в состав Visual Studio последних версий, так что не забывай о них.



Интерпретирование Delphi
Конечно VB, VS .NET это все круто, но как же быть тем, кто пишет программы на Delphi? Delphi не умеет создавать интерпретируемый код. Именно поэтому программы написанные на данном языке программирования работают быстрее программ, написанных например на Visual Basic’е. Скажу по секрету - ломаются они тоже быстрее :) Так как же защитить Delphi проги, спросишь ты? До недавнего времени я бы тебе ответил, что никак, но теперь есть замечательная программа DotFix NiceProtect, написанная автором данной статьи. Умеет она не много не мало, а переводить отдельные процедуры твоей программы на трудноломаемый псевдокод. Я пошел дальше стандартных интерпретируемых языков, приделав к протектору метаморфный преобразователь машинного кода для еще большего усложнения защищаемых участков программы. Конечно всю программу защищать не имеет смысла и я тебе советую ограничиться защитой только тех функций, в которых твоя программа производит проверку пароля, активацию, разблокирование опций, установку trial’а... короче тех функций, которые крэкеру видеть нежелательно. Теперь расскажу тебе как использовать эту чудо прогу. Для начала определись, какие функции в своей программе ты решил перевести в p-code. Затем открывай Delphi, после этого в меню Project выбирай Options и щелкай по вкладке Linker. Там радиокнопку Map File установи в Detailed, жми OK и можешь компилировать свою программу. В папке с ней должен появиться *.map файл. Из него то и будет брать процедуры DotFix NiceProtect. Теперь, когда все готово запускай DotFix NiceProtect, задавай "Input file" и щелкай по разделу "Code Protection". Если ты все сделал правильно должен появиться список процедур, используемых в твоей программе. Там будет куча ненужных процедур, созданных компилятором и ближе к концу списка будут те, которые писал ты. Выбирай те функции, которуе хочешь защитить и помечай галочками. Теперь можно полазить по настройкам протектора и включить при надобности упаковку файла и прочее. Затем иди на вкладку "Protection" и жми кнопку "Start". Все! Программа защищена. Теперь нелишне сохранить проект на будущее. Фишка сохранения в том, то при последующих компиляциях если адреса процедур в EXE файле будут меняться - DotFix NiceProtect автоматически их найдет и пересчитает адреса, если ты и новую версию программы тоже решишь защитить по аналогии с предыдущей. Потому все телодвижения можно делать всего один раз, а потом юзать готовый проект. Ладно, совсем я тебя заговорил - жми кнопку "Start" и радуйся - кряки к твоей программе появятся нескоро, а может и вообще не появятся - все зависит от того насколько грамотно ты реализовал защиту (внешнюю в лице DotFix NiceProtect и внутреннюю).

DotFix NiceProtect. Лучший друг шароварщика :)


Принцип работы DotFix NiceProtect’а
Думаю не лишним будет узнать что делает DotFix NiceProtect с твоим EXE файлом после того как ты нажмешь на кнопку "Start". Протектор имеет встроенный дизассемблер, позволяющий проанализировать код защищаемых функций и перевести все это дело в формат, понятный только интерпретатору DotFix NiceProtect’а. Затем в программе создаются две секции. Одна содержит движок виртуальной машины и содержит интерпретатор кода, вторая секция содержит функции твоей программы, переведенные в p-code. А что же остается на том месте, где располагался оригинальный код защищенных функций? Туда размещается вызов интерпретатора, а остальное пространство попросту не используется, поэтому не поленись включить опцию упаковки. Вот в общем и вся идея VM. Несмотря на то, что в принципе интерпретации ничего нового нет, он обрел новую силу только сейчас, так как стал применяться для более качественной защиты программного обеспечения.

Кодим свой интерпретатор
Чтобы лучше разобраться с принципом интерпретирования напишем простой скриптовый язык, обрабатывающий команды, понятные только ему и выполняющий их используя понятные процессору команды. Думаю, ты наверняка найдешь применение этому коду, так как сейчас авторы многих программ стремятся помочь пользователю поднастроить программу под себя и нередко включают в свои программы для этих целей скриптовые языки, которые сами и придумывают. Рекомендую и тебе не пренебрегать в своих прогах использованием скриптов, так как если ты когда-нибудь забросишь программу - пользователи смогут самостоятельно повышать ее возможности за счет написания скриптов к ней. Как альтарнативу скриптам многие используют плагины в программах, но плагины нельзя модифицировать, а это не всегда полезно. Ну вот, когда мы знаем что писать, давай определимся с требованиями к интерпретатору скриптового языка. Во первых он должен уметь понимать не только команды, но и передаваемые им параметры, при этом параметров командам может быть передано сколько угодно - это будет определяться тем, какое число данных требуется для команды. во вторых интерпретатор должен иметь простоту добавления в него команд, с минимальными изменениями в исходном коде. В третьих - мы должны передавать интерпретатору команду и параметры одной строкой, а он должен сам понять что есть команда, а что параметры и правильно выполнить все это, верно отделив парметры друг от друга. Данные будут передаваться нашему интерпретатору в следующем виде: "команда параметр_1, параметр_2, параметр_3, ... ,параметр_n". Число параметров ограничим десятью (от 0 до 9) - думаю больше использовать нам вряд ли придется. Возьмем эти данные за основу и напишем движок интерпретатора.

CODE NOW!
procedure EngineVM(strScript: string);
var
//общие переменные
strCommand, strParameters: string;
strParametr: array[0..9] of string;
sSpace: integer;
i: integer;
//переменные команды зашифровать
strText: pointer;
strLen: integer;
strByte: byte;
label
encrypt;
begin
//находим первый пробел в строке
sSpace:=InStr(1,strScript,#32)-1;
if sSpace<>-1 then begin
//все что находится до пробела считаем командой
strCommand:=copy(strScript,1,sSpace);
//остальное - параметры
strParameters:=copy(strScript,sSpace+2,Length(strScript));
//заносим параметры записанные через запятую в массив
i:=0;
while InStr(1,strParameters,’,’)>0 do begin
//ищем запятую
sSpace:=InStr(1,strParameters,’,’)-1;
//сохраняем в массив параметр от начала строки до найденной запятой
strParametr:=trim(copy(strParameters,1,sSpace));
//отрезаем от строки strParameters добавленный параметр
strParameters:=copy(strParameters,sSpace+2,Length(strParameters));
//увеличиваем счетчик параметров
i:=i+1;
end;
//добавляем последний параметр отдельно,
//так как после него вместо запятой конец строки
strParametr:=copy(strParameters,1,Length(strParameters));
end else begin
//исли пробелов в строке нет, значит у команды скрипта
//нет параметров, потому за команду принимается все строка
strCommand:=strScript;
end;
//Выполняем скрипт
//сюда ты можешь добавить сколько угодно команд
//команда вывода сообщения (требует 3 параметра)
if strCommand=’сообщение’ then begin
MessageBox(Form1.Handle,pchar(strParametr[0]),pchar(strParametr[1]),StrToInt(strParametr[2]));
//команда шифровки строки и вывода шифрованной строки на экран
end else if strCommand=’зашифровать’ then begin
strText:=pointer(strParametr[0]);
strByte:=StrToInt(strParametr[1]);
strLen:=Length(strParametr[0]);
asm
mov ecx, strLen
mov eax, strText
dec eax
mov bh, strByte
@encrypt:
xor byte ptr[eax+ecx],bh
loop @encrypt
end;
ShowMessage(strParametr[0]);
end else begin
ShowMessage(’Invalid command’);
end;
end;





Несмотря на то что в коде интерпретатора откомментировано практически все - я рассмотрю некоторые участки поподробнее для более глубокого понимания. Первое, что ты встретишь непонятное - это функция InStr. Такой функции нет в Delphi, она присутствует только в Visual Basic’е и служит для нахождения подстроки в строке. В отличии от дельфевой функции strpos рассматриваемая функция позволяет искать подстроку не только с начала строки, но и начиная с любого символа, что для нас крайне полезно так как мы ищем по очереди запятые в строчке скрипта чтобы отделить все параметры скриптовой команды и занести их в массив. Тут я не стал париться и написал аналог VB’шной функции InStr на Delphi. Функция эта очень проста, так что думаю ты разберешься с ней сам - вот она:

CODE NOW!
function InStr(index: integer; str1: string; str2: string): integer;
var
i,len, pos: integer;
begin
pos:=0;
len:=length(str2);
for i:=index to length(str1) do begin
if copy(str1,i,len)=str2 then begin
pos:=i;
break;
end;
end;
result:=pos;
end;



Здесь Index - номер символа строки Str1, с которого начинать искать строчку Str2. Рекомендую тебе использовать эту функцию в своих программах, как лучшую на мой взгляд замену strpos. Еще у тебя может возникнуть вопрос, для чего нужен препроцессинг параметров и занесение их в массив. Все просто - для удобства работы с параметрами в дальнейшем.
Теперь перейдем к самому интересному куску кода. Среди кучи нормальных Delphi команд красуется небольшой листинг:

CODE NOW!
strText:=pointer(strParametr[0]);
strByte:=StrToInt(strParametr[1]);
strLen:=Length(strParametr[0]);
asm
mov ecx, strLen
mov eax, strText
dec eax
mov bh, strByte
@encrypt:
xor byte ptr[eax+ecx],bh
loop @encrypt
end;



В ассемблерном отладчике Delphi очень наглядно видно, что ассемблерная вставка в нашей функции присутствует практически без изменений



Ассемблерная вставка была использована по двум причинам: для удобства (на ассемблере написать простенькую шифровку намного проще, чем на Delphi) и для того, чтобы ты смог наглядно представить перевод скриптовых команд в понятный процессору машинный код :) Думаю не лишним будет рассмотреть данный код поподробнее. Начнем с регистров процессора eax, ebx и ecx. Они служат для хранения 4 байтных данных. Чтобы получить доступ к 2 младшим байтам например регистра ebx - нужно обращаться к регистру bx (соответственно для eax - ax, ecx - cx). Если нам нужно использовать только старший или младший байт 2 байтного регистра bx - юзаем регистры bh и bl соответственно. В нашем конкретном случае каждый байт строки ксорится с байтом, переданным во втором параметре скриптовой командой, потому для хранения этой команды мы используем однобайтный регистр bh. @encrypt - это метка. Собака перед именем метки ставится для того, чтобы указать компилятору что метка локальная, а не глобальная для всей процедуры нашего интерпретатора. Командам ассемблера передаются 2 операнда или значение и операнд. Команда mov перемещает в регистр, являющийся первым операндом содержащиеся во втором операнде данные. Данные во втором операнде должны быть представлены либо регистром либо числом, переданным напрямую. Главное о чем нужно всегда помнить, что размер данных должен соответствовать или быть меньше размера регистра. Команда dec имеет дело с одним операндом, который должен быть представлен исключительно в виде регистра. Эта команда уменьшает число, записанное в передаваемом регистре на 1. Последовательность encrypt и loop encrypt представляет собой цикл. Счетчик цикла записывается в регистр ecx. Команда xor byte ptr[eax+ecx],bh означает что нужно отXORить byte по адресу в памяти равному eax+ecx с байтом который содержится в регистре bh. Так как ecx содержит длину строчки, а eax - адрес этой строчки в памяти, то учитывая автоматическое уменьшение ecx (так как этот регистр является счетчиком цикла) можно определить что строчка шифруется с конца. Так оно и есть - процессору так проще. Мы конечно можем добавить пару лишних команд чтобы шифровка производилась с начала строки, но зачем усложнять программу и тратить на это лишние такты работы процессора?
Теперь, когда думаю ты во всем алгоритме интерпретатора разобрался - добавь на форму Memo и кнопку. В Memo мы будем вводить команды движку, а при нажатии на кнопку они будут циклично выполняться движком интерпретатора. Собственно обработчик нажатия на кнопку будет выглядеть так:

CODE NOW!
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
for i:=0 to Memo1.Lines.Count-1 do begin
//вызываем интерпретатор
EngineVM(Memo1.Lines.Strings);
end;
end;



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

Заключение
Надеюсь у тебя не осталось вопросов по интерпретированию и принципам защиты программ этим методом. Если же они все же есть, а также есть желание углубленно все это дело изучить - почитай статейки например на wasm.ru. Поняв ассемблер и зная один из языков высокого уровня типа Delphi или C++ ты легко поймешь принцип работы виртуальных машин. Удачи тебе.


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

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

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

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

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

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



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
Администрация сайта:
Водостоки и водосточные системы: системы водостока. Строительство дома. Форум. . Производитель свадебных, вечерних - вечерние платья. Женская одежда: костюмы, платья.