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

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

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

Руководство по исследованию программ, написанных на Visual Basic 6.0


[Вступление]

Многие почему-то считают, что Visual Basic самый плохой язык программирования, который не может компилировать программы в машинный код, не может работать с адресами переменных в памяти и не позволяюет вставлять ассемблерные процедуры в высокоуровневый код программы. Так вот, все это неправда. Начиная с версии 5.0 данный язык позволяет компилировать программы в машинный Native Code, а также имеется возможность работы с адресами переменных в оперативной памяти (для этого существуют функции VarPtr и StrPtr). Ассемблерные процедуры вставлять тоже можно, но не так просто. Для этого я написал уже 2 части статьи по вставке ассемблерных процедур в код на Visual Basic. Первая статья размещена здесь: Ассемблер в VB6 часть 1, вторая тут: Ассемблер в VB6 часть 2. Как видите, недостатков у VB не так уж и много, а преимуществ настолько много, что я расскажу лишь о самых очевидных и важных:

- Полноценная компиляция и маленький размер получаемых exe файлов. Несмотря на то, что программы требуют библиотеку MSVBVM60.DLL, это не является недостатком, так как эта библиотека интегрирована во все новые операционные системы и таскать ее вместе с приложением не нужно.

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

Так вот, исследователи программ почему-то считают, что программы, написанные на Visual Basic'е невозможно анализировать. Если программа скомпилирована в P-Code - их частично можно понять, но даже для этого вида компиляции уже написано пара отладчиков, способных понимать псевдокодовые инструкции. Что же касается Native Code, то тут все исследуется так же, как и любой x86 программный код, написанный например на C++ или Delphi. Но есть ряд особенностей. Все операции, которые выполняет программа выполняются с использованием узкоспециализированных функций из библиотеки MSVBVM60.DLL. Имена этих функций напоминают операторы Visual Basic'а, поэтому глядя на названия большинства из них, можно понять, какие операции они позволяют выполнять. Но есть и такие, которые не поддаются логике. О них я и расскажу в данной статье.


[Функции]

Самые непонятные аналитикам функции - функции для преобразования данных из одного типа в другой. Чуть ниже я для удобства приведу существующие типы данных, используемых в Visual Basic 6.0 и функции для работы с ними.

bool - boolean
str - string
i2 - integer (2 байтный integer)
ui2 - unsigned integer (2 байтный unsigned integer)
i4 - long (4 байтный integer)
r4 - single (4 байтный float)
r8 - double (8 байтный float)
cy - currency
var - variant (VB) или variable (OLEAUT)

Названия части функций:

fp - работа с float данными, переданными через st регистры или сохраняемые туда командами сопроцессора
cmp - сравнение аргументов
comp - сравнение аргументов

- Функции ля преобразования типов данных:
__vbaI2Str 'преобразует String в Integer
__vbaI4Str 'преобразует String в Long
__vbar4Str 'преобразует String в Single
__vbar8Str 'преобразует String в Double
VarCyFromStr 'преобразует String в Currency
VarBstrFromI2 'преобразует Integer в String

- Перенос данных
__vbaStrCopy 'копирует строку в память - аналог API функции HMEMCPY
__vbaVarCopy 'копирует переменный тип (variant) в память
__vbaVarMove 'копирует переменный тип (variant) в память

- Математические функции
__vbavaradd 'сложение двух переменных типа Variant
__vbavarsub 'деление двух переменных типа Variant
__vbavarmul 'умножение двух переменных типа Variant
__vbavaridiv 'сложение двух переменных типа Variant
'с выводом результата в переменную типа Integer
__vbavarxor 'XOR

- Другие функции
__vbavarfornext 'используется в конструкциях For... Next... (Loop)
__vbafreestr 'удаление переменной
__vbafreeobj 'удаление объекта
__vbastrvarval 'получения численного значения из строки
multibytetowidechar 'преобразование кодировки
rtcMsgBox 'показывает сообщение - аналог API messagebox/a/exa
__vbavarcat 'объединяет две переменные типа Variant
__vbafreevar 'удаляет переменную типа Variant
__vbaobjset 'создает объект
__vbaLenBstr 'определяет длину строки
rtcInputBox 'показывает форму с полем ввода (используются также API функции getwindowtext/a, GetDlgItemtext/a)
__vbaNew 'аналог API функции Dialogbox
__vbaNew2 'аналог API функции Dialogboxparam/a
rtcTrimBstr 'удаляет пробелы вначале и в конце строки

- Функции сравнения
__vbastrcomp 'сравнивает 2 строковые переменные - аналог API функции lstrcmp
__vbastrcmp 'сравнивает 2 строковые переменные - аналог API функции lstrcmp
__vbavartsteq 'сравнивает 2 Variant переменные
__vbaFpCmpCy 'сравнивает значение с плавающей точкой с Currency значением


[Разблокирование элементов управления]

Любой элемент управления на форме может быть видимым или невидимым, доступным или заблокированным. Для установки свойств объектов существует функция __vbaObjSet. Именно с помощью нее можно заблокировать или разблокировать элемент управления и изменить любое из его свойств. Поэтому нам необходимо отлавливать вызов именно этой функции. Откроем например Olly Debugger, найдем эту функцию среди вызываемых анализируемым файлом и поставим на нее breakpoint нажав кнопку F2. Затем запустим исследуемый файл. Когда breakpoint сработает - посмотрите окружающий код. Если он напоминает

50 push eax 52 push edx FFD7 call edi 8BD8 mov ebx, eax 6A00 push 00 53 push ebx 8B03 mov eax, dword ptr [ebx]

то Вы на верном пути. Как вы думаете, что это за "push 00"? 00h в VB означает FALSE, а FFh TRUE, из этого следует, что данная команда устанавливает свойство в FALSE, то, возможно, это и есть блокировка элемента управления на форме. Но это может быть и установка любого другого свойства формы в TRUE. Так как синтаксис один и тот же, то принадлежность данной команды к изменению свойства блокировки можно установить только анализом окружающего кода. Но думаю с этой мелочью Вы справитесь сами.

[Методика анализа простейших проверок строковых данных]

Нижеследующий текст - мой вольный перевод статьи How to Research Visual Basic с одного из зарубежных сайтов infonegocio.com. За английскую версию текста спасибо ее авторам.

Что нам может понадобиться? Любой дизассемблер/отладчик. Подойдет Win32Dasm или Olly Debugger Если вы используете W32Dasm, то функции, используемые программой вы можете посмотреть в меню "Functions" -> "Imports".

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

Рассмотрим пример блокировки работы кода проверкой строки. Для удобства поиска нужного кода поставим MessageBox. Откройте Visual Basic, добавьте на форму текстовое поле и кнопку, затем в обработчике щелчка по кнопке напишите следующий код:

Private Sub Command1_Click() Dim X& X = 43690 MsgBox "Checking value" If CLng(Trim$(Text1.Text)) = X Then MsgBox ("Complete") End Sub

Теперь откроем Olly Debugger, загрузим в него файл (предварительно откомпилируем его в VB6) и ищем вызов функции rtcMsgBox.

00401F54 FF1520104000 CALL [MSVBVM60!rtcMsgBox] ;MsgBox "Checking value" 00401F5A 8D459C LEA EAX,[EBP-64] 00401F5D 8D4DAC LEA ECX,[EBP-54] 00401F60 50 PUSH EAX 00401F61 8D55BC LEA EDX,[EBP-44] 00401F64 51 PUSH ECX 00401F65 8D45CCLEA EAX,[EBP-34] 00401F68 52 PUSH EDX 00401F69 50 PUSH EAX 00401F6A 6A04 PUSH 04 00401F6C FF1508104000 CALL [MSVBVM60!__vbaFreeVarList] 00401F72 8B0E MOV ECX,[ESI] 00401F74 83C414 ADD ESP,14 00401F77 56 PUSH ESI 00401F78 FF9104030000 CALL [ECX+00000304] 00401F7E 8D55DCLEA EDX,[EBP-24] 00401F81 50 PUSH EAX 00401F82 52 PUSH EDX 00401F83 FF1524104000 CALL [MSVBVM60!__vbaObjSet] 00401F89 8BF0 MOV ESI,EAX 00401F8B 8D4DE4LEA ECX,[EBP-1C] 00401F8E 51 PUSH ECX 00401F8F 56 PUSH ESI 00401F90 8B06 MOV EAX,[ESI] 00401F92 FF90A0000000 CALL [EAX+000000A0] 00401F98 3BC7 CMP EAX,EDI 00401F9A DBE2 FCLEX 00401F9C 7D12 JGE 00401FB0 00401F9E 68A0000000 PUSH 000000A0 00401FA3 6804184000 PUSH 00401804 00401FA8 56 PUSH ESI 00401FA9 50 PUSH EAX 00401FAA FF1518104000 CALL [MSVBVM60!__vbaHresultCheckObj] ;получение содержимого Text1.Text 00401FB0 8B55E4MOV EDX,[EBP-1C] 00401FB3 52 PUSH EDX 00401FB4 FF1514104000 CALL [MSVBVM60!rtcTrimBstr] ;Trim$ 00401FBA 8BD0 MOV EDX,EAX 00401FBC 8D4DE0 LEA ECX,[EBP-20] 00401FBF FF1584104000 CALL [MSVBVM60!__vbaStrMove] 00401FC5 50 PUSH EAX 00401FC6 FF1568104000 CALL [MSVBVM60!__vbaI4Str] ;CLng 00401FCC 33C9 XOR ECX,ECX 00401FCE 3DAAAA0000 CMP EAX,0000AAAA ;Число 43690 в HEX виде 00401FD3 8D55E0LEA EDX,[EBP-20] 00401FD6 8D45E4LEA EAX,[EBP-1C] 00401FD9 0F94C1 SETZ CL 00401FDC 52 PUSH EDX 00401FDD 50 PUSH EAX 00401FDE F7D9 NEG ECX 00401FE0 6A02 PUSH 02 00401FE2 8BF1 MOV ESI,ECX 00401FE4 FF156C104000 CALL [MSVBVM60!__vbaFreeStrList] 00401FEA 83C40C ADD ESP,0C 00401FED 8D4DDC LEA ECX,[EBP-24] 00401FF0 FF1594104000 CALL [MSVBVM60!__vbaFreeObj] 00401FF6 663BF7 CMP SI,DI 00401FF9 7463 JZ 0040205E ;переход на Complete код (MsgBox "Complete") 00401FFB B804000280 MOV EAX,80020004 00402000 8D558C LEA EDX,[EBP-74] 00402003 8D4DCCLEA ECX,[EBP-34] 00402006 8945A4 MOV [EBP-5C],EAX 00402009 895D9C MOV [EBP-64],EBX 0040200C 8945B4 MOV [EBP-4C],EAX 0040200F 895DAC MOV [EBP-54],EBX 00402012 8945C4 MOV [EBP-3C],EAX 00402015 895DBC MOV [EBP-44],EBX 00402018 C7459418184000 MOV DWORD PTR [EBP-6C],00401818 0040201F C7458C08000000 MOV DWORD PTR [EBP-74],00000008 00402026 FF157C104000 CALL [MSVBVM60!__vbaVarDup] 0040202C 8D4D9C LEA ECX,[EBP-64]

Чтобы отключить проверку значения - нужно изменить переход по адресу 00401FF9 с условного на безусловный. Для этого меняем JE на JMP (то есть 74h на EBh). Как видите - такие простенькие проверки в VB коде также малоэффективны, как и их аналоги на С++.

[Анализ значений в виде разных типов данных]

Нижеследующий текст - мой вольный перевод статей с уже указанного выше сайта: infonegocio.com. За английские версии этих статей спасибо их авторам.

- Получение данных, если они лежат строкой в открытом виде

Когда VB копирует строку в память - Вы ее можете отловить.
Для этого нужно поставить breakpoint на функцию __vbaStrCopy и когда он у Вас сработает, посмотрите код после je 66047B00 (он содержится в библиотеке и един для всех программ). Затем нужно посмотреть содержимое edx-04 (в SoftICE нужно ввести команду d edx-04). При этом вы увидите строчку, с которой работает в данный момент функция.

Exported fn(): __vbaStrCopy - Ord:008Ah :66024532 56 PUSH ESI :66024533 57 PUSH EDI :66024534 85D2 TEST EDX, EDX :66024536 8BF9 MOV EDI, ECX :66024538 0F84C2350200 JE 66047B00 :6602453E FF72FC PUSH [EDX-04] ;по этому адресу лежит строка :66024541 52 PUSH EDX

- Получение строки, если она сравнивается с введенной

Поставьте breakpoint на функцию __vbaStrComp и чуть выше ее вызова будут два push'а - они заносят в стек две Variant переменные для сравнения. Посмотрите содержимое EAX чтобы узнать адрес текста. Глянем что лежит по этому адресу, наверняка это нужная строка.

00401BC7 50 PUSH EAX ;то что Вы ввели 00401BC8 6880174000 PUSH 00401780 ;верная строка 00401BCD FF1530104000 CALL [MSVBVM60!__vbaStrComp]

- Получение данных, если происходит сравнение 4х байтовых числовых переменных

Поставьте breakpoint на функцию __vbai4str. Эта функция используется программой для перевода введенной текстовой строки в 4х байтовое число. Когда breakpoint сработает - посмотрите код ниже - там наверняка будет процедура сравнения строк. Но значение нужное нам будет в HEX виде (например десятичное число 987654321 равно шестнадцатеричному 3ADE68B1).

00401B77 FF155C104000 CALL [MSVBVM60!__vbaI4Str] 00401B7D 8D4DE0 LEA ECX, [EBP-20] 00401B80 8BF8 MOV EDI, EAX 00401B82 FF157C104000 CALL [MSVBVM60!__vbaFreeStr] 00401B88 8D4DDC LEA ECX, [EBP-24] 00401B8B FF1580104000 CALL [MSVBVM60!__vbaFreeObj] 00401B91 81FFB168DE3A CMP EDI, 3ADE68B1 ; 3ADE68B1 - это нужное значение 00401B97 7520 JNZ 00401BB9 ;функция вернет ноль если введенное ;и сравниваемое значения равны ;следовательно этот переход сработает только если ;сравниваемые данные отличны друг от друга

Тут мы можем узнать верное значение или отключить проверку вовсе (для этого нужно JNZ заменить на NOP). То есть в данном случае менять нужно байты по адресу 00401B97 с 7520 на 9090)

- Получение правильных данных при сравнивании 2х байтовых чисел

Отличие от предыдущего примера в том, что нужно ставить breakpoint на функцию __vbaI2Str. Но иногда предварительно производится преобразование строки сначала в 4х байтовое число посредством функции __vbaI2I4, а затем 4х байтовое число преобразуется в двухбайтовое.

- Обход сравнения двух переменных типа Single

Эту проверку обойти довольно просто. Ставьте breakpoint на функцию __vbaR4Str и когда он сработает - пролистните код вниз. Вы увидите переход после проверки. Просто измените условие перехода на обратное.

00401C0C FF153C104000 CALL [MSVBVM60!_vbaR4Str] 00401C12 D95DE4 FSTP REAL4 PTR [EBP-1C] 00401C15 8D4DDC LEA ECX, [EBP-24] 00401C18 8D55E0 LEA EDX, [EBP-20] 00401C1B 51 PUSH ECX 00401C1C 52 PUSH EDX 00401C1D 6A02 PUSH 02 00401C1F FF1570104000 CALL [MSVBVM60!__vbaFreeStrList] 00401C25 83C40C ADD ESP, 0C 00401C28 8D4DD8 LEA ECX, [EBP-28] 00401C2B FF1594104000 CALL [MSVBVM60!__vbaFreeObj] 00401C31 817DE43A92FC42 CMP DWORD PTR [EBP-1C], 42FC923A 00401C38 7520 JNZ 00401C5A ;если ноль, то введенно неверное значение

Замените инструкцию по адресу 00401C38 на 9090 (чтобы в данном месте не выполнялось никаких операций) или измените условный переход на противоположный. На Visual Basic нельзя проверить то, что вы заменили эти байты на два NOP'а (9090). Если у Вас все же возникают опасения, что код перестанет работать при отсутствии команд, просто введите команды, которые ничего не поменяют, например:

inc eax dec eax

Эти команды однобайтовые и как раз впишутся на место условного перехода

- Обход проверки значений с плавающей точкой (double).

Это также сделать очень просто. Ставьте breakpoint на функцию __vbaR8Str и пролистните код чуть ниже останова на breakpoint'е, Вы увидите процедуру сравнения (FCOMP REAL8 PTR [Address]), после нее идет test и jmp. Измените условие перехода на противоположное.

00401C55 FF1510104000 CALL [MSVBVM60!rtcTrimBstr] 00401C5B 8BD0 MOV EDX, EAX 00401C5D 8D4DD8 LEA ECX, DWORD PTR [EBP-28] 00401C60 FF1580104000 CALL [MSVBVM60!__vbaStrMove] 00401C66 50 PUSH EAX 00401C67 FF1560104000 CALL [MSVBVM60!__vbaR8Str] 00401C6D DC1DD8104000 FCOMP REAL8 PTR [004010D8] ;сравнение значений 00401C73 DFE0 FSTSW AX ;обработка сопроцессором 00401C75 F6C440 TEST AH, 40 ;проверка правильности значений 00401C78 7409 JE 00401C83 ;переход

Замените инструкцию по адресу 00401C78 на 9090 (чтобы в данном месте не выполнялось никаких операций) или измените условный переход на противоположный. На Visual Basic нельзя проверить то, что вы заменили эти байты на два NOP'а (9090). Если у Вас все же возникают опасения, что код перестанет работать при отсутствии команд, просто введите команды, которые ничего не поменяют, например:

inc eax dec eax

Эти команды однобайтовые и как раз впишутся на место условного перехода.

- Получение строки, если она поXORена.

Алгоритм работы бейсиковской процедуры таков. Введенная строка посимвольно переводится в ASCII коды, которые по очереди XORятся. Затем производится обратный перевод, возможно с предварительными математическими манипуляциями с ASCII кодами. ANSI коды могут XORиться со случайным или фиксированным числом.

00401E6C 50 PUSH EAX 00401E6D FF1544104000 CALL [MSVBVM60!rtcMidCharBstr] 00401E73 8BD0 MOV EDX, EAX 00401E75 8D4DC8 LEA ECX, [EBP-38] 00401E78 FFD6 CALL ESI 00401E7A 50 PUSH EAX 00401E7B FF1518104000 CALL [MSVBVM60!rtcAnsiValueBstr] 00401E81 0FBFC8 MOVSX ECX, AX 00401E84 81F191000000 XOR ECX, 00000091 ;ANSI XOR 91 00401E8A 51 PUSH ECX ;результат XOR'а 00401E8B FF1570104000 CALL [MSVBVM60!rtcBstrFromAnsi]

Ставим breakpoint на 00401E8A 51 PUSH ECX и отслеживаем изменение содержимого регистра ECX при прохождении цикла. Когда программа пройдет весь цикл - вы получите расшифрованную строку.

CALL [MSVBVM60!rtcMidCharBstr] ;получает один символ из строки CALL [MSVBVM60!rtcAnsiValueBstr] ;получает из символа Ansi код CALL [MSVBVM60!rtcBstrFromAnsi] ;преобразует Ansi код в строку

В этом случае ниже скорее всего будет сравнение и переход, при этом CALL [MSVBVM60!__vbaStrComp] может быть использован для сравнения строк. Если не производится обращение к MSVBVM60!rtcBstrFromAnsi тогда со строкой производятся определенные математические манипуляции, при этом она может быть представлена как 2х или 4х байтное число или число с плавающей точкой. Об этих случаях было сказано выше.

[Заключение]

Надеюсь, что прочитав данную статью Вас уже больше не пугает анализ VB кода. Чтобы посмотреть как выглядить VB код вживую - советую посмотреть мои примеры (лежат на данном сайте в разделе "Разное").

Еще раз благодарю авторов сайта infonegocio.com, так как если бы не их туториалы на английском, то этой статьи возможно и не было бы.

Удачи и спасибо за то, что дочитали статью до конца.


Комментарии

отсутствуют

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


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

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

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

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