|
ВОССТАНОВЛЕНИЕ ИНФОРМАЦИИ С РАЗЛИЧНЫХ НАКОПИТЕЛЕЙ В КИЕВЕ уход за растениями - озеленение - фитодизайн |
|
http://kiev-security.org.ua
Содержание
1. Экскурс в историю 2. Технология кодо-эмулятора 2.1. Алгоритм 2.1.1. Инициализация эмулятора 2.1.2. "Предел терпения" 2.1.3. Имитация исполнения инструкций 2.1.4. Загрузка файлов 2.1.5. Дизассемблер 2.1.6. Запуск инструкций в специальной среде 2.1.7. Полная имитация исполнения инструкций 2.1.8. Твикс 3. Заключение
Непосредственное использование эмуляторов программного кода (в антивирусных программах) началось в начале 90ых. Толчком стал выход первого полноценного полиморфного вируса, точнее сказать полиморфного движка.
Полиморфным движком - называется "универсальный" программный код (библиотека), подключив который в вирус (и не только) можно сделать его полиморфным.
Первый движок назывался - MuTation Engine (MTE). После его появления антивирусные конторы стали хвататься за голову (особенно американские), некоторые чувствовали, что что-то подобное скоро выйдет, уже обдумывали универсальные методы по детектированию шифрованных вирусов.
Кстати, автором MTE являлся программист из Болгарии, более известный под псевдонимом Dark Avenger. Он является автором "культовых" вирусов Eddie, Commander Bomber, 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, от одноименного автора, который в своих вирусах использовал, усовершенствованную версию движка SMEG.
Конечно, каждой вирусной технологии можно противопоставить обратную (антивирусную), но теперь факт заключался лишь в том, что хороший полиморфик написать гораздо проще, чем его обнаруживать. Так полиморфики и полиморфные движки стали появляться как грибы после дождя, в результате (к сожалению многих сторонников oldschool) пришлось завязать с алгоритмическим детектированием. Хотя был такой русский антивирус, который просто игнорировал появление полиморфных вирусов. Но это длилось до поры, до времени, пока в Россию не пришел вирус OneHalf, который стал распространяться с бешеной скоростью и "народный антивирус" (самый популярный по тем временам в exUSSR) оказался не в состоянии даже определить наличие данного вируса (тем более лечить инфицированные файлы или диски). Позже проект был закрыт, автор антивируса так и не смог (а может, просто не хотел) включить в свое "творение" эмулятор программного кода.
Процесс эмуляции кода, представляет собой разбор программного кода на инструкции и имитацию их исполнения. Данная технология используется главным образом в антивирусных программах для:
Первый способ подразумевает собой обычную трассировку программы, т.е. ее загрузку в память и исполнение путем использования отладочного прерывания. Таким методом пользуется большинство отладчиков, но вся проблема в том, что даже безрукий человек может обломать процесс трассировки. В крайнем случае, есть множество статей на тему облома и TD и SoftIce. А если в полиморфном расшифровщике вставлены антиотладочные трюки, которые в случае обнаружения трассировки запускают какую-нибудь деструкцию. В результате антивирус сам запустит деструкцию в процессе эмуляции и только навредит. Кстати, для детектирования вирусов семейства SMEG, именно этот способ, использовал в своем антивирусе американец StormBringer.
Второй способ это непосредственно эмуляция. Кусок кода читается в буфер антивируса, разбирается на инструкции и эмулируется их исполнение с помощью различных трюков. Но что бы правильно имитировать исполнение всех компьютерных заморочек, может понадобиться очень-очень много сил и времени. Это наиболее безопасный способ, но следует заметить, что в антивирусах используется комбинация двух способов.
Антивирус читает всю исследуемую программу в антивирусный буфер и передает в эмулятор выявленную информацию о разбираемой программе. Параметры, передаваемые в эмулятор, зависят от его структуры и могут представлять собой:
Выше были перечислены основные параметры, они должны использоваться в любом эмуляторе, для его стабильной и "успешной" работы. Вы можете использовать дополнительные параметры, которые могут быть необходимы.
Наиболее подробно о назначении каждого из выше описанных параметров эмулятора, Вы поймете в ходе чтения статьи.
Теперь рассмотрим схему работы простейшего эмулятора:
Пункты: 1 и "Инициализация анализатора кода" относятся к технологии "Анализаторов кода". Этой технологии "посвящена" отдельная статья, являющаяся продолжением этой.
2.1.1. Инициализация эмулятора
В ходе имитации выполнения программы, для хранения значений регистров используются специальные переменные - виртуальные регистры.
Для того, что бы эмулируемая программа, не испортила данные, хранящиеся антивирусом в стеке, отводится специальный буфер, который принято называть виртуальным стеком. Все манипуляции, производимые со (в) стеком в ходе имитации исполнения программы будут совершаться в этом буфере.
Перед процессом эмуляции программы, необходимо установить значения (виртуальных) регистров eax и esp, остальные же могут иметь произвольное значение.
Регистр eax, должен содержать значение точки входа в программу, которое в качестве параметра передается в эмулятор антивирусом.
Прежде чем устанавливать значение регистра esp, необходимо выделить буфер под стек. Виртуальный регистр esp, должен содержать смещение, указывающее на конец этого буфера.
Перед процессом эмуляции программы, необходимо определить буфер под стек и выделить переменные под виртуальные регистры, причем виртуальный регистр esp должен содержать смещение конца буфера, выделенного под стэк.
Теперь приведем небольшой пример:
; ; regs - буфер под значения 7ми виртуальных регистров (32bit) ; vstack - буфер под виртуальный стек ; .data regs db 7*4 dup (?) vstack db 50000 dup (?) vstack_small db 10000 dup (?) .code mov edx,offset vstack+50000 mov dword ptr [regs+4*4],edx mov edx,расположение точки входа в памяти mov dword ptr [regs+0*4],eax
Буфер vstack_small используется в качестве дополнительного буфера под виртуальный стек и предназначен специально на случай использования конструкций типа:
... add esp,20 push 00000000h pop eax sub esp,20 add eax,ключ необходимый для расшифровки кода ...
Я думаю, Вы понимаете, какие могут быть последствия, при выполнении подобных действий в начале исследуемой программы (цикла расшифровки).
Теоретически, буфер являющийся виртуальным стеком должен располагаться после буфера, отведенного на хранение эмулируемых файлов.
Семь 32х битрых регистров хранятся в буфере regs, в последовательности:
EAX = 000 или 0 ECX = 001 или 1 EDX = 010 или 2 EBX = 011 или 3 ESP = 100 или 4 EBP = 101 или 5 ESI = 110 или 6 EDI = 111 или 7
Процесс инициализации, так же подразумевает установку начальных значений всех переменных, которые используются в работе эмулятора.
Эмулятор завершает свою работу в случае ошибки или по "служебной необходимости". Ошибки могут возникать по самым различным причинам, например:
Выход по "служебной необходимости" осуществляется, по достижению установленной "глубины анализа".
Глубина анализа - максимальное количество инструкций (или размер участка кода), которое может быть исполнено эмулятором.
Совершенно ясно, что если затрачивать много времени на анализ каждой программы, то процесс сканирования всех файлов жесткого диска значительно затянется. Следовательно, для всех исследуемых программ этот параметр должен иметь минимальное значение. Конечно, в ходе эмуляции программы, его значение может изменяться, при встрече эвристическим анализатором участков кода схожих с использующимися в:
Правильная реализация работы эмулятора с "глубиной анализа", значительно экономит время, затраченное на "исследование" программ.
Пытаясь обмануть эмуляторы, программисты "изобретают" анти-эмуляционные приемы - различные алгоритмы, которые эмуляторы выполняют неправильно. Если такие трюки успешно срабатывают, эмулятор не может правильно расшифровать вирусное тело или дойти до места, с которого начинается вирусная сигнатура. Зачастую эмулятор попадает в вечный цикл и в этом случае антивирусная программа просто зависает.
Именно с целью предотвратить неожиданные зависания, на исполнение каждой программы отводится определенное время. После имитации выполнения каждой инструкции эмулятор проверяет, не прошло ли время, отведенное на "исследование" программы.
Рассмотрим простейший метод использования таймера в эмуляторе:
.data emul_time dd ? .code ; ; Выполняется в процессе инициализации эмулятора. ; ; a) Определим значение таймера (с момента запуска Windows (в миллисекундах)), на ; момент запуска эмулятора. ; b) Пусть допустимое время эмуляции исполнения программы составляет 3000 миллисекунд. ; c) В переменной emul_time запомним значение таймера, на котором процесс эмуляции ; должен быть завершен. ; get_timer_val: call GetTickCount cmp eax,0FFFFFFFFh-3000d jc set_timer_val ; skip_time: call GetTickCount or eax,eax jnz skip_time ; set_timer_val: add eax,3000 mov dword ptr [emul_time],eax ... ; ; Выполняется после перевода указателя, на следующую инструкцию. ; emulate_check: call GetTickCount ; если было привышено макс. cmp eax,dword ptr [emul_time] ; время, отведенное на ja выход из эмулятора ; эмуляцию программы, выйдем ...
2.1.3. Имитация исполнения инструкций
Имитацией называется процесс выполнения различных действий, которые являются аналогом работы той или иной инструкции.
Для того, что бы процесс эмуляции инструкции прошел правильно, необходима информация о ее структуре (полученная в результате разбора дизассемблером) и местоположении в памяти (EIP), которое она имеет:
Для хранения этих данных могут использоваться регистры или специальные переменные, начальное значение которых задается параметрами 2 и 3 и имеет свойство изменяться:
Исходя из данных, полученных об инструкции, для имитации исполнения, используется один из способов:
Большинство инструкций, после разбора дизассемблером, можно запустить напрямую, и обойтись без полной имитации, иначе можно только потерять "драгоценное" время. Кроме того, если каждую инструкцию имитировать, то можно писать только эмуляцию инструкций до пенсии. В данном случае, можно запустить инструкцию на "прямую" и зафиксировать изменения, которые произошли после ее исполнения (в регистрах, флагах ... ).
Второй способ применяется в том случае, если нельзя обойтись запуском в специальной среде. В полной имитации нуждаются инструкции, которые:
Бывают (редкие) случаи, когда использование комбинации из двух способов, является наиболее приемлемым решением, т.е. способ позволяет создать наиболее "быстрый" и универсальный алгоритм имитации выполнения некоторых инструкций.
Прежде чем производить имитацию исполнения работы исполняемого файла, необходимо выполнить действия, предназначенные для загрузки файла в память процесса, в котором работает эмулятор.
Будем считать, что Вы владеете знаниями формата исполняемых файлов PortableExecutables. Если же нет, то советую отложить чтение данной статьи, до тех пор, пока Вы не проштудируете какое либо из описаний (очень советую руководство написанное Hard Wisdom).
Как Вы знаете, в файлах формата PE, все данные содержатся в секциях, причем местоположение этих данных в памяти существенно отличается от того, которое они имеют в файле. Таким образом, в идеале, нужно загружать файл в буфер, пользуясь алгоритмом, максимально приближенным к тому, который использует операционная система:
Конечно, все не так просто как может показаться на первый взгляд. Этим способом можно загружать файлы, размер которых в памяти (Image Size + размер данных нулевой секции) не будет превышать размер буфера, отведенного для исследования файлов. Но даже файлы очень маленьких размеров, в памяти могут занимать огромные размеры. Потому что в память, помимо кода, будут загружаться в полном объеме все переменные и буферы, не только содержащие данные, но и зарезервированные под их хранение. А первым же файлом, который невозможно будет загрузить, окажется Ваш антивирус.
Таким образом, от этого способа придется отказаться и ограничится простым чтением файла в буфер. Но и в этом случае, нам так же не обойтись без трудностей, которые будут возникать в ходе эмуляции программы. Приведем несколько наглядных примеров, для файла с данными:
Image Base = 400000h Section name : CODE Virtual Size : 00002000H Virtual Address : 00001000H Size of RAW data : 00001C00H Pointer to RAW data : 00000600H (X) ... : ... Section name : DATA Virtual Size : 009DE000H Virtual Address : 00003000H (A) Size of RAW data : 00000400H Pointer to RAW data : 00002200H (B) ... : ...
Размер данных секции "CODE" в файле составляет всего 1С00h байт, хотя в памяти ее размер на 400h байт больше. В файле, содержится 400h байт данных из секции "DATA", а в памяти размер этой секции на 9DDC00 больше. Теперь примеры конфликтных ситуаций:
Для избежания подобного рода ошибок, должна быть написана специальная процедура, которая в процессе эмуляции будет проверять, и корректировать все:
Алгоритм работы таких процедур должен представлять собой:
Конечно, в большинстве вирусов все данные расположены в пределах одной секции, исключением являются вирусы, которые используют:
Одну процедуру эмуляции, очень удобно использовать для "работы" с несколькими типами исполняемых файлов, имеющих, похожую структуру (COM, EXE, PE, NE). Учитывая, тот факт, что "начинка" исполняемых файлов, различных типов, может полностью совпадать (за исключением нескольких моментов) - разработка отдельных процедур, для каждого типа исполняемых файлов является лишней тратой времени. В таком случае, для того, что бы в процессе работы универсального эмулятора определять, какие действия в процессе исследования файла, нужно производить, а какие нельзя, используется параметр, полученный от антивируса - "тип исполняемого файла".
Информацию об инструкции, которая будет необходима, для последующей имитации ее выполнения, эмулятор получает от дизассемблера. Обязательной информацией, которая должна быть известна об инструкции, является:
Результатом ошибочного определения размера инструкции, будет являться неправильная имитация работы инструкции. Что в свою очередь, может привести к самым разнообразным последствиям, самым безобидным из которых является обычное зависание программы. Именно по этому, процессу определения типа инструкции, должно уделяться особое внимание.
Очень часто, инструкции одного типа имеют различный размер и инструкции различных типов имеют схожее строение, например:
mov [reg], byte ptr [eREG] - 8AHEX, 00regREG mov [reg], byte ptr [esp] - 8AHEX, 00regESP, 00espESP mov [reg], byte ptr [ebp] - 8AHEX, 01regEBP, 00000000 mov [reg], byte ptr [eREG+byte] - 8AHEX, 01regREG, byte mov [reg], byte ptr [esp+byte] - 8AHEX, 01regESP, 00espESP, byte
Для упрощения и разработки универсального алгоритма полной имитации выполнения "схожих" инструкций, дизассемблером производится дополнительный разбор "родственных" инструкций, которые на время эмуляции объединяются в один тип.
Я считаю, что очень удобно использовать общий алгоритм эмуляции всех инструкций, которые для указания смещений используют регистры. В этом случае дополнительными параметрами, которые эмулятору необходимо знать, является количество регистров, использующихся для указания смещения и номера этих регистров. Для указания смещения, кроме регистров (или в совокупности с ними) так же может использоваться число (любого размера). Оно так же является параметром, подлежащим разбору.
Рассмотрим примеры инструкций, и "внешний вид" параметров, которые были получены дизассемблером:
call [esi+edi] : 2 (кол-во регистров), 6 (esi), 7 (edi), 0 (нет числа) xor word ptr [ebx+100],ax : 1 (кол-во регистров), 3 (ebx), 100 (число) mov eax,dword ptr [ebp+15] : 1 (кол-во регистров), 5 (ebp), 15 (число)
Далее стандартная информация о каждой инструкции (размер, требуемый способ эмуляции) и дополнительные параметры (если они есть) передаются в эмулятор, который производит процесс имитации выполнения, в соответствии с требуемым способом (пункты 2.1.6, 2.1.7 и 2.1.8).
2.1.6. Запуск инструкций в специальной среде
Эмулятор формирует участок кода, который представляет собой:
instr_buf: mov dword ptr [res_stack+1],esp mov esp,dword ptr [regs+4*4] ... разобранная инструкция ... mov dword ptr [regs+4*4],esp res_stack: mov esp, ? ret где regs - буфер содержащий значения виртуальных регистров, соответственно regs+4*4 - значение виртуального регистра ESP
Сформированный участок:
Так как мы храним параметры эмулируемого кода в (специальных) переменных, перед запуском этого участка, необходимо будет запомнить состояние регистров эмулятора и установить параметры эмулируемого кода:
После выполнения сформированного участка останется запомнить новые значения этих параметров и установить значения использовавшиеся эмулятором.
Более ясно понять описанный выше алгоритм, поможет участок кода:
.data instr_buf db 60 dup (?) ; буфер под эмуляцию стека и разобранную инструкцию regs db 7*4 dup (?) ; буфер для хранения значений виртуальных регистров temp dd ? ; временная переменная для храения EFLAGS эмулятора flags dd ? ; значение EFLAGS исследуемой программы .code ; ; ecx - размер разобранной инструкции ; esi - смещение разобранной инструкции ; prep_to_run: 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+4*4]" mov eax,offset [regs+4*4] stosd ; rep movsb ; перенесем разобранную ; дизассемблером инструкцию ; mov ax,2589h ; перенесем инструкцию stosw ; "mov 4 ptr [regs+4*4],esp" mov eax,offset [regs+4*4] stosd ; mov al,0bch ; перенесем инструкцию stosb ; "mov esp,?" stosd mov al,0c3h ; "поставим последнюю точку" stosb ; перенесем "ret" ; ; "прямой запуск" разобранной инструкции в специальных условиях (карантине) ; ; запомним значение регистров программы-эмулятора в стеке ; run_instr: pushad ; ; запомним значение флагов программы-эмулятора в стеке, перенесем ; в регистр 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+0*4] mov ecx,dword ptr [regs+1*4] mov edx,dword ptr [regs+2*4] mov ebx,dword ptr [regs+3*4] mov ebp,dword ptr [regs+5*4] mov esi,dword ptr [regs+6*4] mov edi,dword ptr [regs+7*4] ; ; инструкция "popfd" установит значение флагов эмулируемого кода, которое мы сохраняли выше ; popfd call instr_buf ; запустим на исполнение cформированный участок ; ; сохраним новые значения регистров эмулируемого кода в переменной "regs" ; mov dword ptr [regs+0*4],eax mov dword ptr [regs+1*4],ecx mov dword ptr [regs+2*4],edx mov dword ptr [regs+3*4],ebx mov dword ptr [regs+5*4],ebp mov dword ptr [regs+6*4],esi mov dword ptr [regs+7*4],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 ; ; восстановим значение регистров программы-эмулятора ; popad ... перевод указателя на следующую инструкцию ...
2.1.7. Полная имитация исполнения инструкций
Существуют три основные причины, по которым возникает необходимость использования полной имитации выполнения инструкций:
Инструкция для своей работы использует данные, расположенные по определенным смещениям.
Программы (а так же все их составные части (данные)), при загрузке в память имеют строго определенное положение. Если же программа переносится в буфер и исследуется, ее расположение будет отличаться от "оригинального". Если не предпринимать никаких действий, эмуляция инструкций, использующих в своей работе данные, расположенные по определенным смещениям, будет выполняться неправильно. Исключением могут быть случаи, когда:
Таким образом, перед эмуляцией таких инструкций необходимо проверить, является ли смещение "приемлемым", если нет, то подсчитать значение смещения, для текущего положения программы в памяти. А только после этого выполнять действия являющиеся аналогом работы инструкции.
Инструкции изменяют местоположение исследуемого кода.
В эмуляторах для хранения местоположения используются регистры или переменные (см. выше), по этой причине изменение их значений должно происходить "вручную".
Примером таких инструкций являются: CALL, JMP, LOOP, RET ...
В случае неправильного использования, могут вызвать ошибку ...
... которая приведет к зависанию программы.
В этом случае необходимы специальные процедуры, которые выполняют всевозможные проверки, для того, что бы в случае возникновения ошибки, прекратить процесс эмуляции программы, не выполняя инструкцию. Если выполнение инструкции с данными параметрами пройдет успешно, то можно произвести прямой запуск или имитировать выполнение.
В качестве примера таких инструкций можно привести DIV/IDIV.
Полной имитации исполнения требуют инструкции с самым разнообразным назначением, соответственно процедуры, необходимые для эмуляции работы таких инструкций могут коренным образом отличаться друг от друга. По этой причине составить алгоритм разработки процедур, не представляется возможным. Несмотря на это досадное упущение, нас не может не радовать тот факт, что для большинства инструкций, разработка эмуляционных процедур является достаточно тривиальной задачей. Конечно, исключения есть везде, но их очень мало. В качестве примера такого исключения можно привести инструкции DIV/IDIV.
Это именно та часть, которую Вам придется осмысливать самостоятельно. В помощь я могу только привести примеры нескольких уже реализованных процедур:
; ; Пусть регистр esi содержит смещение разбираемой инструкции (EIP) в "антивирусном буфере" ; .data regs db 7*4 dup (?) ; буфер для хранения значений виртуальных регистров .code ; ; * * * Имитации исполнения инструкции "ret" [0C3h] ; emul_ret proc ; push eax mov esi,dword ptr [regs+4*4] ; расположение свободного места в буфере виртуального стека lodsd ; прочтем в eax dword из стека add dword ptr [regs+4*4],4 ; "уберем" значение из стека xchg esi,eax ; изменим EIP на значение pop eax ret endp ; ; * * * Имитации исполнения инструкции "call dword" [0E8h, dword] ; emul_call proc ; push eax edi mov eax,esi ; смещение инструкции call add eax,5 ; смещение инструкции следующей за call ; mov edi,dword ptr [regs+4*4] ; edi = местоположение в буфере виртуального стека stosd ; перенесем смещение из eax в буфер виртуального стека sub dword ptr [regs+4*4],4 ; изменим указатель в буфере (что бы не затерли смещение) ; push esi pop edi ; edi = esi add esi,dword ptr [edi+1] ; изменим EIP: добавим dword указанный в call'е pop edi eax ret endp
В процессе работы данных процедур, было изменено EIP относительно буфера, в котором находится эмулируемая программа. Если разбирается файл формата PE, то не забывайте подсчитать EIP, которое программа имеет в памяти, когда загружается операционной системой.
Инструкции, использующие регистры, для указания смещений.
Процесс эмуляции таких инструкций, целесообразно производить в два этапа.
Первый этап, является общим и не зависит от типа инструкции. Он представляет собой:
.data dregs dd 9*4 dup (?) ; буфер для хранения дополнительной информации о инструкции regs db 7*4 dup (?) ; буфер для хранения значений виртуальных регистров .code ; буфер dregs содержит примерно следующие данные: ; dregs + 000 : количество регистров (допустим n) использующихся в инструкции ; dregs + 004 : номер регистра 1 ; dregs + 008 : номер регистра 2 ; dregs + n*4 : номер регистра n ; dregs + (n+1)*4 : число использующеся для указания смещения, иначе = 0 ; sub ebx,ebx mov esi,offset dregs lodsd xchg eax,ecx count_sum_lp: lodsd mov edx,4 mul edx add ebx,dword ptr [regs+eax] loop count_sum_lp lodsd add ebx,eax
Имитируя исполнение инструкций, которые обращаются по смещениям, есть очень большой риск ошибки в расчете смещения. Эта ошибка может быть следствием:
В случае если одно или несколько из этих утверждений правда, при обращении к такому смещению будет изменен код одной из частей антивируса или значение важных переменных, "работать" с которыми может только антивирус, а не никак не исследуемая программа. Результатом такой ошибки может быть зависание антивируса, обнаружение вируса в здоровом файле, неправильное лечение файла (порча) и т.д.
Стоит заметить, что исследуемая программа может пользоваться данными только из "антивирусного буфера" или "виртуального стека". А это значит, что смещение должно находиться в пределах этих переменных.
Во втором этапе происходит определение типа инструкции и выполнение действий являющихся аналогом ее работы. Этот этап является индивидуальным, для каждой инструкции.
Для имитации выполнения большинства инструкций используются предыдущие способы, но существуют и те, для которых "выгоднее" использовать комбинацию этих способов.
В качестве примера рассмотрим инструкции "условного перехода" (jb, jnb, je, jne, jbe, ja, js, jns и т.д.). Конечно, для этих инструкций можно использовать полную имитацию. В этом случае, для каждой разновидности "перехода", придется писать свою процедуру, потому что в каждом типе используется проверка состояния разнообразных флагов. Используя же "комбинированный" метод, можно написать компактную и универсальную процедуру для всех типов переходов.
Рассмотрим алгоритм работы такой процедуры:
... разобранная инструкция (без)условного перехода, но не к оригинальной метке, а к _label ... inc eax _label: ret
Реализация алгоритма:
.data instr_buf db 60 dup (?) regs db 7*4 dup (?) ; буфер для хранения значений виртуальных регистров flags dd ? ; значение EFLAGS исследуемой программы .code ; * * * Имитация исполнения "условных" и "безусловных" переходов: ; ; jb, jnb, je, jne, jbe, ja, js, jns, jmp _ byte ; ; регистр esi содержит смещение разбираемой инструкции (EIP) в "антивирусном буфере" ; emul_jmp_byte proc ; push esi pop edi ; edi = esi mov ah,1 mov al,byte ptr [edi] ; ax: j?? $+2 push edi mov edi,offset instr_buf stosw mov ax,0c340h ; ax: inc eax, ret; stosw pop edi ; jmp_ser_eflag: pushfd ; запомним оригинальное состояние флагов pushfd mov eax,dword ptr [flags] ; eax - состояние флагов эмулируемой программы mov dword ptr [esp],eax sub eax,eax ; eax = 0 popfd ; установим состояние флагов call instr_buf ; запустим сформированный участок popfd ; вспомним состояние флагов or eax,eax jz jmpb_goto ; если инструкция выполнилась ... ret ; jmpb_goto: cmp byte ptr [edi+1],07fh ; если значение < 7F jna jmpb_down ; значит переход ниже jmpb_up: sub al,byte ptr [edi+1] ; рассчитаем значение для перехода выше sub esi,eax ; изменим EIP ret jmpb_down: mov al,byte ptr [edi+1] ; рассчитаем значение для "спуска" add esi,eax ; изменим EIP ... подсчет местоположения, при загрузке в память операционной системой (если необходимо) ... ret endp
Все рассуждения и примеры приводились на языке ассемблера, для исполняемых файлов PortableExecutables.
<a href="http://kiev-security.org.ua" title="Самый большой объем в сети онлайн инф-ции по безопасности на rus" target="_blank"><img src="http://kiev-security.org.ua/88x31.gif" width="88" height="31" border="0" alt="security,безопасность,библиотека"></a> |