破解 DNGuard HVM 2007 软件保护功能(更新加源码) - Aplo

时间:2024-03-06 14:13:51
今天有机会继续跟踪瑞克的软件了。上次分析结果请参见

初步研究 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框架,运行环境的技术。