c++ 64位异常还原
前面可能看一下c++ 32位异常还原比较好
本文中的例子下载地址
https://wwmf.lanzout.com/i8SIl18zs8ne
密码:20w5
确定try的位置
看到main函数
里面有抛出异常代码,所以猜测main函数中有异常处理,我们对main函数
进行引用查找
RUNTIME_FUNCTION结构
发现main函数
果然在RUNTIME_FUNCTION
结构
typedef struct _RUNTIME_FUNCTION {
ULONG BeginAddress;
ULONG EndAddress;
ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
BeginAddress
和EndAddress
代表包含异常处理的函数的开始和结束的RVA
,UnwindData
则是和这个函数相关的异常处理信息。转到UnwindData
。
UNWIND_INFO结构
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;
CountOfCodes
这里为1,代表UnwindCode
的数量为1,UNWIND_CODE
的大小为2个字节,但是UnwindCode
的大小需要对齐4,所以后面需要再留2个字节来对齐,如下图所示
接下来是FunctionEntry
,这里存的是异常处理函数
接下来是ExceptionData
,这个字段是要传递给异常处理函数的异常数据,这里是FuncInfo
的RVA
FuncInfo的结构
nTryBlocks
的值为2,说明main函数
有两个try块,我们转到dispTryBlockMap
确实有两个TryBlockMapEntry
,两个try块的state范围分别为00、22(通过TryBlockMapEntry
的tryLow
和tryHigh
确定)
在32位应用程序中,使用栈空间的一个变量state(var_4)
表示try块的状态索引,在x64中不再使用该变量,而是通过一个IP状态映射表lptoStateMapEntry
来获取try块索引。
lptoStateMapEntry
是FuncInfo
的第7个成员,在本例中lptoStateMapEntry
的值为stru_14001C590
,我们转到stru_14001C590
当程序执行到_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块的代码
它们可还原为
printf("main\r\n");
try{
printf("try1 begin\r\n");
throw 'a';
}
try{
printf("try2 begin\r\n");
throw 2;
}
还原try的catch函数
我们继续转到图2
其中try1的TryBlockMapEntry
结构中的第5个成员,dispHandlerArray
存储了catch结构HandlerType
的地址,我们转到stru_14001C540
,HandlerType
有几个成员需要关注,dispType
表明了catch的类型,dispOfHandler
表示catch后执行的函数,dispFrame
表示catch后异常对象在栈中的偏移(以进入main时的RSP为基准线)
我们转到第一个catch的dispOfHandler
上图就是第一个catch的catch函数,由dispType
可知它是一个char类型的catch,那么上图中var_38这个变量是什么,请看dispFrame
的值,如下图
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函数(其实随便一个就可以),
看到上图,catch函数最后返回了一个地址loc_140001043
,这个地址其实是catch函数执行完后,程序继续执行的地址。我们跟过去
这里是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;
}