首页
论坛
专栏
课程

[.NET平台] [原创]利用压栈不足拦截JIT实现.NET程序动态变化[MASSADA0019]

2009-3-12 21:18 7605

[.NET平台] [原创]利用压栈不足拦截JIT实现.NET程序动态变化[MASSADA0019]

2009-3-12 21:18
7605
简介:

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

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平台的高手多多指教。

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

上传的附件:
最新回复 (12)
friedriech 2009-3-13 11:58
2
0
加百力,搞得东西还真多。MSIL也搞过?
加百力 12 2009-3-15 15:27
3
0
去年夏天搞过几周,论坛上玩MSIL的会员似乎不多,很快就沉下去了。
tankaiha 29 2009-3-15 15:57
4
0
下来看看,貌似原理和.net壳的原理差不多了
加百力 12 2009-3-15 17:33
5
0
要想实现加壳或其他高级技术,代码动态变化是一项基础,所以当初就专门研究了一下。
blackboy 2009-3-19 12:37
6
0
1、感觉是不是你的ShowJudge 写的有问题

2、感觉楼主应该把Test独立起来,别放到Main函数中调用。

3、感觉不是压栈不足还是过度的问题,而是JIT是否能成功编译的问题

都是个人感觉,没有实际测试,如有不妥,请见谅
blackboy 2009-3-19 12:39
7
0
其实还有一个感觉,就是lz可能参加过传xiao,
开个玩笑~~
加百力 12 2009-3-20 09:13
8
0
对于第一个点,我看到的返回结果是0。

对于第二个点,没明白您的意思是什么?

对于第三个点,我在文章中打“★”的位置已经说明了。
blackboy 2009-3-20 14:38
9
0
1. VirtualProtect返回true
2. 增加一个函数Test1,里面调用Test;在Main中调用Test1,而不是Test,应该就会明白我说的意思
加百力 12 2009-3-20 15:23
10
0
我是直接从堆栈顶部读取的VirtualProtect的返回值。这样做应该没有问题吧?你是从什么地方读到返回值为true?
blackboy 2009-3-21 18:02
11
0
我直接用C#写的,反出来的IL:

    call bool Test.Form1::VirtualProtect(native int, uint32, uint32, uint32&)
    brfalse.s L_0057
    ldstr "VirtualProtect Ok"
显示的是OK,也就是返回true

另外,这个测试做的不完整,你可以做两个测试
一个Test就像你写的,另一个Test里面内容写多一些,也就是针对tiny和fat两种方法体做修改
得出来的结论会比较完整
加百力 12 2009-3-22 08:57
12
0
多谢blackboy!

确实还可以深入研究一下。
lixupeng 2009-3-22 22:37
13
0
收下了
游客
登录 | 注册 方可回帖
返回