简介:

程序动态变化是指:当程序在运行时动态修改内存中的自身代码并执行。程序动态变化是加壳技术的重要基础。

2008年6月我研究了一下.NET平台的MSIL汇编语言,借用微软提供的ILASM.exe做了一个MSIL的汇编开发系统:ROS(所罗门封印)。本文中的试验就是在ROS系统中编译运行的。

要实现程序动态变化对于本地代码无论使用X86还是ARM处理器都比较简单。修改内存属性(静态或动态修改皆可),确定待修改代码起点终点,直接进行处理即可。 但是.NET程序并没有使用真实的CPU而是在虚拟机上运行,最重要的是:方法在调用时才使用JIT技术编译成本地代码。 同一个.NET程序在不同CPU平台会编译成不同的本地代码,要想确定代码的起点终点和类型将变得很困难。

试验思路和过程:

我的思路是:以方法为单位,在某个方法被调用前使用MSIL汇编代码修改在内存中该方法的代码。当该方法被调用时.NET平台编译的就是修改后的代码,从而实现了.NET程序的动态变化。


这样有两个问题需要解决:

01、确定方法在内存中的地址。

02、修改代码内存属性使之可写。

针对第一个问题我采用的方法是:方法在内存中的地址=imagebase + 方法的RVA。

针对第二个问题我采用的方法是:调用VirtualProtect函数修改内存属性。示例代码X01.IL中用这个函数修改内存成功,但是很有趣的问题是:虽然成功但是函数返回的结果却是FALSE。

通过输出Test方法首部的MSIL代码说明动态修改成功。但是调用Test方法却发现结果依然是1,而不是10。

★  这说明我们对Test修改之前Test方法已经被JIT编译过了!
    我的解释是当我们调用VirtualProtect修改Test属性时访问了Test内存,可能触发了JIT编译Test的MSIL代码使修改滞后了。

一个直观的想法是:我们可以静态修改.text区块的属性使之可写,就不用调用VirtualProtect函数修改Test方法的属性,也就不会触发JIT了。但是这个方法经试验验证不行。因为如果修改了.text区块的属性,程序将出现“初始化错误”而无法运行!

那么我们现在要解决的问题就必须是想到一个办法拦截JIT。

通过反复试验我找到一个方法可以拦截JIT,即“压栈不足”方法。在X02.IL中,对于Test方法,需要将一个数据压入堆栈才能使用ShowDec显示,但是前4条指令没有这样作。属于压栈不足情况。通过试验表明如果一个方法压栈不足,那么程序会按照修改后的代码编译并执行得到结果。对比X01.EXE和X02.EXE试验效果图,您会发现只有调用Test方法后输出的结果发生了改变。说明动态修改代码成功。

★我的解释是:当压栈不足时虽然运行VirtualProtect时JIT想编译Test方法的代码但是发现代码有错,没有编译。所以当我们修改了Test代码后再次调用Test时JIT重新尝试编译代码取得成功!如同SEH,我再给你一次机会,看你行不行。如果有错的方法永远不被调用,系统也不报错。


试验还表明压栈过度动态修改代码不会成功。X03.EXE执行效果可以看出来。因为.NET是允许子方法压栈过度的,主方法不行!


理论上的应用:

本轮试验虽然使用了一些手动操作,但是用程序实现是可行的。从理论上讲如果我们要保护一个方法的代码。可以首先将方法的代码取出压缩加密保存在某个位置。然后填充一些使方法堆栈压栈不足的代码以拦截JIT。当程序运行时用VirtualProtect修改方法对应内存属性并恢复MSIL代码。之后当调用方法时JIT会编译修改后的代码。在静态情况下,反汇编得到方法的代码是错误而无意义的。


源码和试验说明:

源码还是使用当初研究时编写的ROS源码,没有ROS系统无法编译,但肯定可以看懂。有EXE文件反汇编也可以分析MSIL。试验效果有对应的效果图,也是ROS系统截图。运行程序需要BFC.dll,Blitz Force Class库。


最后声明:
本人研究.NET时间很短只有数周,虽然可以看到试验效果,但是解释未必正确!欢迎.NET平台的高手多多指教。

上传的附件 源代码示例程序和效果图.rar