.net下某强壳的不完全分析
其实题目有点偏离内容,因为本文并不是对壳的本身进行深入的分析,而是从另一个角度对其保护和破解进行分析。这个壳是啥偶就不说了,呵呵。(壳作者可是我的偶像)下面开始。
1、壳的保护方式简介
该壳的网站上已经有很多篇文章介绍过他的方式,但并不深入。(这是当然,因为已经商业化了。)总的来说,.net的运行机制和win32平台下有很多不同,比如.net的PE载入内存后,不是立刻将所有的代码从MSIL编译至native code,而是每运行一个method,编译一个method。当某个method编译过后,它在一定时间内的native code总是存在于内存中,便于下次直接调用。而由于有垃圾收集系统,一段时间不用后,该method所占用的资源又可能被释放。
该壳便是利用了.net的这种特性,被保护的程序所有的method的代码块大小都被置为0,而真正的代码被壳用多种算法加密保存,但每个method的定义仍在,当jit引擎调用某个method时,被挂钩的程序就是转到壳的解密代码中,将源代码解密完再传给jit。其中包括重建方法表的结构,因为原程序的方法表中代码块的大小是0。这些加密和解密代码都在一个本地dll中,所以该壳的唯一缺点就是被其加密过的程序无法跨平台。其实这一点对共享软件作者来说并不重要,一个程序可以在所有装.net framework的平台上运行就足够了。
大家都知道.net中有Profiler,它可以JIT开始编译时输出IL代码。如果这样,那不是可以直接用Profiler将IL代码输出吗?非也。壳的作者也在网站上说过,他很了解profiler和jit之间的通讯,可以很容易的避开Profiler的监测。下面,先来看看profiler和jit是怎么通讯的。
代码:--------------------------------------------------------------------------------
TADDR MethodDesc::MakeJitWorker(COR_ILMETHOD_DECODER* ILHeader, DWORD flags)
{
...
...
if (CORProfilerTrackJITInfo() && !IsNoMetadata())
{
{
PROFILER_CALL;
g_profControlBlock.pProfInterface->JITCompilationStarted((ThreadID) GetThread(),(CodeID) this,TRUE);
}
COR_ILMETHOD *pilHeader = GetILHeader();
new (ILHeader) COR_ILMETHOD_DECODER(pilHeader, GetMDImport(), NULL);
}
...
...
--------------------------------------------------------------------------------
红色的代码就是JIT通过接口通知Profiler开始JITCompilationStarted,这时我们可以在Profiler中dump内存中的il代码。如果这时IL代码还没有解密呢?(事实也是如此)壳完全可以在发送了该通知后,再调用解密代码,这样,光用Profiler是没法取得程序的源代码的。
例如,对用该壳加密过的某程序用Profiler在JITCompilationStarted里dump得到的内容如下:
JITCompilationStarted:
Class name is:
CFunction name is: x
enter fat codeFlags: FFF
MaxStack: 9600
CodeSize: FFFF07E7
LocalVarSigTok: FFFFFFF
E707FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFF
FFFF0083E707FFFFFFFFFFFFFFFFFFFFFFFF1A28790000062AFFFF8089E707FFFFFF
FFFFFFFFFFFFFFFF8088E707
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC08BE707FFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFF80B6E707
FFFFFFFFFFFFFFFFFFFFFFFFFFFF03300A002B000000000000002879000006286503000
A80ED0B000428F807000A
80EE0B000428F700000A80EF0B000420000000802CE52AFFFF80A6E707FFFF1A287900
00062AFFFF80A3E707FFFF
1A28790000062AFFFF80A0E707FFFFFFFFFFFFFF80A1E707FFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFF40BBE707FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFF00B7E707
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0B1E707FFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFF
FF........
不用说了,一堆乱码。下面,我们就开始看怎么dump真正的源代码。
2、取得解密后源代码的思路
本文的主要内容就是分析怎样取得壳解密过的源代码,而真正完全的脱壳目前还不行,当然,偶觉得也没必要。我们的思路,既然壳挂钩了jit与ee(execute engine)的通讯,我们也可以中断,在jit即将进行编译的一瞬前,代码总是解密过的吧,我们要找到这个断点。
rotor是源代码是我们了解以上内容的好资料。我们在fjit.cpp里可以看到这个函数:
代码:--------------------------------------------------------------------------------
/******************************************************************************
******/
/* jit the method. if successful, return number of bytes jitted, else return 0 */
FJitResult FJit::jitCompile(
BYTE ** ReturnAddress,
unsigned * ReturncodeSize
)
--------------------------------------------------------------------------------
看名称很像jit的关键函数,只是rotor的代码并不是windows上.net framework的代码,所以jitCompile也不是framework中相对应的函数的名称。想想别的方法。
mscorjit.dll是jit引擎的核心文件,其中肯定有从ee读入IL的函数。用ida对其进行反编译,当ida提示从网上下载文件的符号时,点确认。仔细搜索一下,有这样一个函数
.text:7906E7F4 private: virtual enum CorJitResult __stdcall CILJit::compileMethod(class ICorJitInfo *, struct CORINFO_METHOD_INFO *, unsigned int, unsigned char * *, unsigned long *) proc near
compileMethod,光看这个名字就知道他的功能是编译method。看一下输入参数,其中第二个参数是struct CORINFO_METHOD_INFO *
在rotor的源代码中搜索该结构,可以得到如下定义(尽管不知道两个结构是否完全相同,不妨先假设相同,死马当活马医)
struct CORINFO_METHOD_INFO
{
CORINFO_METHOD_HANDLE ftn;
CORINFO_MODULE_HANDLE scope;
BYTE * ILCode;
unsigned ILCodeSize;
unsigned short maxStack;
unsigned short EHcount;
CorInfoOptions options;
CORINFO_SIG_INFO args;
CORINFO_SIG_INFO locals;
};
前两个又是指向结构的指针
typedef struct CORINFO_MODULE_STRUCT_* CORINFO_MODULE_HANDLE;
typedef struct CORINFO_METHOD_STRUCT_* CORINFO_METHOD_HANDLE;
只是我搜遍了本机和网络也没有找到它们的具体定义,看来又是UnDocumented的。无所谓,关键是第三和第四个成员。ILCode和ILCodeSize,这是不是就是指向了IL代码的内存地址呢?而指向的代码是否又被解过密呢?如果是,我们就可以在这里进行中断,从而输出解密后的代码。
3、调试
既然已经深入到mscorjit.dll内部,就无需用.net调试了,直接用最熟悉的OllyDbg。先运行被加密过的程序,然后用OD附加到该进程上,接着bp 7906E7F4,很快,OD便中断了。看堆栈值
0012D750 79E9776F 返回到 m
来源:网络转载 [hr]