之前分析一款 .net 软件,使用 dnspy 打开一些可执行文件后,发现方法都无法被正常反编译,经对壳代码一些字样进行分析和搜索后,判断是加了德国一款保护软件的 .NET JIT 壳,把函数的方法体指令即 ILCode 都给抽取走了,会在运行时进行解密提供使用。接着我开始琢磨怎么还原,搜索到 wwh1004 前辈的分析文章《.NET JIT脱壳指南与工具源码》 和相应的开源项目 JitUnpacker-Framework,但经过简单尝试后发现并不能解决我碰到的保护壳类型。主要因为该保护程序有着较为复杂的保护模块和运行逻辑,无法简单地通过项目工具对单个 .net 可执行文件进行加载解析所有方法,再通过 MethodDesc::DoPrestub 主动调用获取还原需要的信息,因为保护程序模块还没启动起来,其次哪怕能模拟启动起来了,还有一堆检查反调试在等着呢。比如经测试,如果直接调试或者 frida 注入会被检测到,保护程序的授权会立马被注销掉,软件也无法正常运行启动了。其次的话 JitUnpacker-Framework 项目工具是六年前发布的,支持范围是 .NET 2.0~4.72,.NET 4.8+ 的版本是没有进行适配的。而现在新版 Windows 系统几乎默认安装高版本 4.8+ 的 Framework,并跟随 Windows 系统进行更新升级,可以兼容执行 .NET 4.0+ 的程序,无疑给工具带来不少局限性。随后我在 wwh1004 前辈对 .net 的 JIT 编译流程详细分析的基础,在解决工具方案上另辟蹊径,选择使用 Windbg TTD 对该软件执行过程进行录制,对录制文件进行调试分析,结合 Windbg 对 .net 的 SOS 调试器拓展,完成了在 .NET 4.8+ 平台上对被保护程序关键函数方法方法体指令的恢复提取,最后再使用 Javascript 脚本实现自动化。事后整理时我又仔细阅读了 wwh1004 前辈的文章和项目代码,发现分析的德国保护软件所加的 .NET JIT 保护,只是简单的抽取了方法体指令,并没有动方法的局部变量签名和异常子句信息,更不提进阶保护对 token 进行加密了。于是我又下载了 .NET Reactor 6.9.0,开启拥有对方法体指令、局部变量签名和异常子句信息三者进行保护的 NecroBit 选项,使用 Windbg TTD 又进行了一番探索尝试,成功也完成了对后两者的还原恢复。虽然整个过程不算难,但目前互联网似乎并没有多少对 .NET Framework 4.8+ 平台上 .NET JIT 壳分析与恢复的资料和工具,更是搜索不到使用 Windbg TTD 来进行恢复还原的内容,此文便是使用 Windbg TTD 还原一般性的 .NET JIT 保护壳的说明与记录,提供一种更为简单灵活的新方案。wwh1004 前辈文章中提到的 CLR 与 JIT 学习文档 The Book of the Runtime 所属的项目 dotnet/coreclr 已经被归档为只读状态,项目代码现在转移到了 donet/runtime 中,文档也有一个单独的 Github Pages 构建仓库,可以在相应的构建网站上直接阅读 RyuJIT 部分。如果翻一翻 donet/runtime 项目仓库的话,会发现是从 .NET 5.0 开始进行的持续开发,到现在刚不久已经发布到 .NET 10.0 了,并没有我们在 Windows 上运行的 .NET Framework 4.8+ 版本。经搜索了解到 .NET Framework 未开源是一个历史遗留问题,其在代码设计上与 Windows 系统深度耦合,还有一些闭源组件依赖和许可限制问题,导致剥离开源成本高。所以即使微软曾发布过各个版本 .NET Framework 的 Reference Source 源码,但也并非真正开源,是没有核心组件如 CLR、JIT 代码的。微软对 .NET 的开发重心放在跨平台的 donet/runtime 后,目前 4.8 就作为 .NET Framework 的最后一个主要版本,只持续进行安全和稳定性修复。 我们分析的 JIT 编译流程在 .NET Framework 和 .NET Runtime 中基本是保持一致变动不大,所以我们分析自己本机 .NET Framework 4.8 的 clr.dll 和 clrjit.dll 执行的 JIT 编译时候,可以下载 donet/runtime 的源码,以及阅读 RyuJIT 模块文档,来进行参考和辅助分析。此外在使用 Winbdg 调试的过程中,会使用到 .NET 的 SOS 调试拓展来解析一些 C# 代码结构体的不透明句柄,相关资料可以阅读官方的文档。阅读文章资料,我们知道 .NET JIT 不同版本都会变动,加壳出于实现的稳定和兼容性,都会通过 Hook ICorJitCompiler 虚表来实现,如果选择 Hook 更其他的函数,可能会面临麻烦的棘手版本偏移变化问题,而 ICorJitCompiler 可以直接通过 getJit 函数获取。我们可以在 Runtime 文档的 RyuJIT Overview 的 Execution Environment and External Interface 部分看到对 ICorJitCompiler 接口和其方法的介绍,里面写到 compileMethod 是 JIT 的主要入口点,EE(执行引擎)会向其传递一个 ICorJitInfo 对象,以及包含 IL 代码、方法头和各种其他有用信息的参数。先来看下 .NET Rntime 项目中 ICorJitCompiler 的定义代码及注释,可以看到很详细,32 位和 64 位系统有着不同的 compileMethod 函数实现。此文以 32 位的 .NET Reactor 保护程序举例,所以就找到 CILInterp::compileMethod 方法实现。注意到其中的 CORINFO_METHOD_INFO 结构体,找到其实现可以看到有 ILCode 信息、maxStack 和异常子句信息数量 EHcount,我们使用 Windbg 可以断在此函数获取这些信息。 额外的还有 CORINFO_METHOD_HANDLE 和 CORINFO_MODULE_HANDLE,看起来是和方法与模块信息有关,继续查看定义。经过注释和搜索分析知道,这两个值在 JIT 和 CLR/CoreCLR 运行时中是作为不透明句柄,或者说是标识符,标识着 CLR/CoreCLR 运行时中对应的方法元数据和内部表示。因为其结构可能非常复杂,还可能随着CLR版本的不同而变化,以及 JIT 并不需要知道这个结构体内部的具体字段和布局,只需要一个标识就行,所以就是不透明句柄的形式了。反过来,如果 JIT 直接依赖 CLR/CoreCLR 运行时内部数据结构的具体布局,那么 CLR/CoreCLR 运行时的任何内部重构都可能破坏 JIT,这会使得维护和发展变得非常困难。简而言之,这导致在使用 Windbg 调试分析 compileMethod 函数时候,看起来无法通过简
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-287241.htm
[原创]Windbg TTD 还原 .NET JIT 保护壳探索
216 浏览
3 回复
感谢分享
感谢分享
这个对DNGuardHVM是不是不行了,还是我不会用