第 9章一辅助保护手段
用反编译软件载入一个程序后,根本 无法查看其结构,而直接弹出异常窗口;或者用ildasm顺利反编译后,怎么找也无法用ilasm 再次编译。 原因正是加密软件采#A的一些特 殊的保护手段。 利用的是系统的BUG 或是元数据的缺陷,
9.1用户字符串编码
在.NET中,用户字符串是被保存在#US流中的特殊元娄数据,因此 反编译软件可以通过枚举#US流的数据得到所有的字符串数据,由于用户字符串包含着大量敏感信息,可让调试者轻易定位至关键代弋码,因此它 是保护软件的加密重点之一 .
最常见的用户字符串保护方式,根据强度的不同,将其分为两类:一般编码和使用强名称的编码。
9.2给程序集添加错误元数据
让反编译软件失效。
9.2.1、」#GUID堆大小错误
被这种方式保护的程序在Reflector 无法查看其任何结构和源代码,而仅能显示出错信息.
,#GUID堆的大小只可能是16的倍数,
一般有两种方法,使用ilasm再编译或使用CFF直接修 用CFF修改时只需选中MetaData Streams节 点,将#GUID项的大小改为16即可.
9.2.2 .TypeDef的Extends项错误
TypeDef表中的Extends项值指明了该类型的父类型。大; Extends项指明了.NET程序中类型的继承关系,对于一般类型来说是不能随意修改 ,但每个程序都包含一项特殊的类型”<Module>”,它的Extends项错误看来比##GUID大小错误要严重些,Reflecto:程序发 生异常,:弹出了报错窗口,异常的信息为:索引超出了数组界限.这种改变Extends项的方式仅仅适用于<<Module>类型,可以利 用.NET SDK提供的peverify工具了: 将<<1Viodule>类型的Extends项改回; 0就可以了,
9.2.3利用PE结构
9.2.4加多个Module
9.3打包
当一个程序集含有多个模块时,可以通过打包的方式整合为一个单独的可执行文件, 这种整合的过程称为打包。一般采取运行时在内存或硬盘上动态释放文件的方法。 一 http://research.microsoft.com/~mbanett/ILMerge.aspx,其作 其作用就是:将多个个.NET程序集合并 为一个.NET程序集。勺打包原理相当于将DLL中的代码在原程 序中进行了集成
另一种常见的打包方式是将需要的文件(主文件、库文件等)集成在资源中,一这里的 “资源”所指范围较广,可以是一段数据,也可以是托管资源,这些资a也可以被加密或者压缩。
9.4特殊的..NET属性
DebuggerHiddenAttribute起的作用。从字面上看,该属 性是面向调试器隐藏代码。除- 除了DebuggerHidden外,.NET中还有两个与专调试器相关的属 :
DebuggerNonUserCodeAUribute 此属性禁止在调试器窗口中显示这些附属类型甩和成员,并自 动逐句通过而不会进入并单步执行设计器提供的代码。
DebuggerStepThroughAttribute 公共语言运行库不向该属性附加任何语义。方法中不停止,
防止ildasm反编译的SuppresslldasmAttribute属性。当在源代码中删除并 重新反编译后,便可以直接使用ildasm反编译- 用十六进制编辑器将ildasm 中的” SuppressIldasmAttribute”字符串改掉.
9.5;利用系统特性 Windows的文件名 只支持少部分字符,且不支持乱码、某些符号、空字 符串作为文件名。 在原文件中将资源改名为Windows文件系统允许的名称,然 后使用ildasm反编译即可。再次编译回可执行文件时,尽量将名称再修改回去,以保证源 代码中对资源的调用正常。
第10章壳保护
完全基于.NET的壳,加密的层次也从最初的纯.NET“降低”为Win32、直至 挂钩.NET框架内核。
10.1什么是程序集整体保护
将.NET的壳产品按保护机理分类,可大体分为两种:程序集整体保护(Whole Assembly Protection)和基于每个方法的保护(Per-Method Protection).
Win32壳的加密对象是asm指令,相应的,.NET壳的加密对象则是元数据和IL代码。
程序集整体保护是指,无论壳采用了什么 加密机制,但最终运行状态时,原始程序集的数据(主要是元数据和IL代码)在某个时刻 完整出现在了内存中。 ’该类壳的几大特点:
1)加密机制多样。有的壳是纯.NET实现的,有的可能用Win32代码进行加密,还有的可能对.NET内 核DLL进行包装或挂钩。只有理解了不同壳的实现机理,逆向时才能更有针对性。
2)最终运行时,内存中会出现完整的元数据和B.。这就意味着,对付此类壳的终级 手段就)v- dump.不会修复PE文件,也就无法继续进行分析。
3)只在某个时刻可以dump。元数据和IL再次加密。吧握时机,“该出手时就出手
不掌握壳的原理是不行的。
10.2纯.NET实现的压缩壳
纯.NET实现的压缩壳有两层含义:一是壳本身是.NET程序;二是程序被加壳后仍 是.NET的。这类壳一般加密功能较弱 .NETZ:全名为“.NET Executables Compressor”,开源的.NET压缩引擎,目前仍在更 推荐使用。
使用工具自动进仃dump的内容:
运行WinDBG,载入被加壳程序,运行如下命令,
(1) .load sos(载入.NET调试扩展。) (2) sxe ld:mscorwks(在mscorwks载入时设置断点。) (3) 9(继续执行,程序将在mscorwks载入时中断。) (4) Id mscorwks(载入mscorwks的符号。) (5) Id mscoree(载入mscoree的符号。) (6) by mscorwks!Assembly::GetEntryPoint(在Assembly::GetEntryPoint方法中断,注 意此处的EntryPoint是被加壳程序的入口方法。) 7)g(继续执行)
!bpmd AoRE-Keygenmel.NET.exe netz.NETzStarter. GetAssembly,回车后继续执行程序,取得MethodDesc后,便可以使用!u命令查看方法对应的JIT汇编代码:
匕采用Wi11HeX进行dIdump。运行wjnHeX, 打开keygenme进程的全部内存(Entire Memory), Alt+G弹出偏移选择窗口(如E WinHex中内存块的选择由“Beginning of block”和“End of block”定义( 内存块定义完毕后,单击“Edit-Copy Block-Into New File”,将该段数据保存在文件 .
10.3;基于Win32的壳
传统Win32壳增加一了对.NET的支持,典型的如Themida, NsPack等耳熟能详的猛壳,
:使用专用dump工具,比如.NET下比较好用的Task Explorer,它和CFF配套打包 在Explorer Suite中。运行Task Explorer,选择进程今模块,单击鼠标右键选择“Dump PE “, 保存在硬盘j上就可以了.
使用现成的工具,如.NET Assembly ReBuilder. 在PE文件结构上修改.
10.4挂钩内核的壳
一个.NET下很重要的壳:.NET Reactor它是专门 面向.NET程序的加壳工具,
.NET Generic Unpacker(简称NET Unpacker)是.NET下专用的脱壳工具,可垃 可以搜索进 程中的托管程序集并dump为文件。
:分析另一种非常流行的.NET整体保护的壳CodeVeil, } :NET内核被hook了! 挂钩compileMethod方法的后果是 每当.NET内核需要对一个方法进行JIT时,都会 调用挂钩后的方法,CodeVeil便得到了预先处理的机会。在方法体处设置硬件写入断点,便可得到再次加密的核心代码。(注意,这里不能设置内存断点,因为内存断点改变了数据,会造成解密出的结果出错。
10.5什么是基于每个方法的保护
NET的一大特点是即时编译:只在需要执行某个方法时才将其编译为本地代码。壳保护可以以单个方法为保护单位,在需要JIT时才将该方法的代码解密,
10.5.1常见的挂钩形式
基于每个方法保护的壳的实现,从方式上说就是Win32下的hook,关键是hook的位 一 最简单的原理是:hook的位置越少,对内核改变的地方越少,则壳的兼容性越高。要掌握 为什么壳要hook这些位置,首先要掌握.NET内核JIT的过程。
:WinDbg调试来演示JIT的一些工作原理
主使用!!name2ee分析主窗口类Form 1的内部信息: 目前最值得关注的是 MethodTable,这是每一个类型对应的方法表。使用!dumpmt -and命令可以进一步显示该方 法表中的全部细节:
一个.NET程序的方法在JIT前的 执行就是一段预先准备的本地代码,在这段代码中调用mscorwks!PreStubWorker,最终再调用JIT引擎进行编译。 ,NET规定:当某个方法被JIT后就无须再次编译,而是直接调用己经编译生成的林地代码,
对PreStubWorke:的反汇编中可以发现对DoPreStub的调用, 关键之处就是MethodDesc: : GetILHeader,该函数的功能是返巨 MethodDesc对应方法的RVA地址。RVA指向了方法头。因此,该调用是基于每个方法保护的壳常用的hook位置之一。- 在DoPreStub方法中,含发现对MakeJitWorke:的调用: !UnsafeJitFunction是另一个常被hook的函数,本身不是线程安全的, ,MakeJitWorker会解决.NET内核在进行JIT时的同步,已最终它调用 ,compileMethod这个函数。因此,壳只需要在.NET内核最终调用compileMethod前将IL代码解密并传递给JIT引 擎即可。 。二得到正确脚数据,挂钩是在EE层的,因此可以称之为 EE层的保护。
10.5.2。更进一步的保护
:EE层的挂钩解决了程序集整体保护叮中最大的弱点 在任何时候都不在内存中完整出现解密过的程I IJ}集。但 是传入JIT的总是解密过的IL,因此,只要守住JIT的入p,便可以得到程序的源ILL。更具体一点,这个 JIT的入口就是compileMethod函数。N此,如果找到二种遍历程序集所有有.方法的途径,通 过简单地挂钩compileMethod函数井截联共传人J甲的几.if便可以将原程序集恢复,让 Iv保护失效.
‘壳的解密代码”被融合进JIT层中,这乖 壳会实现 部分JIT的代码,这部分代码改变了JIT引擎的工作模式,一是由于JIT引擎完全(或部分)由为用户代码实现,脱壳者若想继续通过挂钩来得 到原IL,则至少要将该部分引擎代码的反汇编读懂。t :二是壳可以改变JIT引擎的运行流程,
10.5.3实现方式
基于每个方法保护的壳是如何实现的呢,或者更确切地说,运行库是如何加载并挂 钩..NET内核的呢?第一种方式较常见,壳在程序集中添加加载程序集的代码(下面} ,并 给程序集的每个个类型添加静态构造函数.cctor,在其中加对loader代码的调用,之后loader 代码转入运行库中并调用核心解密代码。这种方式的特点之一是静态构造函数和loader代 码无法加密,因为在调用这两处代码时,运行库还没有加载。第二种运行库的加载方式则是利用Window,系统的PE加载时读入,比如’Win32下已 经非常成熟的修改导入表加载DLL的方法。这种方法的好处是程序集中所有的方法都可以 加密,但无法像前一二种方法那样在运行时选择加载的运行库版本‘
10.5.4·一般分析方法
目前对于这种保护的常用的脱壳方法是“反射一调用一挂钩一重建”。“反射”是指利用.NET 的反射机制遍历程序集中的所有方法,并通过Invoke机制对其进行“调用,,此时会触发 JIT引擎工作从而使壳的运行库进行解密,在适当的位置“挂钩”,便可获取解密后的方法 体和元数据,最后用合适的方法“重建”程序集。这里的“挂钩”还可以引申为建立自己 的虚拟机环境竟,让运行库在自己的掌控下运行。Re-Max正是利用这一原理编写的脱壳机.
第11 章其他保护万式
11.1许可证保护
许可证以授权文件(K或者注册表数据的形式存在,文件中存有经 过加密的用户授权信息,Win23下较成熟的保护系统是FLEXlm.
11.1.1一许可证机制*01介
p .NET自身提供的许可证 机制便是入门的最好材料。
许可证编译器Lc.exe,LC是.NET提供的命令行工具,即许可证编译器(License Compiler),
11.1.2 .NET许可证机制的扩展
11.1.3·一般分析方法
色可证保护的基本要素总结一下。一个典型的.NET许可证保 护应至少包含下面的三个组成部分:
(1)许可证(License)提供需要验证的数据,供授权机制使用。
(2)许可证提供者((LicenseProvider)::授权机制的核心,负责取得许可证及执行验证 数据的任务。
(3)许可证管理者(Licensemanager ):负责按照元数据中的、LicenseProviderAttribute 属性初始化LicenseProvider,被保护控件调用它的Validate或者IsValid方法进进行验证。
;商业级授权保护系统的三个共同特点。
共同特点之一,许可证中包含大量信息。
共同特点之二,采用大强度的非对称算法。,许可证中的大量信息以及许可证的结构本 身都是非常敏感的信息,必需要采用大强度的算法进行加密与解密。 RSA被选择的频率较高。
共同特点之三,结合其他保护方式。
(1)多处验证。一是验证许可证信息的位置较多,二 是验证时不是简单地以true或false判别,而是返回许可证本身(通常是个类),而后再利 用其中的信息控制程序的流程,卜因此简单的patch验证方法的返回值为true或null只会造 sv异常,必须更全面地考虑许可证的内容;
(2)部分授权系统利用了本地代码的Whole Assembly保护以及部分Per-Method保护 分析前需要dun刀P或者脱壳;
(3)部分授权系统使用了虚拟机,为了保证运行效率,仅在关键的算法处(或者用户 算定的方法处)将MS1L转换为虚拟机代码,被保护后的程序是无法直接分析的。
破解整个授权系统,最完美的方法无疑是分析出许可证结构及加密解密算法,但算法是授权系统的核心,因此能轻易逆向的情 况并不多。通常是采取次完美的方法,既分析出许可证结构,替换程序中的公钥,以对应 的私钥进行加密,这样做是以最小的补丁得到的最好结果。
11.2.1 , .NET提供的算法空间
System. Security. Cryptography是.NET提供的加密解密算法相关命名空间,大1 CryptoServiceProvide:结尾的类表示该类的实现是对底层Win32的相关API进行了包装, i以Managed结尾的算法类则完全是基于托管代码的。 ,.NET还提供了众多辅助算法实现的类,比如随机数的生成、基于XML 的加密
对称密钥的算法有两个最大的缺陷:一是无法解 决密钥的发布问题,一旦密钥公布,则无安全性可言;二是无法解决信任问题,即无法对 被加密的信息进行验证,因为无法得知密钥的真实性。非对称算法最大的优势是利用了密钥对,其中不公开的私钥用于解密,而 「发布的公钥用于加密。,最基 本的原则是:私钥永远在自己手里,不要公开,发布的程序中只能带有公钥数教据。在此原 则基础上,有两种基本使用方法。一种是公钥加密,私钥解密。比如判断这些信息的有效性.另一种是 私钥加密,公钥解密。这种情况要注意,公钥的解密只用于验证,而不是得到原始信息。
10.3虚拟机保护
虚拟机在Win32下已经发展得非常成熟,保护强度大,破解难度高,「将.NET下的虚拟机分为两大类:整体级的和代码级的。 整体级的虚拟机保护是指程序集本身(特别是MSIL指令)’没有改变,’添护软件只是 虚拟了文件 在PostBuild和Studi。两个软件中实现的虚拟机(女 这种虚拟机保护的最 大应用是:方便了程序的发布,可以将多个文件打包为一个可执行文件,甚至可以将.NET框架打包使得.NET程序运行在没有安装.NET框架的机器上。 当程序运行后所有的原始程 序集均会在内存中释放,利用内存 dump工具可以很轻松地得到这些原始程序集,NET Unpacker就是一个不错的选择。;
代码级的虚拟机保护是指以虚拟彩 的伪代码替换部分或全部MSIL指令,使得逆向者在内存中得到的代码不是有效的MSIL, 或者根本就不是MSIL,这样就无法采用传统的逆向工具直接反编译,而必须花大量的时间 来解读伪代码 替换部分MSIL指令的虚拟机可以通过修改JIT引擎实现,迷 这样的.NET执行文件虽然在PE和元数据结构上都没有改变,但大部 分反编译软件都无法正常反编译。 要想实现替换全部(至少是大部分)MSIL指令为伪指令,则需要解释执行的虚拟机。 这种方式最大的缺点是运行效率会明显降低,L 出于运行效能的考虑,通常将关键方法替换 为伪指令.
先来看 中,用d.1T命令查看目录,便可以看见一些以·Nativelmages·开头的目录,,里面保存晶便 是程序集的本地版本,通常以.ni.dll或:.ni.exe结尾。(在explorer中无法直接妾看到目录结构, 可以修改注册表以屏蔽此扩展,方法为 HKLM\SOFTWARE\1VIicrosoft\Fusion中新建DisableCacheViewer键,令其值为1,)本机映像中保存的是汇编代码,区别于托管程序集中保存 的是托管IL,这也是其名称的由来。
.NET的命令粤具ng势(全称CLR Native Image Generator) #t了林机映像的管 理,包括安装、显示、卸载和更新等功能。i 可对任意一个托管程序集采用ngen install 命令将其安装至全局缓存中,这样当再次运行该程序集时,.NET发现缓存中有相对应的本 机映像则运行之。ngen生成的本机映像是无法直接运行的,只能由.NET调用。
利用编译为本机代码的保护方式的实现过程就清楚了:利用.NET的本机映像机制, 将MSIL编译为汇编代码,同时将托管和本机两个版本的映像中的原始元数据和MSIL都 删除,以达到保护效果。 MegaX编写了一个脱机使用.NET程序的工具DotNetBox, 不安装.NET框架便运行.NET程序,其编写也多少使用了上面的原理。
11.5动态方法委托调用
委托(Delegate)和动态方法(DynamicMethod),1.0版.NET中就出现了委托,而2.0版以后则出现了动态方法,i It用动态方法保护关键方法的另类加密,
在Win32程序设计时,经常用到回调函数,在Windows系系统中,窗口过 程、钩子过程、异步过程的实现都需要使用回调函数。但在非托管代码中,回调函数的地 址就是一个内存地址,该地址不会携带任何额外的信息,例如函数期望的参数左个数、参数基类型、函数的返回值类型以及函数的调用约定。一言以蔽之,非托管代码中的回调函数是 非类型安全的。.NET框架则使用一种称做委托的技术来提供回调函数机制,,更重要的是, 委托是类型安全的。;
NET除了 支持将方法静态地编译到程序集文件中外,刃还支持动态方法:即在程序运行时,使用 DynamicMethod类生成和执行方法,且不必同时生成动态程序集和动态类型卫来包含该方法。 动态方法是生成和执行少量代码的最有效方式。
第12章非托管API
。非托管 API是.NET提供的一组非托管编程接口,利用这些API,可以实现一些无月法利用托管代码 实现的功能。如果将C#, VB.NET, MSIL等托管语言看做最顶层,将Winiin32/64汇编看做 为最底层,则可以将非托管API看做中间层,它利用本地代码实现操作托乍管程序的功能。 利用非托管API可以做许多很底层的事,比如编写调试器、元数据查看器等。
12.1非托管API缘述
非托管API可分为10大类,这些API存在着交互引用的现象,之间的联系是很密权切的。
在1.1版.NET框架SDK的安装目录中,有一个子目录名为Tool Developers Guide,其 中的docs文件夹中包含了数个Word文档,其中有六篇文档介绍了四种非托管API:元数 据、分析、调试和宿主,可见这四类在所有非托管API中的重要性,因习 自2.0版开始,非托管API的文件不再单独随SDK发布,而是整合在了MSDN .1.1版中的帮助文档仍极具参考价值。
非托管API之所以被称为“非托管”是因为这些API的实现代码是本地代码,其调用 机制是COM组件。实际使用中,既可以在本地代码中调用这些API,也可以在托管代码中 通过互操作机制调用。
12.2宿主API Hosting
概括宿主API的功能:通过宿主API,非托管程序可以加载CLR,从而在进 程中运行托管誉程序,并控制CLR和托管程序的行为。 利用宿主API可以管理:程序集的加载、策略、宿主 保护、内存、垃圾回收、调试、公共语言运行库事件、任务、线程池、同步和I/O完成, 涉及CLR运行各个方面的行为。
Visual Studio的命令行 :cl *.cpp mscoree.lib
程序流程:
(1)定义ICLRRuntimeHost接口; (2)调用CorBindToRuntimeEx将CLR加载到内存中,根据输入的参数,取得需要的 ICLRRuntimeHost接口的实例; (3)调用Start方法将CLR初始化至进程中; (4)通过ExecuteInDefaultAppDomain调用托管程序集中某个类型的某个方法。
!ICLRRuntimeHost接口,这种以字母“I”开头的写法来源于 COM编程.NET框架实际也源于COM.
VISDN中关于宿主接口的列表,接前缀分类,大致划分为三类:ICLRxxxx, IHostxxxx 和其他。,其中形如ICLRxxxxManager的接口实例需要通过GetCLRManager方法取得,而 IHostxxxxManager则需要通过SetHostControl指定IHostControl接口实例后通过 GetHostMan得到相应的接口实例后,便可以使用接口提供的功能。比如ICLRValidator提供了 Validate方法,该方法能对指定程序集的元数据和IL进行验证,这· 经常用到的peverify.exe正是利用该接口编写的;取得了ICLRDebugManager接口, 便可以调用IsDebuggerAttached方法检测当前进程是否被调试。
12.3合成API
合成API允许运行库宿主访问应用程序资源的属性,以 便定位这些应用程序资源的正确版本。或者更简单地概括,合成API是用于定位程序集以 及相关资资源的。
IAssemblyCache是用来控制全局程序集缓存的接口,通过它可以将指定的程序集安装 到缓存中,或是将指定的程序集从缓存中删除,也可以获取缓存中程序集的相关信息。
合成API作者之一一的博客: http://blogs.msdn.com/junfeng.
取得lAssemblyCache接口实例的方法为CreateAssemblyCache,:该方法由 fusion.dll导出。熟悉Win32的读者都知道,获取某个DLL导出方法可以调用GetProcAddress,之前需要用LoadLibrary载入该库,不过这里行不通,因为fusion.dll属 于.NET的动态链接库,不在System32目录下,因此正确的方法是通过mscoree的 LoadLibraryShim载入fusion.dll后再调用GetProcAddress.
,NET提供合成API有什么用处呢?两个同名的DLL同时并存于系统中,程序可以根据自己的需要进行选择调用,而 在Win32下这是不可能实现的,因为DLL中的唯一区分就是名称。通过合成巧妙以解决了长久以来一直存在的“DLL陷阱”问题,
12.4强名称API
在MSDN中查看3.0版之前的框架帮助文档,则找不到专门的强名称API 因为过去强名称相关的API是整合在全局静态函数中介绍的,直到3.0版之后的MSDN才 将其单独分为一类。强名称API是..NET提供的专门用于操作(包括签署、移除等)强名称 的编程接口,强名称替换工具RE-Sign也正是利用强名称API来进行的。
强名称API的定义位于头文件StrongName.h中,包含了各API的功能及输入输出参数 的定义。非托管API还可以对强名称进行签署和删除,并且在托 管程序中也可以使用强名称API,当然是通过P/Invoke机制。
12.5元数据API
元数据API 的功能是读写程f序集中的元数据,可操作的对象几乎囊括了元数据的各个方面,比如逻辑结构上的程序集、类型、成员方法等,物理结构上的方法RVA, #US流、#Blob流、元数据表等。元数据API是分析与调试API的基础,凡是针对元数据的调用都需要通过它来实现,这就免去了用户完全重新编程来实现元数据读取的麻烦。
一反射的底层实现原理和元数据API是 一样的。
12.6分析API
更过分析API,可以得到以下过程的相关信息并控制创:Application的开始/结束,Assembly的载入/卸载,Methods的开始/缎束,Modul。的载 入/卸载,Class的载入/卸载,与COM接口的互操作,托管/非托管代码的即时编扁译等。 换句话说,基本上.NET的所有核心操作都可以通过分析API来得到信息并进行控制。
分析API最初被设计用来优化.NET程序。比如最常见的内存优化和防资源泄露,一般 说来是依靠.NET自身的垃圾收集(GC)机制进行处理的,但有时GC的自动处理效果并 不尽如人意,这时就需要找出影响效率的关键点,利用代码手动释放内存。通过分析API 监测.NET程序的内存使用状况和各对象存在的时间,并将存在时间过长的对象采用Close 或Dispose方法手动释放。
分析器有 开 源的,界面简洁,功能简单实用,关键是有源代码可以学习,比如微软CLR Profilerl.l和 2.0版、NProf等。
利用分析API 还可以得到如方法的相互调用关系、方法的IL代码、方法的输入参数及返回值等关键信息。 分析器的运行原 里基于COM服务,因此本身需要以非托管C++编写,以DLL的形式编译。一个最简单的 分析器源代码大致包含三个文件:一个实现DLL框架的文件、一个实现分析器核心代码的 文件和一个分析器的头文件,
由于分析API可以监视.NET的许多行为,故收集全部监测信息既没有必要也影响速 因此要设定感兴趣的事件: }GetEventMask方法是需要重点修改的地方之一,该方法返回一个掩码,用于设需要监测的事件。
分析器是作为COM服务加载的.
·如何运用分析器动态的给程序打内存补丁。 可参考文献《Modifying IL at Runtime)),文章 中详细演示了分析API与元数据API 结合。一般的Anti-Profiling有两个层次:一是高层的方法,通过在 程序开始运行时修改环境变量,将COR_ENABLEee PROFILING由1改为。,这种anti自然 是非常容易被anti-anti的了;另一种方法相对低层,通过给运行中的.NET内核DLL打补 丁使得分析器失效.
《Debuggers Under.NET》这篇文章。
第13章 MONO SSCLI与.Net注下内核调试
‘基于Linux的开源项目MONO,和可跨平台的精简.NET框架SSCLI(又 被称作ROTOR ,,MONO的部分着 重介绍Cecil这个子项目在逆向工程中.
13.1 MONO简介
它包含了一个C#语言的编译器,一个符合ECMA标准 的CLR运行时和一组类库。
该类库既包含微软.NET框架的部分类库,也包含MONO独有的一些类库及第三方类库如Gtk#.
oCecil是MONO的一个子库,用于对符合ECMA CIL 标准的程序和库进行操作,简单地说,就是可以用Cecil读取托管程序集,浏览其中所有的 类型,修改这些类型,并保存至程序集中。运 月Cecil编写.NET逆向工具,程序员便无须 和烦琐的PE与元数据结构打交道,极大地提高开发效率。并且Cecil项目还有一揽子开发 计划,比如实现编译器和反编译器,实现代码优化工具等。目 比如WiCKY Hu编写的Simple Assembly Explorer. Reflector的插件 Reflexil, CLI Secure的脱壳机,、等等。而且最重要的是:Cecil是开源的!
通过将各种操作分类合并,Cecil共划分为以下七个命名空间。
(1) Mono.Cecil:最基础的命名空间,定义了许多表示..NET基本概念的类
(2) Mono.Cecil.Binary:从名字就可以看出来,该名称空间主要定义了二进制操作相 关的代码和概念。i 这其中包括基本PE结构及其操作,同时还将一些PE中的概 念扩展为类,提供了资源和文件(Image)读写的相关类.
(3) Mono.Cecil.Cil:主要用于方法、参数、变量、IL指令及操作数等的读取、修改
(4) Mono. Cecil.Metadata:用于对元数据进行操作,支持绝大部分元数据流。
(5) Mono.Cecil.Signatures:用于操作各类Signature
(6 )Mono.Cecil.Text:仅包含UnicodeEncoding类,用于Unicode编码的相关操作,而 了.NET 内核I正是采用该种编码。
(7 Mono.Xml:执行Xml的相关操作。
Cecil编译后的可执行文件(DLL)可以用于编写各类.NET下PE、元数据、MSIL指 令的相关工具。通常编写此类工具有三种方法。第一完全从头开始。一个结构一个结构 Y9分析,所可有的签名0解码都需要掌握,所有的特殊情况都需要考虑,但没有人愿意这样做. 第二,运用.NET类库提供的Reflection功能。这似乎是个不错的方法,但可惜反射命名当 间中没有提供全部的功能,比如 比如反射就不支持操作方法的异常处理表。第三,运用Cecil类库。该类库不仅loo%兼容微软.NET可执行文件,而且对各种元素进行了抽象,便于用 户在高级层次上实现其他功能(比如将指令抽象为Instruction类,方便各种修改MSIL尔 操作),更重要的是,Cecil是完全静态的,而反射是需要载入程序集的,在某些些情况下, 完全静态的修改兼容性更强。
Cecil源代码更是学习元数据结构不可多得的“葵花宝典”,下 利用Mono.Cecil的源码来学习PE与元数据结构,同时,Cecil也是海内外 众多.NET逆向高手钟爱的类库,值得好好学习和利用。目前,利用Cecil编写且使用月较广 泛的两个工具是Simple Assembly Explorer和Reflexil,读者“ 尝试一下在Linux平台下安装 MONO Develop开发.NET程序的感觉 .
13.2 SSCIL简介
。由于提交了ECMA标准,因此微软多多少少也开放了二些源码, 这便是SSCLI (Shared Source CLI ),又被称作ROTOR. 。SSCLI是简化版的.NET但两者的核心代码有许多相似之处,,。是学习.NET内核不可多得的好材料。
从微软的网站下载SSCLI 2.0版的压缩包并解压,会得到1200()多个文件,其中核心 代码位于clr\src目录里。有 有两种方式可以方便地浏览源代码,一是使用Source Insight建立 新项目,二是利用网上的VS项目文件,在Visual Studio中浏览。
通过SSCLI,不仅可以方便地阅读.NET内核函数的C++代码,还可以深入了解一些框 架内部执行过程
NET内核动作的各个方面都可以通过源代码清楚地 知道其原理,只要SSCLI包含该机制。
NET内核概念与SSCLI源代码文件的对应表
13.3 .NET框架内核调试
想通过给内核打补丁来使Profiler失效,关键是要找到合适的位置来patch.因此第一的任务就是分析.NET是如何从内核转而执行Profiler中的用户代码的,这个过程利用 WinDbg可以很轻松地得到。
使用命令便可以在加载Profiler.dll时中断。
NET内核正在取Cor Enable Profiling环境变量的设置,而这不正是对应分析器启动时的环境变量吗?因此,最简单的方法就是在这里进行补丁,让.NET内核 误以为没有设置该变量,自然也就不会再向下执行初始化分析器的代码了。
第14章Win64平台上的.NET
14.2 C++编程的改变
在64位编程时,一般会遇到三种变量类型。第一类是传统的C++数据类型,如int,lang(这两者在64位系统中的长度为32),微软不建议使用这一类型。
第二类是固定精度的数据类型,这些类型的名称以32或64结尾用以标示精度,使用 }J时,不管处理器的精度大小如何,它们的精度都是固定的(女 第三类数据类型是指针精度的数据类型,又称“多态”数据类型,因为它们的精度随处理器的精度大小而改变,这些类型常以-PTR结尾。当需要编写同时面向32位与64位的程序时,采用这种数据类型会避免很多移植上的麻烦.
另一个问题是printf与sprintf.在Win32中通过%X或%08X来显示指针的值是很常见的用法,而在Win64下应采用%P。对于与大小相 关的类型,printf和sprintf还具有I前缀,可以使用%Iu来打印UINT PTR变量.
14.2.2 . NET编程的改变
Win32的托管程序通过P/Invoke机制调用了本地API,或者使用了本地COM服务。一旦涉及到本地代码、内存指针、内存对齐、浮点数运算这四种情 况时,要特别注意测试程序的兼容性,这种程序便很有可能无法在Win64系统下运行。
14.3 64位PE结构
PE格式反映了系统的底层机制,系统的改变也会或多或少地体现在PE格式的改变中。 Win64系统中对Win32的PE文件格式进行了扩展,扩展后的格式称为PE32+。从大体结 构看,PE32+与PE32几乎是一样的,这是出于Windows可执行问题兼容性的考虑,
第一个主要的改变在可选头结构(Optinal Header)中,64位系统的SDK头文件WinNT.h中包含了针对64位的头结构IMAGE OPTIONAL_HEADER64,另外还有一些结构的变化(主要是长度被增加至64位), 导要记住一个原则:win64系统下,虚拟地址(VA)是被扩展至64位长 的,而相对虚拟地址(RVA)仍旧保持32位长。
旦64位的托管程序有一个最大的特点:不再有本 地代码入口点。可以用cFF来验证,cFF是目前较好的同时支持32和64位平台PE 文件及元数据结构的查看工具,同时用其载入一个程序的32和64位版本,对比可发现32 位的程序Import Directory的RVA和Size都不为。,且CFF左方多出一项Import Directory 节点,‘而64位程序则没有.
14.4 64位.NET程序调试