今天有机会继续跟踪瑞克的软件了。上次分析结果请参见
初步研究 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框架,运行环境的技术。
初步研究 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框架,运行环境的技术。