Skip to content

异常处理原理及栈展开相关操作

Updated: at 22:10

异常处理原理及栈展开相关操作

windows系统的异常处理方式

windows的异常处理方式在逆向工程核心原理那本书里已经了解过了,这里再写一遍c代码如何实现异常处理,加深对之前汇编代码实现异常处理的理解。简而言之,windows的异常处理就是调用windows的API来实现的

windows处理异常的相关结构体:

_EXCEPTION_RECORD结构体:

操作系统用于记录异常信息的结构体

typedef struct _EXCEPTION_RECORD {
    DWORD    ExceptionCode; // 异常编号,记录是哪种异常
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord; // 嵌套的异常
    PVOID ExceptionAddress; // 导致异常的指令地址
    DWORD NumberParameters;
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; // 异常的详细信息
    } EXCEPTION_RECORD;

NT_TIB结构体:

FS寄存器里面放的就是这个结构体的地址

typedef struct _NT_TIB {
    struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //fs:[0]
    PVOID StackBase;
    PVOID StackLimit;
    PVOID SubSystemTib;
#if defined(_MSC_EXTENSIONS)
    union {
        PVOID FiberData;
        DWORD Version;
    };
#else
    PVOID FiberData;
#endif
    PVOID ArbitraryUserPointer;
    struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;

_EXCEPTION_REGISTRATION_RECORD结构体:

位于SHE链上的结构体

typedef struct _EXCEPTION_REGISTRATION_RECORD {
    struct _EXCEPTION_REGISTRATION_RECORD *Next; //ext成员指向下一个_EXCEPTION_REGISTRATION_RECORD结构体指针
    PEXCEPTION_ROUTINE Handler;  //handler成员是异常处理函数
} EXCEPTION_REGISTRATION_RECORD;
//若Next成员的值为FFFFFFFF,则表示它是链表最后一个结点

异常处理函数的结构

c语言在编写异常处理时的函数声明

EXCEPTION_DISPOSITION __cdecl _except_handler (
  EXCEPTION_RECORD      *pRecord, // 上面有写到,操作系统记录异常信息的结构体
  EXCEPTION_REGISTRATION_RECORD *pFrame, // 上面同样也有,SHE链上的结构体
  CONTEXT        *pContext,  // 线程上下文,用来备份cpu寄存器的值
  PVOID          pValue
);

windows系统API异常处理简单的代码实现:

#include <iostream>
#include <windows.h>
using namespace std;

int const ConstantZero = 0;

EXCEPTION_DISPOSITION MyExceptionHandler(
    EXCEPTION_RECORD* ExceptionRecord,
    EXCEPTION_REGISTRATION_RECORD* EstablisherFrame,
    CONTEXT* pContext,
    PVOID pValue
)
{
    printf(
        "An exception occurred at address 0x%p, with ExceptionCode = 0x%08x!\n",
        ExceptionRecord->ExceptionAddress,
        ExceptionRecord->ExceptionCode
    );
    return ExceptionContinueSearch; // 继续处理异常
}

int main() {
    NT_TIB* TIB = (NT_TIB*)NtCurrentTeb();
    _EXCEPTION_REGISTRATION_RECORD Registration;
    Registration.Handler = (PEXCEPTION_ROUTINE)&MyExceptionHandler;
    Registration.Next = &Registration;
         printf("my word:%d\n",ConstantZero);
         const_cast<int&>(ConstantZero) = 1;
         printf("new word:%d\n",ConstantZero);
    TIB->ExceptionList = TIB->ExceptionList->Next;
    return 0;
}

VS提供的代码异常处理

**try/**except 和 __try/finally 语句

注意:不是标准c++的try语句

异常处理常量:

以下是代码示例

void MyWonderfulProgram()
{
    __try
    {

    }
    __except( MyExceptionFilter() )
    {

    }

}

MyExceptionFilter() 异常评估器(异常过滤器),在这个函数里面也可以处理异常,返回一个异常代码如ExceptionContinueSearch,当然如果返回的是ExceptionContinueSearch着表示__except下面的代码块中会无条件的处理异常,下面的块就没有任何用处。

只能在__except内部能够使用的函数

异常信息记录结构体

typedef struct _EXCEPTION_POINTERS
{ PEXCEPTION_RECORD		ExceptionRecord;
  PCONTEXT			ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS, *LPEXCEPTION_POINTERS;

返回该结构体的函数GetExceptionInformation

EXCEPTION_POINTERS* GetExceptionInformation();

返回异常代码的函数GetExceptionCode

DWORD GetExceptionCode()

VS提供的代码异常处理对上面代码的实现:

#include <iostream>
#include <Windows.h>
#include <excpt.h>
using namespace std;

int const ConstantZero = 0;

int MyExceptionFilter(_EXCEPTION_POINTERS* Pointers) {
    if(Pointers->ExceptionRecord->ExceptionCode != STATUS_ACCESS_VIOLATION||
        Pointers->ExceptionRecord->ExceptionInformation[0] != EXCEPTION_WRITE_FAULT)
    {
        return EXCEPTION_CONTINUE_SEARCH;
    }
    void* WriteAddress = (void*)Pointers->ExceptionRecord->ExceptionInformation[1];
    if (!VirtualProtect(WriteAddress, sizeof(int), PAGE_READWRITE)) {
        return EXCEPTION_CONTINUE_SEARCH;
    }
    return EXCEPTION_CONTINUE_EXECUTION;
}

int main() {
    printf("my word:%d\n", ConstantZero);
    __try
    {
        const_cast<int&>(ConstantZero) = 1;
    }
    __except ( MyExceptionFilter(GetExceptionInformation()) )
    {

    }
    printf("new word:%d\n", ConstantZero);
    return 0;
}

****try/fially块:

在运行完**try的块后,**finally都将执行。

通过这个块可以自定义在栈展开期间发生的操作。(栈展开将在下面介绍)

如下:

Untitled

保证了资源在即使出现了异常的情况下也能正常解锁。

返回EXCEPTION_EXECUTE_HANDLER进行栈展开

也就是在运行程序会跳转到__except下面的代码块中执行。

当多级函数嵌套的时候,如果在很深层的函数内部发生异常,则其工作会很复杂,下面就对其工作原理进行了解,这也是本篇博客要重点写的部分。

实现原理:

当有多个__try块异常代码嵌套的时候,如图

Untitled

编译器会将每个块都分一个状态等级,越往里越大,越往下也越大。

ScopeTable数组:

构建一个ScopeTable数组来表示这些状态,比如这里这个数组中的数据有三个,-1不用

当发生异常的时候,只需要直接冲表中查找对应的处理方式就行

struct SCOPETABLE_ENTRY
{
	int32_t EnclosingLevel; // 状态等级
	FILTER_CALLBACK* Filter; // 指向异常过滤器的地址,如果是finally没有位nullptr
	HANDLER_CALLBACK* Handler; // 指向except/finally的块地址
}

Untitled

C_EXCEPTION_REGISTARATION_RECORD结构体:

编译器用来记录异常的注册情况(比我们注册异常时用的SHE链的结构体更详细)

struct C_EXCEPTION_REGISTARATION_RECORD
{
	void* StackPointer;// 记录栈帧,便于在发生异常时定位栈位置
	EXCEPTION_POINTERS* Exception;//  包含线程上下文以及操作系统记录异常信息的结构体
	EXCEPTION_REGISTRATION_RECORD HandlerRegistration;//异常处理结构体SHE链上的那个
	SCOPETABLE_ENTRY* ScopeTable;//上面创建的指针
	int TryLevel;	// 当前状态等级
}

**try/**except 和 __try/finall的实现

EXCEPTION_DISPOSITION _except_handler3(
    EXCEPTION_RECORD* ExceptionRecord,
    EXCEPTION_REGISTRATION_RECORD* EstablisherFrame,
    CONTEXT* ContextRecord,
    void* DispatcherContext
    )
{
    C_EXCEPTION_REGISTARATION_RECORD* RN = RNFromEstablisherFrame(EstablisherFrame);
    EXCEPTION_POINTERS tmp{ ExceptionRecord,ContextRecord };
    RN->ExceptionPointers = &tmp;
    if ((ExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING) == 0) {
        for (int I = RN->TryLevel; I != 1; I = RN->ScopeTable[I].EnclosingLevel) {
            if (RN->ScopeTable[I].Filter == nullptr) { continue; }

            int FilterResult = RN->ScopeTable[I].Filter();
            switch (FilterResult)
            {
            case EXCEPTION_CONTINUE_SEARCH: continue;
            case EXCEPTION_CONTINUE_EXECUTION: return ExceptionContinueExecution;
            case EXCEPTION_EXECUTE_HANDLER:
                RtlUnwind(EstablisherFrame, ExceptionRecord);// 这一步是从内向外找过滤器来处理异常

                _local_unwind(RN, RN->TryLevel);// 将内部全部finally运行结束

                RN->ScopeTable[I].Handler();// 运行目标__except /__finall的块

                assert(false);
            }
        }
    }
    else {
        _local_unwind(RN, -1);
    }
}

栈展开:

采用windowsAPI的RtlUnwind函数,其内部实现代码如下

void RtlUnwind(
    EXCEPTION_REGISTRATION_RECORD* TargetFrame,
    void* TargetTp,
    EXCEPTION_RECORD* ExceptionRecord,
    void* ReturnValue
)
{
    ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING; // 告诉系统这个异常处理正在被展开
    NT_TIB* TIB = (NT_TIB*)NtCurrentTeb();
    while (TIB->ExceptionList != TargetFrame)
    {
        TIB->ExceptionList->Handler(ExceptionRecord, TIB->ExceptionList);

        TIB->ExceptionList = CurrentRecord->next;// 遍历到目标
    }
}
void _local_unwind(
    C_EXCEPTION_REGISTARATION_RECORD* RN,
    int stop
)
{
    while (RN->TryLevel != stop)
    {
        SCOPETABLE_ENTRY* CurrentEntry = &RN->ScopeTable[RN->TryLevel];
        if (CurrentEntry->Filter == nullptr)
        {
            CurrentEntry->Handler();// 调用finally的块
        }
        RN->TryLevel = CurrentEntry->EnclosingLevel;
    }
}

对上面的栈展开进行一遍解释

首先RtlUnwind(EstablisherFrame, ExceptionRecord)会从异常触发点开始向外层遍历依次进入过滤器,寻找处理异常的位置,然后调用_local_unwind(RN, RN->TryLevel)运行异常点到处理异常位置之间的所有finally。最后,在调用RN->ScopeTable[I].Handler()类的代码运行之前,会把堆栈更新指针更新,然后释放深层的堆栈内存(改栈指针) ,开始执行except块中的内容。

总结

c++与vs的处理方式极其类似,这里就不赘述了。本来没打算写这文章的,刚开始在研究vm混淆的,结果vm要模拟堆栈,对堆栈有个栈展开操作,所以就先来把这个学了。

参考视频:https://www.bilibili.com/video/BV1UU4y1K7et?spm_id_from=333.337.search-card.all.click