查看被调试进程指定线程的栈回溯信息的方法,除了直接从TEB中找相关结构获取信息外,还可以利用栈帧关系来回溯父辈函数.
原理是大部分编译器生成的win32汇编代码在新的call的开始使用如下方式保护栈帧:
push ebp
mov ebp,esp
执行过这两条指令后,ebp实际上指向的是被push的ebp的位置.
利用这个技巧,我们就可以遍历所有栈帧的ebp.又因为上图的关系,我们可以利用Ebp的位置计算出上个函数的返回地址.进而完成栈回溯.
当然还有一个问题,什么时候停止?用户模块的第一个栈桢中ebp的值是0.所以当我们发现ebp的值为0时即可认为栈回溯完成.
//栈回溯递归搜寻
void kRecur(DWORD* dwEbp)
{
if(nullptr==dwEbp)
{return;}
DWORD dwNewEbp=0;
if( false==ReadDebuggedMemory((PVOID)dwEbp,4,_Out_ (BYTE*)(&dwNewEbp)))
{
return ;
}
DWORD dwFunReturnPath=0;
if (false==ReadDebuggedMemory((PVOID)(dwEbp+1),4,_Out_ (BYTE*)(&dwFunReturnPath)))
{
return;
}
//printf("函数返回地址:%p\r\n",(DWORD)dwFunReturnPath);
if (0!=(DWORD)dwNewEbp)
{
kRecur((DWORD*)dwNewEbp);
}
return ;
}
值得注意的是上面的ReadDebuggedMemory()是我们自己对系统API进行的封装:
其中g_hProc是被调试进程的句柄.
/*
//用途:
通用读被调试者内存函数.读失败不做改变权限尝试.
//参数:
//起始读地址
LPVOID lpAddress
//希望获得目标进程指定地址内存内容字节的数量
DWORD dwGetNumber
//用于存放获取内容的地方.例如:BYTE byA[20]中的byA
_Out_ BYTE* wcGetValue
//返回值
成功 true
读失败 false
*/
bool ReadDebuggedMemory(LPVOID lpAddress,DWORD dwGetNumber,_Out_ BYTE* wcGetValue)
{
DWORD dwRetN = 0;
if(!ReadProcessMemory(g_hProc,lpAddress,wcGetValue,dwGetNumber,&dwRetN))
{
return false;
}
return true;
}
2015年11月16日 14:05:55