通过X64-SEH 的学习(http://blog.csdn.net/qq_18218335/article/details/72722320)我们知道了一个函数RtlVirtualUnwind,虚拟展开函数,该函数根据epilog 和 prolog 进行虚拟的寄存器操作(在一个CONTEXT结构体中),函数执行完,CONTEXT结构体中即为函数虚拟执行完后寄存器应该有的状态,其中rip 和 rsp、rbp等与运行堆栈有关的寄存器可用于得到X64 函数调用堆栈,循环调用RtlVirtualUnwind即可得到我们想要的调用堆栈(http://blog.csdn.net/qq_18218335/article/details/71598945)。
#include
#include
// FILTER HANDLER FINALLY_HANDLER
#define UNW_FLAG_NHANDLER 0x0 // NO NO
#define UNW_FLAG_EHANDLER 0x1 // YES YES
#define UNW_FLAG_UHANDLER 0x2 // YES
#define UNW_FLAG_CHAININFO 0x4 // 多个UNWIND_INFO
void Show(PVOID dwAddress)
{
printf("Call指令地址:%p\r\n",dwAddress);
}
VOID
StackTrace64(
VOID
)
{
CONTEXT Context;
KNONVOLATILE_CONTEXT_POINTERS NvContext;
UNWIND_HISTORY_TABLE UnwindHistoryTable;
PRUNTIME_FUNCTION RuntimeFunction;
PVOID HandlerData;
ULONG64 EstablisherFrame;
ULONG64 ImageBase;
// 首先得到当前Context
RtlCaptureContext(&Context);
//
// 初始化 UnwindHistoryTable,这个结构体主要用于多次查找RUNTIME_FUNCTION时加快查找效率
//
RtlZeroMemory(
&UnwindHistoryTable,
sizeof(UNWIND_HISTORY_TABLE));
// 下面的循环就是得到调用堆栈
for (ULONG Frame = 0;
;
Frame++)
{
//
// 在PE+ 的.pdata 段中找到函数对应的RuntimeFunction 结构
// 只有叶函数没有此结构
// 既不调用函数、又没有修改栈指针,也没有使用 SEH 的函数就叫做“叶函数”。
RuntimeFunction = RtlLookupFunctionEntry(
Context.Rip,
&ImageBase,
&UnwindHistoryTable
);
RtlZeroMemory(
&NvContext,
sizeof(KNONVOLATILE_CONTEXT_POINTERS));
if (!RuntimeFunction)
{
//
// 我们没有得到结构体,我们当前得到了一个叶函数
// 此时Rsp 直接指向Rip,调用叶函数只包含Call 操作,即只包含push eip 操作,Rsp += 8即可
Context.Rip = (ULONG64)(*(PULONG64)Context.Rsp);
Context.Rsp += 8;
}
else
{
//
// 虚拟展开,得到调用堆栈
// 第一个参数表示Rip 所在函数没有过滤和处理函数
// 仅仅进行虚拟展开得到上层调用堆栈
RtlVirtualUnwind(
UNW_FLAG_NHANDLER,
ImageBase,
Context.Rip,
RuntimeFunction,
&Context,
&HandlerData,
&EstablisherFrame,
&NvContext);
}
//
// 没有得到Rip 即是调用失败
//
if (!Context.Rip)
break;
//
// 展示相关信息
//
printf(
"FRAME %02x: CallAddress=%p\r\n",
Frame,
Context.Rip);
}
getchar();
getchar();
return;
}
void Test2()
{
StackTrace64();
}
void Test1()
{
Test2();
}
int main()
{
Test1();
return 0;
}
FRAME 00: CallAddress=00007FF7E5691159
FRAME 01: CallAddress=00007FF7E5691169
FRAME 02: CallAddress=00007FF7E5691179
FRAME 03: CallAddress=00007FF7E569130A
FRAME 04: CallAddress=00007FF851C22774
FRAME 05: CallAddress=00007FF854410D61
0:001> u 0x00007FF7E5691159 - 5
Show!Test2+0x4 [f:\program\show\show\show.cpp @ 109]:
00007ff7`e5691154 e8a7feffff call Show!StackTrace64 (00007ff7`e5691000)
00007ff7`e5691159 4883c428 add rsp,28h
00007ff7`e569115d c3 ret
00007ff7`e569115e cc int 3
0:001> u 0x00007FF7E5691169 - 5
Show!Test1+0x4 [f:\program\show\show\show.cpp @ 113]:
00007ff7`e5691164 e8e7ffffff call Show!Test2 (00007ff7`e5691150)
00007ff7`e5691169 4883c428 add rsp,28h
00007ff7`e569116d c3 ret
0:001> u 0x00007FF854410D61 - 5
ntdll!RtlUserThreadStart+0x1c:
00007ff8`54410d5c 159fe20f00 adc eax,0FE29Fh
00007ff8`54410d61 eb20 jmp ntdll!RtlUserThreadStart+0x43 (00007ff8`54410d83)
00007ff8`54410d63 488bca mov rcx,rdx
00007ff8`54410d66 498bc1 mov rax,r9
00007ff8`54410d69 ff1591e20f00 call qword ptr [ntdll!_guard_dispatch_icall_fptr (00007ff8`5450f000)]
00007ff8`54410d6f 8bc8 mov ecx,eax
00007ff8`54410d71 e87accfcff call ntdll!RtlExitUserThread (00007ff8`543dd9f0)
00007ff8`54410d76 90 nop
同样的,我们已经掌握了X64 栈回溯的方法。