Главная
 
НовостиПятница, 17.05.2024, 07:41



Приветствую Вас Гость | RSS
Главная
Меню сайта

Категории каталога
Игры [50]
Программирование [1]
Безопасность [1]

Мини-чат

Наш опрос
Оцените мой сайт
Всего ответов: 14

Главная » Статьи » Компьютер » Безопасность

Вирусология

Содержание

1. Введение
2. Экскурс в историю
3. Способы эмуляции
4. Пример использования технологии
4.1. Лирическое отступление
4.2. Имитация исполнения инструкций
4.2.1. Запуск инструкции в специальной среде
4.2.2. Полная имитация исполнения инструкции
4.2.3. Комбинация двух способов
4.3. Поворот не туда
4.3.1. Дизассемблер
4.3.2. Эмулятор
4.4. "Предел терпения" (Enough)
5. Пример использования технологии для детектирования вирусов
5.1. Кодо-анализатор
6. Заключение

1. Введение

 Эта статья не является продолжением пособия по написанию антивирусных программ, это всего лишь теоретическое описание технологии используемой в «хороших» антивирусных программах. Причем технологии далеко не примитивной, а очень и очень сложной для понимания и реализации …

 Это описание не является отличным или хорошим это всего лишь базис (основы) технологии, как я понимаю это, вполне возможно я понимаю ее неправильно, этого я не отрицаю.

 В этой статье не будет информации о нахождении точки входа в запускаемых файлах, информации о различных терминах (полиморфик, сигнатура …), детектировании и лечении вирусов … если вы читаете эту статью, значит, все это вы уже должны знать.

 Тем, кому не интересны мои размышления о возможной истории появления технологии, могут пропустить «Экскурс в историю» и перейти непосредственно к описанию алгоритма технологии.

2. Экскурс в историю

 Непосредственное использование эмуляторов программного кода (в антивирусных программах) появилось в начале 90ых. Толчком стал выход первого полноценного полиморфного вируса, точнее сказать полиморфного движка.

 Полиморфным движком – называется «универсальный» программный код (библиотека), подключив который в вирус (и не только) можно сделать его полиморфным.

 Этот движок назывался просто - MuTation Engine (MTE). После его выхода антивирусные конторы стали хвататься за голову (особенно американские), некоторые чувствовали, что что-то подобное скоро выйдет, уже обдумывали универсальные методы по детектированию шифрованных вирусов.

  Кстати автором MTE являлся программист из Болгарии, более известный под псевдонимом Dark Avenger. Он же автор культовых вирусов Eddie и если я не ошибаюсь Dir так же поделки этого человека. Именно этот человек двигал «прогресс» (в плане разработки новых вирусных технологий) в конце 80ых годов.

  Полиморфикам предшествовали шифрованные (иногда их так же называют «само шифрующимися») вирусы, для детектирования которых антивирусы в качестве сигнатур использовали постоянные участки расшифровщиков вирусного кода. Если сигнатура совпадала, то из расшифровщика брались необходимые данные (например, алгоритм шифровки, если он менялся, ключ …) и использовались для расшифровки вирусного кода, затем вторая сигнатура проверяла расшифрованный код на наличие вируса. Однако это очень и очень неудобно, процесс детектирования одного такого вируса занимает много кода, и используя такой тип детектирования шифрованных вирусов для каждого вируса придется иметь две сигнатуры, специальную процедуру по расшифровке … но задумываться об универсальном способе детектирования шифрованных вирусов создатели не хотели, пока … не появился MTE.

 Конечно, очень многие полиморфики можно детектировать не эмуляцией кода расшифровщика, а различными алгоритмическими процедурами. Но опять же это очень сложные процедуры, причем они редко дают сто процентный результат определения зараженности файла вирусом, особенно если полиморфность расшифровщика достаточно высока (т.е. расшифровщик имеет очень мало сигнатур или не имеет их вообще). В итоге из 100 файлов зараженных вирусом использующим «слабенький» полиморфный алгоритм, антивирусы обнаруживали только 60 или немного больше (меньше).

 Несмотря на геморрой, связанный с таким детектированием многие антивирусные компании решили выбрать этот сложный путь (например, взять ту же McAfee), то время, пока авторы вирусов только учились новой технологии полиморфизма, еще можно было использовать. Но в 1994 году, в Англии (уже не в Болгарии), появляется очень серьезный полиморфный движок SMEG (Simulated Metamorphic Encryption Generator). Определять «старым способом» декрипторы созданные по алгоритмам использовавшимся в этом движке практически невозможно, а кроме того нужно еще и расшифровывать вирусное тело, что бы излечить инфицированный файл! Ходили слухи, что английская полиция летом 1995 года, арестовала автора известного под кличкой Black Baron, за создание опасных вирусов Pathogen и Quueg и трех версий полиморфных движков SMEG. Но к этому моменту было уже достаточно умных программистов, способных писать гораздо более продвинутые полиморфики. Примерно в это же время, в Словакии, появляется Explosion’s Mutation Machine (от «культовой» личности – Vyvojar, он же автор знаменитого вируса OneHalf). Немного позже в России (Санкт-Петербурге) появляется Zhengxi, от одноименного автора. Но наилучшей разработкой под DOS (по моему мнению) стал Red Team Polymorphy от человека под псевдонимом SoulManager (который был выпущен в 1997 году, с тех пор так и остался «неконкурентоспособным» ;-) ).

 Конечно, каждой вирусной технологии можно противопоставить обратную (антивирусную), но теперь факт заключался лишь в том, что хороший полиморфик написать гораздо проще, чем его обнаруживать. Так полиморфики и полиморфные движки стали появляться как грибы после дождя, в результате (к сожалению многих сторонников oldschool) пришлось завязать с алгоритмическим детектированием. Хотя был такой русский антивирус, который просто игнорировал появление полиморфных вирусов. Но это длилось до поры, до времени, пока в Россию не пришел вирус OneHalf, который стал распространяться с бешеной скоростью и этот антивирус оказался бессильным против OH. Позже проект был закрыт, автор антивируса так и не смог (а может, просто не хотел) включить в свое творение эмулятор программного кода.

 Затем появился русский Dr. Web. Насколько я знаю, он изначально создавался именно для детектирования полиморфных вирусов, одной из первых процедур в нем был заложен эмулятор кода.

3. Способы эмуляции

 Каждый уважающий себя антивирус должен содержать в себе эмулятор. Если мы исключим известные антивирусы и возьмем «кустарные», то я знаю только несколько программ, которые  содержали реализацию эмулятора (конечно кривые, убогие, но они работали и достаточно сносно). Это MultiScan (конечно, кустарным, его можно назвать с натяжкой), Lecar (содержал некоторое подобие) и мое творение the_Sweeper. Я смотрел исходные тексты некоторых иностранных программ, но там все детектировалось по плавающим сигнатурам или алгоритмами, иногда (но очень редко) использовали обычную трассировку.

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

 Первый способ подразумевает собой обычную трассировку программы, т.е. ее загрузку в память и исполнение путем использования отладочного прерывания (int 1). Таким методом пользуется большинство отладчиков, но вся проблема в том, что даже безрукий человек может обломать процесс трассировки. В крайнем случае, есть множество статей на тему облома и TD и SoftIce. А если в полиморфном расшифровщике вставлены антиотладочные трюки, которые в случае обнаружения трассировки запускают какую-нибудь деструкцию. В результате антивирус сам запустит деструкцию в процессе эмуляции и только навредит. Кстати такой способ для детектирования вирусов семейства SMEG, использовал в своем антивирусе американец StormBringer.

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

4. Пример использования технологии

 В своем антивирусе “the_Sweeper” я использовал некоторую комбинацию этих двух способов, в результате получилось что-то совсем простенькое и кривое. Но антивирус мог ловить OneHalf, TMC, Pieck Примитивную реализацию алгоритма схожего с тем, что я использовал в антивирусе, представляю на Ваш суд.

 

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

 Программа состоит из:

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

 Программа работает по алгоритму:

  • Процедура (рас)шифровки вместе с текстом, который она должна шифровать (или расшифровывать) переносится в буфер.
  • Эмулятор запускает код, который был перенесен в этот буфер на «псевдоисполнение».  В результате текстовая строка, которая также содержалась в этом буфере за процедурой (рас)шифровки будет зашифрована.
  • Далее используется процедура (рас)шифровки, которая содержится в программе и расшифровывается текстовую строку из буфера (которуа, ранее, была зашифрована эмулятором).
  • На экран выводится содержимое текстовой строки,содержащейся в буфере.

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

[emul.asm]

; name : "code emulation" example
; author : andy [Most Needful Things]
; home : http://amethyst.nm.ru
;
; Пример программы умеющей выполнять "псевдо-эмуляцию" программного кода.
;
; компилировать:
; tasm32 -ml -m5 emul.asm
; tlink32 -Tpe -c -x emul.obj ,,, import32
;
.386P
.model flat, stdcall
jumps
;
extrn lstrlenA:near ; список needful апи
extrn _wsprintfA:near
extrn GetStdHandle:near
extrn WriteConsoleA:near
extrn ExitProcess:near
;
.data
_stack db 10000 dup (?) ; буфер под стэк
temp dd ?
instr_buf: db 30 dup (?) ; буфер для запуска инструкции в "карантине"
buffer db 10000 dup (?) ; буфер под код
flags dd ? ; значение флагового регистра EFLAGS эмулируемого кода
regs dd 08 dup (?) ; значения eax/ecx/edx/ebx/esp/ebp/esi/edi эмулируемого кода
;
.code
start: mov esi,offset decryptor ; смещ. (рас)шифрощика и текста
mov ecx,(emulate-decryptor)/4 ; размер (рас)шифровщика и текста
mov edi,offset buffer ; перенесем данные в буфер
rep movsd
;
push offset decryptor ; старое значение ip (рас)шифровщика
push (emulate-decryptor) ; размер кода для эмуляции
push offset buffer ; расположение кода
call emulate ; эмулируем, т.е. зашифровываем
; текст идущий после (рас)шифровщика
;
mov edi,offset buffer ; esi - смещ. буфера
add edi,(message-decryptor)
push edi
call decryptor_sze ; расшифруем текст из буфера
pop esi
;
; В данный момент ESI содержит смещение расшифрованного текста, теперь осталось вывести
; текст на экран. Поддержку консоли я убрал из исходных текстов, представленных в статье (полные
; доступны в архиве).
;

;
exit: push 0
call ExitProcess
;
; decryptor - простейший алгоритм (раз)шифроки текста идущего после ...
;
; Эмулятор "не понимает" инструкцию nop, которая следует после цикла
; (рас)шифровки, по этому когда он дойдет до инструкции, то выйдет с "ошибкой".
; Но самое главное, что данные будут (за/рас)шифрованны ... в противном случае
; эмулятор бы просто завис в вечном цикле. ;-)
;
decryptor:mov edi,offset message
pushfd
popfd
call aaa
jmp decryptor_sze
db 125 dup (?)
aaa: sub edi,0ffffffh
add edi,0ffffffh-004h
scasd
ret
decryptor_sze:
mov ecx,0
add cl,( (emulate-message) / 4 )
decrypt_loop:
xor dword ptr [edi],0ffffffh
scasd
loop decrypt_loop
nop
ret
message db 'Hey you, ugly face!',13,10
db 'Give me the bottle of tequilla!',13,10
db 'Hurry up, don''t make me mad!',13,10,0
;
include inc\emulator.inc ; эмуляция кода
include inc\disasm.inc ; дизассемблер
;
end start
[inc\emulator.inc]
.code
;
; emulate - эмуляция участка кода
;
; on start : ip - оригинальный ip блока
; codesize - размер блока для эмуляции
; codeloc - смещение на код для эмуляции
; on exit : - - - - -
;
emulate proc codeloc: dword, codesize: dword, ip: dword
;
; процесс инициализации эмулятора, перед работой с участком кода
; а) сохранение значения регистров
; б) установка параметров переданных в процедуру (ip/codesize/codeloc)
; в) установка значения стека, для эмуляции кода
;
emul_init:
push eax ecx edx ebx ebp esi edi
mov esi,offset _stack+10000 ; буфер под стек, для эмуляции
mov dword ptr [regs+016],esi ; установим значение
mov esi,codeloc ; смещение на начало кода
mov ebx,esi
add ebx,codesize ; смещение на конец кода
;
emulate_loop:
push esi ; включаем кодо-анализатор
call disasm ; помощью дизассемблера
cmp eax,-1 ; если инструкция известна
jnz copy_instr ; дизассемблеру, продолжим разбор
emulate_error:
jmp emulate_ret ; иначе (!) тихо выйдем
;
; перенесем в буфер "instr_buf" инструкции для эмуляции стека и
; разобранную дизассемблером инструкцию размера ecx
;
; в результате получим в "instr_buf" примерно:
;
; mov dword ptr [res_stack+1],esp
; mov esp,dword ptr [regs+016]
; ... разобранная инструкция ...
; mov dword ptr [regs+016],esp
; res_stack: mov esp, ?
; ret
;
copy_instr:
push eax ecx ; запомним данные о инструкции, которую разбирал дизассемблер
mov edi,offset instr_buf
mov ax,2589h
stosw ; перенесем "mov 4 ptr [?],esp"
;
mov eax,offset instr_buf ; рассчитаем смещение
add eax,ecx ; "res_stack+1" (см. выше) и
add eax,6+6+6+1 ; перенесем остаток инструкции
stosd
;
mov ax,258Bh ; перенесем инструкцию
stosw ; "mov esp,4 ptr [regs+16]"
mov eax,offset regs+016
stosd
;
rep movsb ; перенесем разобранную, дизассемблером инструкцию
;
mov ax,2589h ; перенесем инструкцию
stosw ; "mov 4 ptr [regs+16],esp"
mov eax,offset regs+016
stosd
;
mov al,0bch ; перенесем инструкцию
stosb ; "mov esp,?"
stosd
mov al,0c3h ; "поставим последнюю точку" - перенесем "ret"
stosb

;
pop ecx eax ; вспомним данные инструкции
;
; работа с инструкциями, которые требуют специальной имитации исполнения
; первый этап - определение типа инструкции
;
push esi
pop edi ; edi - смещение инструкции
sub edi,ecx ; которую разбирали
; инструкции размером 1 байт
@one_byte:
cmp cl,1
jnz @two_byte
cmp byte ptr [edi],0c3h ;
jz @found_ret ; найден "ret" ?
; инструкции размером 2 байта
@two_byte:
cmp cl,2
jc run_instr ; если размер меньше или не равен
jnz @three_byte
@check_xor: cmp byte ptr [edi],031h ;
jz @found_xor ; (де)шифрующая инструкция xor?
@check_loop: cmp byte ptr [edi],0e2h ;
jz @found_loop ; найден "loop"?
jmp run_instr
; инструкции размером 3 байта
@three_byte:
cmp cl,3
jc run_instr ; если размер меньше или не равен
jnz @five_byte
@check_xor2:
cmp byte ptr [edi],030h ; найдена разновидность
jz @found_xor ; (де)шифрующей инструкции xor?
jmp run_instr
; инструкции размером 5 байт
@five_byte:
cmp cl,5
jc run_instr ; если размер меньше или не равен
jnz @six_byte ; 5 байтам запустим в "карантине"
cmp byte ptr [edi],0e8h ;
jz @found_call ; найден "call" ?
cmp byte ptr [edi],0e9h ;
jz @found_jmp ; найден "jmp" ?
jmp run_instr
; инструкции размером 6 байт
@six_byte:
cmp cl,6
jc run_instr ; если размер меньше или не равен
jnz run_instr ; 6 байтам запустим в "карантине"
cmp byte ptr [edi],081h ; "add/sub/xor [ereg], dword" ?
jnz run_instr ; нет, запустим в "карантине"
cmp byte ptr [edi+1],037h ; нам нужен только "xor", а
jna @found_xor ; остальные можно просто запустить
jmp run_instr ; на исполнение в "карантине"
;
; работа с инструкциями, которые требуют специальной имитации исполнения
; второй этап - имитация исполнения инструкции
;
; инструкции размером 1 байт
; имитировать исполнение инструкции "ret" очень просто:
; а) необходимо взять двойное слово из стека эмулируемого кода
; смещение на это слово содержится в "regs+16"
; б) это слово будет новым указателем на смещение разбираемой эмулятором
; инструкции (значение регистра esi)
; в) убрать из стека эмулируемого кода, взятое значение
; add dword ptr [regs+16],4
;
@found_ret:
mov esi,dword ptr [regs+016]
lodsd
add dword ptr [regs+016],4
xchg esi,eax
jmp emulate_check
; инструкции размером 2 байта
; Первая часть процесса имитации выполнения дешифрующей инструкции
;
; сейчас буфер "dregs" содержит примерно следующие данные:
; dregs + 000 : ????? (количество регистров использующихся в инструкции)
; dregs + 004 : номер регистра 1
; dregs + 008 : номер регистра 2
; dregs + n : номер регистра n / 4
;
; Нам необходимо определить сумму значений регистров (в данном случае двух)
; использующихся в инструкции и передать ее во вторую часть процесса. В нашем
; случае это будет выглядеть примерно так:
;
; a = [dregs+004] * 4
; x = [regs+a]
; a = [dregs+008] * 4
; x = x + [regs+a]
; и так далее ...
;
; где x является той самой суммой, которую необходимо узнать
;
@found_xor:
push ebx esi
sub edx,edx
mov dl,4
sub ebx,ebx
mov esi,offset dregs
lodsd
xchg eax,ecx
@fxor_load_lp:
push edx
lodsd
mul edx
add ebx,dword ptr [regs+eax]
pop edx
loop @fxor_load_lp
xchg ebx,ecx
pop esi ebx
;
; Вторая часть процесса имитации выполнения …
;
; допустим (только допустим!), что максимальный размер расшифровщика
; может составлять 200 байт
; значит (!) если сумма значений регистров (x) находится в промежутке
; от ip до ip+200,
; то менять значение не обязательно, вполне возможно что:
; - декриптор содержит в себе процедуру определения ip
; - значение регистра уже менялось раньше, т.е. инструкция выполняется
; не первый раз
; если одно из вышеуказанных утверждений правда (true), то просто запускаем
; инструкцию на выполнение в специальных условиях
;
@found_xor_ip:
mov edx,ip
cmp ecx,edx
jc run_instr
add edx,200
cmp ecx,edx
ja run_instr
;
; Третья часть процесса имитации выполнения …
;
; если значение x выпадает из промежутка,
; то: a) необходимо рассчитать разницу между старым ip и x (суммой
; значений регистров)
; б) добавить эту разницу к смещению начала буфера (codeloc), в который
; мы копировали эмулируемый код
; в) полученное смещение поместить в значение регистра "dreg+004"
; г) значение остальных регистров использующихся в инструкции обнулить
;
sub ecx,ip
add ecx,codeloc
;
; новое смещение зашифрованного кода, полученное действиями, описанными
; в пунктах "а" и "б" (см. выше), положив в регистр номер "dreg+004"
;
mov eax,dword ptr [dregs+004]
sub edx,edx
mov dl,4
push edx
mul edx
pop edx
mov dword ptr [regs+eax],ecx
;
; цикл, для обнуления значений всех регистров начиная с "dreg+008"
;
push esi
mov esi,offset dregs
lodsd
cmp al,02
jc @fxor_emu_ret
xchg eax,ecx
dec ecx
lodsd
@fxor_setz_lp:
push edx
lodsd
mul edx
mov dword ptr [regs+eax],0
pop edx
loop @fxor_setz_lp
@fxor_emu_ret:
pop esi
jmp run_instr ; можно запустить инструкцию на выполнение
;
; имитируя исполнение инструкции "loop", мы должны уменьшить значение ecx
; эмулируемого блока на 1, если он не будет равен 0, то перейти к "началу цикла"
;
@found_loop:
dec dword ptr [regs+004] ; уменьшим значение регистра
cmp dword ptr [regs+004],0 ; "ecx" эмулируемого кода
jz emulate_check ; если равно 0, выйдем из цикла
sub esi,ecx ; иначе рассчитаем смещение
sub esi,eax ; начала цикла
jmp emulate_check ; перейдем к следующей инструкии
; инструкции размером 5 байт
; если перед нами инструкции "call" и "jmp" то необходимо изменить смещение
; (значение esi) инструкции, которая будет следующей исполняться эмулятором
; НО в случае если мы разбираем "call", нужно сохранить в стеке эмулируемого
; участка смещение команды следующей прямо за call'ом, что бы потом по встрече
; "ret" вернуться "на путь истинный"
;
@found_call:
push eax edi
mov eax,esi
sub dword ptr [regs+016],4
mov edi,dword ptr [regs+016]
stosd
pop edi eax
@found_jmp:
add esi,eax ; изменим esi
;
; esi - указатель на инструкцию для разбора
; ebx - предел, за который выйти "непростительно"
;
emulate_check:
cmp esi,ebx ; если мы не вышли за пределы
jc emulate_loop ; продолжим цикл эмуляции
emulate_ret:
pop edi esi ebp ebx edx ecx eax
ret
;
; "прямой запуск" разобранной инструкции в специальных условиях (карантине)
;
; запомним значение регистров программы-эмулятора в стеке
;
run_instr:push eax ecx edx ebx ebp esi edi
;
; запомним значение флагов программы-эмулятора в стеке, затем перенесем
; в регистр eax, а из него в переменную "temp"
;
save_orig_efl:
pushfd
mov eax,dword ptr [esp]
mov dword ptr [temp],eax
popfd
;
; значение флагов эмулируемого кода перенесем из переменной "flags" в регистр
; eax, а из него в стек использующийся программой-эмулятором
;
set_emul_efl:
pushfd
mov eax,dword ptr [flags]
mov dword ptr [esp],eax
;
; установим значения регистров эмулируемого кода из переменной "regs"
; значение esp не устанавливается, иначе мы рискуем испортить значение флагов
; эмулируемого кода, стек будет перенаправлен в подпрограмме "instr_buf"
;
mov eax,dword ptr [regs+000]
mov ecx,dword ptr [regs+004]
mov edx,dword ptr [regs+008]
mov ebx,dword ptr [regs+012]
mov ebp,dword ptr [regs+020]
mov esi,dword ptr [regs+024]
mov edi,dword ptr [regs+028]
;
; инструкция "popfd" установит значение флагов эмулируемого кода, которое мы сохраняли выше
;
popfd
call instr_buf ; запустим на исполнение
;
; сохраним новые значения регистров эмулируемого кода в переменной "regs"
;
mov dword ptr [regs+000],eax
mov dword ptr [regs+004],ecx
mov dword ptr [regs+008],edx
mov dword ptr [regs+012],ebx
mov dword ptr [regs+020],ebp
mov dword ptr [regs+024],esi
mov dword ptr [regs+028],edi
;
; новое значение флагов эмулируемого кода запомним в стеке, затем перенесем
; в регистр eax и сохраним в переменной "flags"
;
save_emul_efl:
pushfd
mov eax,dword ptr [esp]
mov dword ptr [flags],eax
popfd
;
; установим значение флагов программы-эмулятора из переменной "temp"
;
set_orig_efl:
pushfd
mov eax,dword ptr [temp]
mov dword ptr [esp],eax
popfd
;
; восстановим значение регистров программы-эмулятора
;
pop edi esi ebp ebx edx ecx eax
jmp emulate_check
endp
;
[inc\disasm.inc]
.data
dregs dd 010 dup (?) ; буфер для хранения данных о регистрах использующихся в дешифрующих инструкциях
.code
;
; disasm - дизассемблер, предназначается для "работы" с инструкциями
;
; Определяет тип, размер и параметры инструкции (использующиеся регистры, значения и тд). В том
; случае, если данные присутствуют в базе.
; Данные о дешифрующих инструкциях (количество регистров использующихся для указания
; смещения расшифровываемых данных и регистры) складываются в буффер "dregs".
;
; on start : oins - смещение инструкции
; on exit : eax = -1 если указанная инструкция неизвестна процедуре
; ecx - размер инструкции
; eax, edx - параметры, использующиеся в инструкции
;
disasm proc oins: dword
;
mov dword ptr [dregs],0
sub ecx,ecx
push esi ; запомним значение
mov esi,oins ; esi - смещение инструкции
lodsb ; al - первый байт инструкции
;
; по первому байту распознаем тип инструкции
;
@add_al_byte:
cmp al,004h
jz _@add_al_byte
@add_eax_dd:
cmp al,005h
jz _@add_eax_dd
@sub_ereg_ereg:
cmp al,02bh
jz _@sub_ereg_ereg
@xor_2ereg_b:
cmp al,030h
jz _@xor_2ereg_b
@xor_ereg_ereg:
cmp al,031h
jz _@xor_ereg_ereg
;
@inc_ereg:
cmp al,040h
jc unknown_instr
cmp al,047h
jbe _@inc_ereg
@push_ereg:
cmp al,050h
jc unknown_instr
cmp al,057h
jbe _@push_ereg
@pop_ereg: cmp al,058h
jc unknown_instr
cmp al,05Fh
jbe _@pop_ereg
@pushad:
cmp al,060h
jz _@one_byte
@add_reg_byte:
cmp al,080h
jz _@add_reg_byte
@xas_ereg_dd:
cmp al,081h ; может быть XOR/ADD/SUB [EREG],DD
jz _@xas_ereg ; проверка в "_@xas_ereg"
@xas_ereg_b:
cmp al,083h
jz _@xas_ereg
@lea_er_er_dd:
cmp al,08Dh
jz _@lea_er_er_dd
@pushfd:
cmp al,09Ch
jz _@one_byte
@popfd:
cmp al,09Dh
jz _@one_byte
@scasd: cmp al,0AFh
jz _@one_byte
@mov_reg_byte:
cmp al,0B0h
jc unknown_instr
cmp al,0B7h
jbe _@mov_reg_byte
@mov_ereg_dd:
cmp al,0B8h
jc unknown_instr
cmp al,0BFh
jbe _@mov_ereg_dd
@ret_byte:
cmp al,0C3h
jz _@one_byte
@loop_byte:
cmp al,0E2h
jz _@loop_byte
@call_dword:
cmp al,0E8h ;
jz _@cj_dword ; инструкции call и jmp
@jmp_dword: ;
cmp al,0E9h ; требуют одинакового разбора
jz _@cj_dword ;
@cld: cmp al,0FCh
jz _@one_byte
unknown_instr:
sub eax,eax ; инструкции нет в базе
dec eax ; eax = -1
disasm_ret:
pop esi ; вспомним оригинальное значение и …
ret ; выйдем в ...
;
; inc [ereg] 10000reg
;
_@inc_ereg:
xor al,1000000b ; eax - регистр "ereg"
inc ecx ; ecx - размер инструкции
jmp disasm_ret
;
; push [ereg] 1010reg
;
_@push_ereg:
xor al,1010000b ; eax - регистр "ereg"
inc ecx ; ecx - размер инструкции
jmp disasm_ret
;
; pop [ereg] 1011reg
;
_@pop_ereg:
xor al,1011000b ; eax - регистр "ereg"
inc ecx ; ecx - размер инструкции
jmp disasm_ret
;
; sub [eregA],[eregB] 02BHEX, 11rgArgB
;
_@sub_ereg_ereg:
lodsb
xor al,11000000b
call two_regs ; edx - "rgA" / eax - "rgB"
inc ecx
inc ecx ; ecx - размер инструкции
jmp disasm_ret
;
; xor [eregA+eregB],reg 030HEX, 00reg100, 00rgArgB
;
_@xor_2ereg_b:
lodsb
lodsb
call two_regs ; edx - "rgA" / eax - "rgB"
mov cl,3 ; ecx - размер инструкции
inc dword ptr [dregs]
mov dword ptr [dregs+008],eax
jmp _@xor_2ereg_dr
;
; xor [eregA],[eregB] 031HEX, 00rgArgB
;
_@xor_ereg_ereg:
lodsb
call two_regs ; edx - "rgA" / eax - "rgB"
inc ecx
inc ecx ; ecx - размер инструкции
_@xor_2ereg_dr:
inc dword ptr [dregs]
mov dword ptr [dregs+004],edx
jmp disasm_ret
;
; add al,byte 004HEX, byte
;
_@add_al_byte:
mov cl,2 ; ecx - размер инструкции
sub eax,eax ; eax - значение рег. (al)
jmp disasm_ret
;
; add eax,dword 005HEX, dword = 5 bytes
;
_@add_eax_dd:
mov cl,5 ; ecx - размер инструкции
sub eax,eax ; eax - значение рег. (eax)
jmp disasm_ret
;
; add [reg],byte 080HEX, 11000reg, byte
;
_@add_reg_byte:
lodsb
cmp al,0c0h
jc unknown_instr
cmp al,0c7h
ja unknown_instr
xor al,11000000b ; eax - значение рег. (eax)
mov cl,3 ; ecx - размер инструкции
jmp disasm_ret
;
; xor [ereg],dword 081HEX, 00110reg, dword = 6 bytes
; add [ereg],dword 081HEX, 11000reg, dword = 6 bytes
; sub [ereg],dword 081HEX, 11101reg, dword = 6 bytes
; add [ereg],byte 083HEX, 11000reg, byte = 3 bytes
; sub [ereg],byte 081HEX, 11101reg, byte = 3 bytes
;
_@xas_ereg:
mov cl,3
cmp al,83h
jz _@xas_ereg_nfo
add cl,3
_@xas_ereg_nfo:
lodsb ; al - второй байт инструкции
cmp al,037h ; если байт больше 37HEX
ja _@xas_ereg_chk ; то перед нами "ADD" или "SUB"
_@xor_ereg: ; разбор инструкции "xor"
xor al,00110000b ; используемый в инструкции
inc dword ptr [dregs]
mov dword ptr [dregs+004],eax
jmp disasm_ret ; регистр кладем в EDX
_@xas_ereg_chk:
cmp al,0C7h ; если байт больше 37HEX
ja _@sub_ereg ; то перед нами "SUB"
_@add_ereg:
xor al,11000000b
jmp disasm_ret ; разбор инструкции "add"
_@sub_ereg:
xor al,11101000b
jmp disasm_ret ; разбор инструкции "sub"
;
; lea ergA,ergB+dword 08DHEX,10rgArgB, dword
;
_@lea_er_er_dd:
lodsb
xor al,10000000b ; оставим в al только регистры
call two_regs ; edx - "rgA" / eax - "rgB"
mov cl,6 ; ecx - размер инструкции
jmp disasm_ret
;
; mov [reg],byte 10110reg, byte
;
_@mov_reg_byte:
xor al,10110000b ; eax - регистр "reg"
inc ecx
inc ecx ; ecx - размер инструкции
jmp disasm_ret
;
; mov [ereg],dword 10111reg, dword
;
_@mov_ereg_dd:
xor al,10111000b ; eax - регистр "ereg"
mov cl,5 ; ecx - размер инструкции
jmp disasm_ret
;
; loop byte 0E2HEX, FE-byte = num ; jump to IP-num
;
_@loop_byte:
sub eax,eax
mov al,0feh ; al = 0FE
sub al,byte ptr [esi] ; al = 0FE-byte
inc ecx
inc ecx ; размер инструкции
jmp disasm_ret
;
; call dword 0E8HEX, dword
; jmp dword 0E9HEX, dword
;
_@cj_dword:
lodsd ; eax - dword
mov cl,5 ; ecx - размер инструкции
jmp disasm_ret
;
; разбор простейших однобайтовых инструкций
;
_@one_byte:
sub eax,eax
inc ecx
jmp disasm_ret
endp
;
; two_regs - если регистр al содержит 00rgArgB (где rgA, rgB два регистра)
; то на выходе edx - rgA, eax - rgB
;
two_regs proc
push eax
shr al,3 ; al = regB
pop edx
push eax
rol al,3
xor dl,al ; dl = regA
pop eax
ret
endp
;

 Вы рассмотрели этот небольшую программу, написана она лишь для примера, однако можно попытаться усовершенствовать и использовать его в «своих» целях. Опытному программисту все покажется очень просто и ясно, скорее всего, Вы уже знали это, только технологию называли другим именем. Если это действительно так, прошу прощения за отнятое время, дальше в этой статье Вы не найдете для себя ничего нового.

 Я не претендую на звание специалиста в этой области исследований, но попытаюсь рассказать о некоторых вопросах, которые вполне возможно у Вас возникли после просмотра выше представленных исходных текстов.

4.1. Лирическое отступление

 Как становится ясно принцип «создания» эмулятора похож на алгоритм создания полиморфных движков.

 При создании инструкций полиморфные движки используют различного рода битовые операции. А в эмуляторе битовые операции используются дизассемблером, для разбора «встречающихся» инструкций (получения данных о размере инструкции, использующихся в ней регистрах и т.д.) и последующей передачи этих данных непосредственно в эмулятор.

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

4.2. Имитация исполнения инструкций

 Как Вы уже поняли, для имитации исполнения инструкций эмулятор использует три способа:

  1. Инструкция запускается «напрямую», но в специальной среде.
  2. Исполнение инструкции имитируется полностью, без запуска.
  3. Исполнение инструкции имитируется до или после запуска в специальной среде.

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

4.2.1. Запуск инструкции в специальной среде

 Многие инструкции гораздо проще запустить напрямую, специальная имитация может обойтись «дороже». Т.е. будет потеря в скорости работы эмулятора (а это очень щекотливый вопрос), если каждую инструкцию имитировать, то можно писать только эмуляцию инструкций до пенсии. А зачем заниматься ерундой, если можно просто запустить инструкцию и зафиксировать изменения, которые произошли после ее исполнения (в регистрах, стеке, флагах … ).

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

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

 После запуска инструкции запомнить новые значения и установить значения использовавшиеся эмулятором.

 В качестве примера, более подробно рассмотрим участок кода из программы примера:

.data ; сегмент данных
regs dd 8 dup (?) ; переменная под значения регистров эмулируемого блока
flags dd ? ; переменная под значения флагов эмулируемого блока
temp dd ? ; переменная под значение флагов программы-эмулятора
;
; "instr_buf" - переменная под исполняемую инструкцию и имитацию стека
; должна содержать:
; mov dword ptr [set_esp+1],esp
; mov esp,dword ptr [regs+16]
; исполняемая инструкция
; mov dword ptr [regs+16],esp
; set_esp:mov esp,?
; ret
;
instr_buf: db 30 dup (?)
.code ; сегмент кода
; "прямой запуск" инструкции в (карантине) специальных условиях
; запомним значение регистров программы-эмулятора в стеке
run_instr: push eax ecx edx ebx ebp esi edi
; запомним значение флагов программы-эмулятора в стеке, затем перенесем в регистр eax,
; а из него в переменную "temp"
pushfd
mov eax,dword ptr [esp]
mov dword ptr [temp],eax
popfd
; значение флагов эмулируемого кода перенесем из переменной "flags" в регистр eax,
; а из него в стек использующийся программой-эмулятором
pushfd
mov eax,dword ptr [flags]
mov dword ptr [esp],eax
; установим значения регистров эмулируемого кода из переменной "regs"
; значение esp не устанавливается, иначе мы рискуем испортить значение флагов эмулируемого кода
; стек будет перенаправлен в подпрограмме "instr_buf"
mov eax,dword ptr [regs+000]
mov ecx,dword ptr [regs+004]
mov edx,dword ptr [regs+008]
mov ebx,dword ptr [regs+012]
mov ebp,dword ptr [regs+020]
mov esi,dword ptr [regs+024]
mov edi,dword ptr [regs+028]
; инструкция "popfd" установит значение флагов эмулируемого кода, которое мы сохраняли выше
popfd
call instr_buf ; запустим на исполнение инструкцию
; сохраним новые значения регистров эмулируемого кода в переменной "regs"
mov dword ptr [regs+000],eax
mov dword ptr [regs+004],ecx
mov dword ptr [regs+008],edx
mov dword ptr [regs+012],ebx
mov dword ptr [regs+020],ebp
mov dword ptr [regs+024],esi
mov dword ptr [regs+028],edi
; новое значение флагов эмулируемого кода запомним в стеке, затем перенесем в регистр eax
; и сохраним в переменной "flags"
pushfd
mov eax,dword ptr [esp]
mov dword ptr [flags],eax
popfd
; установим значение флагов программы-эмулятора из переменной "temp"

pushfd
mov eax,dword ptr [temp]
mov dword ptr [esp],eax
popfd
; восстановим значение регистров программы-эмулятора
pop edi esi ebp ebx edx ecx eax

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

.data ; сегмент данных
buf_stack db 50000 dup (?)
.code ; сегмент кода
mov edx,offset buf_stack+50000
mov dword ptr [regs+16],edx

4.2.2. Полная имитация исполнения инструкции

 Этот алгоритм применяется там, где нельзя обойтись запуском в специальной среде. В программе-эмуляторе из примера, я использовал регистр “esi”, что бы указывать на смещение исполняемой в «данный момент» инструкции. Обычно полностью имитируется (совершаются различные действия, которые и будут являться аналогом работы инструкции) выполнение инструкций, которые изменяют этот указатель, т.е. CALL, JUMP, LOOP, RET и т.д.

 Не будем ходить далеко и в качестве примера возьмем простейшую инструкцию LOOP:

 Инструкция LOOP уменьшает значение регистра ECX на единицу, если после этого значение ECX не равняется нулю, то переводит указатель на указанную позицию.

 dec dword ptr [regs+4]
cmp dword ptr [regs+4],0
jz проверка на остановку эмуляции (emulate_check в программе примере)
изменяем местоположение регистра esi, используя данные инструкции LOOP
jmp проверка на остановку эмуляции

4.2.3. Комбинация двух способов

 Существуют такие, инструкции, которые просто выполнить в «карантине» нельзя, да и полностью имитировать их совсем не обязательно (точнее сказать, выйдет «дороже»). Для работы с такими инструкциями используется комбинация из двух вышеописанных способов.

 В программе-примере присутствует один тип инструкции, которая имитируется этим способом:

xor dword ptr [erega],? и ее аналог xor dword ptr [erega],eregb


Категория: Безопасность | Добавил: reloto (30.01.2008)
Просмотров: 1403 | Комментарии: 1 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Имя *:
Email *:
Код *:
Форма входа

Поиск

Друзья сайта

plentyofish.com counter gratis счетчик сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Copyright MyCorp © 2024
Хостинг от uCoz