c++ 64位异常还原

前面可能看一下c++ 32位异常还原比较好

本文中的例子下载地址

https://wwmf.lanzout.com/i8SIl18zs8ne
密码:20w5

确定try的位置

image

图1 main函数

看到main函数里面有抛出异常代码,所以猜测main函数中有异常处理,我们对main函数进行引用查找

image

RUNTIME_FUNCTION结构

发现main函数果然在RUNTIME_FUNCTION结构

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

BeginAddressEndAddress代表包含异常处理的函数的开始和结束的RVAUnwindData则是和这个函数相关的异常处理信息。转到UnwindData

UNWIND_INFO结构

image

UnwindData的类型为UNWIND_INFO

#define UNW_FLAG_NHANDLER 0x0		// 表示既没有 EXCEPT_FILTER 也没有 EXCEPT_HANDLER。
#define UNW_FLAG_EHANDLER 0x1		// 表示该函数有 EXCEPT_FILTER & EXCEPT_HANDLER。
#define UNW_FLAG_UHANDLER 0x2		// 表示该函数有 FINALLY_HANDLER。
#define UNW_FLAG_CHAININFO 0x4		// 表示该函数有多个 UNWIND_INFO,它们串接在一起(所谓的 chain)。

typedef struct _UNWIND_INFO {
    UBYTE Version         : 3;		// 版本号,目前为1
    UBYTE Flags           : 5;		// Flags,值为上述define定义的值
    UBYTE SizeOfProlog;				// Prolog 的大小(字节单位)
    UBYTE CountOfCodes;				// 展开代码的计数(表示当前UNWIND_INFO包含多少个UNWIND_CODE结构)
    UBYTE FrameRegister  : 4;		// 如果函数建立了栈帧,它表示栈帧的索引(相对于 CONTEXT::RAX 的偏移)。否则该成员的值为0。
    UBYTE FrameOffset    : 4;		// 表示FrameRegister距离函数最初栈顶(完成prolog,没有执行其他指令时的栈顶)偏移
    UNWIND_CODE UnwindCode[1];		// 展开代码数组 USHORT * n
									// UnwindCode 数组详细记录了函数修改栈、保存非易失性寄存器的指令
    //
    // 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 {
        //
        // If (Flags & UNW_FLAG_EHANDLER)
        //
    	OPTIONAL ULONG ExceptionHandler;
        //
        // Else if (Flags & UNW_FLAG_CHAININFO)
        //
        OPTIONAL ULONG FunctionEntry;
	};
    //
    // If (Flags & UNW_FLAG_EHANDLER)
    //
    OPTIONAL ULONG ExceptionData[];
} UNWIND_INFO, *PUNWIND_INFO;

typedef union _UNWIND_CODE {
	struct {
		UBYTE CodeOffset;		// Prolog 中的偏移量
		UBYTE UnwindOp : 4;		// 展开操作码
		UBYTE OpInfo : 4;		// 操作信息
	};
	USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
UNWIND_INFO结构

CountOfCodes这里为1,代表UnwindCode的数量为1,UNWIND_CODE的大小为2个字节,但是UnwindCode的大小需要对齐4,所以后面需要再留2个字节来对齐,如下图所示

image

接下来是FunctionEntry,这里存的是异常处理函数

image

接下来是ExceptionData,这个字段是要传递给异常处理函数的异常数据,这里是FuncInfoRVA

image

image

FuncInfo的结构

image

FuncInfo的结构

nTryBlocks的值为2,说明main函数有两个try块,我们转到dispTryBlockMap

image

image

图2

确实有两个TryBlockMapEntry,两个try块的state范围分别为00、22(通过TryBlockMapEntrytryLowtryHigh确定)

在32位应用程序中,使用栈空间的一个变量state(var_4)表示try块的状态索引,在x64中不再使用该变量,而是通过一个IP状态映射表lptoStateMapEntry来获取try块索引。

image

lptoStateMapEntryFuncInfo的第7个成员,在本例中lptoStateMapEntry的值为stru_14001C590,我们转到stru_14001C590

image

图3

当程序执行到_Ip所存储的地址时,state的值变为State所存储的状态。由图2可知,try1的state范围为00,try2的state范围为22;由图3可知,lptoStateMapEntry的第一项State为-1,表示程序执行_Ip所存储地址main函数时,此时未进入try块,lptoStateMapEntry的第二项State为0,刚好为try1块的state范围,且lptoStateMapEntry的第二项State为-1,因此try1块的范围为loc_14000101A`loc_14000103F`,try2的state为2,代码范围为`loc_140001050`loc_140001078,其余项的为catch的代码,不用管

下面分别转到loc_14000101A`loc_14000103F`和`loc_140001050`loc_140001078,还原两个try块的代码

image

image

它们可还原为

 printf("main\r\n");
try{
	printf("try1 begin\r\n");
    throw 'a';
}
try{
    printf("try2 begin\r\n");
    throw 2;
}

还原try的catch函数

我们继续转到图2

image

其中try1的TryBlockMapEntry结构中的第5个成员,dispHandlerArray存储了catch结构HandlerType的地址,我们转到stru_14001C540,HandlerType有几个成员需要关注,dispType表明了catch的类型,dispOfHandler表示catch后执行的函数,dispFrame表示catch后异常对象在栈中的偏移(以进入main时的RSP为基准线)

我们转到第一个catch的dispOfHandler

image

上图就是第一个catch的catch函数,由dispType可知它是一个char类型的catch,那么上图中var_38这个变量是什么,请看dispFrame的值,如下图

image

dispFrame为56,换算为16进制,则为38,所以var_3C刚好为RSP-0x3c,所以这就是异常对象,不过这里异常对象并没有被catch函数使用

这个catch函数可还原为

catch(char e){
    printf("try1 catch (char e)\r\n");
}

try1的所有catch可还原为

catch(char e){
    printf("try1 catch (char e)\r\n");
}
catch(short e){
    printf("try1 catch (short e)\r\n");
}

还原try的结尾

我们还是找第一个catch异常的catch函数(其实随便一个就可以),

image

看到上图,catch函数最后返回了一个地址loc_140001043,这个地址其实是catch函数执行完后,程序继续执行的地址。我们跟过去

image

这里是try1的catch执行完后要执行的位置,这里可还原为

printf("try1 end\r\n");

到目前为止,已还原的代码为

printf("main\r\n");
try{
	printf("try1 begin\r\n");
    throw 'a';
}
catch(char e){
    printf("try1 catch (char e)\r\n");
}
catch(short e){
    printf("try1 catch (short e)\r\n");
}
printf("try1 end\r\n");
try{
    printf("try2 begin\r\n");
    throw 2;
}

try2的catch函数和结尾和try1的基本一样,这里直接给出

完整代码

#include <iostream>
int main(){

	printf("main\r\n");
	try{

		printf("try1 begin\r\n");
		throw 'a';
	}
	catch(char a){
		printf("try1 catch (char e)\r\n");
	}
	catch(short a){
		printf("try1 catch (short e)\r\n");
	}
	printf("try1 end\r\n");
	try{
		printf("try2 begin\r\n");
		throw 2;
	}
	catch(int a){
		printf("try2 catch (int e)\r\n");
	}
	catch(float a){
		printf("try2 catch (float e)\r\n");
	}
	printf("try2 end\r\n");
	return 0;
}
posted @ 2023-09-20 02:10  乘舟凉  阅读(220)  评论(0编辑  收藏  举报