posts - 4, comments - 117, trackbacks - 0, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2008年3月17日

    (博主:好久没有写东西了,真不知道该如何下笔,也许我的思路比较混乱,还请大家见谅。)

心得:
      以前我一直是做dotnet开发,对于整个clr的架构非常感兴趣。而且对整个运行环境也进行过一些分析与跟踪。在这里给希望研究 CLR 内部原理以及实现的用户一个建议:sscli 不等于 DotNet Framework,不等于CLR。在分析 sscli 更多的时候是参考其内部的数据结构和大体的代码流程而不是具体实现,如果你用WinDbg或是OllyDbg跟踪过CLR,你会发现 sscli 和你所看到的根本不相同。

配置及环境:
一.代码分析工具
      在 sscli 茫茫的代码中确实需要一个有效的分析工具,我这里选择的是Source Insight,它可以帮助你找到相关类型,并且有效的定位到代码的声明或实现的地方。
图1:新建一个项目,sscli,项目文件保存地点需要与你的 sscli 文件夹存放在同一个磁盘上


2.


3.选择sscli20中的CLR文件夹点击 AddTree 按钮,他会寻找到所有相关代码


4.进行文件同步,source insight就会同步所有代码文件。


3.分析后的代码


二.CLR调试工具
      WinDbg 利用微软提供的调试 symbol 你可以真实的跟踪出具体实现的函数以及类型(从这点你就可以发现 sscli 并不是微软的实现代码)。具体配置如下:

 1.设置symbol,我这里选择的 D:\WinSymbols 文件夹你可选择别的地方


2.加载dotnet程序进行调试,几个比较实用的命令 lm 列出所有加载的模块, ld 加载制定模块,例如:ld mscoree,列出指定模块symbol:x /n mscoree!*


之后就可以进行 CLR 的调试。如图:调用堆栈显示 CLR 的执行过程。


下面列表就是使用 x /n mscorjit! 命令列出 JIT 编译器实现的一部分接口(这里只粘贴了一部分):
// 获取即时编译对象
790a6082 mscorjit!getJit = <no type information>

790a60a0 mscorjit!CILJit::`vftable' = <no type information>
7906edef mscorjit!CILJit::clearCache = <no type information>
7906114f mscorjit!CILJit::compileMethod = <no type information>                    // 编译 IL 代码 (jit层源代码保护一般来说就是在这里实现的,详情请参看我以前写的文章,MaxToCode DNGuard HVM 都可以由此分析破解)
790610a0 mscorjit!CILJit::isCacheCleanupRequired = <no type information>


7908f1b2 mscorjit!Compiler::EHblkDsc::CheckIsInHandlerRegion = <no type information>
790867ff mscorjit!Compiler::EHblkDsc::CheckIsInTryRegion = <no type information>
79085d2e mscorjit!Compiler::EHblkDsc::IsInFilterRegion = <no type information>
790a5515 mscorjit!Compiler::EHblkDsc::IsInHandlerRegion = <no type information>
79066e44 mscorjit!Compiler::FlatFPAllocFPState = <no type information>
...
.....
.....
.....
7909af92 mscorjit!Compiler::verVerifyCall = <no type information>
7909bbf9 mscorjit!Compiler::verVerifyCond = <no type information>
7909ba31 mscorjit!Compiler::verVerifyField = <no type information>
790a086e mscorjit!Compiler::verVerifyLDIND = <no type information>
790a0805 mscorjit!Compiler::verVerifySTIND = <no type information>
7909aae3 mscorjit!Compiler::verVerifyThisPtrInitialised = <no type information>


以上仅仅是我经过一些分析的心得,在此与大家分享,希望大家踊跃拍砖 :>

posted @ 2008-03-17 19:28 Aplo 阅读(1783) | 评论 (5)编辑

2007年9月7日

     摘要: 写在最前面: 无论是用什么编程语言编写应用程序,都会涉及到函数调用之间的问题。而调用过程可以分为两种,一种是主动请求调用,一种是被动等待调用。这也就是我们常说的调用与回调。下面我将说明DotNet(C#)与ISO C++关于函数回调的实现分析。一、DotNet(C#)函数回调。 在DotNet中实现函数调用是通过委托(delegate)实现的,首先你要声明委托原型:delegatevoidNoti... 阅读全文

posted @ 2007-09-07 18:54 Aplo 阅读(3810) | 评论 (19)编辑

2007年8月31日

今天有机会继续跟踪瑞克的软件了。上次分析结果请参见

初步研究 DNGuard HVM 2007 软件

当IL进行即时编译的时候,会执行0x60008B00处代码,可能由于是试用版的缘故,代码没有做过多限制。
一路跟下来最终明白了DNGuard HVM 2007 的执行过程。具体如下:

此过程也可以作为dotNet软件保护的基本框架:

1.软件加载运行
2.安装解密代码运行环境。即HVMRuntm.dll
3.DotNet框架加载即时编译器,即mscorjit.dll
4.mscorjit.dll中通过调用 getJit() 导出函数,可以得到即时编译对象FJitCompiler的地址为 0x790AF170, 通过该地址,找到该对象的虚函数表地址,并替换第一个字节为你的解密函数地址(注意你的函数应该符合compileMethod函数的形参表),另外要保存原来的编译地址。
5.当框架需要编译函数时,会根据即时编译对象的虚函数表调用你替换的函数。
6.进行解密
7.解密完后调用原有编译对象实现的函数,这里我直接给出地址0x7906E7F4
8.即时编译器进行编译。
9.执行编译代码

大多数Jit层保护程序都是按照这种流程进行。
并且可以破解 DNGuard HVM 2007 , MaxToCode 等等软件的保护。

下面我给出一些实现源码进行说明。

typedef DWORD (*pfGetJit)();
DWORD dwCompilerObjAddr = NULL;
DWORD dwCompileMethodAddr = NULL;

class MyCompilerObject :
        public ICorJitCompiler {
public:
        virtual CorJitResult __stdcall compileMethod (
                         ICorJitInfo                                                  *comp,                           /* IN */
                         struct CORINFO_METHOD_INFO         *info,                              /* IN */
                         unsigned                                                      flags,                              /* IN */
                         BYTE                                                         **nativeEntry,                /* OUT */
                         ULONG                                                     *nativeSizeOfCode        /* OUT */
             );

       void DecodeMethod(CORINFO_METHOD_INFO *info);
       void EncodeMethod(CORINFO_METHOD_INFO *info);
              
};

// 自己实现的编译函数
CorJitResult MyCompilerObject::compileMethod (
                         ICorJitInfo                                                  *comp,                           /* IN */
                         struct CORINFO_METHOD_INFO         *info,                              /* IN */
                         unsigned                                                      flags,                              /* IN */
                         BYTE                                                         **nativeEntry,                /* OUT */
                         ULONG                                                     *nativeSizeOfCode        /* OUT */
             ) {
         // 每当框架需要编译IL代码的时候会调用这个函数
        
        // 解密函数
        DecodeMethod(info);

        // 如果在这里安装一个钩子就可以获得解密后的源代码了
        // 这里就是DNGuard HVM 2007 的缺陷
        // MaxToCode早期版本不是Jit层解密,所以也可以钩住并获取解密后的代码
        HookCompileMethod(info);

        // 调用原本的编译函数,对已经解码的IL进行编译
        (*dwCompileMethodAddr)( comp, info, flags, nativeEntry, nativeSizeOfCode );
        // 再次加密解密的函数
        EncodeMethod(info);
}

void InstallDecodeProc()
{
        HMODULE hModule = LoadLibrary(TEXT("mscorjit.dll"))
        pfGetJit pf = (pfGetJit)GetProcAddress( hModule, "getJit" );
        
         // 获取编译对象地址
        dwCompilerObjAddr = (*pf)();
        // 获取虚函数compileMethod地址
        dwCompileMethodAddr = *(((DWORD*)(*((DWORD*)dwCompilerObjAddr))));
        // 获取虚函数表
        DWORD* pVTable = (DWORD*)(*((DWORD*)dwCompilerObjAddr));
        // 设置你自己的解密编译函数地址到原有的虚函数表中
        *pVTable = &MyCompilerObject::compileMethod;
}

上面的代码会有问题并不能通过编译,但是大体流程就是这样的,不过这样做不安全,比如我们在原有编译对象实现的编译函数0x7906E7F4这个地址进行HOOK(应用程序mscorjit.dll模块,在0x7906E7F4下断点),就可以得到解密后的IL代码,按照这个方法就可以动态脱壳了,而且这并不需要编写解密函数,因为运行时HVMRuntm.dll已经帮我们解密了,这也是DNGuard HVM 2007软件的缺陷。

正如瑞克所说的话,完全可以跳过HVMRuntm.dll 的保护得到真正的IL代码,看来瑞克要重新设计设计了。

另外,看来作者真是下功夫了,竟然加了个非常强悍的壳Themida(Themida壳会产生垃圾代码,会损耗性能,可怜的DotNet代码,本身效率就不算高了外面又加了个壳),害得我搞了半天,不过还是给脱了,不易啊,不过我想瑞克应该用的是盗版的Themida,哈哈哈,在此呼吁一下“打击盗版,使用正版,我用正版我自豪”。

此外如果有机会写下一篇关于DotNet保护的文章的话,我可以以sscli 源码为例,讲解dotnet框架,运行环境的技术。



posted @ 2007-08-31 18:27 Aplo 阅读(2705) | 评论 (13)编辑

2007年8月28日

我一直很关注瑞克的博客,自从他已开始发布dotnet保护文章开始,因为我也很关注这方面的技术。毕竟是BCG的成员嘛,哈哈哈,看到瑞克终于把 DNGuard HVM 推出,于是就小试一把。看看他把自己的软件说的很棒,到底做到什么保护程度。

不过由于时间有限,我只研究了一个开头,不过我会继续向下深入的分析的。

废话少说,言归正传:

拿到 DNGuard HVM 2007 (可能是使用版,具体给问瑞克了,我是从他bbs上下载的,他说标准版和专业版不提供下载,我也没有办法了)

首先,dotnet实现即时编译是通过mscorjit.dll实现的。其提供一个导出函数getJit,就可以得到FJitCompiler对象的地址,该对象是Singleton模式。无论你调用多少回只会返回一个对象实例。该对象实现了ICorJitCompiler接口,可以进行对dotnet程序即时编译。

DNGuard HVM 的做法是将 mscorjit.dll 中导出函数 getJit 修改,直接跳转他的运行环境 HVMRuntm.dll 0x600085D0 地址,

0x600085D0    mov  eax,0x6001FEE4
                         ret

这样在执行完 getJit 就会返回 0x6001FEE4 地址,这个地址是 DNGuard HVM 实现的一个即时编译对象的地址,当然他也是继承了ICorJitCompiler接口,并实现了compileMethod 虚函数。这样我们就可以通过虚函数表找到这个函数地址。

这里是虚函数表:
0x600188B4   ==>   0x60008AD0
                                 0x60008AE0
                                 0x60008AF0

其中0x60008AD0 就是 compileMethod 虚函数的函数地址。具体内容如下:

0x60008AD0      mov  eax, [0x60021934]
                           mov  ecx, [eax]
                           mov  [esp+04],  eax
                           mov  eax,  [ecx]
                           jmp  eax

这里是什么意思呢?
哈哈哈,我来解释一下:

mov  eax, [0x60021934]          [0x60021934]内存的内容就是 0x790AF70

0x790AF70这个就是微软实现即时编译对象的地址。它将原来 getJit 本该返回的对象地址传给eax,然后

mov  ecx, [eax]

这条语句就可以得到原对象的虚函数表。

mov  eax,  [ecx]

接着获取原对象实现的 compileMethod 虚函数。

jmp  eax

最后执行它。虽然我们有执行回原有对象,可是你别小看它,为什么呢?
DNGuard HVM 2007 将原有对象的虚函数表替换了, compileMethod 虚函数的地址现在变成了 HVMRuntm.dll库中的0x60008B00,这就是HVMRuntm.dll实现的解密函数地址,进行IL解密。

我猜想,可能是瑞克先写的解密函数,然后再写的即时编译对象,否则干什么这么麻烦,跳来跳去的,哈哈哈,(To:瑞克,不知道我说的对不对?)

由于时间关系,我没有跟踪HVMRuntm.dll库中的0x60008B00解密函数,不过下回会继续,也希望给研究 DNGuard HVM 2007 的人有所帮助。

今天就写到这里了。

我的Email是dreamvoice@21cn.com 有机会大家可以切磋一下,BCG万岁~~~~!也祝大家网安啦~~~~!

posted @ 2007-08-28 16:46 Aplo 阅读(2740) | 评论 (24)编辑