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