iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Security

Windows Security 101系列 第 23

[Day23] Exception Handler

  • 分享至 

  • xImage
  •  

今天要介紹的是 Exception Handler,Windows Application 中常見的兩種 Exception Handler,分別是 SEH 和 VEH。

Having a look at the Windows' User/Kernel exceptions dispatcher

在 x86 處理器中, IDT 會存放 exception routine,而 IDTR 會指向 IDT

只能透過指令 sidtlidt 存取 IDTR

每個 IDT 的 entry 中會存放 ISR address 和 segment selector (GDT 的 index)

dt nt!_KIDTENTRY 80b95400
    +0x000 Offset           : 0xd200
    +0x002 Selector         : 8
    +0x004 Access           : 0x8e00
    +0x006 ExtendedOffset   : 0x8464

根據 offset 就可以找到 exception routine。

GDT 會存放這些 segment selector 在 physical memory 的起始值,像是常見的 cs , ds , fs , gs,也就是在拿 PEB 會用到的 TIB (Thread Information Block),也就是 CPU 正在在執行時可以參照的 thread 資訊。

struct _TEB64
{
    struct _NT_TIB64 NtTib; 
    ULONGLONG EnvironmentPointer; 
    struct _CLIENT_ID64 ClientId;
    ULONGLONG ActiveRpcHandle;
    ULONGLONG ThreadLocalStoragePointer; 
    ULONGLONG ProcessEnvironmentBlock;
		...
};

其中 TEB→TIB 會存放 ExceptionList

struct _NT_TIB64
{
    ULONGLONG ExceptionList;
    ULONGLONG StackBase;
    ULONGLONG StackLimit; 
    ULONGLONG SubSystemTib;
    union
    {
        ULONGLONG FiberData; 
        ULONG Version; 
    };
    ULONGLONG ArbitraryUserPointer;
    ULONGLONG Self;
};

Exception Handler Routine 是從 kernel 的 KiDispatchException開始

https://ithelp.ithome.com.tw/upload/images/20231007/20120098hTFX9gVknF.png

雖然 ReactOS 並沒有完整實作 x64 的 RtlDispatchException ,但是可以知道執行的順序:

  1. VEH
  2. SEH (unwind routines)
/*
 * @unimplemented
 */
BOOLEAN
NTAPI
RtlDispatchException(
    _In_ PEXCEPTION_RECORD ExceptionRecord,
    _In_ PCONTEXT ContextRecord)
{
    BOOLEAN Handled;

    /* Perform vectored exception handling for user mode */
    if (RtlCallVectoredExceptionHandlers(ExceptionRecord, ContextRecord))
    {
        /* Exception handled, now call vectored continue handlers */
        RtlCallVectoredContinueHandlers(ExceptionRecord, ContextRecord);

        /* Continue execution */
        return TRUE;
    }

    /* Call the internal unwind routine */
    Handled = RtlpUnwindInternal(NULL, // TargetFrame
                                 NULL, // TargetIp
                                 ExceptionRecord,
                                 0, // ReturnValue
                                 ContextRecord,
                                 NULL, // HistoryTable
                                 UNW_FLAG_EHANDLER);

    /* In user mode, call any registered vectored continue handlers */
    RtlCallVectoredContinueHandlers(ExceptionRecord, ContextRecord);

    return Handled;
}

ReactOS 在 32-bits 的實作上,在執行完 VEH routine 後,會遞迴 EXCEPTION_REGISTRATION_RECORD 執行 SEH routine

/*
 * @implemented
 */
BOOLEAN
NTAPI
RtlDispatchException(IN PEXCEPTION_RECORD ExceptionRecord,
                     IN PCONTEXT Context)
{
    PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, NestedFrame = NULL;
    DISPATCHER_CONTEXT DispatcherContext;
    EXCEPTION_RECORD ExceptionRecord2;
    EXCEPTION_DISPOSITION Disposition;
    ULONG_PTR StackLow, StackHigh;
    ULONG_PTR RegistrationFrameEnd;

    /* Perform vectored exception handling for user mode */
    if (RtlCallVectoredExceptionHandlers(ExceptionRecord, Context))
    {
        /* Exception handled, now call vectored continue handlers */
        RtlCallVectoredContinueHandlers(ExceptionRecord, Context);

        /* Continue execution */
        return TRUE;
    }

    /* Get the current stack limits and registration frame */
    RtlpGetStackLimits(&StackLow, &StackHigh);
    RegistrationFrame = RtlpGetExceptionList();

    /* Now loop every frame */
    while (RegistrationFrame != EXCEPTION_CHAIN_END)
    {
        /* Registration chain entries are never NULL */
        ASSERT(RegistrationFrame != NULL);

        /* Find out where it ends */
        RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame +
                                sizeof(EXCEPTION_REGISTRATION_RECORD);

        /* Make sure the registration frame is located within the stack */
        if ((RegistrationFrameEnd > StackHigh) ||
            ((ULONG_PTR)RegistrationFrame < StackLow) ||
            ((ULONG_PTR)RegistrationFrame & 0x3))
        {
            /* Check if this happened in the DPC Stack */
            if (RtlpHandleDpcStackException(RegistrationFrame,
                                            RegistrationFrameEnd,
                                            &StackLow,
                                            &StackHigh))
            {
                /* Use DPC Stack Limits and restart */
                continue;
            }

            /* Set invalid stack and bail out */
            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
            return FALSE;
        }

        //
        // TODO: Implement and call here RtlIsValidHandler(RegistrationFrame->Handler)
        // for supporting SafeSEH functionality, see the following articles:
        // https://www.optiv.com/blog/old-meets-new-microsoft-windows-safeseh-incompatibility
        // https://msrc-blog.microsoft.com/2012/01/10/more-information-on-the-impact-of-ms12-001/
        //

        /* Check if logging is enabled */
        RtlpCheckLogException(ExceptionRecord,
                              Context,
                              RegistrationFrame,
                              sizeof(*RegistrationFrame));

        /* Call the handler */
        Disposition = RtlpExecuteHandlerForException(ExceptionRecord,
                                                     RegistrationFrame,
                                                     Context,
                                                     &DispatcherContext,
                                                     RegistrationFrame->Handler);

        /* Check if this is a nested frame */
        if (RegistrationFrame == NestedFrame)
        {
            /* Mask out the flag and the nested frame */
            ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL;
            NestedFrame = NULL;
        }

        /* Handle the dispositions */
        switch (Disposition)
        {
            /* Continue execution */
            case ExceptionContinueExecution:
            {
                /* Check if it was non-continuable */
                if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
                {
                    /* Set up the exception record */
                    ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                    ExceptionRecord2.ExceptionCode =
                        STATUS_NONCONTINUABLE_EXCEPTION;
                    ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                    ExceptionRecord2.NumberParameters = 0;

                    /* Raise the exception */
                    RtlRaiseException(&ExceptionRecord2);
                }
                else
                {
                    /* In user mode, call any registered vectored continue handlers */
                    RtlCallVectoredContinueHandlers(ExceptionRecord, Context);

                    /* Execution continues */
                    return TRUE;
                }
            }

            /* Continue searching */
            case ExceptionContinueSearch:
                if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID)
                {
                    /* We have an invalid stack, bail out */
                    return FALSE;
                }
                break;

            /* Nested exception */
            case ExceptionNestedException:
            {
                /* Turn the nested flag on */
                ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;

                /* Update the current nested frame */
                if (DispatcherContext.RegistrationPointer > NestedFrame)
                {
                    /* Get the frame from the dispatcher context */
                    NestedFrame = DispatcherContext.RegistrationPointer;
                }
                break;
            }

            /* Anything else */
            default:
            {
                /* Set up the exception record */
                ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
                ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                ExceptionRecord2.NumberParameters = 0;

                /* Raise the exception */
                RtlRaiseException(&ExceptionRecord2);
                break;
            }
        }

        /* Go to the next frame */
        RegistrationFrame = RegistrationFrame->Next;
    }

    /* Unhandled, bail out */
    return FALSE;
}

有了背景知識就可以來簡單介紹 SEH 和 VEH。

Structured Exception Handling (SEH)

SEH 其實就是 try+catch (try+except) 中的 exception

int main()
{
    __try
    {
		    __asm int 0x29
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
		    printf("SEH catched the exception!\n");
    }
    return 0;
}

在 64-bits PE 中,多了 Exception Directory data directory 取代了在 32 bits 中會將 SEH 存在 Stack 的設計。在 32-bits PE,Stack-based Buffer Overflow 可以利用存放在 stack 的 SEH 執行指令。

64-bits

在 64-bits PE 中,Exception Directory data directory,會有許多 _RUNTIME_FUNCTION 表示 exception handler 的結構

typedef struct _RUNTIME_FUNCTION {
    DWORD BeginAddress;    // Start RVA of SEH code chunk
    DWORD EndAddress;      // End RVA of SEH code chunk
    DWORD UnwindData;      // Rva of an UNWIND_INFO structure that describes this code frame
  } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

UNWIND_INFO 有 Exception Handler routine 的 address

// Unwind info flags
#define UNW_FLAG_EHANDLER 0x01
#define UNW_FLAG_UHANDLER 0x02
#define UNW_FLAG_CHAININFO 0x04

// UNWIND_CODE 3 bytes structure
typedef union _UNWIND_CODE {
  struct {
    UBYTE CodeOffset;
    UBYTE UnwindOp : 4;
    UBYTE OpInfo : 4;
  };
  USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

typedef struct _UNWIND_INFO {
  UBYTE Version : 3;          // + 0x00 - Unwind info structure version
  UBYTE Flags : 5;            // + 0x00 - Flags (see above)
  UBYTE SizeOfProlog;         // + 0x01
  UBYTE CountOfCodes;         // + 0x02 - Count of unwind codes
  UBYTE FrameRegister : 4;    // + 0x03
  UBYTE FrameOffset : 4;      // + 0x03
  UNWIND_CODE UnwindCode[1];  // + 0x04 - Unwind code array
  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
  union {
    OPTIONAL ULONG ExceptionHandler;    // Exception handler routine
    OPTIONAL ULONG FunctionEntry;
  };
  OPTIONAL ULONG ExceptionData[];       // C++ Scope table structure
} UNWIND_INFO, *PUNWIND_INFO;

32-bits

在 32-bits PE 中,SEH chain 可以從 TEB 查找,最後會指向 catch 內的程式碼。

https://ithelp.ithome.com.tw/upload/images/20231007/20120098DW9w7tfSJb.png

(ref: https://www.ired.team/offensive-security/code-injection-process-injection/binary-exploitation/seh-based-buffer-overflow)

32-bits PE 也可以在編譯時加入 Safe SEH 的 flag,SEH 就不會存放在 stack。

Vectored Exception Handling (VEH)

VEH 的使用和 SEH 不太一樣,必須先使用 AddVectoredExceptionHandler 註冊。

以下是根據這篇文章中提供的一個簡單的 VEH Handler 範例:

LONG NTAPI MyVEHHandler(PEXCEPTION_POINTERS ExceptionInfo) {
  printf("MyVEHHandler (0x%x)\n", ExceptionInfo->ExceptionRecord->ExceptionCode);

  if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) {
    printf("  Divide by zero at 0x%p\n", ExceptionInfo->ExceptionRecord->ExceptionAddress);
    ExceptionInfo->ContextRecord->Eip += 2;
    return EXCEPTION_CONTINUE_EXECUTION;
  }

  return EXCEPTION_CONTINUE_SEARCH;
}

int main() {
  AddVectoredExceptionHandler(1, MyVEHHandler);
  int except = 5;
  except /= 0;
  return 0;
}

RtlpCallVectoredHandlers 會將 EXCEPTION_RECORD 傳給 VectoredHandlerList 的每個 VEH Handler,由 VectoredHandler 決定是否處理。

在範例程式碼中,只有在 ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO 的時候,VectoredHandler 才會作出後續的處理。

在取得 VEH Handler 的過程中會需要進行 decode,decode VEH Handler 的過程可以參考這篇 Dump VEH,下圖也呈現如何進行 decode:

https://ithelp.ithome.com.tw/upload/images/20231007/20120098stlZI9GTfE.png

(ref: https://dimitrifourny.github.io/2020/06/11/dumping-veh-win10.html)

WindowsNoExec

另外想提一下這個有趣的專案,作者利用 VEH 執行存在 data section 中的程式碼。

利用 VEH Handler 可以控制 ExceptionInfo->ContextRecord 的特性,將本來只有 RW 的 assembly 複製到 ExceptionInfo->ContextRecord 中執行。

其實跟許多 Windows Process Injection 技巧的起源都差不多是這樣,找到執行過程中具有 RWX 的 page 便可以加以濫用。

References


上一篇
[Day22] .NET - Deserialization Exploit
下一篇
[Day24] Introduction to Logical Privilege Escalation on Windows (Part 1): Path Canonicalization
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言