воскресенье, 29 апреля 2012 г.

Типы объектов ядра

Стало любопытно, как изменялось количество объектов ядра в разных ОС семейства NT.

Начнем с WindowsXP. Cписок объектов ядра выглядит следующим образом:

IoFileObjectType
MmSectionObjectType
IoControllerObjectType
IoDriverObjectType
IoDeviceObjectType
IoCompletionObjectType
LpcPortObjectType
LpcWaitablePortObjectType
ObpDirectoryObjectType
ObpSymbolicLinkObjectType
PsThreadType
PsProcessType
PsJobType
SeTokenObjectType
WmipGuidObjectType
ExCallbackObjectType
ExEventObjectType
ExSemaphoreObjectType

ExTimerObjectType
ExEventPairObjectType
ExMutantObjectType
ExProfileObjectType
ExpKeyedEventObjectType
CmpKeyObjectType
DbgkDebugObjectType


Набор объектов ядра в Windows Vista был существенно расширен, по сравнению с WindowsXP.

Прежде всего, в Vista появились advanced local procedure calls (ALPC), что привело к замене объектов LpcPortObjectType и LpcWaitablePortObjectType которые существовали в WindowsXP на объект AlpcPortObjectType.

Также был усовершенствован механизм реализации пула потоков - его перенесли в ядро, что привело к появлению нового объекта ядра: ExpWorkerFactoryObjectType.

Был переделан механизм сессий (изоляция нулевой сессии и т.д.), что вылилось в добавление нового объекта: MmSessionObjectType.

Новый механизм под названием менеджер транзакций привнес в ядро сразу 4 новых объекта: TmEnlistmentObjectType, TmTransactionManagerObjectType, TmTransactionObjectType, TmResourceManagerObjectType.

Ну и наконец, реализация механизма event tracing'a также добавила новый объект: EtwpRegistrationObjectType.

В Windows7 количество объектов ядра также увеличилось, по сравнению с Vista.

Добавлены объекты UserApcReserve и IoCompletionReserve, они служат для хранения соответствующих объектов (apc/completion port) в заранее выделенной памяти, а не из пула. Оба этих объекта создаются через native-api NtAllocateReserveObject.

Обновление механизма ETW в Windows7 также привнесло в ядро новый объект: EtwpRealTimeConnectionObjectType.

Изменение в Power Management'е привело к появлению еще одного объекта: PopPowerRequestObjectType.

четверг, 26 апреля 2012 г.

Privilege escalation protections in Windows 8

Грядущий выход windows 8 принесет несколько новинок в плане безопасности, о двух из которых, относящихся к защите от повышения привилегий и будет сегодняшняя заметка.

Первая из них закрывает целый класс атак(kernel-mode NULL dereference), связанных с повышением привилегий.

Сценарий атаки следующий: 

1) находится уязвимость, позволяющая записать ноль по любому адресу в ядре
2) в юзермоде выделяется память по нулевому адресу (флаг MEM_TOP_DOWN)
3) перезаписывается указатель в HAL_DISPATCH
4) вызывается NtQueryIntervalProfile которая в приводит к вызову ф-ции из HAL_DISPATCH и соответственно к вызову кода в нулевой странице

Таких уязвимостей было открыто довольно много и Microsoft в windows 8 решила закрыть саму возможность реализации атаки, запретив выделять нижние 64кб юзерской памяти.

И второе нововведение - поддержка SMEP.

Supervisor Mode Execution Protection (SMEP) - это технология, призванная предотвратить выполнение кода в ядре из тех страниц, которые помечены как юзерские.

То есть, каждая страничка в системе будет помечена определенным битом, который либо будет давать ей право исполняться в ядре, либо нет.
Реакцией на попытку выполнения странички недопустимой для исполнения в ядре будет fault.

Аппаратная поддержка SMEP появилась в процессорах на базе архитектуры Ivy-Bridge, которые были презентованы буквально на днях.

Не знаю, насколько эти новинки будут эффективными, и как скоро появятся публикации по их обходу, но общая тенденция развития windows в плане безопасности безусловно радует.

суббота, 31 марта 2012 г.

Карта распределения Page Fault's при старте приложения

В Windows используется концепция demand paging, означающая, что физическая память выделяется только при обращении к виртуальному адресу, но никак не при выделении виртуальных адресов.

На уровне ОС это реализуется через дерево VAD, привязанное к процессу(EPROCESS->VadRoot) и описывающее диапазоны и другие атрибуты этой выделенной ранее через сервисные функции памяти. При обращении к памяти срабатывает обработчик страничных фолтов(KiTrap0E), который через VAD ищет соответствующий диапазон, и в случае успеха(для private memory это наличие атрибута закоммиченой памяти) из free/standby/zeroed списков выделяется физическая страница и заполняется PDE/PTE(проставляются атрибуты, valid, pfn и т.д.).

Стало интересно, какие странички подгружаются первыми при старте приложения.
Логично было бы предположить, что это страничка с apc диспатчером и другие страницы ntdll.dll, однако это не так.

Оказалось, что первая страничка это KUSER_SHARED_DATA, к которой идет обращение при создании секции для ехе процесса:

MiMapViewOfImageSection()
{
...
    if ( ControlArea->Segment->u2.ImageInformation->ImageContainsCode &&
         ((ControlArea->Segment->u2.ImageInformation->Machine < USER_SHARED_DATA->ImageNumberLow) ||
        (ControlArea->Segment->u2.ImageInformation->Machine > USER_SHARED_DATA->ImageNumberHigh) )
...
}

Затем при создании и инициализации PEB будет второй #PF:

MmCreatePeb()
{
...
    Status = MiCreatePebOrTeb (TargetProcess, sizeof(PEB), (PVOID)&PebBase);
...
    try
    {
        PebBase->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
...
}

Следом идет создание TEB для первичного потока, при её инициализации случится еще один #PF:

MmCreateTeb()
{
...
    Status = MiCreatePebOrTeb (TargetProcess, TebSize, (PVOID) &TebBase);
...
    try
    {
        TebBase->NtTib.ExceptionList = EXCEPTION_CHAIN_END; // только на x86
...
}

Перед тем, как запустить юзермодную APC стартующую поток, ядро копирует контекст в юзермодный стек, что приводит к #PF:

KiInitializeUserApc()
{
...
    RtlCopyMemory ((PULONG)(UserStack + sizeof(KAPC_RECORD)), &ContextFrame, sizeof(CONTEXT));
...
    TrapFrame->Eip = (ULONG)KeUserApcDispatcher;
...
}

И только потом, при переходе в user mode, при выполнении KiUserApcDispatcher подгрузится страничка ntdll.dll.
Хотя подгрузится это не совсем подходящее слово, т.к. подгрузилась с диска эта dll еще при старте ОС, а в последующих случаях, т.е. при старте новых процессов все её странички разрешались ввиде soft fault's, т.е. из памяти, без обращений к диску.

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

Необходимость может наступить, например, при достижении лимита(данные для х86):

ULONG PspMinimumWorkingSet = 20;
ULONG PspMaximumWorkingSet = 45;

Soft fault's практически бесплатны в плане временных затрат( мне думается самое затратное там - это переключение контекста при срабатывании обработчика страничного фолта ), и в дальнейшем при необходимости, ранее выкинутые странички будут вновь добавлены в рабочий набор процесса.

воскресенье, 11 марта 2012 г.

Определение id некоторых сервисных функций без таблиц

Многие программы на х86 перехватывают функции через патч SDT, индексы этих функций меняются в зависимости от версии ОС.

Решение проблемы - это как правило хардкод индексов в коде, но есть и определенные хитрости, позволяющие избежать хардкода в некоторых случаях.

Рассмотрим любую Zw функцию, экпортируемую ядром, например ZwDuplicateToken:

nt!ZwDuplicateToken:
804fe30c b845000000      mov     eax,45h
804fe311 8d542404          lea     edx,[esp+4]
804fe315 9c                    pushfd
804fe316 6a08                 push    8
804fe318 e874f10300       call    nt!KiSystemService (8053d491)
804fe31d c21800             ret     18h

Можно заметить, что id можно получить отсюда, например таким кодом:

offsetToId = (PUCHAR)ZwDuplicateToken;
id = *(PULONG)(offsetToId + 1);

Такое решение подходит лишь для экспортируемых ядром Zw функций, их число хотя и достаточно велико, но все же не покрывает всего диапазона ф-ций в SDT. То есть, решение частное, а не общее.

вторник, 28 февраля 2012 г.

Баг PsSetCreateThreadNotifyRoutine на WinXp

Забавный баг существует на Windows XP, в нотификаторе на создание потока нельзя получить объект потока по приходящему в него tid'у.

А все потому, что:

NTSTATUS PspCreateThread(...)
{
...
   ExGetCallBackBlockRoutine - вызов колбека на XP
...
   ObInsertObject - а добавление объекта в таблицу хендлов идет уже после
...
}

На Windows Vista баг пофикшен:

NTSTATUS PspInsertThread(...)
{
...
     call    ObInsertObject - сначала объект добавляется в таблицу хендлов
...
     mov     [ebp+arg_14], offset _PspCreateProcessNotifyRoutine
     push    [ebp+arg_14]
     call    _ExReferenceCallBackBlock
...
     call    dword ptr [edi+4] - а уже потом идет вызов колбека
...
     call    _ExDereferenceCallBackBlock
...
}

четверг, 23 февраля 2012 г.

Branch Tracing в ядре

Оказывается, в ядре windows есть и поддержка Branch Tracing(начиная с висты).
Этой функциональностью пользуется driver verifier:

На этапе инициализации:

IoInitSystem(...)
{
...
    if ( ViVerifierEnabled )
        VfNotifyVerifierOfEvent(0); // initialize branch tracing
...
}

Phase1InitializationDiscard(...):
{
...
    push    5
    pop     eax
    call    VfNotifyVerifierOfEvent(x) // start branch tracing
...
}


VOID VfNotifyVerifierOfEvent( ULONG eventId )
{
    switch ( eventId )
    {
        case 0:                      
             VfInitializeBranchTracing();
        break;
 ...
        case 5:
             VfStartBranchTracing();
        break; 

    }


Включение branch tracing:

VfStartBranchTracing
{
...
    push    0
    push    0C0h
    push    1D9h // IA32_DEBUGCTL
    WRMSR(x,x,x)
...
}

К сожалению, чаще всего драйвера тестируются на виртуалках, а там branch tracing не поддерживается, поэтому полноценно поиграться с ним не выйдет.

пятница, 27 января 2012 г.

Bound import

О bound import'e писали неоднократно, поэтому подробно останавливаться на нем не вижу смысла, напомню лишь что это, в общих чертах.

Bound import позволяет сэкономить время загрузки модуля, за счет кеширования адресов ф-ций в импорте.
То есть, системный загрузчик не делает получение адресов ф-ций из их имен, если выполнены некоторые условия для этого.

Условия следующие:

* Виртуальный адрес у директории bound import'a не должен быть равен нулю
* временные штампы в IMAGE_BOUND_IMPORT_DESCRIPTOR и в хедере импортируемого модуля совпадают
* Для dll: модуль не должен быть ребазирован (незнаю правда, насколько эта информация справедлива для систем с aslr)

Стандартные приложения windows используют bound import очень активно.

Мне стало интересно, много ли инструкций экономит эта фича.

Проверялось число инструкций для notepad.exe ( win xp sp3 ), с apc диспатчера, то есть с первых инструкций(в UM) после старта процесса и до entry point приложения.

Для notepad без bound import'a число инструкций = 2280899
Для notepad с bound import'ом  число инструкций = 2268677

Разница 12222, т.е. всего пол процента от общего числа инструкций ( до точки входа ).

Однако стоит помнить, что обход импорта это рекурсивная процедура:

LdrpWalkImportDescriptor => LdrpLoadImportModule => LdrpWalkImportDescriptor
LdrpWalkImportDescriptor => LdrLoadDll => LdrpWalkImportDescriptor

То есть прирост в производительности от использования bound import'a может быть заметным, если задействовано большое количество импортируемых модулей.