Описание INT 2E под Win9X
INT 2E services (VMM/NTKERN.VxD)

(x) 2000 Z0MBiE
http://z0mbie.host.sk

Содержание

Введение

Рассмотрим некую Win32-программу. Как известно, программа эта вызывает kernel, а kernel уже вызывает ring-0.

Под Win9X VMM/VWIN32 реализует специальные сервисы для kernelа. Вызываются они так:

       kernel@int21:
  015F:BFF712B9  push    ecx
                 push    eax
                 push    002A0010    ; <-- service-number
                 call    kernel@ord0
                 ret
       kernel@ord0:
  015F:BFF713D4  mov     eax, [esp+4]
                 pop     dword ptr [esp]
                 call    far cs:[BFFC9734]
                 ...
  015F:BFF79734  dd      000003C8h   ; offset
                 dw      003Bh       ; selector
                 ...
  003B:03C8      int     30h
                 ...

где service-number -- номер сервиса, например 0x002A0010 для INT 21, 0x002A0029 для INT 31, и так далее. Надо сказать, что с номерами VxD-callов эти сервисы не имеют ничего общего. Найти полный список соответствий всяких номеров именам сервисов можно в питреке.

Под WinNT ноль вызывается из кернела посредством INT 2E.

Но, как выяснилось, в маздае в VMMе существует NTKERN.VXD, коий реализует NT-евые сервисы. Называются они типа ntoskrnl!DbkBreakPoint и вызываются так же -- через INT 2E. И, о чудо, у такого рулезного инта DPL=3, то есть его можно вызывать прямо из PE файла. Более того, в обработчике нет никаких хитрожопых проверок - типа откуда пришел вызов.

Дальше-интереснее. Оказывается, INT 2E активно используется при загрузке маздая. А во время работы маздай может пользовать функции типа ntoskrnl!NtPowerInformation.

Короче говоря, можно вызывать INT 2E из PE файлов одним из следующих способов:

; 1.
        mov     eax, service-number
        lea     edx, stk
        int     2Eh

stk:    dd      param1
        dd      param2
        dd      param3
        ...
; 2.
        ...
        push    param3
        push    param2
        push    param1
        mov     edx, esp
        mov     eax, service-number
        int     2Eh
        add     esp, 4*n

Как видим, при вызове INT 2E в EAX должен быть номер сервиса, а в EDX указатель на кадр стэка. Перед тем как вызвать соответствующую функцию, обработчик инта копирует данные из *EDX в свой стэк.

Список всех номеров и имен функций лежит в ntoskrnl.inc

Далее идут описания некоторых наиболее интересных функций INT 2E.

Переход в RING-0

PsCreateSystemThread

Тут все и так ясно. Создаем нить прямо в нуле. При выходе из нити (по RET) она автоматически убивается ф-цией PsTerminateSystemThread.

                        ...
                        mov     eax, i2E_PsCreateSystemThread
                        lea     edx, stk
                        int     2Eh

__cycle:                cmp     r0_finished, 1
                        jne     __cycle
                        ...

stk:                    dd      offset thread_handle ; 0 or *thread_handle
                        dd      0               ; 0 or 0x1F03FF
                        dd      0               ; 0
                        dd      0               ; 0
                        dd      0               ; 0
                        dd      offset ring0    ; thread EIP, near proc
                        dd      12345678h       ; thread-parameter

; input: [ESP+4]=EDI=thread_parameter

ring0:                  int 3
                        mov     r0_finished, 1
                        ret

PoCallDriver

Никакими драйверами тут и не пахнет. Эта функция просто передает управление (в нуле) туда, куда ей скажут. Единственный минус -- ей надо уж очень по-изъебски передавать параметры. Кадр стэка выглядит так:

stk                     dd      offset x1
                        dd      offset x2
x1                      db      8 dup (0)
                        dd      offset x3
x2                      db      60h dup (0)
                        dd      offset x4+24h
x4                      db      18h dup (0)
x3                      db      38h dup (0)
                        dd      ring_0

А реальный код, коий я по возможности соптимизировал, такой:

                        lea     esi, r0proc
                        call    callring0
                        ...
r0proc:                 int 3
                        ret

; subroutine: callring0
; input:      ESI=offset ring_0, proc NEAR

callring0:              pusha
                        call    @@X
                        pusha
                        call    dword ptr [ecx]
                        popa
                        ret     8
@@X:                    sub     esp, 14h
                        xor     eax, eax
                        push    eax
                        lea     edx, [esp+24h]
                        push    edx
                        sub     esp, 54h
                        lea     edx, [esp+38h]
                        push    edx
                        push    edx
                        push    esi
                        mov     edx, esp
                        push    edx
                        push    edx
                        mov     edx, esp
                        mov     al, i2E_PoCallDriver
                        int     2Eh
                        popa
                        add     esp, 88h-20h
                        popa
                        ret

Работа с памятью

Эти функции подразумевают работу с памятью через нулевое кольцо. Это значит, что вы из третьего кольца передаете параметры, а в нуле производятся чтение/запись памяти. Таким образом можно читать/писать в защищенную в третьем кольце память, например в kernel.

Причем для работы с памятью из нуля существует просто охуительное количество всяких функций, включая всякие InterlockedIncrement'ы и функции для работы со строками, юникодными и не очень.

RtlCopyMemory, RtlMoveMemory

Отличаются эти две функции тем, что RtlCopyMemory просто берет и копирует буфера командой movs, а RtlMoveMemory сначала анализирует esi и edi, а потом копирует буфер по одному байту, причем начиная либо с начала либо с конца буфера. Таким образом RtlMoveMemory корректно обработает перекрывающиеся области esi...esi+ecx и edi...edi+ecx.

                        mov     eax, i2E_RtlCopyMemory  ; or RtlMoveMemory
                        lea     edx, stk
                        int     2Eh
                        ...
stk:                    dd      0BFF7xxxxh      ; edi (destination)
                        dd      offset vir_code ; esi (source)
                        dd      vir_size        ; ecx (length in bytes)

READ_REGISTER_BUFFER_UCHAR/ULONG/USHORT

Реализации команд REP MOVSB, REP MOVSD и REP MOVSW соответственно.

                        push    ecx
                        push    edi
                        push    esi
                        mov     edx, esp
                        mov     eax, i2E_READ_REGISTER_BUFFER_ULONG
                        int     2Eh
                        add     esp, 3*4

WRITE_REGISTER_BUFFER_UCHAR/ULONG/USHORT

Аналогично предыдущим: REP MOVSB, REP MOVSD и REP MOVSW, НО источник и приемник поменялись местами.

                        push    ecx
                        push    esi
                        push    edi
                        mov     edx, esp
                        mov     eax, i2E_WRITE_REGISTER_BUFFER_ULONG
                        int     2Eh
                        add     esp, 3*4

READ_REGISTER_UCHAR/ULONG/USHORT

Считать BYTE/DWORD/WORD. (MOV AL,[ESI], MOV EAX,[ESI] и MOV AX,[ESI]) Значение возвращается в EAX.

                        push    esi
                        mov     edx, esp
                        mov     eax, i2E_READ_REGISTER_UCHAR
                        int     2Eh
                        add     esp, 1*4

WRITE_REGISTER_UCHAR/ULONG/USHORT

Записать BYTE/DWORD/WORD. (MOV [EDI],AL, MOV [EDI],EAX и MOV [EDI],AX)

                        push    eax
                        push    edi
                        mov     edx, esp
                        mov     eax, i2E_WRITE_REGISTER_UCHAR
                        int     2Eh
                        add     esp, 2*4

Работа с портами

READ_PORT_BUFFER_UCHAR/ULONG/USHORT

Выполнить REP INSB, REP INSD и REP INSW соответственно.

                        push    ecx
                        push    edi
                        push    edx
                        mov     edx, esp
                        mov     eax, i2E_READ_PORT_BUFFER_ULONG
                        int     2Eh
                        add     esp, 3*4

WRITE_PORT_BUFFER_UCHAR/ULONG/USHORT

REP OUTSB, REP OUTSD и REP OUTSW

                        push    ecx
                        push    esi
                        push    edx
                        mov     edx, esp
                        mov     eax, i2E_WRITE_PORT_BUFFER_ULONG
                        int     2Eh
                        add     esp, 3*4

READ_PORT_UCHAR/ULONG/USHORT

Выполнить IN AL,DX, IN EAX,DX и IN AX,DX соответственно.

                        push    edx
                        mov     edx, esp
                        mov     eax, i2E_READ_PORT_ULONG
                        int     2Eh
                        add     esp, 1*4

WRITE_PORT_UCHAR/ULONG/USHORT

OUT DX,AL, OUT DX,EAX и OUT DX,AX.

                        push    eax
                        push    edx
                        mov     edx, esp
                        mov     eax, i2E_WRITE_PORT_UCHAR
                        int     2Eh
                        add     esp, 2*4

Процессы и нити

IoGetCurrentProcess, PsGetCurrentProcess

Обе функции указывают на один и тот же обработчик. Хендл текущего процесса возвращается в EAX.

                        mov     eax, i2E_IoGetCurrentProcess
                        int     2Eh

Обработчик GetCurrentProcess'а изнутри реализует следующее:

                        call    ntoskrnl!KeGetCurrentThread
                        mov     eax, [eax+4]
                        ret

KeGetCurrentThread, PsGetCurrentThread

Опять один и тот же обработчик. Хендл текущей нити возвращается в EAX.

                        mov     eax, i2E_KeGetCurrentThread
                        int     2Eh

Прочие функции

KeQuerySystemTime

                        push    offset systime
                        mov     edx, esp
                        mov     eax, i2E_KeQuerySystemTime
                        int     2Eh
                        add     esp, 4
                        ...
systime                 dq      ?

Коментарии

Совершенно непонятно как обстоит дело с функциями для работы с файлами. (IoCreateFile, NtCreateFile, ZwCreateFile, ZwReadFile, ZwWriteFile, DeviceIoControlFile, etc.) Я слышал, есть книжка про недокументированные возможности WinNT. Но те параметры, которые там описаны для соответствующих функций, передаются из программы в кернел, а ведь из кернела в ноль передаются уже совсем другие вещи, даже число параметров не совпадает. Ну а трассировать обработчик CreateFile выясняя все его 11 хитроизъебских параметров -- как-то лениво.

Существуют также функции для работы с registry. (RtlDeleteRegistryValue, RtlQueryRegistryValues, RtlWriteRegistryValue, IoOpenDeviceInterfaceRegistryKey, IoOpenDeviceRegistryKey, может быть -- ZwCreateKey, ZwDeleteKey, ZwEnumerateKey, ZwEnumerateValueKey, ZwOpenKey и т.п.) Этих функция я не проверял, но они явно показывают на какой-то нормальный код и могут быть вызваны.

Большинство функций, для которых не указано число параметров (в ntoskrnl.inc написан '-'), являются ВНУТРЕННИМИ, то есть параметры им передаются, но не на стэке а в регистрах, и вызвать их, минуя похеривание регистров в обработчике INT 2E нет никакой возможности. Обычно эти функции написаны полностью маленькими буквами или начинаются с подчеркивания, типа memmove, memset, qsort, rand, sprintf, _except_handler2, _global_unwind2 и т.п.

* * *

см. также примеры в ntoskrnl.zip

(c)