估计没有符号的调用堆栈深度

逆向工程 调用栈
2021-07-04 23:11:26

假设我们在 x86-64 机器上,并且我们可以读取给定时刻的程序状态,但是我们没有调试符号;特别是,我们可以读取程序的地址空间和寄存器。

是否可以可靠地确定调用堆栈的深度?在哪些假设下(例如,调用约定)?

调用堆栈深度是调用指令数量减去在程序执行过程中总共发生的 ret 指令数量在下面的伪跟踪中,括号中的数字是相应行中指令后调用堆栈的深度:

add eax ebx [0]
...
call 0x1234 [1]
call 0x2345 [2]
call 0x3456 [3]
add eax ebx [3]
jmp 0x4567  [3]
...
ret         [2]
...
ret         [1]
call 0x1234 [2]
...
ret         [1]
ret         [0]

我还不是 x86、调用约定和堆栈布局方面的专家。但是,我模糊地认为可以在ebpesp寄存器的帮助下“遍历”堆栈

2个回答

ret指令不会进入调用堆栈。当函数执行时call,下一条指令地址被压入堆栈,当ret指令被执行时,eip用堆栈中的下一条指令更新。

下一条指令地址从堆栈中弹出后,堆栈中ret的地址很可能会被函数的堆栈帧覆盖。因此,不可能知道在给定的时间,从一开始到现在为止进行了多少次调用 - 您调用的是什么depth of the call stack

当前深度的一个可能实现是从esp堆栈底部解析值,寻找指针 - 指向.text内存或任何可执行页面的

对于 Windows x64 系统,Microsoft 的StackWalk64函数来自dbghelp.h. 根据我的经验,它可靠地识别堆栈上的堆栈帧,因此可用于确定调用堆栈的深度。

唯一的问题是获取CONTEXT当前线程的有效值,因为根据文档, GetThreadContext不能用于当前线程。您可能必须先挂起线程;就我而言,图书馆正在为我做这件事,所以我不在这里包含代码。

给定一个有效的CONTEXT Context,设置必要的数据:

STACKFRAME64 StackFrame;
StackFrame.AddrPC.Offset = Context.Rip;
StackFrame.AddrPC.Mode = AddrModeFlat;
StackFrame.AddrFrame.Offset = Context.Rbp; // maybe .Rsp
StackFrame.AddrFrame.Mode = AddrModeFlat;
StackFrame.AddrStack.Offset = Context.Rsp;
StackFrame.AddrStack.Mode = AddrModeFlat;

// Arguments for StackWalk64
DWORD MachineType = IMAGE_FILE_MACHINE_AMD64;
HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
LPSTACKFRAME64 pStackFrame = &StackFrame;
PVOID ContextRecord = &Context;
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine = NULL;
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine = SymFunctionTableAccess64;
PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine = SymGetModuleBase64;
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress = NULL;

然后走茎:

int depth = 0;

while (StackWalk64(
        MachineType,
        hProcess,
        hThread,
        &StackFrame,
        ContextRecord,
        ReadMemoryRoutine,
        FunctionTableAccessRoutine,
        GetModuleBaseRoutine,
        TranslateAddress)
    )
{
    ++depth;
}