пятница, 30 декабря 2011 г.

Мысли вслух

Подумалось недавно, как много Microsoft делает для обеспечения безопасности своих ОС: куча защитных механизмов против эксплойтов( aslr/dep/safeseh/function pointer obfuscation/sehop/stack cookies и т.д. ), uac, встроенная в ядро система защиты от патчей ( patch guard ) призванная бороться в том числе и с руткитами, скоро появится безопасная загрузка через UEFI, призванная бороться с буткитами(в win8), цикл SDL, призванный делать код безопасным еще на этапе написания, тулзы повышающие безопасность устаревших ОС семейства windows (EMET),  средство удаления вредоносных программ и даже бесплатный антивирус (Microsoft Security Essentials).

Эффективны ли эти меры? Безусловно да, безопасность ОС со временем развивается и улучшается, для того, чтобы обойти все защитные механизмы, малварь должна быть все изощреннее и изощреннее, уровень малварописателей также обязан повышаться. Таким образом, несмотря на то, что практически каждый защитный механизм обходится, в целом, картина имеет положительную динамику. Порог вхождения для малварописателей также с течением времени повышается.

Придет ли момент, когда малварь в принципе исчезнет? Тут можно только гадать. Но в принципе, с течением времени могут появиться механизмы защиты, не обходимые принципиально(как пример, можно взять какой-нибудь патч гвард сидящий на уровне гипервизора, и контролирующий не только изменение кодосекций и системных структур, но и остальные лазейки), или требующие для обхода затрат, превосходящих прибыль малварописателей, т.е. экономически не выгодных.

Пока все выглядит как вечная война щита и меча, кончится ли она когда нибудь? Поживем, увидим.

p.s. Всех с Наступающим!

четверг, 15 декабря 2011 г.

Подарок для отладчико-писателей

Те, кто писал отладчики знают, что существуют определенные трудности с отрисовкой элементов GUI. Сложности были и с выводом в видеопамять на видеокартах, где эта видеопамять фрагментирована, и с копанием в недокументированных структурах драйвера дисплея. Ни о каких стандартах и речи не шло. Но все течет, все меняется, развивается как software, так и hardware. И с появлением UEFI появился и стандарт GOP (Graphic Output Protocol).

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

QueryMode Returns information for an available graphics mode that the graphics device and the set of active video output devices supports.
SetMode Set the video device into the specified mode and clears the visible portions of the output display to black.
Blt Software abstraction to draw on the video device’s frame buffer.

Так что с использованием GOP можно наваять практически любой GUI для того же отладчика уровня ядра, если конечно вы являетесь счастливым обладателем платы с UEFI( как я например :) ).

Пример того, как выглядит графика выведенная через GOP(собственно весь интерфейс UEFI выведен через него):

пятница, 9 декабря 2011 г.

BURNMEMORY

Есть любопытная опция для загрузчика ОС: http://msdn.microsoft.com/en-us/library/windows/hardware/ff556246%28v=vs.85%29.aspx

На этапе загрузки ядра она обрабатывается следующим образом: получается число страниц, нужное для представления указанного в опциях количества MB, затем эти страницы включаются в список BadPageList, напомню, списков всего 8:

typedef enum _MMLISTS
{
    ZeroedPageList,
    FreePageList,
    StandbyPageList,
    ModifiedPageList,
    ModifiedNoWritePageList,
    BadPageList,
    ActiveAndValid,
    TransitionPage
} MMLISTS;

Также, данная память не учитывается в MmNumberOfPhysicalPages, то есть невидима для системы.

Но вот практический смысл данной опции для меня как-то ускользает.
Кто-нибудь знает, зачем нужна опция BURNMEMORY ?

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

Ищем код Patch-Guard

Стало интересно, можно ли найти код Patch Guard используя лишь факт того, что он каким-то образом считает хеши секций.
В итоге родился скрипт для IDA, который ищет во всех ф-циях инструкции используемые для подсчета хешей, т.е. xor/rol и прочие.

Получился такой список:

RtlpValidatePeHeaderHash2 xor count = 2 <=== данная ф-ция не интересна
RtlpValidatePeHeaderHash2 shr count = 1
RtlpValidatePeHeaderHash2 rol count = 1

sub_565580 xor count = 2
sub_565580 shr count = 1
sub_565580 rol count = 2

sub_565730 xor count = 24
sub_565730 rdtsc count = 6
sub_565730 shl count = 6
sub_565730 shr count = 4
sub_565730 ror count = 6

sub_565950 xor count = 3
sub_565950 shr count = 4
sub_565950 rol count = 2

sub_565B10 xor count = 3
sub_565B10 shr count = 5
sub_565B10 rol count = 2

sub_566090 xor count = 6
sub_566090 rdtsc count = 1
sub_566090 shl count = 1
sub_566090 shr count = 1
sub_566090 ror count = 1

sub_566140 xor count = 3
sub_566140 shr count = 3
sub_566140 rol count = 2

sub_5667D0 xor count = 4
sub_5667D0 shr count = 5
sub_5667D0 rol count = 2

sub_566A50 xor count = 3
sub_566A50 shl count = 1
sub_566A50 shr count = 2
sub_566A50 rol count = 2

sub_566BC0 xor count = 3
sub_566BC0 shl count = 1
sub_566BC0 shr count = 3
sub_566BC0 rol count = 2

sub_566D30 xor count = 13
sub_566D30 rdtsc count = 3
sub_566D30 shl count = 5
sub_566D30 shr count = 1
sub_566D30 ror count = 3

FsRtlMdlReadCompleteDevEx xor count = 12
FsRtlMdlReadCompleteDevEx rdtsc count = 2
FsRtlMdlReadCompleteDevEx shl count = 2
FsRtlMdlReadCompleteDevEx shr count = 4
FsRtlMdlReadCompleteDevEx ror count = 2

ObLogSecurityDescriptor xor count = 2 <=== данная ф-ция не интересна
ObLogSecurityDescriptor shr count = 1
ObLogSecurityDescriptor rol count = 2

KiSystemStartup xor count = 1 <=== данная ф-ция не интересна, т.к. данные инструкции используются только для инициализации security_cookie
KiSystemStartup rdtsc count = 1
KiSystemStartup shl count = 1
KiSystemStartup shr count = 2
KiSystemStartup rol count = 1
KiSystemStartup ror count = 2

SEH хендлеры IDA не нашла, поэтому используя скрипт из предыдущего поста, добавив в него вывод BeginAddress/EndAddress в RUNTIME_FUNCTION.

После отработки скрипта, запустим скрипт нахождения xor/ror/rol инструкций, выкинув лишнее(ObLogSecurityDescriptor, KiSystemStartup) получим:

sub_410820 xor count = 1 <=== Pg Seh handler
sub_410820 rol count = 1
sub_410820 ror count = 1

sub_5010D0 xor count = 1 <=== Pg Seh handler
sub_5010D0 rol count = 1
sub_5010D0 ror count = 1

sub_502360 xor count = 1 <=== Pg Seh handler
sub_502360 rol count = 1
sub_502360 ror count = 1

sub_565580 xor count = 2
sub_565580 shr count = 1
sub_565580 rol count = 2

sub_565730 xor count = 24
sub_565730 rdtsc count = 6
sub_565730 shl count = 6
sub_565730 shr count = 4
sub_565730 ror count = 6

sub_565950 xor count = 3
sub_565950 shr count = 4
sub_565950 rol count = 2

sub_565B10 xor count = 3
sub_565B10 shr count = 5
sub_565B10 rol count = 2

sub_566090 xor count = 6
sub_566090 rdtsc count = 1
sub_566090 shl count = 1
sub_566090 shr count = 1
sub_566090 ror count = 1

sub_566140 xor count = 3
sub_566140 shr count = 3
sub_566140 rol count = 2

sub_5667D0 xor count = 4
sub_5667D0 shr count = 5
sub_5667D0 rol count = 2

sub_566A50 xor count = 3
sub_566A50 shl count = 1
sub_566A50 shr count = 2
sub_566A50 rol count = 2

sub_566BC0 xor count = 3
sub_566BC0 shl count = 1
sub_566BC0 shr count = 3
sub_566BC0 rol count = 2

sub_566D30 xor count = 13
sub_566D30 rdtsc count = 3
sub_566D30 shl count = 5
sub_566D30 shr count = 1
sub_566D30 ror count = 3

FsRtlMdlReadCompleteDevEx xor count = 12
FsRtlMdlReadCompleteDevEx rdtsc count = 2
FsRtlMdlReadCompleteDevEx shl count = 2
FsRtlMdlReadCompleteDevEx shr count = 4
FsRtlMdlReadCompleteDevEx ror count = 2

Видно, что операции для всех трех SEH обработчиков одинаковые - xor, rol, ror.
Осталось определить к какой ф-ции они относятся. Для этого смотрим лог:

RuntimeFunctionAddress = 5a5bc4, beginAddress = 4107f0, endAddress = 410811, UnwindData = 531044, ExceptionHandler = 429830
Scope Handler = 410820
Scope Handler = 410820

RuntimeFunctionAddress = 5c9a8c, beginAddress = 501090, endAddress = 5010bd, UnwindData = 53101c, ExceptionHandler = 429830
Scope Handler = 5010d0

RuntimeFunctionAddress = 5b0e20, beginAddress = 43f130, endAddress = 43f1b1, UnwindData = 5312b4, ExceptionHandler = 429830
Scope Handler = 502360

Получаем DPC ф-ции используемые патчгвардом:

.text:00000000004107F0 ExpTimeZoneDpcRoutine proc near
.text:0000000000501090 ExpTimeRefreshDpcRoutine proc near
.text:000000000043F130 ExpTimerDpcRoutine proc near

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

Получим две основные цепочки, пример:

403AA2(KiFilterFiberContext) -> 403aa2 -> 8212f0 -> 804810 -> 565950

и

567450(FsRtlUninitializeSmallMcb) -> 566f00 -> 5667d0 -> 566260 -> 565ed0 -> 565b10 -> 565950 -> 565730

Очевидно, что одна из них - инициализация PG, а вторая - ф-ция проверки хешей.

Далее, построив дерево вызовов от этих 2х родительских ф-ций получим все ф-ции используемые патч гвардом.

Таким образом, статическим анализом будет найдено 95% кода патч гварда, на долю динамического анализа придется небольшая часть,
без которой полностью логику восстановить будет сложно.

p.s. Речь шла о PG первой версии.

четверг, 24 ноября 2011 г.

Определение SEH обработчиков в х64 коде

IDA плохо распознает код, если на него нет перекрестных ссылок. Наглядный пример - SEH обработчики для x64 кода.
Как известно, модель исключений в х64 изменилась, в ней используется data-driven подход, в котором роль метаданных играет содержимое секции PE файла. Данная секция содержит все необходимые для раскрутки данные, а именно, массив ф-ций RUNTIME_FUNCTION:

typedef struct _RUNTIME_FUNCTION
{
     ULONG BeginAddress;
     ULONG EndAddress;
     ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

UnwindData представлена следующей структурой переменной длины:

typedef struct _UNWIND_INFO
{
    UCHAR Version : 3;
    UCHAR Flags : 5;
    UCHAR SizeOfProlog;
    UCHAR CountOfCodes;
    UCHAR FrameRegister : 4;
    UCHAR FrameOffset : 4;
    UNWIND_CODE UnwindCode[1];

//
// The unwind codes are followed by an optional DWORD aligned field that
// contains the exception handler address or a function table entry if
// chained unwind information is specified. If an exception handler address
// is specified, then it is followed by the language specified exception
// handler data.
//
//  union
// {
//      struct
//      {
//          ULONG ExceptionHandler;
//          ULONG ExceptionData[];
//      };
//
//      RUNTIME_FUNCTION FunctionEntry;
//  };
//

} UNWIND_INFO, *PUNWIND_INFO;

Соответственно, если установлен флаг UNW_FLAG_EHANDLER, то в данной структуре присутствует SEH обработчик, сразу за которым следует структура SCOPE_TABLE:

typedef struct _SCOPE_TABLE
{
     ULONG Count;
     struct
     {
         ULONG BeginAddress;
         ULONG EndAddress;
         ULONG HandlerAddress;
         ULONG JumpTarget;
     } ScopeRecord[1];
 } SCOPE_TABLE, *PSCOPE_TABLE;

Именно массив записей ScopeRecord и содержат SEH обработчики для ф-ции.

В ExceptionHandler структуры UNWIND_INFO чаще всего сидит _C_specific_handler, который извлекает все HandlerAddress из ScopeRecord,
проверяет принадлежность инструкции вызвавшей исключение на принадлежность к RUNTIME_FUNCTION (т.е. попадает ли она в диапазон BeginAddress - EndAddress), и если хендлер != 1 и попадает в диапазон - он вызывается.

Обладая данными знаниями несложно написать скрипт, который распознает все seh обработчики в файле.
Зачем это может понадобиться будет рассмотрено в следующей заметке.

До обработки скриптом:

PAGE:00000000007A1290 NtAllocateUuids endp
PAGE:00000000007A1290
PAGE:00000000007A1290 ; ---------------------------------------------------------------------------
PAGE:00000000007A1291                 db 0Fh dup(90h)
PAGE:00000000007A12A0                 db 8Bh, 0C0h
PAGE:00000000007A12A2                 dw 5553h, 8348h, 28ECh
PAGE:00000000007A12A8                 dq 25048B4865EA8B48h, 4845894800000188h, 983848458B48DB33h
PAGE:00000000007A12A8                 dq 8BC3950F00000153h, 5D28C48348C38BC3h, 909090909090C35Bh
PAGE:00000000007A12A8                 dq 9090909090909090h, 28EC83485553C08Bh, 25048B4865EA8B48h
PAGE:00000000007A12A8                 dq 4045894800000188h, 9938404D8B48DB33h, 8BC3950F00000153h
PAGE:00000000007A12A8                 dq 5D28C48348C38BC3h, 909090909090C35Bh, 9090909090909090h
PAGE:00000000007A12A8                 dq 4820EC834855C08Bh, 8825048B4865EA8Bh, 3320458948000001h
PAGE:00000000007A12A8                 dq 538138204D8B48C0h, 8348C0950F000001h, 90909090C35D20C4h
PAGE:00000000007A12A8                 dq 2 dup(9090909090909090h)
PAGE:00000000007A1360
PAGE:00000000007A1360 ; =============== S U B R O U T I N E =======================================
PAGE:00000000007A1360
PAGE:00000000007A1360
PAGE:00000000007A1360 ; int __fastcall XIPLocatePages(PVOID Object, __int64, __int64, __int64, __int64)

После обработки скриптом:

PAGE:00000000007A1290 NtAllocateUuids endp
PAGE:00000000007A1290
PAGE:00000000007A1290 ; ---------------------------------------------------------------------------
PAGE:00000000007A1291                 db 0Fh dup(90h)
PAGE:00000000007A12A0
PAGE:00000000007A12A0 ; =============== S U B R O U T I N E =======================================
PAGE:00000000007A12A0
PAGE:00000000007A12A0
PAGE:00000000007A12A0 sub_7A12A0      proc near
PAGE:00000000007A12A0                 mov     eax, eax
PAGE:00000000007A12A2                 push    rbx
PAGE:00000000007A12A3                 push    rbp
PAGE:00000000007A12A4                 sub     rsp, 28h
PAGE:00000000007A12A8                 mov     rbp, rdx
PAGE:00000000007A12AB                 mov     rax, gs:188h
PAGE:00000000007A12B4                 mov     [rbp+48h], rax
PAGE:00000000007A12B8                 xor     ebx, ebx
PAGE:00000000007A12BA                 mov     rax, [rbp+48h]
PAGE:00000000007A12BE                 cmp     [rax+153h], bl
PAGE:00000000007A12C4                 setnz   bl
PAGE:00000000007A12C7                 mov     eax, ebx
PAGE:00000000007A12C9                 mov     eax, ebx
PAGE:00000000007A12CB                 add     rsp, 28h
PAGE:00000000007A12CF                 pop     rbp
PAGE:00000000007A12D0                 pop     rbx
PAGE:00000000007A12D1                 retn
PAGE:00000000007A12D1 sub_7A12A0      endp
PAGE:00000000007A12D1
PAGE:00000000007A12D1 ; ---------------------------------------------------------------------------
PAGE:00000000007A12D2                 align 20h
PAGE:00000000007A12E0
PAGE:00000000007A12E0 ; =============== S U B R O U T I N E =======================================
PAGE:00000000007A12E0
PAGE:00000000007A12E0
PAGE:00000000007A12E0 sub_7A12E0      proc near
PAGE:00000000007A12E0                 mov     eax, eax
PAGE:00000000007A12E2                 push    rbx
PAGE:00000000007A12E3                 push    rbp
PAGE:00000000007A12E4                 sub     rsp, 28h
PAGE:00000000007A12E8                 mov     rbp, rdx
PAGE:00000000007A12EB                 mov     rax, gs:188h
PAGE:00000000007A12F4                 mov     [rbp+40h], rax
PAGE:00000000007A12F8                 xor     ebx, ebx
PAGE:00000000007A12FA                 mov     rcx, [rbp+40h]
PAGE:00000000007A12FE                 cmp     [rcx+153h], bl
PAGE:00000000007A1304                 setnz   bl
PAGE:00000000007A1307                 mov     eax, ebx
PAGE:00000000007A1309                 mov     eax, ebx
PAGE:00000000007A130B                 add     rsp, 28h
PAGE:00000000007A130F                 pop     rbp
PAGE:00000000007A1310                 pop     rbx
PAGE:00000000007A1311                 retn
PAGE:00000000007A1311 sub_7A12E0      endp
PAGE:00000000007A1311
PAGE:00000000007A1311 ; ---------------------------------------------------------------------------
PAGE:00000000007A1312                 align 20h
PAGE:00000000007A1320
PAGE:00000000007A1320 ; =============== S U B R O U T I N E =======================================
PAGE:00000000007A1320
PAGE:00000000007A1320
PAGE:00000000007A1320 sub_7A1320      proc near
PAGE:00000000007A1320                 mov     eax, eax
PAGE:00000000007A1322                 push    rbp
PAGE:00000000007A1323                 sub     rsp, 20h
PAGE:00000000007A1327                 mov     rbp, rdx
PAGE:00000000007A132A                 mov     rax, gs:188h
PAGE:00000000007A1333                 mov     [rbp+20h], rax
PAGE:00000000007A1337                 xor     eax, eax
PAGE:00000000007A1339                 mov     rcx, [rbp+20h]
PAGE:00000000007A133D                 cmp     [rcx+153h], al
PAGE:00000000007A1343                 setnz   al
PAGE:00000000007A1346                 add     rsp, 20h
PAGE:00000000007A134A                 pop     rbp
PAGE:00000000007A134B                 retn
PAGE:00000000007A134B sub_7A1320      endp
PAGE:00000000007A134B
PAGE:00000000007A134B ; ---------------------------------------------------------------------------
PAGE:00000000007A134C                 db  90h

Собственно, сам скрипт:

import idaapi
import idautils
import idc

class ExceptionDetector():
    def __init__( self ):
        self.imageBase = self.GetModuleBase()

    def GetModuleBase( self ):
        defaultHeaderSize = 0x1000
        for segEa in Segments():
            imageBase = segEa - defaultHeaderSize
            return imageBase

    def GetExceptionSectionAddress( self ):
        for ea in Segments():
            if SegName( ea ) == ".pdata":
                return ea
        return 0

    def GetHandlerAddressFromUnwindData( self, unwindDataAddress ):
        versionFlags = Byte( unwindDataAddress )
        countOfCodes = Byte( unwindDataAddress + 2 )
        # only even count allowed
        if countOfCodes % 2:
            countOfCodes = countOfCodes + 1

        # have flag UNW_FLAG_EHANDLER?
        if versionFlags & 8:
            handlerAddress = unwindDataAddress + 4 + countOfCodes * 2
            return handlerAddress

        return 0

    def GetScopeHandlers( self, scopeTableAddress, unwindDataAddress ):
        scopeHandlers = []
        sizeOfScopeRecord = 16
        scopeTableEntriesCount = Dword( scopeTableAddress )

        # FIXME: how many scope entries is possible?
        if scopeTableEntriesCount > 10:
            return scopeHandlers

        # read scope table and fill list with HandlerAddress values
        for i in range( 0, scopeTableEntriesCount ):
            scopeRecord = struct.unpack( "4I", idaapi.get_many_bytes( scopeTableAddress, sizeOfScopeRecord ) )
            scopeHandler = scopeRecord[3]

            if scopeHandler != 1:
                scopeHandlers.append( self.imageBase + scopeHandler )           

            scopeTableAddress = scopeTableAddress + sizeOfScopeRecord

        return scopeHandlers

    # WARN: FindFuncEnd failed at times, so desirable run script twice for better results ( on second time IDA analyser works much better )
    def MakeFunctionForHandler( self, scopeHandler ):
        MakeUnkn( scopeHandler, DOUNK_SIMPLE )
        MakeCode( scopeHandler )
        functionStart = scopeHandler
        functionEnd = FindFuncEnd( functionStart )

        if functionEnd == BADADDR:
            functionEnd = functionStart + 1

        MakeFunction( functionStart, functionEnd )

    def CreateFunctionsForHandlers( self ):
        sizeOfRuntimeFunction = 12
        ea = self.GetExceptionSectionAddress()
        if ea == 0:
            print "CreateFunctionsForHandlers: can't find exception section"
            return
        else:
            print "-------------------------------------------"
            print "exception section finded at address %x" % ea
            print "-------------------------------------------"

        while True:
            runtimeFunction = struct.unpack( "3I", idaapi.get_many_bytes( ea, sizeOfRuntimeFunction ) )
            unwindDataRel = runtimeFunction[2]

            # end of exception section - zero padding bytes
            if unwindDataRel == 0:
                break

            handlerAddress = self.GetHandlerAddressFromUnwindData( self.imageBase + unwindDataRel )

            if handlerAddress:
                handler = Dword( handlerAddress )
                scopeTableAddress = handlerAddress + 4
                scopeHandlers = self.GetScopeHandlers( scopeTableAddress, self.imageBase + unwindDataRel )
                scopeHandlersLength = len( scopeHandlers )

                if scopeHandlersLength > 0:
                    print "RuntimeFunction address = %x UnwindData = %x, ExceptionHandler = %x" % ( ea, self.imageBase + unwindDataRel, self.imageBase + handler )

                    for i in range( 0, scopeHandlersLength ):
                        self.MakeFunctionForHandler( scopeHandlers[i] )
                        print "Scope Handler = %x" % ( scopeHandlers[i] )

                    print "-----------------------------------------"

            ea = ea + sizeOfRuntimeFunction

#      
# entry point
#

print "Script Start...\n"

detector = ExceptionDetector()
detector.CreateFunctionsForHandlers()

print "\nScript End..."

p.s. Из-за особенности анализатора IDA, скрипт лучше всего запускать дважды.
После первого запуска IDA похоже обновляет свои внутренние данные и на второй запуск определяется практически все хендлеры.

среда, 9 ноября 2011 г.

Bug в RKU

Есть одна замечательная програмка, широко известная в узких кругах, под названием RootKit Unhooker, которая довольно стабильна (у меня за все время использования падала всего пару раз). Однако идеальных программ не бывает.

Баг сидит в драйвере RKU и заключается в некорректном поиске неэскпортируемой переменной PspCidTable.

RKU ищет её сканируя байты от ф-ции PsLookupProcessByProcessId, причем делается это даже без дизассемблера длин.

PsLookupProcessByProcessId:

805d3152 8bff            mov     edi,edi
805d3154 55              push    ebp
805d3155 8bec            mov     ebp,esp
805d3157 53              push    ebx
805d3158 56              push    esi
805d3159 64a124010000    mov     eax,dword ptr fs:[00000124h]
805d315f ff7508          push    dword ptr [ebp+8]
805d3162 8bf0            mov     esi,eax
805d3164 ff8ed4000000    dec     dword ptr [esi+0D4h]
805d316a ff35c0395680    push    dword ptr [nt!PspCidTable (805639c0)]  <===== нужная переменная, FF35 - нужные байты
805d3170 e861b60300      call    nt!ExMapHandleToPointer (8060e7d6)  <===== дополнительно проверяется байт E8

Собственно код RKU:

.text:00012BF0                 mov     esi, ds:PsLookupProcessByProcessId
...
loc_12C44:                    

.text:00012C44                 cmp     byte ptr [esi+eax], 0FFh
.text:00012C48                 jnz     short loc_12C58
.text:00012C4A                 cmp     byte ptr [esi+eax+1], 35h
.text:00012C4F                 jnz     short loc_12C58
.text:00012C51                 cmp     byte ptr [esi+eax+6], 0E8h
.text:00012C56                 jz      short loc_12C62

loc_12C58:

.text:00012C58                 inc     eax
.text:00012C59                 cmp     eax, 100h
.text:00012C5E                 jl      short loc_12C44
.text:00012C60                 jmp     short loc_12C6B

loc_12C62:                            

.text:00012C62                 mov     [esp+4Ch+var_39], 1
.text:00012C67                 lea     esi, [esi+eax+2] ; esi = указателю на PspCidTable

...

А вот сюда прилетаем независимо от того, нашлись ли искомые байты или нет:

.text:00013031                 cmp     [edi], bx
.text:00013034                 mov     eax, [esi]       ; eax = адресу PspCidTable
.text:00013036                 mov     eax, [eax]       ; достается первый PHANDLE_TABLE
.text:00013038                 mov     dword_1571C, eax ; сохраняется в глобальной переменной

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

При стандартном прологе, новая ф-ция будет выглядеть так:

8bff            mov     edi,edi ; место для хотпатча
55              push    ebp
8bec            mov     ebp,esp

И тогда, вот здесь все эпично рухнет в синий экран:

.text:00013031                 cmp     [edi], bx
.text:00013034                 mov     eax, [esi]       ; esi не сместится на офсет указывающий на PspCidTable, т.к. поиск ничего не найдет и eax будет равен 8b55ff8b, то есть начальным байтам перехваченной ф-ции
.text:00013036                 mov     eax, [eax]       ; естественно адрес 8b55ff8b будет не валиден и все упадет в бсод

kd> !pte 8b55ff8b
                    VA 8b55ff8b
PDE at C06022D0            PTE at C045AAF8
contains 0000000006853963  contains 0000000000000000
pfn 6853      -G-DA--KWEV   not valid

Сценарий выглядит несколько искусственным, однако его реальность можно проверить на коммерческом продукте - китайском антивирусе Jiangmin.

Он как раз патчит IAT загружаемых драйверов, выглядит это так:

kd> dds f7939f98 f7939f98+0x1000
f7939f98  8052576c nt!ObfDereferenceObject
f7939f9c  805c9c46 nt!NtOpenProcess
f7939fa0  8053f960 nt!KeInitializeSpinLock
f7939fa4  804f0218 nt!IoThreadToProcess
f7939fa8  8054a968 nt!ExAllocatePoolWithTag
f7939fac  804f1aa4 nt!IoVolumeDeviceToDosName
f7939fb0  f7491a46                                     <== hooked ( PsLookupProcesByProcessId )
f7939fb4  8052d764 nt!RtlInitUnicodeString
f7939fb8  804f1614 nt!IoDeleteDevice
f7939fbc  804f9f38 nt!KeSetEvent
f7939fc0  f74919ea                                    <== hooked ( MmGetSystemRoutineAddress )
f7939fc4  80575736 nt!IoCreateFile
f7939fc8  804f9eac nt!KeInitializeEvent
f7939fcc  804ffe24 nt!ZwQuerySystemInformation
f7939fd0  804ef2be nt!IoFreeMdl
f7939fd4  804ff974 nt!ZwOpenDirectoryObject
f7939fd8  80506eb8 nt!MmGetVirtualForPhysical
f7939fdc  804f8300 nt!KeDetachProcess
f7939fe0  804fa56e nt!KeDelayExecutionThread
f7939fe4  805c242c nt!ObQueryNameString
f7939fe8  80506e0a nt!MmGetPhysicalAddress
f7939fec  80559d58 nt!IoFileObjectType
f7939ff0  80559d60 nt!IoDriverObjectType
f7939ff4  804ff384 nt!ZwCreateFile
f7939ff8  805cf92a nt!PsCreateSystemThread
f7939ffc  804f0f20 nt!IoBuildAsynchronousFsdRequest
f793a000  80535fec nt!ExAllocatePool
f793a004  80543f24 nt!__KeGetCurrentThread
f793a008  f7491a86                                    <== hooked ( PsTerminateSystemThread )
f793a00c  8054a950 nt!ExFreePool
f793a010  804ef3c8 nt!IoGetCurrentProcess
f793a014  804ff294 nt!ZwClose
f793a018  804eef48 nt!IofCompleteRequest
f793a01c  804fabaa nt!KeWaitForSingleObject
f793a020  80512a96 nt!MmIsAddressValid
f793a024  804ef1b2 nt!IoFreeIrp
f793a028  805628b4 nt!PsInitialSystemProcess
f793a02c  80508998 nt!MmProbeAndLockPages
f793a030  804f8826 nt!KeAttachProcess
f793a034  804ff578 nt!ZwDeleteFile
f793a038  805449a0 nt!KiDispatchInterrupt
f793a03c  805628bc nt!PsThreadType
f793a040  804eedb2 nt!IoAllocateIrp
f793a044  804ffa28 nt!ZwOpenProcess
f793a048  805085ec nt!MmUnlockPages
f793a04c  8052a02e nt!PsGetCurrentThreadId
f793a050  8059ffda nt!KeAddSystemServiceTable
f793a054  804fcf5e nt!KeSetSystemAffinityThread
f793a058  80574702 nt!IoCreateDevice
f793a05c  80559d64 nt!IoDeviceObjectType
f793a060  805004b4 nt!ZwTerminateProcess
f793a064  f7491b7a                                    <== hooked ( ObOpenObjectByPointer )
f793a068  8052a85e nt!DbgPrint
f793a06c  80560e80 nt!MmSectionObjectType
f793a070  f7491bb2                                    <== hooked ( PsLookupThreadByThreadId )
f793a074  805bc890 nt!NtDuplicateObject
f793a078  804f0c6e nt!IoAllocateMdl
f793a07c  804ff9ec nt!ZwOpenKey
f793a080  804fb412 nt!KeSetTargetProcessorDpc
f793a084  8054a2e0 nt!ExFreePoolWithTag
f793a088  805ba2ae nt!ObOpenObjectByName
f793a08c  804ff910 nt!ZwMapViewOfSection
f793a090  805b17c0 nt!MmUnmapViewOfSection
f793a094  804fb3cc nt!KeInitializeDpc
f793a098  804fb4a4 nt!KeInsertQueueDpc
f793a09c  80554a60 nt!KeNumberProcessors
f793a0a0  80500608 nt!ZwWriteFile
f793a0a4  8052a01c nt!PsGetCurrentProcessId
f793a0a8  805b9e5a nt!ObReferenceObjectByHandle
f793a0ac  8054c0e8 nt!NtBuildNumber
f793a0b0  805628b8 nt!PsProcessType
f793a0b4  804f9c1c nt!KeBugCheckEx
f793a0b8  80561118 nt!MmSystemRangeStart
f793a0bc  805300e6 nt!RtlUnwind

Правильно было бы немедленно завершить RKU или попробовать другой вариант поиска данной неэскпортируемой переменной, если требуемая сигнатура для PspCidTable не найдена.

p.s. версия драйвера RKU 3.8.0.6001

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

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

среда, 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 файл, а при следующем запуске префетчер заранее загрузит необходимые файлы для процесса.

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

вторник, 26 июля 2011 г.

Мое отношение к открытой "уязвимости"

Updated: Так как данная заметка показалась некоторым товарищам не политкорректной, я её изменил.

http://exelab.ru/f/index.php?action=vthread&forum=1&topic=18837

Цитата оттуда:

"Похоже что в топике на microsoft.com наметилось непонимание сути уязвимости. Объясняю подробней: допустим у нас в системе есть небезопасное приложение, например браузер, содержащее уязвимости, которое тем не менее нам нужно использовать. Для запуска этого приложения создается аккаунт ограниченного пользователя и запуск производится с помощью runas. Этим мы желаем добиться изоляции уязвимого приложения от остальной системы, чтобы любая атака на него не вышла за пределы ограниченного пользователя. В Windows XP/2003 всё так и работало, а в Windows 7 благодаря вышеописанной уязвимости атакующий может получить доступ к данным основного пользователя и других ограниченных пользователей, от имени которых через runas могут быть запущены другие изолированные приложения, чего не должно быть.Как я уже сказал, я не надеюсь на какую-либо адекватную реакцию Microsoft и тем более на исправление уязвимости, поэтому собираюсь через некоторое время максимально широко распространить информацию о ней, включая эксплоит и свою заплатку."


UIPI, Integrity Level's, UAC и иже с ними позволяют решить эту проблему. Тот же IE у микрософт запущен с Low Integrity Level(protected mode, не путать с режимом процессора), и таким образом не может никак воздействовать на приложения с более высокими IL, ни инжектить код, ни создавать файлы, ни писать в реестр(в HKCU\software\AppDataLow правда может). Да даже Medium IL дает создавать файлы всего лишь в c:\$Recycle.Bin, c:\ProgramData. А в реестр изза no write up policy по умолчанию писать получится только в HKCU.

То есть достаточно запустить процесс через runas как процесс с Low Integrity Level. Да, процессы с одинаковыми IL могут читать данные других пользователей(и убивать процессы с более высоким IL насколько я помню), но это архитектурная особенность, а не баг ( имхо ).


p.s. К кряклабовцам отношусь сугубо положительно ( Archer, не надо строить проекций ).
p.p.s. Кстати одна из причин, по которой мне лениво регистрироваться на кряклабе это граждане с третьей копией 21-й хромосомы, такие как Gideon Vi.

Диалог на форуме:

ntldr: уязвимость есть
TSS: уязвимости нет
Gideon Vi: словоблуд, бгг. Передаю суть с/п: да, топикстартер прав, но мне срочно надо что-нибудь ляпнуть.

Занавес.

То есть гражданин Gideon Vi с тонной флудовых постов на кряклабе - не словоблуд, а я с техническим блогом и 0 постами на кряклабе словоблуд. Окей :)

вторник, 28 июня 2011 г.

Как поймать то, чего нет?

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

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

У меня был на руках только путь к перемещенному файлу. Поэтому пришлось придумывать нечто новое. И это новое было придумано.

Решение основывалось на системном механизме, называемом кеш менеджер. Информации по нему достаточно, поэтому описывать целиком не вижу смысла. Основная идея - кеширование файловых потоков, файловые потоки это не только данные файла, но и метаданные ФС, такие как Extended Attributes/Alternate Data Streams (а вот Reparse Point врятли кешируются).

Для моего случая интересен факт того, что каждый раз при 1й записи ( то есть при создании исполняемого файла ), файловая система вызывала
один из 4х интерфейсов кеш менеджера, чтобы закешировать файловые потоки ( предварительно создав и инициализировав CacheMap и прочие внутренние структуры связанные с файлом ). Узнать закеширован ли тот или иной файл можно, посмотрев соот-ю структуру FILE_OBJECT, а точнее поле PrivateCacheMap, если оно не нулевое - файл закеширован.

У кешменеджера есть рабочие потоки выполняющие операции write-behind/read-ahead. Write-behind скидывает содержимое кеша на диск с интервалом 1-3 секунды, если это не сделано явно. То есть можно записать в файл, а данные все равно какоето время будут в кеше, т.е. в виртуальной памяти, а не на диске. Таким образом накапливая данные в кеше, и сбрасывая их спустя время одномоментно, уменьшается количество дисковых операций ввода-вывода(если быть более точным, то уменьшает число позиционирования головок жесткого диска). Собственно это и есть главная ф-ция кешменеджера. Read-ahead в свою очередь выполняет упреждающее чтение файлов, когда инициирована операция чтения.

Также, с кешем связан еще один важный механизм, называемый fast I/O.
Везде, где возможно, чтение и запись в закешированные файлы разруливается через высокоскоростной механизм fast I/O.
Fast I/O означает чтение и запись кешированных файлов без работы по генерации IRP.
Это дает существенное быстродействие в некоторых случаях(пример - memory-mapped files).

Именно fast i/o запросы к ФС при создании процесса для получения метаданных(уже не помню, но скорее всего там идет получение EA) позволили ловить запуск несуществующих процессов.

Конкретно для моей задачи нужно было ловить FastIoQueryOpen в минифильтре.

Эквивалент операции FastIoQueryOpen для минифильтра это IRP_MJ_NETWORK_QUERY_OPEN.
Поэтому регистрируем эту операцию, устанавливаем Pre и Post колбеки.

В Pre колбеке просто возвращаем FLT_PREOP_SUCCESS_WITH_CALLBACK, чтобы вызвался Post колбек, а уже в нем проверяем статус операции
( на момент Post колбека операция завершена ), и если тот равен STATUS_OBJECT_NAME_NOT_FOUND, то просто сравниваем имя файла для данной операции, со списком сохраненных ранее файлов. Если совпало - это запуск процесса.

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

вторник, 21 июня 2011 г.

hex-rays или собственный декомпилятор?

Наткнулся недавно на ветку http://exelab.ru/f/index.php?action=vthread&forum=3&topic=15481&page=11, в которой веселые ребята собираются реверсить hex-rays (декомпилятор сишного кода ввиде плагина к IDA, если кто не знает).

Собсно маразм данной идеи даже не в том, что нужно реверсить больше мегабайта бинарного кода, а в том, что это вобще предлагается.
Ведь написать аналог hex-rays в сотни раз проще, чем реверсить бинарь, к тому же необязательно делать декомпилятор ввиде плагина и писать на с++, когда есть IDA-python например, питон для подобных целей гораздо предпочтительнее, т.к. придется работать с довольно сложными конструкциями.

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

Открываем свежие версии IDA/IDA-python, смотрим api, видим классы с заманчивыми названиями FlowChart/BasicBlock/GraphViewer.
Таки да, "все уже украдено до нас" (с)
FlowChart возвращает заботливо построенный за нас список BasicBlock's, а BasicBlock содержит помимо node id, start/end адресов для блока, еще и списки из нодов, на которые ссылается текущий и нодов которые ссылаются на текущий.
Таким образом есть вся достаточная инфа для построения бинарного дерева нодов, которое потребуется впоследствии для свертки оного в Сишные конструкции (if/else/switch и т.д.).

Несколько минут шаманства и готов вывод блоков в окно представленное классом GraphViewer:



Таким образом построение нодов если и вызывала какие-то телодвижения на старых версиях IDA/IDA-python, то сейчас делается буквально в несколько строк.

Какие еще проблемы могут возникнуть при написании декомпиля уровня хекс-рейз?

Свертка нодов в if/else? Да ни разу не проблема
Поиск свитчей/циклов/seh'ов? Тысячи раз обсуждалось, куда статей на эту тему, абсолютно ничего сложного
Отслеживание цепочек live-register's? Элементарно делается
Распарсить type library? Тоже ерундовое дело

Даже не знаю что вызовет затруднение, разве что просто сесть и начать?
Имхо, за неделю будет уже что-то подобное хекс-рейзу версии 1.1.
Да, если писать декомпилятор С++ придется изрядно попотеть.
Но не нужно забывать, что hex-rays и сам то С++ непереваривает, это сишный декомпилятор.

четверг, 16 июня 2011 г.

И не баг и не фича

Что же тогда? Архитектурная особенность! Речь пойдет об уязвимостях.

Каждый кто хотя бы как-то относится к computer security знает, что типов уязвимостей довольно много.

Это buffer/heap/integer overflows, format string vulnerabilities, use after free, race conditions и так далее, имя им легион.

Но всех их объединяет одна вещь - так или иначе, они могут быть выявлены автоматически/полуавтоматически, либо на этапе компиляции( компиляторы, статические анализаторы и т.д. ), либо по методу черного ящика ( fuzzing, binary code analysis и т.д. ).

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

Мне вспомнилось всего два примера таких уязвимостей, это:

1) (MS06-001) уязвимость в Windows Metafile files (.wmf) файлах

"Архитектурная особенность" в данной уязвимости позволяет абсолютно документированно содержать код в .wmf файлах и выполнять его, причем сидела эта ф-ция незамеченной со времен Windows 3.0 с 1990го года по 2006й, 16 лет.

2) (MS10-046) уязвимость в .lnk файлах, которую явил миру Stuxnet.

"Архитектурная особенность" в библиотеке shell32.dll приводит к тому, что открываемый ярлык (.lnk файл) приводит к загрузке и выполнению explorer'ом вредоносной .dll(для так называемые dynamic icons) путь к которой содержится в нем же самом.

Эта же особенность, но уже в самом формате .lnk файла позволяет файлу находиться вобще на удаленном компе ( Lnk File Format => File Location Info => Offset to the network volume table => Network share name ) в расшаренных папках.

Такого рода уязвимости будут всегда находиться только человеком, по крайней мере до тех пор, пока не придумают AI :)

p.s. Если вспомните еще какие-либо уязвимости подобного типа - пишите в комментах.

суббота, 21 мая 2011 г.

Анти-обход перехвата APC dispatcher'a

Нашел у себя в закромах интересный способ обхода перехвата диспетчера APC, который придумал Clerk:

Запуск пользовательского потока в обход перехвата диспетчера APC(R3).
 o Восстанавливаем RtlpCalloutEntryList, для исключения глобальных VEH.
 o Создаём удалённый поток.
 o Формируем SEH-фрейм.
 o Взводим TF в контексте.
Поток стартует с взведённым TF. После исполнения первой инструкции диспетчера апк (KiUserApcDispatcher()) генерируется трассировочное исключение. Управление получает диспетчер исключений (KiUserExceptionDispatcher()). Векторные обработчики не вызываются (RtlCallVectoredExceptionHandlers()). Вызывается установленный SEH. Если диспетчер APC захвачен сплайсингом, останов генерируется после исполнения джампа. Если диспетчер APC перенаправлен на диспетчер исключений, посредством установки точки останова, прежде будет вызван SEH(если хэндлер установлен как VEH).

А я затем задумался, можно ли перехватить диспетчер APC в ring3, чтобы даже при установленном TF можно было сплайсить себе наздоровье?

Казалось бы - нет, нереально. Даже учитывая тот факт, что исключение будет сгенерировано лишь на второй инструкции, это ничего не дает.
Да, можно вместо первой записать short jump, но после прыжка опять таки окажемся в SEH.
Сбросить TF флаг наверное также не получится, т.к. lea edi, [esp+arg_C] это всего 4 байта. См. код диспатчера:

.text:7C90EAC0 _KiUserApcDispatcher@20 proc near
.text:7C90EAC0
.text:7C90EAC0 arg_C           = byte ptr  10h
.text:7C90EAC0
.text:7C90EAC0                 lea     edi, [esp+arg_C]
.text:7C90EAC4                 pop     eax
.text:7C90EAC5                 call    eax
.text:7C90EAC7                 push    1
.text:7C90EAC9                 push    edi
.text:7C90EACA                 call    _ZwContinue@8   ; ZwContinue(x,x)
.text:7C90EACF                 nop

Однако, мне пришел в голову один способ обхода, который требует изменения даже не байтов, а всего 1го бита.

Речь идет о фиче Single-Stepping on Branches, Exceptions, and Interrupts.

Устанавливаем BTF флаг (Single-step on branches) в регистре IA32_DEBUGCTL MSR.
Intel пишет, что исключение будет сгенерировано процессором только при branch, services an interrupt, or generates an exception, то есть мы отключаем по сути TF флаг до первого бранча. А это дает возможность спокойно перехватывать диспетчер APC сплайсингом, но через call myFunc, а не через джамп. А уже в myFunc можно делать все что угодно, включая сброс TF флага.

p.s. Кстати как дополнение, всплыл способ обнаружения( до этого мне не известный ) VmWare и VirtualBox - они не виртуализируют IA32_DEBUGCTL MSR, т.е. детект выглядит как запись в IA32_DEBUGCTL MSR любого значения и чтение его, прочитанное значение всегда будет нулем в виртуалках.