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

Комментариев нет:

Отправить комментарий