本文主要对 《.Net 反射脱壳机核心源代码 》一文代码的原理和使用进行详细介绍。
首先介绍一下代码主要流程:
入口函数
void DumpAssembly(Assembly ass,string path)
枚举所有type,调用
void DumpType(Type tp, BinaryWriter sw)
枚举所有方法,调用
void DumpMethod(MethodBase mb, BinaryWriter sw)
{
MethodBody mbd = mb.GetMethodBody();
if (mbd == null)
return;
SetOffset(sw, mb.MetadataToken);
WriteHeader(sw, mbd);
WriteILCode(sw, mbd);
WriteSEH(sw, mbd);
}
对于 DumpAssembly, DumpType 在很久以前的文章里面就已经介绍过了,这里就不再重复。本次主要介绍 DumpMethod 以及被 DumpMethod直接或间接调用的函数。
在我之前的一篇文章《Net内存程序集通用脱壳机实现原理(二、反射以及重建方法头) 》 中介绍过,方法体是由三部分组成的, 方法头+IL字节码+SEH Table。
再来看 DumpMethod 的代码,首先获取 MethodBody ,这里要注意不是所有方法都存在 MethodBody,像PInvoke 调用 api的方法就没有MethodBody。在元数据中 rva 等于 0 的方法,就是没有MethodBody的方法。在C#中我们可以直接判断返回值是否 null 来确定这一点。
获取方法体后调用SetOffset , 这个函数用来设置当前方法体在文件中的偏移量。设置好偏移量后,我就可以直接把方法体的三部分写入文件了。
SetOffset函数,通过元数据查找方法体的rva,然后
int offsetra = (int)(offsetrva - 0x1000);
计算出文件中的偏移量,注意这里的 0x1000 是硬编码,你可能需要调整这个值,或者根据pe的section自动计算这个值。
WriteHeader 函数 中,首先调用 IsTiny 判断当前方法体是否 Tiny方法体,然后进行相应的方法头重构并写入文件。
WriteILCode 这个函数很简单,就是直接把IL字节码写入文件,在这个函数的最后处理了4字节对齐问题,SEH TABLE起始位置需要要4 byte对齐的。
最后调用 WriteSEH(sw, mbd) 重构SEH TABLE并写入文件,完成一个方体的dump工作。
WriteSEH 函数中,首先判断当前是否包含异常处理结构,如果没有就直接返回了。
然后 判断 SEH TABLE 是 Tiny的还是 Fat的。
再分别重构相应格式的SEH TABLE。
SEH TABLE 也是由两部分组成的,sehHeader + sehRows。
其中 不管是tiny还是fat的seh,其sehHeader都占用 4 字节空间。
按照cli标准重构seh比较简单,其中有一个麻烦事,就是 catch子句中,被catch的异常类在当前程序集中的token值。
我们能够在C#中直接得到这个 异常类的 type 对象,但是通过 type的metatoken得的值是它在其被定义程序集中的token值,也就是它是一个 typedef值,如果它就是在当前程序集中定义的,那么可以直接使用。如果不是,就需要解析它的 typeref token值了。这个由函数 int GetTypeToken(Type tp) 来实现。
注意 GetTypeToken 使用了 if (tp.Assembly == Assembly.GetEntryAssembly())
来判断 是否同一程序集,因为这里假定了 当前dump的就是 EntryAssembly。你可能需要根据实际情况修改。
查找 typeref值的原理,首先通过type对象获取 异常类的完整名称,然后通过元数据枚举所有引用的 类型,通过 名称比较。名字一样的就是了。
使用:
如何使用这个类来自己实现反射脱壳机?
首先你需要修改 DumpAssembly 为 public的函数。
然后实例化这个类,调用 DumpAssembly 函数即可。
第一个参数你你要dump的 Assembly 对象,第二个参数是这个 Assembly dump后的存储路径。注意第二参数,这里没有实现pe dumper 的功能,你需要先用pe dumper把程序集dump到 磁盘,然后把这个路径 作为参数传入。
尝试过直接用pe dump的人应该都清楚,直接从内存里面整个dump出来的程序集,方法体也是空的,然后这个类实现的功能,就是补充方法体的内容。
另外前面提到的这个类需要改造的地方
1,SetOffset函数。
2,GetTypeToken函数。
还有就是这个类中使用的 WrapperClass 实际上是 。net 元数据API的包装类,元数据api可以参考 msdn。