首页
社区
课程
招聘
[原创] dotNET 动态脱壳技术要点
发表于: 2025-3-10 11:59 5645

[原创] dotNET 动态脱壳技术要点

htg 活跃值
4
2025-3-10 11:59
5645

dotNET 程序文件内主要存储的是 IL 字节码,一般加壳之后,其 IL 字节码会隐藏或屏蔽,而脱壳的关键在于恢复 IL 字节码,进而近似获取程序源代码。

dotNET 程序一般在 JIT 的时候会将 IL 全部或部分解析出来,一般来说不对 token 加密的话,此时JIT的时候,会出现完整的IL。

dotNET JIT 流程大致是:

dotNET 程序在运行的时候,具体到方法执行时,CLR 会为每个方法生成一个 stub,然后调用一堆JIT 函数,生成机器码。这里面关键 JIT 有两个,一个是 DoPrestub 函数,一个是 compileMethod 函数。相对来说,compileMethod 更偏底层。

如果 dotNET 未加壳,那么在 DoPrestub 里就能够获取到 IL ,DoPrestub 的一个参数就是 MethodDesc,也就是方法描述符,通过它就可以获取IL。
但是 dotNET 加壳了,在 DoPreStub 里获取的 MethodDesc ,其找到 IL 是加密的或是错误的。一般壳体会劫持一些 JIT 函数,调用一些解密或恢复函数,进而获取真正的IL,然后再交由所劫持的 JIT 函数去生成机器码,进而执行该方法。
一般壳体不会完整实现更底层的功能,比如如何将每一个IL代码转换成一堆机器码,以及一些代码优化工作,这个工作量太大,而且不太会比原始 JIT 更好,还有可能出现运行错误。

一种策略是,遍历所有的方法,获取方法的 MethoDesc,然后手动触发 DoPrestub,进而触发壳体内部的 IL 解密或恢复函数,获取想要的IL。但是这里存在五个问题:

dotNET 加壳后,如何只触发壳体的IL解密或恢复函数,而不让具体的方法进行执行了。

可能是代码进行了内联,即将子函数的代码包络进来了。同样,调用 MakeJitWorker 函数也一样。

clr!ThePreStub
clr!PreStubWorker
clr!MethodDesc::DoPrestub
clr!MethodDesc::MakeJitWorker
clr!UnsafeJitFunction
clr!CallCompileMethodWithSEHWrapper
clr!invokeCompileMethod
clr!invokeCompileMethodHelper
clrjit!CILJit::compileMethod
clr!ThePreStub
clr!PreStubWorker
clr!MethodDesc::DoPrestub
clr!MethodDesc::MakeJitWorker
clr!UnsafeJitFunction
clr!CallCompileMethodWithSEHWrapper
clr!invokeCompileMethod
clr!invokeCompileMethodHelper
clrjit!CILJit::compileMethod
public static void CallJit(void* methodHandle) {
 
    Logger.Instance.LogInfo("即将执行:Reset");
    RuntimeFunctions.Reset(methodHandle);
 
    Logger.Instance.LogInfo("即将执行:DoPrestub");
    RuntimeFunctions.DoPrestub(methodHandle);
    if (RuntimeFunctions.IsUnboxingStub(methodHandle)) {
        Logger.Instance.LogInfo("IsUnboxingStub:True");
        // CLR内部存在UnboxingStub的方法不会被JIT直接编译,所以需要对UnboxingStub进行编译
        void* pUnboxingStub;
        Logger.Instance.LogInfo("即将执行:GetWrappedMethodDesc");
        pUnboxingStub = RuntimeFunctions.GetWrappedMethodDesc(methodHandle);
 
        Logger.Instance.LogInfo("即将执行:Reset");
        RuntimeFunctions.Reset(pUnboxingStub);
 
        Logger.Instance.LogInfo("即将执行:DoPrestub");
        RuntimeFunctions.DoPrestub(pUnboxingStub);
 
        Logger.Instance.LogInfo("即将执行:Reset");
        RuntimeFunctions.Reset(pUnboxingStub);
    }
 
    Logger.Instance.LogInfo("即将执行:Reset");
    RuntimeFunctions.Reset(methodHandle);
public static void CallJit(void* methodHandle) {
 
    Logger.Instance.LogInfo("即将执行:Reset");
    RuntimeFunctions.Reset(methodHandle);
 
    Logger.Instance.LogInfo("即将执行:DoPrestub");
    RuntimeFunctions.DoPrestub(methodHandle);
    if (RuntimeFunctions.IsUnboxingStub(methodHandle)) {
        Logger.Instance.LogInfo("IsUnboxingStub:True");
        // CLR内部存在UnboxingStub的方法不会被JIT直接编译,所以需要对UnboxingStub进行编译
        void* pUnboxingStub;
        Logger.Instance.LogInfo("即将执行:GetWrappedMethodDesc");
        pUnboxingStub = RuntimeFunctions.GetWrappedMethodDesc(methodHandle);
 
        Logger.Instance.LogInfo("即将执行:Reset");
        RuntimeFunctions.Reset(pUnboxingStub);
 
        Logger.Instance.LogInfo("即将执行:DoPrestub");
        RuntimeFunctions.DoPrestub(pUnboxingStub);
 
        Logger.Instance.LogInfo("即将执行:Reset");
        RuntimeFunctions.Reset(pUnboxingStub);
    }
 
    Logger.Instance.LogInfo("即将执行:Reset");
    RuntimeFunctions.Reset(methodHandle);
//DoPreStub 源代码里没有直接给出
// 检查该方法所属的类型是否需要运行静态构造函数
    if (!pMD->CheckRunClassInitThrowing()) {
        // 初始化失败(如抛出异常)
        return;
    }
//DoPreStub 源代码里没有直接给出
// 检查该方法所属的类型是否需要运行静态构造函数
    if (!pMD->CheckRunClassInitThrowing()) {
        // 初始化失败(如抛出异常)
        return;
    }
//伪代码
 if ( ((*(_DWORD *)(v89 + 0x38) & 0x100000) == 0// if (IsClassConstructorTriggeredViaPrestub())
             || (unsigned int)CORProfilerDisableOptimizations()
             || (*((_DWORD *)MethodTable::GetModule(v88) + 8) & 0x800) == 0
             && ((g_CORDebuggerControlFlags & 8) == 0 || (*((_DWORD *)MethodTable::GetModule(v88) + 8) & 0x400) != 0))
            && !*(_DWORD *)(*((_QWORD *)MethodTable::GetModule(v88) + 6) + 0x114i64) )
          {//【Patch,直接nop掉,不执行该调用】
            MethodTable::CheckRunClassInitThrowing(v6);//
// 触发静态构造器的执行
// pMD->GetMethodTable()->CheckRunClassInitThrowing();
          }
//伪代码
 if ( ((*(_DWORD *)(v89 + 0x38) & 0x100000) == 0// if (IsClassConstructorTriggeredViaPrestub())
             || (unsigned int)CORProfilerDisableOptimizations()
             || (*((_DWORD *)MethodTable::GetModule(v88) + 8) & 0x800) == 0
             && ((g_CORDebuggerControlFlags & 8) == 0 || (*((_DWORD *)MethodTable::GetModule(v88) + 8) & 0x400) != 0))
            && !*(_DWORD *)(*((_QWORD *)MethodTable::GetModule(v88) + 6) + 0x114i64) )
          {//【Patch,直接nop掉,不执行该调用】
            MethodTable::CheckRunClassInitThrowing(v6);//
// 触发静态构造器的执行
// pMD->GetMethodTable()->CheckRunClassInitThrowing();
          }
//源代码
if (ContainsGenericVariables())
    {
        COMPlusThrow(kInvalidOperationException, IDS_EE_CODEEXECUTION_CONTAINSGENERICVAR);
    }
//源代码
if (ContainsGenericVariables())
    {
        COMPlusThrow(kInvalidOperationException, IDS_EE_CODEEXECUTION_CONTAINSGENERICVAR);
    }

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2025-3-10 13:00 被htg编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 23
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
vbox能脱么
2025-3-25 14:10
0
雪    币: 5583
活跃值: (3573)
能力值: ( LV12,RANK:417 )
在线值:
发帖
回帖
粉丝
3
mb_dimjtfnf vbox能脱么
我没有试过,但是可以用上述的方法试一试,也就是用 动态脱壳方式来还原 IL。
但是,要进行带壳调试,找到并patch掉一些反调试坑才行。
2025-3-25 16:21
0
游客
登录 | 注册 方可回帖
返回