воскресенье, 30 октября 2011 г.

Пример некачественной эмуляции инструкций в Dr.Web

Виртуальные машины есть везде, в протекторах исполняемых файлов(VMprotect, Themida etc), в антивирусах(Dr.Web, KAV etc), в специальных средах эмулирующих ОС(VmWare, VirtualPc etc), и даже в самих ОС(эмуляция инструкций V8086 режима в винде).

Способы эмуляции также отличаются у перечисленных програмных продуктов, ктото пытается чесно эмулировать, ктото перекомпилирует на лету и сканирует внутренние буфера на предмет интересующих инструкций, а остальное пускает на исполнение, ктото трейсит. Качество эмуляции зависит от многих факторов, в данной заметке я поверхностно рассмотрю эмулятор антивируса Dr.Web и соответственно баг в эмуляции, найденный мной.

Dr.Web эмулирует сравнительно чесно, есть гигантская ф-ция в которой разбираются префиксы инструкции, обрабатываются опкоды, путем сопоставления их и обработчиков в таблицах. Имеется внутренняя память, виртуальные базовые и сегментные регистры и прочее, все это добро лежит в одной структуре, довольно большой, её я приводить не буду.

Почему я говорю про сравнительно чесную эмуляцию? Потому, что не абсолютно все инструкции эмулируются, а также потому, что есть пропуски групп инструкций(для увеличения быстродействия):

nop_handler:

    cmp     dword ptr [ebx+4890h], 0
    jz      short nop_skip_cycle
    mov     ecx, [esp+138h+var_120]
    inc     ecx
    inc     ebp
    mov     [esp+138h+var_120], ecx
    jmp     next

nop_skip_cycle:                        

    inc     [esp+138h+var_120]
    mov     cl, [ebp+1]
    inc     ebp
    cmp     cl, 90h
    jz      short nop_skip_cycle ; в цикле скипаются nop'ы идущие подрят
    jmp     next

Рассмотрим теперь обработчик инструкции bswap, которая и содержит баг в эмуляции:

    xor     eax, eax       
    mov     al, [ebp+1]
    mov     edx, [esi+eax*4-320h] ; esi = VM context, edx = VM_reg
    mov     [esp+138h+saved_reg], edx
    mov     ecx, [esp+138h+saved_reg]
    shr     edx, 8       
    shr     ecx, 18h  
    and     edx, 0FF00h
    or      ecx, edx   
    mov     edx, [esp+138h+saved_reg]
    shl     edx, 8        
    and     edx, 0FF0000h 
    or      ecx, edx      
    mov     edx, [esp+138h+saved_reg]
    shl     edx, 18h      
    and     edx, 0FF000000h
    or      ecx, edx      
    mov     [esi+eax*4-320h], ecx
    mov     edx, [esp+138h+var_120]
    add     edx, 2
    mov     [esp+138h+saved_reg], ecx
    mov     [esp+138h+var_120], edx
    jmp     next

Или, для удобочитаемости, псевдокод:

    edx = VM_reg;
    saved_reg = edx;
    ecx = saved_reg;
    edx >>= 8;
    ecx >>= 0x18;
    edx &= 0xFF00;
    ecx |= edx;
    edx = saved_reg;
    edx <<= 8;
    edx &= 0xFF0000;
    ecx |= edx;
    edx = saved_reg;
    edx <<= 0x18;
    edx &= 0xFF000000;
    ecx |= edx;
    VM_reg = ecx;
    instr_length += 2;
    goto next_opcode

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

Однако, если взять инструкцию, полудокументированную, например:

66 0fc8: bswap ax - которая обнулит младшее слово, то станет ясно, что эмуляция такого не учитывает и проэмулирует это криво.

Пример на масме(так как он тоже не знает про такую инструкцию - конструируем её сами):

start:
    mov eax, 11112222h
    mov byte ptr [lbl], 66h
lbl:
    nop
    bswap eax
    ret

После эмуляции bswap, eax будет равен 22221111h
На реальной машине, eax будет равен 11110000h

Конечно, винить программистов Др.Веб наверное не стоит, ибо в мануале про это упомянуто вскользь, однако мне почему-то кажется, что если покопать поглубже можно найти еще много всего интересного.

понедельник, 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ти секунд), может быть превращен из резидентного в нерезидентный.

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