|
ВОССТАНОВЛЕНИЕ ИНФОРМАЦИИ С РАЗЛИЧНЫХ НАКОПИТЕЛЕЙ В КИЕВЕ уход за растениями - озеленение - фитодизайн |
|
http://kiev-security.org.ua
Содержание
1. Вступление 2. Технология анализа кода 2.1. Инициализация анализатора кода 2.2. Эвристический анализ 2.3. Поиск вирусных сигнатур 2.4. "Поиск" процедуры подсчета "delta value" 2.5. Полная имитация выполнения инструкций 2.6. Изменение параметров работы эмулятора 3. Заключение
Данная статья посвящена технологии анализатора кода, которая используется в антивирусных программах.
Я надеюсь, что Вы имеете понятие о принципе разработки антивирусных программ и о технологиях, которые используются в этом типе программного обеспечения. Потому, что без начальных (хотя бы) знаний понять что-то в этой статье будет очень и очень проблематично.
Все рассуждения и примеры будут приводиться на ассемблере, для исполняемых файлов формата Portable Executables.
Анализатор кода - предназначен для учета особенностей исследуемого (эмулируемого) кода и передачи выявленных особенностей в эмулятор. Обычно кодо-анализатор представляет собой несколько процедур, которые используются:
Начнем разбор технологии, с процедур, которые используются в эмуляторе. В связи с этим, рассмотрим порядок его работы:
В данной статье мы рассматриваем антивирус, эмулятор которого предназначен для имитации выполнения небольших участков кода. Если тело вируса зашифровано, эмулятор расшифровывает его и анализатор кода производит поиск в расшифрованном коде всех известных вирусных сигнатур. Это является основной и единственной задачей эмулятора. В эмуляторе отсутствует вызов эвристического анализатора кода, по этому анализ кода и выявление в нем свойственных вирусам участков (поиск новых вирусов) не производится.
2.1. Инициализация анализатора кода
Инициализация, в данном случае подразумевает собой установку начальных значений всем переменным, которые используются анализатором для хранения в них информации различного рода и последующей их передачи в эмулятор.
.data ; ; тип значений хранящихся в регистрах, в соотвествии с их номерами, например: ; _regs+000 - eax ; _regs+001 - ecx ; ... ; _regs+007 - edi ; ; типы: 2 - смещение расположенное в виртуальном стеке ; 1 - delta value или заготовка его последующего расчета ; 0 - все остальные значения не подходящие типам 1 и 2 ; ; подробнее о типах ; _regs db 8 dup (?) ; ; переменные, необходимые анализатору для поиска процедур по расчету delta value (см. ниже) ; ; ca_ip_stage - номер этапа, на котором находится эта функция кодо-анализатора ; ca_ip_instr - количество инструкций проверенных после вызова "CALL" ; ca_ip_call_off - смещение полученной после вызова "CALL" ; ca_ip_stage db ? ca_ip_instr db ? ca_ip_call_off dd ? .code ; ca_init proc ; push eax ecx edi mov word ptr [ca_ip_stage],0 ; присвоение нулевого значения переменным "ca_ip_stage" и "ca_ip_instr" reg_analyze: sub eax,eax sub ecx,ecx mov cl,8 mov edi,offset _regs ra_init_loop: stosb loop ra_init_loop mov byte ptr [_regs+4],2; регистр в любом случае esp будет смещение на данные внутри виртуального стека pop edi ecx eax ret endp
Эвристические анализаторы кода, предназначены для определения новых типов вирусов, т.е. тех, информации о которых нет в базе антивируса. Это необходимо для того, что бы пользователь мог обнаружить у себя на компьютере файлы, зараженные новыми типами вирусов и выслать их разработчикам программы (для включения их в последующие дополнения).
Участки кода, которые часто используются при создании различных типов вирусов, являются сигнатурами (масками), для эвристического анализатора, иногда их так же называют вирусными break point'ами.
Использование эвристического анализатора является не обязательным, но очень удобным, потому что позволяет не только обнаруживать новые вирусы, но и значительно ускорить процесс проверки файлов.
При наличии функций эвристического анализа, можно производить поиск известных вирусных сигнатур только в тех позициях, в которых "эвристик" встретит одну из масок. Потому что, проверка совпадения всех вирусных сигнатур, после каждой пройденной эмулятором инструкции займет достаточно много времени, особенно если:
Более подробно описывать технологию эвристического анализа я не буду, так как она заслуживает отдельной статьи.
Анализ участков кода и их сравнение с вирусными сигнатурами, записанными в базе. Для детектирования известного программе вируса достаточно именно обычного сравнения участка кода с конкретной (вирусной) сигнатурой.
Приведем пример части анализатора, которая производит проверку всех вирусных сигнатур из базы. А так же пример формата вирусной базы.
; entry_size equ e_type+e_name+e_slen+e_dlen+e_sdata+e_curem ; длина одной записи ; e_type equ 1 ; тип записи для файлов: etype_dos_com equ 0 ; - com (dos) etype_dos_exe equ 1 ; - exe (dos) etype_pe equ 2 ; - PE (win32) ; e_name equ 25 ; длина названия записи (в ASCII символах) e_slen equ 1 ; байт содержит "реальную" длину сигнатуры e_dlen equ 2 ; обязательная длина исполняемого кода (размер декриптора) e_sdata equ 30 ; полная длина сигнатуры e_curem equ 4 ; метод лечения или смещение процедуры для лечения cmethod_unk equ 0 ; - вылечить немогу cmethod_del equ 1 ; - необходимо удалить ; .data ; вирусная база vir_no dd 2 ; количество записей в базе @001_type db etype_pe @001_name db 'Win32/HLLM Mydoom' ; штамм B (32.768 байт) db 008 dup (0) @001_sig_len db 020 @001_dec_len dw 000 ; декриптор отсуствует @001_sig_data db 055h,08Bh,0ECh,081h, '?',005d,056h,057h,0E8h, '?' db 004d,08Dh,085h, '?',004d,050h,06Ah,002h,0FFh,015h db 010 dup (?) @001_cure_loc dd cmethod_del ;----------------------- @002_type db etype_pe @002_name db 'Win32/Parite' ; штамм B db 013 dup (0) @002_sig_len db 020 @002_dec_len dw 049+1 ; размер декриптора постоянен - 49 байт @002_sig_data db 055h,08Bh,0ECh,081h,0C4h,0C0h,0FEh,0FFh,0FFh,08Bh db 0C5h,083h,0C0h,004h, '?',003h,056h,057h,033h,0DBh db 010 dup (?) @002_cure_loc dd offset parite_b_pe ; .code ; ; detect - сравнение сигнатур из базы "bloc" с участком кода из "cloc" ; ; on start : bloc - расположение базы сигнатур ; cloc - расположение кода для сравнения ; ; on exit : eax == 0 - ни одна из сигнатур не совпала ; eax != 0 - содержит номер совпавшей сигнатуры в базе ; detect proc cloc:dword, bloc:dword ; push ecx edx esi sub edx,edx ; edx - регистр под номер записи inc edx ; edx=1 - рассмотрим 1 запись mov esi,bloc mov ecx,dword ptr [esi] ; ecx - кол-во записей в базе add esi,4 ; esi - данные первой записи det_main_loop: sub eax,eax mov al,byte ptr [esi+e_type+e_name] push eax add esi,e_type+e_name+e_slen+e_dlen push esi ; расположение сигнатуры в записи push cloc ; расположение кода для сравнения call detect_sig ; совпадает сигнатура ? or eax,eax jnz det_sig_found ; если сигнатура совпала ; det_main_next: inc edx ; если сигнатура не совпала, add esi,e_sdata+e_curem ; проверим следующую запись loop det_main_loop ; sub eax,eax ; если ни одна из сигнатур не совпала - eax=0 det_main_ret: pop esi edx ecx ret ; det_sig_found: xchg eax,edx ; eax=edx = номер совпавшей сигнатуры jmp det_main_ret endp ; ; проверка участка на совпадение с сигнатурой ; ; on start : slen - длина сигнатуры ; sloc - расположение сигнатуры ; cloc - расположение кода для сверки ; on exit : eax = 0 - не совпадают, иначе сигнатура совпала ; detect_sig proc cloc:dword, sloc: dword, slen: dword ; push ecx edx esi edi ; установим параметры сигнатуры: mov ecx,slen ; ecx - размер mov esi,sloc ; esi - расположение mov edi,cloc ; edi - код для сравнения с сигнатурой detect_loop: lodsb cmp al,'?' jnz det_compare ; ; если встретился символ "?", значит необходимо пропустить количество ; (указанное в следующем байте сигнатуры) байт в исследуемом коде ; sub eax,eax lodsb dec ecx add edi,eax jmp detect_cont+1 det_compare: cmp byte ptr [edi],al ; сравним байты сигнатуры и кода jz detect_cont sub eax,eax jmp detect_ret detect_cont: inc edi loop detect_loop ; если цикл окончен и сигнатура mov al,1 ; совпала полностью EAX = 1 detect_ret: pop edi esi edx ecx ret endp
2.4. «Поиск» процедуры подсчета "delta value"
Delta value - это число, которое является разницей между начальным положением кода (тем, которое он имел после компиляции) в памяти и текущим положением. Прибавляя это значение к смещениям различных "ресурсов", мы всегда получим их текущее местоположение в памяти.
Различные варианты (внешне видоизмененные), таких процедур используются в вирусах, так как местоположение их кода изменяется в зависимости от файлов, которые они заражают. Так же, эти процедуры используются в декрипторах большинства само шифрующихся вирусов и достаточно часто в вирусах использующих простые полиморфные алгоритмы. Это, несмотря на то, что наличие таких процедур, плюс несколько обнаруженных в декрипторе постоянных участков, позволяют составлять достаточно устойчивую сигнатуру расшифровщика, для детектирования с помощью "плавающей сигнатуры".
Процедура подсчета delta value, может выглядеть примерно так:
delta: call get_ip или delta: call get_ip get_ip: pop ebp get_ip: mov ebp,dword ptr [esp] sub ebp,offset get_ip add esp,4 sub ebp,offset get_ip
Первый вариант является самым простым и распространенным, именно по этому он и несколько других, являются дополнительной эвристической сигнатурой у многих антивирусов. Естественно при написании вирусов, стараются использовать видоизмененные варианты этих процедур.
Как работает эмулятор? Кусок кода читается в буфер антивируса, разбирается на инструкции и имитируется их исполнение с помощью различных трюков. Многие инструкции, после разбора (дизассемблером) можно «запускать» в специальной среде, а после этого учитывать произошедшие изменения (новое состояние регистров, EFLAGS). Но есть и такие, работу которых нужно полностью имитировать.
Полностью имитируется работа инструкций, которые в своей работе обращаются к данным по смещениям. В связи с изменением местоположения исследуемого кода в памяти (который мы перенесли в антивирусный буфер), необходимо рассчитать разницу между его оригинальным положением и положением в антивирусном буфере. Эта разница добавляется ко всем смещениям, использующимся в инструкциях, и является эквивалентом значения, которое вычисляется с помощью процедур "delta". Следовательно, если подобные процедуры присутствуют в расшифровщике, то один из регистров (в примерах рассмотренных выше это ebp) будет содержать delta value, если он будет использоваться в указании смещений, то эмулятору просто нельзя никаким образом изменять смещение, потому что без вмешательства эмулятора и так получится "правильное" смещение. Значит анализатору кода необходимо выявлять наличие процедур подсчета delta value и определять номер регистра, в котором будет находиться это число.
Самый удобный метод определения процедур, это поиск по сигнатурам. Но здесь тот случай, когда явную сигнатуру выделить никак нельзя. В обеих процедурах постоянной остается одна инструкция – "CALL", точнее сказать только один ее байт (первый) - 0E8h. В этом случае попытаемся воспользоваться поиском сигнатур другого типа:
Приведем пример простейшей реализации части анализатора кода, которая предназначена для выявления процедур подсчета delta value и регистра, содержащего это значение:
; ; ca_ip_stage - номер этапа, на котором находится эта функция кодо-анализатора ; ca_ip_instr - количество инструкций проверенных после вызова "CALL" ; ca_ip_call_off - смещение полученной после вызова "CALL" ; .data ca_ip_stage db ? ca_ip_instr db ? ca_ip_call_off dd ? .code ; ; начальные параметры : esi - смещение разбираемой инструкции ; ca_ip_srch proc ; push eax ecx edx ebp edi esi ; ; Работа процедуры делится на два этапа. ; После успешного завершения второго этапа, функция прекращает свою работу для текущего файла. ; cmp byte ptr [ca_ip_stage],2 jz ca_ip_srch_ret cmp byte ptr [ca_ip_stage],1 jz ca_ip_stage_2 ; ; Первый этап ; ; - Поиск инструкции CALL (0E8h) ; - Сохранение в "ca_ip_call_off" смещения, полученного в результате работы найденной инструкции "CALL". ; - ca_ip_stage = 1 ; ca_ip_stage_1: lodsb cmp al,0e8h jnz ca_ip_srch_ret lodsd inc byte ptr [ca_ip_stage] mov dword ptr [ca_ip_call_off],esi jmp ca_ip_srch_ret ; ; Второй этап: первая часть ; ; Проверка значений всех регистров (которые храняться в буфере "regs"), на наличие в них значения совпадающего ; с "ca_ip_call_off". ; Ведется счетчик инструкций. Если, в течении разбора эмулятором четырех инструкций, ни один из регистров не содержит ; значения - заготовки для подсчета delta value. То начинаем с первого этапа - обнуляем "ca_ip_percent". ; ca_ip_stage_2: mov esi,offset [regs] sub ecx,ecx mov cl,7+1 ca_ip_srch_lp: lodsd cmp dword ptr [ca_ip_call_off],eax jz ca_ip_stage_3 ; перейдем ко второй части этапа loop ca_ip_srch_lp inc byte ptr [ca_ip_instr] cmp byte ptr [ca_ip_instr],4 jnz ca_ip_srch_ret mov word ptr [ca_ip_stage],0 mov dword ptr [ca_ip_call_off],0 jmp ca_ip_srch_ret ; ; Второй этап: вторая часть ; ; - Рассчитаем номер регистра, который содержит delta_value и укажем это в таблице информации о регистрах ("_regs"). ; - Присвоим значение 2 переменной "ca_ip_percent", это означает, что работа этой части кодо-анализатора прекращена. ; ca_ip_stage_3: inc byte ptr [ca_ip_percent] sub eax,eax mov al,7+1 sub al,cl mov byte ptr [eax+_regs],1 ; в eax номер регистра содержащего "заготовку" для delta value ; ca_ip_srch_ret:pop esi edi ebp edx ecx eax ret endp
Получается, что анализатор – "заведует" информацией о типе значений, которые хранятся в каждом из регистров. В примере, для этого использовался буфер "_regs". Нормальная работа эмулятора без информации такого рода просто невозможна. Ниже я расскажу о том, как эмулятор использует эту информацию.
В процессе работы, эмулятор «предоставляет» разбираемой программе "виртуальный стек". То есть программа будет использовать в своей работе стек расположенный в специальном буфере. Следовательно, если в ходе эмуляции инструкции будут обращаться по смещениям находящимся в стеке, каких либо изменений со стороны эмулятора не требуется. В данном случае при инициализации кодо-анализатора в буфере "_regs", для регистра esp ("regs+4") должна делаться специальная пометка типа регистра (см. выше).
2.5. Полная имитация выполнения инструкций
Допустим в цикле работы эмулятора, дизассемблер встретил инструкцию типа:
xor dword ptr [erega+eregb],eregc или xor dword ptr [erega+eregb],_dword_
Рассмотрим процесс разбора параметров инструкции:
Номера регистров:
AX или AL - 000 = 0 CX или CL - 001 = 1 DX или DL - 010 = 2 BX или BL - 011 = 3 SP или AH - 100 = 4 BP или CH - 101 = 5 SI или DH - 110 = 6 DI или BH - 111 = 7
Для хранения количества регистров использующихся в указании смещения и номеров этих регистров отведем специальный буфер "dregs". Процесс сохранения параметров необходимых для полной имитации выполнения будет выглядеть примерно так:
Рассмотрим участок кода эмулятора, который производит вычисление смещения использующегося в инструкции и последующую иммитацию выполнения:
.data ; ; dregs+000 - кол-во регистров использующихся в инструкции для указания смещения ; dregs+1*4 - номер первого регистра использующегося для ... ; dregs+2*4 - номер второго регистра ... ; dregs+n*4 - немер n-ого регистра ... ; dregs dd 010 dup (?) regs dd 008 dup (?) .code @found_spec: push ebp ; ; настроим регистры: ; ; ecx = dregs+000 - количество регистров использующихся в указании смещения ; ebx = по окончанию цикла, будет содержать смещение, которое содержалось в регистрах ; ebp = 0 - если в инструкции не использовались регистры содержащие delta value или смещение в стеке ; esi = смещение в буфере dregs ; sub ebp,ebp sub ebx,ebx mov esi,offset dregs lodsd xchg eax,ecx count_sum_lp: lodsd cmp byte ptr [eax+_regs],0 ; тип регистра не = 0, значит содержит вычисленное delta value jz count_sum ; или смещение в области стека, ... inc ebp ; ... ebp = ebp + 1 count_sum: push ecx edx sub edx,edx mov dl,4 mul edx pop edx ecx ; ; буфер regs содержит значения регистров эмулируемой программы, добавим значение использующегося регистра в смещение. ; add ebx,dword ptr [regs+eax] loop count_sum_lp ; ; Далее необходимо рассчитать разницу между старым местоположением в файле и тем по которому инструкция ; расположена в антивирусном буфере. Но если в указании смещения участвовал регистр содержащий delta value или ; смещение в области "виртуального стека", подобные изменения будут лишними (смотри пункт 2.4). ; Если значение регистра EBP > 0, то изменений не требуется. ; or ebp,ebp pop ebp jnz @spec_emul_01 sub ebx,старое местоположение кода в файле add ebx,местоположение кода в антивирусном буфере @spec_emul_01: ...
Далее следует:
2.6. Изменение параметров работы эмулятора
При запуске эмулятора, антивирус передает ему некоторые параметры, например:
Рассмотрим часть анализатора кода, которая выполняется перед запуском эмулятора и изменяет размер участка данных, который необходимо выполнить (параметр номер 3). Зачем это нужно? Как я уже говорил, эмулятор антивируса может предназначаться только для имитации работы расшифровщиков (декрипторов). В этом случае основная и единственная его задача - расшифровать вирусное тело и сравнить с сигнатурами известных вирусов (информация о которых присутствует в базе). Как правило длина таких расшифровщиков несколько сотен байт, однако существуют различные утилиты, которые позволяют упаковать или зашифровать код вирусных дропперов или программ зараженных вирусами. Такими утилитами, часто пользуются, когда распространяют вирусы (особенно те, в которых отсутствуют технологии полиморфизма или шифровки - черви или троянские кони), в надежде на то, что антивирусы не смогут обнаружить вирусы в таких файлах. Подобные утилиты прикрепляют к "обработанным" файлам достаточно большие и комплексные декрипторы. Если эмулятор антивируса, способен имитировать работу декрипторов создаваемых, той или иной утилитой, то при их «встрече», размер эмулируемого кода должен равняться максимальному размеру декрипторов создаваемых этой утилитой. Очень часто, декрипторы содержат множество сигнатур, которые и могут являться критерием встречи с "продуктом" какой либо утилиты.
Пример процедуры входящей в состав кодо-анализатора, назначением которой является определение в коде наличие декриптора, создаваемого утилитой UPX:
; ; структура записей полностью аналогична структуре вирусной базы, для поиска сигнатуры используется ; функция описанная (выше) ; .data pck_no dd 1 ; количество записей в базе _001_type db etype_pe ; запись для PE-файлов _001_name db 'UPX' ; название утилиты - UPX db 022 dup (0) _001_sig_len db 006 ; длина сигнатуры 6 байт _001_dec_len dw 1000 ; максимальная длина декриптора ;) _001_sig_data db 060h,0BEh, '?',004d,08Dh,0BEh ; сигнатура db 024 dup (0) _001_cure_loc dd 0 ; это поле не используется, возможно любое значение .code ; ; det_pack - проверка на упаковщики и шифровщики ; ; on exit : ebx - длина кода, которую необходимо разобрать для "распаковки" файла ; det_pack proc ; push eax ecx edx ebp edi esi ; push offset pck_no push смещение точки входа в код call detect or eax,eax jz det_pack_ret ; dec eax mov edx,( (_001_cure_loc+1) - _001_type ) mul edx add eax,offset _001_name ; eax - смещение на имя выведем информацию о том, что файл упакован (зашифрован) утилитой ... sub ebx,ebx mov bx,word ptr [_001_type+e_type+e_name+e_slen] det_pack_ret: pop esi edi ebp edx ecx eax ret endp
<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> |