如何快速从 64 位转储文件中找到异常发生时的线程上下文

admin 2023年10月30日02:07:09评论7 views字数 3857阅读12分51秒阅读模式



前言


经常做调试的朋友可能会遇到在windbg里通过k系列命令得到的调用栈没有太大参考意义。一般是由于线程上下文不对导致的。这时候可以通过!analyze -vwindbg自动帮我们分析出正确的调用栈及异常发生时的线程上下文。有了上下文信息,就可以执行.cxr address_to_context命令切换上下文,这时候再通过k命令查看调用栈,一般可以得到一个有意义的调用栈。


但有时候!analyze -v分析出来的上下文信息也是不对的。这时候就需要我们自己手动查找异常上下文了。


这不,最近我就遇到了一个需要手动查找异常上下文的情况。经过调查发现了一个非常重要的规律 ——64位程序中,KiUserExceptionDispatcher函数对应栈帧的Child-SP的值保存了异常发生时的线程上下文。


本文完整记录了整个查找验证的过程。

吐槽:64位程序的参数传递方式与32位程序大不相同,不能根据ebp定位参数了。而是需要结合反汇编代码来推断某个函数的参数是否保存到栈上。如果没保存到栈上,基本上很难找到相关参数了。





!analyze -v


一般拿到一个转储文件后,我做的第一件事是执行!analyze -v。因为很有可能直接就破案了。但是这次没那么幸运,从!analyze -v给出的调用栈,看不出是什么导致的异常。


如何快速从 64 位转储文件中找到异常发生时的线程上下文


!analyze -v在给出调用栈的同时,也会给出Exception Record(下图中.exr部分)和Context Record(下图中.cxr部分)的地址。


如何快速从 64 位转储文件中找到异常发生时的线程上下文


点击上图中两条命令对应的超链接即可执行对应的命令。但是经过确认,.exr.cxr对应的内容并没有什么实际意义。看来只能手动查找异常发生时的线程上下文了。该从哪里入手呢?





查看反汇编


KiUserExceptionDispatcher()是分发异常的关键函数,可以从次函数入手进行分析。KiUserExceptionDispatcher()函数的原型如下:

void KiUserExceptionDispatcher(__in PEXCEPTION_RECORD ExceptionRecord, __in PCONTEXT ContextRecord)


我的第一反应是查看传递给该函数的rdx64位程序中rdx指向第二个参数) 是否保存到栈上,但是没有找到有用线索。

说明:折腾完才反应过来,此函数是从0环返回到3环的入口函数,不能用普通的函数调用机制来理解。


尝试查看一下KiUserExceptionDispatcher()的反汇编代码?在windbg中输入uf ntdll!KiUserExceptionDispatch查看该函数的反汇编代码,结果如下:


如何快速从 64 位转储文件中找到异常发生时的线程上下文


好在该函数的反汇编代码比较短,让我这个弱鸡能有信心继续调查。

看到了该函数内部会调用几个函数,一个是ntdll!Wow64PrepareForException(),一个是ntdll!RtlDispatchException(),还有一个是ntdll!NtRaiseException()。我在网上搜了一下ntdll!RtlDispatchException()的原型,如下:


BOOLEAN RtlDispatchException(
IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT ContextRecord
);


第一个参数是ExceptionRecord的地址,第二个参数是ContextRecord的地址。结合上图中的反汇编代码及x64调用约定(前两个参数会通过rcxrdx传递)可以猜测,rsp指向了ContextRecordrsp+0x4f0指向了ExceptionRecord。但是为什么是0x4f0呢?难道_CONTEXT结构体的大小是0x4f0





小心求证


windbg中查看这两个结构体的大小,如下图:


如何快速从 64 位转储文件中找到异常发生时的线程上下文

发现0X4f0_CONTEXT的大了32字节 (0x4f0 - 0x4d0 = 0x20 = 32)。如果esp指向了_CONTEXT,那么在_CONTEXT结构体后面偏移32字节的位置存放了_EXCEPTION_RECORD。到底是不是这样呢?


验证一下_EXCEPTION_RECORD的内容(为什么验证这个结构体?因为_EXCEPTION_RECORD的中的ExceptionCode比较容易辨认),如下图所示:


如何快速从 64 位转储文件中找到异常发生时的线程上下文

注意:0000004e 78dfa980是调用栈帧0eChild-SP的值。加上偏移0x4f0就得到了_EXCEPTION_RECORD对象的首地址。


从上图可知,ExceptionCode的值是0xc0000005(常见的访问违例),导致异常的指令地址是0x00007ffd bbde9863。从ExceptionInformation[0]可知导致异常的访问类型是读取0表示读取,1表示写入),从ExceptionInformation[1]可知,导致异常的访问地址是0x000001bc 12e12052

说明:对以上字段的解读可以参考《软件调试》第一版 第12320页。


执行.cxr 0000004e 78dfa980切换线程上下文,然后执行k命令查看调用栈。


如何快速从 64 位转储文件中找到异常发生时的线程上下文

从上图红框高亮部分可以看出,在读取地址0x000001bc 12e12052时出错了,导致异常的指令地址是0x00007ffd bbde9863。与_EXCEPTION_RECORD中的信息完全吻合。


上面的猜测应该是正确的。此时,突然想起早些时候也查过一个类似的问题。当时在看雪群里问了一下,有一位大佬说esp指向了异常上下文,当时没往心里去。


我想我肯定忘不了这个至关重要的规律了!





尝试破案


执行!address 0x1bc12e12052查看地址属性,可以发现,该地址对应的页面确实不可访问。


如何快速从 64 位转储文件中找到异常发生时的线程上下文


本想继续调查一下具体原因,奈何没有对应的调试符号,只简单查看了一下反汇编代码,没有特别的发现。由于手头bug比较多,而且没有调试符号,查起来会比较耗时间,剩下的工作就留给平台同事继续调查吧。





KiUserExceptionDispatcher 伪代码


通过前面的反汇编代码,结合在IDA中对KiUserExceptionDispatcher()进行F5结果,伪代码整理如下:


void KiUserExceptionDispatcher(EXCEPTION_RECORD* exceptionRecord, CONTEXT* contextRecord)
{
if (Wow64PrepareForException)
{
Wow64PrepareForException(exceptionRecord, contextRecord);
}

DWORD result;
if (RtlDispatchException(exceptionRecord, contextRecord) )
{
result = RtlGuardRestoreContext(contextRecord, 0);
}
else
{
result = ZwRaiseException(exceptionRecord, contextRecord, FALSE);
}

RtlRaiseStatus(result);
}

疑问:windbg调用栈中给出的名字是ntdll!KiUserExceptionDispatch,少了er(好像也可以用ntdll!KiUserExceptionDispatcher),在IDA中对应的函数名字是ntdll!KiUserExceptionDispatcher






总结


64位程序中KiUserExceptionDispatch()对应栈帧的Child-SP保存了_CONTEXT参数,rsp+4f0处保存了_EXCEPTION_RECORD参数,可以根据这个规律直接找到异常发生时的线程上下文信息及异常信息。


◆当直接通过k命令得到的调用栈不符合预期时,可以通过.cxr context切换到context指定的线程上下文后再次尝试。


◆可以通过?? sizeof(struct_name)查看某个结构体的大小。


◆可以通过!address addr查看某个地址对应的页面属性。



参考资料


◆《软件调试》第一版 第12章 未处理异常和JIT调试


RtlDispatchException()

http://www.codewarrior.cn/ntdoc/winnt/rtl/mips/RtlDispatchException.htm


◆调试笔记之VTUNE崩溃

http://advdbg.org/blogs/advdbg_system/articles/7063.aspx



More


64位程序发生异常时,可以使用这个规律查找到异常发生时的线程上下文信息,运行在32位系统下的程序是否也满足这个规律?运行在64位系统下的32位程序呢?敬请期待~




如何快速从 64 位转储文件中找到异常发生时的线程上下文


看雪ID:编程难

https://bbs.kanxue.com/user-home-873494.htm

*本文为看雪论坛优秀文章,由 编程难 原创,转载请注明来自看雪社区

如何快速从 64 位转储文件中找到异常发生时的线程上下文

# 往期推荐

1、IOFILE exploit入门

2、入门编译原理之前端体验

3、如何用纯猜的方式逆向喜马拉雅xm文件加密(wasm部分)

4、反恶意软件扫描接口(AMSI)如何帮助您防御恶意软件

5、sRDI — Shellcode反射式DLL注入技术

6、对APP的检测以及参数计算分析


如何快速从 64 位转储文件中找到异常发生时的线程上下文


如何快速从 64 位转储文件中找到异常发生时的线程上下文

球分享

如何快速从 64 位转储文件中找到异常发生时的线程上下文

球点赞

如何快速从 64 位转储文件中找到异常发生时的线程上下文

球在看

原文始发于微信公众号(看雪学苑):如何快速从 64 位转储文件中找到异常发生时的线程上下文

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月30日02:07:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   如何快速从 64 位转储文件中找到异常发生时的线程上下文http://cn-sec.com/archives/2153813.html

发表评论

匿名网友 填写信息