顶级技术:CLR/JIT操纵字符串的内存地址

1.前言

一个字符串的存储基本上是分为前端存储和后端存储两种状态,前端存储即Roslyn编译之后存储在托管DLL里面,后端存储即JIT加载之后,存储在内存里面。如果加密后的字符串则有可能在前端和后端分别被更改和移位。这个时候如果想要找出锁定的字符串,则需要对CLR/JIT进行分析,本篇来看下。

2.概述

一:例子

前端存储意即托管DLL里面的字符串存储位置在托管PE-》.Net目录-》数据流-》#US项里面。如果不加密直接修改#us项即可。
如果加密了,这个时候就可能需要解密,或者到后端去进行寻找。这里看下后端JIT寻找,如何寻找呢?
可以通过以下环境变量,设置JIT的日志。

DOTNET_JItDump=Main

这里是Main函数的日志。以下代码为例:

static void Main(string[] args)
{
   string str = "abcdefg";
   Console.WriteLine(str);
}

很简单的这段代码,字符串实例str存储一段字符串。这个字符串如果被加密之后,如果MSIL位置或者是内存位置都被移位,比如vmp加密。如何找出来呢?

二:printObjectDescription
这个函数是当JIT第一次导入Main函数的IL的时候,打印出变形树(也即JIT操纵IR的阶段)的时候,需要打印出字符串实例str,如下:

Morphing BB02 of 'ConsoleApp4.Program:Main(System.String[])'


fgMorphTree BB02, STMT00001 (before)
               [000001] -----------    *  NO_OP     void


fgMorphTree BB02, STMT00002 (before)
               [000003] DA---------    *  STORE_LCL_VAR ref    V01 loc0
               [000002] -----------    \--*  CNS_STR   ref   <string constant>


fgMorphTree BB02, STMT00002 (after)
               [000003] DA---+-----    *  STORE_LCL_VAR ref    V01 loc0
               [000016] H----+-----    \--*  CNS_INT(h) ref     'abcdefg'

我们看到这颗变形树的最后面有abcdefg这个字符串,它正是str实例。到了这里可以看到在

printObjectDescription函数里面它操作了str实例,根据这一点,我们跟踪下这个函数,然后看下这个str实例是从哪里来的。

三:跟踪
printObjectDescription函数在eePrintObjectDescription函数里面被调用,看下后者的原型:

void Compiler::eePrintObjectDescription(const char* prefix, CORINFO_OBJECT_HANDLE handle)
{
    const size_t maxStrSize = 64;
    char         str[maxStrSize];
    size_t       actualLen = 0;


    // Ignore potential SPMI failures
    bool success = eeRunFunctorWithSPMIErrorTrap(
        [&]() { actualLen = this->info.compCompHnd->printObjectDescription(handle, str, maxStrSize); });
}

函数printObjectDescription的两个参数第二个是str,也即是示例里面的哪个str,它是准被获取的。第一个参数handle则是关键了。

向上跟踪

eePrintObjectDescription(" ", (CORINFO_OBJECT_HANDLE)tree->AsIntCon()->gtIconVal);

这个handle是

(CORINFO_OBJECT_HANDLE)tree->AsIntCon()->gtIconVal

这段代码赋值而来的。AsIntCon()的返回值就是tree。gtIconVal则是tree的字段。这里看下gtIconVal内存

0x000001F300209B28  00007ffdf1076f18 0062006100000007 0066006500640063 0000000000000067.o.??.......a.b.c.d.e.f.g...............

可以看到handle指向的是str实例的MethodTable,而后面跟的是实例字符串的值。所以它函数

printObjectDescription可以直接获取到str的值。也就是说,无论加密软件如何千变万化,它在一般的情况下是不可能改变CLR的内存模型,能够改的只有JIT的流程。

四.续集
上面基本上搞定了如何查找被加密的字符串,当然还有一种方式直接看汇编的机器码,这个是最后的结果,也是最低档次的一种,不再赘述。除了上面的问题之外,这个gtIconVal字段是何时被赋值的呢?

InfoAccessType iat =
        info.compCompHnd->constructStringLiteral(tree->AsStrCon()->gtScpHnd, tree->AsStrCon()->gtSconCPX, &pValue);

constructStringLiteral函数参数gtScpHnd表示当前Module句柄,比如当前是托管模块ConsoleApp4.dll的句柄,gtSconCPX参数表示需要获取字符串的token。比如当前是0x700058e3。通过这两个参数获取到str实例的句柄,句柄里面包含的就是指向str的MethodTalbe,然后通过后者获取到字符串的值。这里是如何找到str实例获取的地方呢?可以通过gtIconVal往上跟踪,也可以跟踪AsStrCon()。同样的道理。

结尾
本篇主要是通过一个小例子,结合CLR的内存模型和JIT的运作流程看下.Net的核心运行模式。以及加密软件的高端玩法,其实非常简单。
作者:jianghupt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值