Windows x64为动态代码添加unwind信息

Windows x64为动态代码添加unwind信息

0x00

64位程序的堆栈回溯与32位差别很大,调试器和 RtlCaptureStackBackTrace 无法再使用 ebp 链来遍历整个堆栈,而只能使用 prolog信息来回溯堆栈。正常的C/C++函数的prolog信息在编译时候生成并记录在PE64文件的unwind信息中,另外当前函数对应的异常处理函数也添加在unwind信息的尾部。所以对于不存在unwind信息的函数,异常处理流程无法找到其异常处理函数,也很可能无法正常展开堆栈找到其调用者,从而找到上层的异常处理函数,从而跳过SEH的处理链,导致抓取异常失败。最终结果是程序崩溃了,但是没有留下任何痕迹。这里之所以说很可能,是因为x64的堆栈做展开时还是有一定容错性的,具体可以参看之前的文章: windows x64异常分发流程
因此对于很多手写汇编,会发现崩溃但是不能正常生成dump的情况,这样对查BUG影响很大。
当然对手写汇编的unwind 信息的添加还是比较简单的,以 MASM汇编器为例,可以通过添加 .PUSHREG, .ALLOCSTACK 来告诉汇编器对应的堆栈操作,具体可见 Directives Reference。当然在手写汇编的过程中,一定要遵循x64 prolog规范,否则展开还是会出错,具体可以参考 x64 software conventions
对于编译好的函数,我们可以在windbg中使用 .fnent 命令查看其unwind信息。以kernelbase!ReadFile为例,以下是其unwind信息:
在这里插入图片描述

0x01

对于动态生成的代码,我们无法使用手写汇编的方法生成unwind信息。这里有两个方法可以比较容易地实现unwind信息的生成。
第一种就是依照 x64 exception handling的描述,手动构造结构体。一般来说,只要不是太复杂的prolog,都比较简单可以实现。
另一种方法比较讨巧,很多时候我们生成的动态代码都有一个基础的汇编代码在。动态代码的构造首先从基础的汇编代码复制而来,之后修改其中的几个变量,从而形成新的代码。这种一般在hook引擎中使用的比较多。对于这种情况,我们可以根据第一节内容为基础汇编添加unwind信息,之后再拷贝给动态代码使用。
以下是为 easyhook 动态代码添加的unwind信息的代码:

typedef union _UNWIND_CODE {
    struct {
        UCHAR CodeOffset;
        UCHAR UnwindOp : 4;
        UCHAR OpInfo : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, * PUNWIND_CODE;

typedef struct _UNWIND_INFO {
    UCHAR Version : 3;
    UCHAR Flags : 5;
    UCHAR SizeOfProlog;
    UCHAR CountOfCodes;
    UCHAR FrameRegister : 4;
    UCHAR FrameOffset : 4;
    // For alignment purposes, this array always has an even number of entries, and the final entry is potentially unused
    UNWIND_CODE UnwindCode[1];
} UNWIND_INFO, * PUNWIND_INFO;

static void StopMe() {}

static EXCEPTION_DISPOSITION JustContinueSearch(PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame, PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
	return ExceptionContinueSearch;
}

static void StopMeToo() {}

UCHAR GetUnwindCodeArrayCnt(UCHAR uc)
{
    return ((uc + 1) >> 1) << 1;;
}

ULONG GetUnwindInfoLength(DWORD64 dwImageBase,PRUNTIME_FUNCTION pFuncEntry)
{
    if (!pFuncEntry) return 0;
    PUNWIND_INFO pInfo = (PUNWIND_INFO)(dwImageBase + pFuncEntry->UnwindInfoAddress);

    ULONG ulSum = FIELD_OFFSET(UNWIND_INFO, UnwindCode);
    __try {
        UCHAR cnt = GetUnwindCodeArrayCnt(pInfo->CountOfCodes);
        ulSum += cnt * sizeof(UNWIND_CODE);
        ulSum += sizeof(ULONG) * 2;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        return 0;
    }

    return ulSum;
}

ULONG GetExceptionFuncLength()
{
    auto dwSM = (DWORD64)StopMe;
    auto dwSM2 = (DWORD64)StopMeToo;
    auto excpFunc = (DWORD64)JustContinueSearch;

    DWORD dwDiff = 0;
    if (dwSM > excpFunc) {
        dwDiff = dwSM - excpFunc;
    }
    else if (dwSM2 > excpFunc) {
        dwDiff = dwSM2 - excpFunc;
    }
    if (!dwDiff || (dwDiff > 0x20)) {
        return 0x20;
    }
    return dwDiff;
}

void LhBuildUnwindInfoForTrampoline(LOCAL_HOOK_INFO* pHook,unsigned char* uc,int nSize)
{
#ifndef _WIN64
    return;
#endif

    if (!uc || !nSize) return;

    if (!pHook || !pHook->Trampoline) {
        return;
    }

    DWORD64 dwImageBase = 0;

    auto pOriFuncEnt = RtlLookupFunctionEntry((ULONG_PTR)GetTrampolinePtr(), &dwImageBase, nullptr);
    if (!pOriFuncEnt) {
        return;
    }

	DWORD64 dwPreImageBase = 0;
	auto pFuncEntry = RtlLookupFunctionEntry((ULONG_PTR)pHook->Trampoline, &dwPreImageBase, nullptr);
	if (pFuncEntry) {
		RtlDeleteFunctionTable(pFuncEntry);
	}

    auto nLen = GetUnwindInfoLength(dwImageBase,pOriFuncEnt);
    if (!nLen) return;

    //ImageBase is pHook
    auto nTotalSize = sizeof(RUNTIME_FUNCTION) + nLen + GetExceptionFuncLength() + 10;//padding 10 bytes to
    if (nTotalSize > nSize) return;
    memset(uc, 0, nTotalSize);

    PRUNTIME_FUNCTION pRF = (PRUNTIME_FUNCTION)uc;
    PUNWIND_INFO pInfo = (PUNWIND_INFO)(pRF + 1);
    pRF->BeginAddress = (DWORD64)pHook->Trampoline - (DWORD64)pHook;
    pRF->EndAddress = pRF->BeginAddress + GetTrampolineSize();
    pRF->UnwindInfoAddress = (DWORD64)pInfo - (DWORD64)pHook;

    PUNWIND_INFO pOriUnwindInfo = (PUNWIND_INFO)(dwImageBase + pOriFuncEnt->UnwindInfoAddress);
    memcpy(pInfo, pOriUnwindInfo, FIELD_OFFSET(UNWIND_INFO, UnwindCode) + pOriUnwindInfo->CountOfCodes * sizeof(UNWIND_CODE));
    auto nIdx = FIELD_OFFSET(UNWIND_INFO, UnwindCode) + GetUnwindCodeArrayCnt(pOriUnwindInfo->CountOfCodes) * sizeof(UNWIND_CODE);
    auto pHandlerVA = (ULONG*)((unsigned char*)pInfo + nIdx);
    auto pExceptFunc = pHandlerVA + 2;
    *pHandlerVA = (ULONG)((DWORD64)pExceptFunc - (DWORD64)pHook);
    *(pHandlerVA + 1) = 1;
    memcpy(pExceptFunc, JustContinueSearch, GetExceptionFuncLength());

    pInfo->Flags |= UNW_FLAG_EHANDLER;

    RtlAddFunctionTable(pRF, 1, (DWORD64)pHook);
}

对以上代码有几个点需要解释下:

  1. 从第一节列出的文档中可以知道,unwind信息是包含在RUNTIME_FUNCTION之中的,所以我们也要填充RUNTIME_FUNCTION结构;
  2. RUNTIME_FUNCTION 与 unwind信息中的地址都是VA与ImageBase的差值,并且差值要可以用DWORD表示。其中ImageBase我们可以随便定,只要其小于所有我们需要索引的结构的地址即可,并且在 RtlAddFunctionTable 第三个参数中传入ImageBase地址。由此,我们只要分配足够容纳动态代码,RUNTIME_FUNCTION ,unwind信息等的Buffer,并将其首地址作为ImageBase使用即可。LhBuildUnwindInfoForTrampoline中第一个参数就是这个作用;
  3. LhBuildUnwindInfoForTrampoline中的第二个参数是存放RUNTIME_FUNCTION与unwind信息的起始地址,其在ImageBase代表的buffer范围之内;第三个参数是uc可用的最大的长度;
  4. GetTrampolinePtr()返回手写汇编的地址,对其调用 RtlLookupFunctionEntry 可以获取其 RUNTIME_FUNCTION 信息,从而可以将其拷贝到我们构造的 RUNTIME_FUNCTION 中;
  5. GetUnwindCodeArrayCnt返回值都是偶数,这个是prolog规范要求的,具体可以见第一节的链接。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值