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

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

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

Вставка ассемблерных процедур в код Visual Basic: миф или реальность? Часть 2


Использование переменных при написании Asm функций и методика работы
с API функциями

ВСТУПЛЕНИЕ

Помнится не так давно я написал статью про встраивание ассемблерных процедур в код на Visual Basic 6.0. Оказалось, что тогда я кое чего не знал и не рассмотрел довольно важные аспекты. Во второй части данной статьи я постараюсь раскрыть возможности объявления переменных в ассемблерном коде, а также вызов API функций. На самом деле это возможно и не так сложно в реализации, как кажется. Думаю, прочитав эту статью Вы без проблем разберетесь с этими возможностями, а также научитесь их применять на практике.

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

ОБЪЯВЛЕНИЕ И РАБОТА С ПЕРЕМЕННЫМИ

Довольно неудобно писать код на ассемблере не используя разного рода переменные. Согласитесь, гораздо полезнее бывает объявить какую либо переменную, занести в нее определенное значение и использовать потом только переменную, чем всегда подставлять численное значение. Как не странно, используемый нами компилятор nasm это позволяет. Для решения подобных задач используется оператор "%define", синтаксис которого мы сейчас и будем рассматривать.
Основная цель данного оператора - описать имя и размещение локальной переменной в стеке, для чего используются два основных параметра - имя и адрес. То есть для того чтобы объявить переменную strColor по адресу ebp-4, мы перед кодом ассемблерной функции до директивы [BITS 32], введем следующее:

%define strColor [ebp-4]

Тем же методом можно объявить любую новую переменную.

Компилятор nasm налагает некоторые ограничения на использование переменных. Мы рассмотрим лишь допустимые варианты использования.

Перемещение адреса в 4х байтовую переменную:

mov ebx, [eax] mov Pic1HDC, ebx

Вызов функции, которая расположена по адресу, находящимся в переменной ApiGetTickCount (команды занесения адреса в переменную я приводить не буду):

mov ebx, ApiGetTickCount call ebx

То есть простейшие операции поддерживаются, более же сложные операции требуют задания размерности переменных:

Из этого следует, что так мы записать не можем:

mov ebx, [eax + strAddressAdd]

а так вполне себе:

mov ebx, [eax + dword[strAddressAdd]]

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

РАБОТА С API ФУНКЦИЯМИ

Прежде чем рассматривать данный вопрос, нам нужно определиться с надобностью подобного. А она заключается в следующем. В некоторых программах есть потребность в цикличном вызове API функций, например при работе с графикой. Функций WIN API для работы с графикой довольно много, вместе с медленной работой циклов в Visual Basic 6.0 их использование не приводит к желаемым результатам в скорости. Попробуйте попиксельно перенести картинку из одного PictureBox’а в другой. Вряд ли вы дождетесь завершения этой операции, хотя на ассемблере это можно выполнить за довольно небольшой промежуток времени.

Сначала рассмотрим объявление API функций в коде на VB. Откройте новый проект и в самой верхней части кода сделайте объявление необходимых функций (включая функцию для вызова ассемблерной процедуры, которая будет рассматриваться ниже).
Еще нам понадобятся два PictureBox’а, в один из которых необходимо поместить любую картинку (ее мы будем перемещать попиксельно в другой PictureBox ассемблерной функцией).

Private Declare Function SetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long) As Long Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long Private Declare Function GetTickCount Lib "kernel32" () As Long

Объявили? Отлично! Переходим далее.
Для начала нам необходимо определить адрес данных функций в памяти, чтобы передать их в ассемблерную программу. Для этого мы используем API функции GetModuleHandle и GetProcAddress. Теперь создадим массив параметров (то есть параметр, передаваемый в API функцию будет один - адрес на первую ячейку массива) и занесем все важные параметры PictureBox’ов: hdc, left, top, width и height, не забыв перевести все линейные размеры PictureBox’а в пиксельное представление (данная операции заключается в делении стандартных размеров на 15).

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

;заносим 4 параметра в стек mov edx, Y push edx mov ecx, X push ecx mov ebx, Pic1HDC push ebx ;вызываем API функцию mov ebx, ApiGetPixel call ebx

Напомню, что результат работы функции всегда будет помещен в регистр eax.

Вот сам код, выполняющий копирование картинки:

;создаем переменные в стеке

%define Pic1HDC [ebp-4]
%define Pic2HDC [ebp-8] %define Pic1Width [ebp-12] %define Pic1Height [ebp-16] %define Pic2Width [ebp-20] %define Pic2Height [ebp-24] %define ApiSetPixel [ebp-28] %define ApiGetPixel [ebp-32] %define ApiGetTickCount [ebp-36] %define X [ebp-40] %define Y [ebp-44] %define Time [ebp-48] ;даем компилятору понять, что все регистры 32х битные [BITS 32] ;изменяем указатель на вершину стека и заносим содержимое ;изменяемых нами регистров в стек push ebp mov ebp, esp sub esp, 48 push ebx push esi push edi push edx ;заносим в eax адрес первой ячейки массива mov eax, [ebp+8] ;задаем значения переменных mov ebx, [eax] mov Pic1HDC, ebx mov ebx, [eax+4] mov Pic1Width, ebx mov ebx, [eax+8] mov Pic1Height, ebx mov ebx, [eax+12] mov Pic2HDC, ebx mov ebx, [eax+16] mov Pic2Width, ebx mov ebx, [eax+20] mov Pic2Height, ebx mov ebx, [eax+24] mov ApiSetPixel, ebx mov ebx, [eax+28] mov ApiGetPixel, ebx mov ebx, [eax+32] mov ApiGetTickCount, ebx ;вызываем API функцию счетчика времени для того, чтобы ;засечь время начала работы функции mov ebx, ApiGetTickCount call ebx ;заносим в переменную Time начальный показатель счетчика времени mov Time, eax ;далее приступаем к цикличному копированию содержимого ;одного PictureBox’а в другой. Данный код не требует ;комментариев для человека, хотя бы поверхностно знающего ассемблер mov ecx, 0 mov Y, ecx for: mov eax, 0 mov X, eax for1: mov edx, Y push edx mov ecx, X push ecx mov ebx, Pic1HDC push ebx mov ebx, ApiGetPixel call ebx push eax mov edx, Y push edx mov ecx, X push ecx mov ebx, Pic2HDC push ebx mov ebx, ApiSetPixel call ebx mov eax, X inc eax mov ebx, Pic1Width cmp eax, ebx mov X, eax jne for1 mov eax, Y inc eax mov ebx, Pic1Height cmp eax, ebx mov Y, eax jne for ;вызываем API функцию счетчика времени для того, чтобы ;засечь время окончания работы функции mov ebx, ApiGetTickCount call ebx ;заносим в eax время работы функции mov edx, Time sbb eax, edx ;считываем ранее сохраненные переменные из стека ;конечно рациональнее было бы использовать popad и pushad, ;но я решил сохранять и восстанавливать только изменяемые ;регистры, что технически более правильно pop edx pop edi pop esi pop ebx mov esp,ebp pop ebp ;возвращаем управление программе ret

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

Теперь для проверки работы написанной функции нам необходимо ее откомпилировать и перевести в HEX последовательность, что с легкостью можно выполнить с помощью программы Asm to VB, которую я написал для упрощения данной задачи. Поле этого нам необходимо присвоить полученную HEX последовательность любой переменной в VB и занести все ее содержимое в байтовый массив используя функцию, приведенную в первой части данной статьи.
Далее останется только вызвать эту функцию из VB, предварительно передав ей параметры. Результат работы функции мы будем передавать в eax (если вы помните - это время работы функции), сами же операции функция производит непосредственно в памяти и результат своей работы не возвращает.

Ниже я приведу возможную реализацию кода-обвязки ассемблерной функции из VB без комментариев, так как все это описано в первой части статьи. Добавлю, что Вы должны поместить на форму CommandButton cmdRunAsm:

Const Temp = "5589E581EC30000000535657528B45088B18895DFC8B58 04895DF48B5808895DF08B580C895DF88B5810895DEC8B5814895DE88B5818895 DE48B581C895DE08B5820895DDC8B5DDCFFD38945D0B900000000894DD4B80000 00008945D88B55D4528B4DD8518B5DFC538B5DE0FFD3508B55D4528B4DD8518B5 DF8538B5DE4FFD38B45D8408B5DF439D88945D875CF8B45D4408B5DF039D88945 D475B98B5DDCFFD38B55D019D05A5F5E5B89EC5DC300" Private AsmProc() As Byte Private AsmAdr As Long Private Param(8) As Long Private ParamAdr As Long Private Sub cmdRunAsm_Click() t = GetTickCount t = GetPixel(Picture1(0).hdc, 0, 0) SetPixel Picture1(1), 0, 0, 0 Param(0) = Picture1(0).hdc Param(1) = Picture1(0).Width / Screen.TwipsPerPixelX Param(2) = Picture1(0).Height / Screen.TwipsPerPixelY Param(3) = Picture1(1).hdc Param(4) = Picture1(1).Width / Screen.TwipsPerPixelX Param(5) = Picture1(1).Height / Screen.TwipsPerPixelY Param(6) = GetProcAddress(GetModuleHandle("gdi32"), "SetPixel") Param(7) = GetProcAddress(GetModuleHandle("gdi32"), "GetPixel") Param(8) = GetProcAddress(GetModuleHandle("kernel32"), "GetTickCount") ParamAdr = VarPtr(Param(0)) n = GetTickCount a = CallWindowProc(AsmAdr, ParamAdr, 0, 0, 0) v = GetTickCount - n MsgBox Str$(a), , "Время прорисовки:" MsgBox Str$(v - a), , "Время, которое барсик вызывал функцию" End Sub Private Sub Form_Load() ReDim AsmProc((Len(Temp) / 2) - 1) For i = 1 To Len(Temp) Step 2 AsmProc(Index) = Val("&H" + Mid$(Temp, i, 2)) Index = Index + 1 Next AsmAdr = VarPtr(AsmProc(0)) End Sub

Теперь жмем F5 и радуемся результату. Теперь Вы умеете совмещать знание ассемблера и Visual Basic 6.0 и применять эти знания для решения сложных ресурсоемких задач, требующих высокой скорости работы.

Исходник к статье можно скачать отсюда

Отдельное спасибо хочу выразить Юрасову Евгению, так как именно он практиковался на вызове API функций и именно он посоветовал описать эту возможность в статье. Также хочется поблагодарить Лапшина Алексея за рецензирование.


Комментарии

отсутствуют

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


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

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

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

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