воскресенье, 13 октября 2013 г.

Бесконечный цикл в Driver Entry

Увидел на wasm.ru любопытный топик:

http://wasm.ru/forum/viewtopic.php?pid=538900#p538900

Содержание:

IRQL = 0. DriverEntry.
Если сделать бесконечный цикл, то на системе с одним процессором всё жутко начинает лагать - обычные приложения только через секунд 10 реагирует на клик мыши. Почему такое происходит? Ведь у кода драйвера IRQL =  0   =>  по хорошему процессор должен отдавать равное количество времени и драйверу и пользовательскому коду(он ведь выполняется тоже на IRQL = 0).

Далее в топике идут пространные рассуждения на тему.

Мне показалось это интересным, и в данной заметке я тоже порассуждаю, есть ли разница между выполнением кода в р0 в driver entry при irql ==PASSIVE LEVEL и в р3( где как известно, весь код также выполняется на PASSIVE LEVEL ).

Разница, разумеется есть. Дело вот в чем: driver entry драйвера выполняется в workitem'e, причем управление из workitem'a не вернется до тех пор, пока не отработает driver entry.

А workitem'ы, они же системные потоки это ценный ресурс, как подсказывает нам MSDN: System worker threads are a limited resource.

По факту, в системе есть несколько очередей с рабочими потоками;

typedef enum _WORK_QUEUE_TYPE
{
    CriticalWorkQueue,
    DelayedWorkQueue,
    HyperCriticalWorkQueue,
    MaximumWorkQueue
} WORK_QUEUE_TYPE;

KQUEUE ExWorkerQueue[MaximumWorkQueue];

Каждый тип очереди воркитемов разребает определенное количество потоков.

Как выше уже писалось, точка входа драйвера обрабатывается в воркитеме, и если там бесконечный цикл - мы просто забираем у системы один из системных потоков. А для DelayedWorkQueue очереди их всего 2-3!

Так в чем проблема? Забираем и забираем, что с того?

Проблема в том, что воркитемы активно используются системой, это и обнуление страниц( C2-level Security требование ) и Wmi/Etw и аудит и errorlog и реестр и page writer и прочие.

Безусловно, наиболее критичные для системы воркитемы находятся в CriticalWorkQueue, которую обслуживает большое количество системных потоков. Однако, на быстродействие системы может повлиять и воркитемы из DelayedWorkQueue, что видимо и происходит в описанном на wasm случае.

Таким образом, на уровне планировщика действительно нет разницы между выполнением р3 и р0 кода, однако кроме планировщика на быстродействие системы влияет много факторов, и в этом плане, разница между р3 и р0 кодом есть. В р0 гораздо угробить убить систему, если нет достаточной квалификации.





пятница, 17 мая 2013 г.

2 самые обычные инструкции

Mateusz "j00ru" Jurczyk, замечательный ресерчер потрохов windows, на конфе NoSuchCon среди прочих интересных вещей показал, как можно двумя инструкциями отправить windows в BSOD:

.text:00401008 start        proc near
.text:00401008                 xor     ebp, ebp
.text:0040100A                 jmp     near ptr 8053D60Ah (KiSystemServiceAccessTeb)
.text:0040100A start        endp
Причина такого поведения кроется в обработчике #PF:

        mov     ecx, offset FLAT:KiSystemServiceAccessTeb
        cmp     [ebp].TsEip, ecx
        jne     short Kt0e07

*       mov     ecx, [ebp].TsEbp        ; (eax)->TrapFrame of SysService
        test    [ecx].TsSegCs, MODE_MASK
        jz      short Kt0e07            ; caller of SysService is k mode, we will let it bugcheck.
        mov     [ebp].TsEip, offset FLAT:kss61
        mov     eax, STATUS_ACCESS_VIOLATION
        mov     [ebp].TsEax, eax
        jmp     _KiExceptionExit

Суть в том, что код обработчика принимает TsEbp за указатель на trap frame ( чем он и является при #PF в ядре ) на строке, выделенной *.
Но в данном примере, в обработчик приходит юзермодный ebp, что и приводит к BSOD'у.

вторник, 2 апреля 2013 г.

Преобразование имен файлов в формате Universal Naming Convention (UNC)

Как можно преобразовать UNC имя в ядре, чтобы оно соответствовало юзермодному имени?

То есть, например, путь:
"\Device\LanmanRedirector\Home\Users\Гость\Desktop\test.exe"
требуется преобразовать в:
"\Home\Users\Гость\Desktop\test.exe"

Немного теории.

Все удаленные файловые операции проходят через multiple UNC provider (MUP), который перенаправляет запросы подходящему сетевому редиректору (UNC провайдеру). Перенаправление происходит через подмену имени файла и установки STATUS_REPARSE, что вынуждает менеджер ввода-вывода перепослать IRP уже с новым именем.

В новое имя входит имя провайдера, ответственного за обработку запроса. Как же MUP выбирает нужного провайдера? Логично предположить, что существует некий список провайдеров. Действительно, такой список есть в реестре, в ветке:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NetworkProvider\Order]
"ProviderOrder"="RDPNP,LanmanWorkstation,webclient"

Через наличие префиксов в имени файла, или через посылку IOCTL_REDIR_QUERY_PATH всем провайдером в списке, MUP выбирает нужного провайдера.

Каждый провайдер является службой, соответственно, имеет свою запись в ветке HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services.
Например, для LanmanWorkstation можно найти соответствующее имя девайса:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\LanmanWorkstation\NetworkProvider]
"DeviceName"="\\Device\\LanmanRedirector"

Таким образом, решение задачи преобразования имени состоит в перечислении всех имен служб UNC провайдеров, и получение имени девайса.
Соот-но последним шагом будет удаление \Device\unc provider name из исходного имени файла. Захардкодить имена провайдеров нельзя, т.к. они могут добавляться вызовом ф-ции FsRtlRegisterUncProvider.

четверг, 10 января 2013 г.

Dos в Win7/8

Обнаружил занятную вещь.
Если попытаться загрузить драйвер diskdump.sys или dumpata.sys из стандартного каталога с драйверами windows, то произойдет BSOD:

SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (7e)
This is a very common bugcheck.  Usually the exception address pinpoints
the driver/function that caused the problem.  Always note this address
as well as the link date of the driver/image that contains this address.
Arguments:
Arg1: c0000005, The exception code that was not handled
Arg2: 82645e85, The address that the exception occurred at
Arg3: 807df9f0, Exception Record Address
Arg4: 807df5d0, Context Record Address

Debugging Details:
------------------

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - <Unable to get error code text>
...
STACK_TEXT: 
807dfab8 965f339f 0052005c 00000000 00010000 nt!memset+0x45
807dfad8 827cf2e6 8647c2b8 86688000 00000000 Diskdump!DriverEntry+0x1b
807dfcbc 827d2d98 00000001 00000000 807dfce4 nt!IopLoadDriver+0x7ed
807dfd00 82689a6b 8a692cd0 00000000 84491640 nt!IopLoadUnloadDriver+0x70
807dfd50 82814f9e 00000001 41adab71 00000000 nt!ExpWorkerThread+0x10d
807dfd90 826bd1f9 8268995e 00000001 00000000 nt!PspSystemThreadStartup+0x9e
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x19

Смотрим код:

Diskdump!DriverEntry:
965f3384 8bff            mov     edi,edi
965f3386 55              push    ebp
965f3387 8bec            mov     ebp,esp
965f3389 53              push    ebx
965f338a 56              push    esi
965f338b 8b750c          mov     esi,dword ptr [ebp+0Ch]
965f338e 57              push    edi
965f338f bf00000100      mov     edi,10000h
965f3394 57              push    edi
965f3395 6a00            push    0
965f3397 ff7608          push    dword ptr [esi+8]
965f339a e805110000      call    Diskdump!memset (965f44a4)

Итак, падение происходит из-за обнуления памяти размером 10000h по невалидному адресу. Адрес виден в стеке: 0052005c

То, что он невалиден понятно по его виду, это юзермодный адрес, а Driver Entry выполняется в контексте системного потока. Но можно и проверить:

kd> !pte 0052005c
                    VA 0052005c
PDE at C0600010            PTE at C0002900
contains 0000000000000000
not valid

Откуда берется невалидный адрес? Он приходит из второго аргумента DriverEntry. По документации там всегда валидная копия юникодной строки, представляющей из себя registry path:
NTSTATUS DriverEntry( _In_ struct _DRIVER_OBJECT *DriverObject, _In_ PUNICODE_STRING RegistryPath )
Собственно, невалидный адрес и является частью этой юникодной строки:

5c 00 52 00 это \.R, первые 2 WCHAR от строки
\.R.E.G.I.S.T.R.Y.\.M.A.C.H.I.N.E.\.S.Y.S.T.E.M.\.C.o.n.t.r.o.l.S.e.t.0.0.1.\.s.e.r.v.i.c.e.s.\.D.i.s.k.d.u.m.p.

Теперь разберемся, почему драйвер diskdump.sys считает, что вторым агрументом придет какая-то структура, которую он должен заполнить, вместо registry path. После непродолжительных поисков находится виновник - драйвер crashdmp.sys, в который начиная с windows vista перекочевал из ядра весь код, связанный с крешдампами.

Драйвер crashdmp.sys загружает ядро в ф-ции IopLoadCrashdumpDriver(), где в качестве второго аргумента в DriverEntry приходит указатель на таблицу колбеков. К слову,  crashdmp.sys также портит память со строкой registry path, но до BSOD'а дело не доходит, по причине слишком длинной строки registry path.

Далее, в CrashdmpLoadDumpStack грузятся остальные драйвера из стека драйверов, в которые также передается вторым параметром в DriverEntry структура, которую они заполняют своими ф-циями.

Собственно, разработчики windows не учли тот факт, что драйвера из стека crash dump i/o path можно загрузить как обычный драйвер. Никаких проверок для предотвращения их загрузки нет. Поэтому и возможен такой локальный DOS.

Данного рода баг существует как минимум на win7 и win8 как х86, так и х64.

Теперь посмотрим, возможна ли тут EoP (elevation of privilege).

.text:0001239F                 push    dword ptr [esi+4Ch] ; size_t <= 00300074
.text:000123A2                 lea     ebx, [esi+0Ch]
.text:000123A5                 push    0               ; int
.text:000123A7                 push    dword ptr [ebx] ; void * <= 00470045
.text:000123A9                 call    _memset
.text:000123AE                 push    dword ptr [esi+4Ch] ; size_t <= 00300074
.text:000123B1                 push    0               ; int
.text:000123B3                 push    dword ptr [esi+10h] ; void * <= 00530049
.text:000123B6                 call    _memset
.text:000123BB                 mov     eax, [esi+8]                 <= 0052005c
.text:000123BE                 lea     ecx, [eax+10h]               <= 52006c
.text:000123C1                 mov     _DeviceExtension, ecx        <= 52006c
.text:000123C7                 and     dword ptr [eax], 0
.text:000123CA                 mov     dword ptr [eax+8], 2F0h
.text:000123D1                 mov     edx, _DeviceExtension
.text:000123D7                 lea     ecx, [eax+2F0h]
.text:000123DD                 mov     [edx+18h], ecx
.text:000123E0                 mov     ecx, edi
...
.text:000123F0                 mov     eax, _DeviceExtension
.text:000123F5                 mov     eax, [eax+18h]
.text:000123F8                 and     dword ptr [eax], 0 <======== bingo!

_DeviceExtension контролируемая атакующим структура, поле [eax+18h] контролируется, можно записать туда ядерный адрес, и он будет обнулен.

Реализация атаки через обнуление произвольного нулевого адреса давно известна, причем не только ресерчерами, но и security командой windows, которая в win8 прикрыла выделение памяти по нулевому адресу.

Однако, напомню, что дело происходит в Driver Entry, которая выполняется в контексте системного потока. То есть даже на win7 нельзя будет проэксплуатировать уязвимость с обнулением dword'а по нулевому адресу.

Кроме того, по этой же причине ( системный поток ), также не получится и просто добраться до инструкции, обнуляющей нужный адрес. Потому как для успешного выполнения нескольких ф-ций memset нужно, чтобы требуемые адреса были валидны. А они будут валидными, только если научиться в контексте системного процесса каким-либо образом выделить память. Мне таких способов не известно, боюсь, их вообще не существует. Для драйвера dumpata.sys картина примерно такая же.

Таким образом, реализация EOP невозможна, только DOS.