首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
10
7
[原创]调试实战 | 从转储文件找出抛出的异常 —— 实战
发表于: 2024-1-11 07:38
13831
[原创]调试实战 | 从转储文件找出抛出的异常 —— 实战
编程难
3
2024-1-11 07:38
13831
## 缘起 我在<a href="elink@592K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1K9h3q4F1j5$3S2W2L8X3N6F1j5h3&6Q4x3X3g2Y4K9i4c8W2k6g2)9J5k6h3W2G2i4K6u0r3j5i4u0@1K9h3y4D9k6i4y4Q4x3V1k6K6k6h3q4J5j5$3S2Q4x3X3c8@1K9s2u0G2N6$3W2F1k6#2)9J5k6r3g2^5j5$3g2H3N6r3W2G2L8W2)9J5k6r3k6J5L8$3#2Q4x3X3c8V1N6h3#2H3i4K6u0V1k6X3W2D9k6g2)9J5k6s2m8S2M7Y4b7I4i4K6u0r3">上一篇文章</a>中介绍了定位抛出异常的理论知识,本文会通过几个实例介绍各种情况下的定位方法。有调试符号如何定位?没有调试符号如何定位?`32` 位程序如何定位?`64` 位程序又该如何定位? 其实,`32` 位程序和 `64` 位程序定位过程大同小异,只不过在解析过程中需要注意,很多关键字段在 `64` 位程序中是偏移,需要加上模块基址得到虚拟地址后才能使用,而在 `32` 位程序中对应的字段就是虚拟地址,可以直接使用。 没有调试符号的时候定位异常类型会比较困难,需要根据上一篇文章中总结的步骤一步步的找到异常类型。有调试符号的情况会比较容易,有很多简便的查看方法。 一起来实战吧! 在开始实战之前,把相关结构体再贴一下,方便参考。 ## 32 位关键结构 ```c++ 0:000> dt EHExceptionRecord TestThrowException!EHExceptionRecord +0x000 ExceptionCode : Uint4B +0x004 ExceptionFlags : Uint4B +0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD +0x00c ExceptionAddress : Ptr32 Void +0x010 NumberParameters : Uint4B +0x014 params : EHExceptionRecord::EHParameters //<---- 0:000> dt EHExceptionRecord::EHParameters TestThrowException!EHExceptionRecord::EHParameters +0x000 magicNumber : Uint4B +0x004 pExceptionObject : Ptr32 Void +0x008 pThrowInfo : Ptr32 _s_ThrowInfo //<---- 0:000> dt _s_ThrowInfo TestThrowException!_s_ThrowInfo +0x000 attributes : Uint4B +0x004 pmfnUnwind : Ptr32 void +0x008 pForwardCompat : Ptr32 int +0x00c pCatchableTypeArray : Ptr32 _s_CatchableTypeArray //<---- 0:000> dt _s_CatchableTypeArray TestThrowException!_s_CatchableTypeArray +0x000 nCatchableTypes : Int4B +0x004 arrayOfCatchableTypes : [0] Ptr32 _s_CatchableType //<---- 0:000> dt _s_CatchableType TestThrowException!_s_CatchableType +0x000 properties : Uint4B +0x004 pType : Ptr32 TypeDescriptor //<---- +0x008 thisDisplacement : PMD +0x014 sizeOrOffset : Int4B +0x018 copyFunction : Ptr32 void 0:000> dt TypeDescriptor TestThrowException!TypeDescriptor +0x000 hash : Uint4B +0x004 spare : Ptr32 Void +0x008 name : [0] Char //<==== ``` ## 64 位关键结构 ```c++ 0:000> dt EHExceptionRecord VCRUNTIME140!EHExceptionRecord +0x000 ExceptionCode : Uint4B +0x004 ExceptionFlags : Uint4B +0x008 ExceptionRecord : Ptr64 _EXCEPTION_RECORD +0x010 ExceptionAddress : Ptr64 Void +0x018 NumberParameters : Uint4B +0x020 params : EHExceptionRecord::EHParameters //<---- 0:000> dt EHExceptionRecord::EHParameters VCRUNTIME140!EHExceptionRecord::EHParameters +0x000 magicNumber : Uint4B +0x008 pExceptionObject : Ptr64 Void +0x010 pThrowInfo : Ptr64 _s_ThrowInfo //<---- +0x018 pThrowImageBase : Ptr64 Void 0:000> dt _s_ThrowInfo VCRUNTIME140!_s_ThrowInfo +0x000 attributes : Uint4B +0x004 pmfnUnwind : Int4B +0x008 pForwardCompat : Int4B +0x00c pCatchableTypeArray : Int4B //<---- 0:000> dt CatchableTypeArray VCRUNTIME140!CatchableTypeArray +0x000 nCatchableTypes : Int4B +0x004 arrayOfCatchableTypes : [0] Int4B //<---- 0:000> dt CatchableType VCRUNTIME140!CatchableType +0x000 properties : Uint4B +0x004 pType : Int4B //<---- +0x008 thisDisplacement : PMD +0x014 sizeOrOffset : Int4B +0x018 copyFunction : Int4B 0:000> dt TypeDescriptor VCRUNTIME140!TypeDescriptor +0x000 pVFTable : Ptr64 Void +0x008 spare : Ptr64 Void +0x010 name : [0] Char //<==== ``` 接下来,先介绍无调试符号时的定位方法,然后再介绍有调试符号时的定位方法。 ## 无调试符号 如果没有导致异常的模块的调试符号,定位过程会比较复杂,需要根据上一篇文章中总结的方法一步步定位。此方法的坏处是麻烦,好处是比较通用,任何情况下都可以使用。 ### 64 位程序 1. 获取 `EHParameters` 的地址。`_CxxThrowException` 栈帧的 `rsp+0x28` 指向了 `EHParameters`。 `_CxxThrowException` 对应栈帧的 `rsp` 是 `000000b9f2effb80`。在 `windbg` 中输入 `? 000000b9f2effb80 + 0x28`,如下图:  由上图红色高亮部分可知 `EHParameters` 的地址是 `000000b9f2effba8`。 2. 获取 `ThrowInfo` 的地址。`EHParameters + 0x10` 的位置保存了 `ThrowInfo` 的地址,`EHParameters + 0x18` 的位置保存了异常模块基址。 在 `windbg` 中输入 `dq 000000b9f2effba8`,如下图:  由上图红色高亮部分可知 `ThrowInfo` 的地址是 `00007ff71c542a20`,异常模块基址是 `00007ff71c540000`。 > **说明:** 如果有 `vcruntimexxx.dll` 的调试符号,可以跳过前两步,直接切换到 `_CxxThrowException` 对应的栈帧即可得到 `ThrowInfo` 的地址和异常模块基址。 3. 获取 `CatchableTypeArray` 的地址。`ThrowInfo + 0xc` 保存了 `CatchableTypeArray` 的偏移。 在 `windbg` 中输入 `dd 00007ff71c542a20`,如下图:  由上图红色高亮部分可知 `CatchableTypeArray` 的偏移是 `000029b8`。 异常模块基址是 `00007ff71c540000`,所以 `CatchableTypeArray` 的地址是 `00007ff71c540000 + 000029b8 = 00007ff71c5429b8`。 4. 获取 `CatchableType` 的地址。`CatchableTypeArray + 0x04` 保存了第一个 `CatchableType` 对象的**偏移**,`CatchableTypeArray + 0x08` 保存了第二个 `CatchableType` 对象的**偏移**,以此类推。 在 `windbg` 中输入 `dd 00007ff71c5429b8`,如下图:  由上图可知,一共有两个 `CatchableType` 类型的对象,第一个偏移是 `000029d0`,第二个偏移是 `000029f8`。 异常模块基址是 `00007ff71c540000`,所以第一个 `CatchableType` 对象的地址是 `00007ff71c540000 + 000029d0 = 00007ff71c5429d0`。 5. 获取 `TypeDescriptor` 的地址。`CatchableType + 0x04` 保存了 `TypeDescriptor` 的偏移。 在 `windbg` 中输入 `dd 00007ff71c5429d0`,如下图:  由上图红色高亮部分可知 `TypeDescriptor` 的偏移是 `00004058`。 异常模块基址是 `00007ff71c540000`,所以 `TypeDescriptor` 的地址是 `00007ff71c540000 + 00004058 = 00007ff71c544058`。 6. 获取异常类型名。`TypeDescriptor + 0x10` 保存了编码后的异常类型名。 在 `windbg` 中输入 `da 00007ff71c544058 + 0x10`,如下图:  从上图可知,异常类型是 `.?AVbad_alloc@std@@`,也就是 `std::bad_alloc`。 ### 32 位程序 `32` 位程序和 `64` 位程序定位过程大同小异,只需要把 `64` 位程序定位过程中的偏移值当成地址使用即可。这里就不赘述了,参考下图:  其实,对于 `32` 位程序,如果有 `vcruntimexxx.dll` 对应的符号,还有一种极其简单的方法,在 `windbg` 中输入 `dt -r3 ThrowInfo address`,如下图:  ## 有调试符号 对于有调试符号的情况,不仅可以使用无调试符号的定位方法,还可以使用更简单的方法查看——通过查看 `pExceptionObject` 对象的虚函数表来推断对应的对象类型。 在测试程序中,`pExceptionObject` 的地址是 `0x000000b9f2effc00`,可以在 `windbg` 中执行 `dps 0x000000b9f2effc00` 即可查看异常对象对应的虚表,如下图:  从上图可知,异常类型是 `std::bad_alloc`,其虚表内容都是其基类(`std::exception`)的虚函数,因为 `std::bad_alloc` 没重写任何虚函数,也没新增任何虚函数。 为什么没调试符号的时候不能用这个方法呢?因为没有调试符号的情况下,从 `dps` 的输出结果中看不到关键的虚表名称,也就不能推断出具体的异常类型了。  ## 亲自动手 对应的程序源码工程文件及对应的转储文件已经上传到我的个人仓库了,感兴趣的小伙伴儿可以从以下链接自行下载: <mark class="encrypted">051K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8W2k6g2)9J5k6h3y4G2L8g2)9J5c8X3u0A6j5h3&6U0K9r3g2F1k6$3&6S2L8W2)9J5c8X3#2&6i4K6u0V1j5X3I4G2k6#2)9J5k6s2y4@1N6h3k6X3i4K6u0r3N6s2u0W2k6g2)9J5c8X3#2S2M7%4c8W2M7W2)9J5c8Y4y4W2j5i4u0U0K9q4)9J5k6s2c8Z5M7X3!0%4K9h3&6Y4i4K6u0V1k6i4S2U0k6i4m8@1K9h3!0F1i4K6u0V1k6Y4u0G2L8g2)9J5k6r3c8#2L8i4m8Q4x3X3c8X3K9h3I4W2i4K6u0V1M7r3q4J5N6o6u0Q4x3V1k6f1k6i4y4@1g2r3S2J5L8%4N6q4P5r3y4W2M7s2c8A6L8$3^5`.</mark> 还有一个更真实的转储文件,可以实战一把。因为比较大,我传到百度云了,可以到这里下载: <mark class="encrypted">594K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4d9K6N6r3P5Y4y4K6k6f1#2D9g2e0k6C8L8i4u0y4N6$3@1K6K9X3^5@1f1g2)9K6c8Y4m8%4k6q4)9K6c8o6S2@1y4o6M7`.</mark> ## 总结 - 获取通过 `throw` 抛出的异常的突破点在 `_CxxThrowException()` 函数,该函数有源码,涉及到的关键结构体都有源码可以查询。 - 查找 `throw` 抛出的异常,关在是掌握对应的数据结构,务必要把关键的数据结构牢记于心。 - 如果有调试符号,还可以直接查看 `pExceptionObject` 对象的虚函数表来进行推断。 - 在解析过程中,需要注意的是在 `64` 位程序中,很多成员变量都是相对于发生异常模块的偏移,而不是直接可用的地址,需要先把偏移转换成虚拟地址后再使用。 ## 参考资料 `vs` 源码
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
#基础知识
#开发技巧
收藏
・
10
点赞
・
7
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
这个讨论对我很有帮助,谢谢!
2026-7-16 10:50
mb_rxjeprrh
为你点赞!
2026-1-14 22:51
萨格拉斯
你的分享对大家帮助很大,非常感谢!
2024-8-23 14:09
PLEBFE
为你点赞~
2024-5-14 01:00
slcn
为你点赞~
2024-2-25 08:04
yp太阳神
为你点赞~
2024-1-11 12:52
KXXTEST
为你点赞~
2024-1-11 08:45
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
秋狝
雪 币:
10075
活跃值:
(34136)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
1571
粉丝
39
关注
私信
秋狝
2
楼
感谢分享
2024-1-11 09:33
1
萨格拉斯
雪 币:
33
活跃值:
(139)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
7
粉丝
0
关注
私信
萨格拉斯
3
楼
基础类型,例如 int 在 name 中没一点体现,楼主有什么看法吗?
2024-8-23 10:52
0
goose007
雪 币:
375
活跃值:
(1579)
能力值:
( LV2,RANK:10 )
在线值:
发帖
11
回帖
27
粉丝
1
关注
私信
goose007
4
楼
学到了,????
2026-1-12 14:23
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
编程难
3
64
发帖
156
回帖
360
RANK
关注
私信
他的文章
[原创]调试技巧 | 从 Win7 到 Win10,DLL引用计数查看全攻略
2816
[原创]调试实战 | 使用 GFlags 与 WinDbg 定位 VS2022 “重复释放” 引发的崩溃
2304
[原创]内存都去哪了?探究 VirtualAlloc 分配背后被“浪费”的 60KB
1779
[原创]调试实战 | 记一次有教益的递归栈查看(续)
11445
[原创]调试实战 | 记一次有教益的递归栈查看
11602
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部