Описание ф-ций INT 2E

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

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

### Содержание

- Введение
- Переход в RING-0
  - PsCreateSystemThread
  - PoCallDriver
- Работа с памятью
  - RtlCopyMemory, RtlMoveMemory
  - READ_REGISTER_BUFFER_UCHAR/ULONG/USHORT
  - WRITE_REGISTER_BUFFER_UCHAR/ULONG/USHORT
  - READ_REGISTER_UCHAR/ULONG/USHORT
  - WRITE_REGISTER_UCHAR/ULONG/USHORT
- Работа с портами
  - READ_PORT_BUFFER_UCHAR/ULONG/USHORT
  - WRITE_PORT_BUFFER_UCHAR/ULONG/USHORT
  - READ_PORT_UCHAR/ULONG/USHORT
  - WRITE_PORT_UCHAR/ULONG/USHORT
- Процессы и нити
  - IoGetCurrentProcess, PsGetCurrentProcess
  - KeGetCurrentThread, PsGetCurrentThread
- Прочие функции
  - KeQuerySystemTime
- Комментарии

### Введение

Рассмотрим некую 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)