原贴地址:https://www.52pojie.cn/forum.php?mod=viewthread&tid=2115655
代码分析(壳体分析)
程序执行入口
static <Module>()
{
//验证主要壳方法是否被修改或优化
\u200D\u200E\u200B\u200E\u200E\u200C.\u200F\u200E\u200F\u200F\u200B();
//....
\u200B\u200C\u200F\u200E\u200F\u200E\u200C\u200E.\u200C\u200E\u200E\u200C\u200D\u200B\u200E\u200D();
// .....
\u200B\u200E\u200F\u200D.\u200C\u200D\u200E\u200C();
//.....
\u200F\u200E\u200B\u200F\u200E\u200F.\u200D\u200F\u200C\u200D();
//.....
<Module>.\u200E\u200F\u200D\u200D\u200C\u200E();
}
验证主要壳方法是否被修改或优化
// Token: 0x02000011 RID: 17
[NullableContext(1)]
[Nullable(0)]
internal static class \u200D\u200E\u200B\u200E\u200E\u200C
{
// Token: 0x0600003E RID: 62 RVA: 0x00009D30 File Offset: 0x00007F30
public static void \u200F\u200E\u200F\u200F\u200B()
方法主要功能是:
-
找到资源"nkyplgcbjbbp":通过判断资源数据的前四个字节为 DE AD BE EF,长度为 0x0000024C
-
解密资源:key 为 第4至7字节,即 0x4D9B2B3D,调用方法 0x06000040 解密第8至末尾的字节。
-
解密后资源结构(第8至末尾):总数(4字节)、方法体Token(4)及字节摘要(4字节)、…………
0000 EFBEADDE 4D9B2B3D 00000048 0600001F ....=+.MH.......
0010 C88A8C26 06000020 4EC67CE4 06000021 &... ....|.N!...
0020 48899709 06000022 5459B784 06000001 ...H".....YT....
0030 14F08ACB 06000002 124CC62B 06000003 ........+.L.....
0040 D75CCA79 06000004 45003C9F 06000005 y.\......<.E....
0050 D5ED6309 06000006 578311A7 06000007 .c.........W....
0060 4F37EEAF 06000008 C11D05FC 06000009 ..7O............
0070 D2966F4B 0600000A 6456825D 0600000B Ko......].Vd....
0080 76590DE3 0600000C 243A1F0C 0600000D ..Yv......:$....
0090 D4E2C0F5 0600000E CAB721D9 0600000F .........!......
00A0 F5DC050E 06000010 81BC7EB5 06000011 .........~......
00B0 DB894762 06000012 54AC4009 06000013 bG.......@.T....
…………
总共校验 0x48 个,而方法表里有 0x4B 个。只有3个不需要校验 0x06000024 0x06000026 0x06000027 。
这三个方法正好没有方法体。
// Token: 0x06000024 RID: 36
[DllImport("kernel32.dll", EntryPoint = "IsDebuggerPresent")]
private static extern bool \u200D\u200B\u200D\u200E\u200C\u200C\u200F();
// Token: 0x06000026 RID: 38
[NullableContext(1)]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetModuleHandle")]
private static extern IntPtr \u200F\u200C\u200B\u200C\u200B\u200E\u200E\u200E(string lpModuleName);
// Token: 0x06000027 RID: 39
[DllImport("kernel32.dll", EntryPoint = "VirtualProtect")]
private unsafe static extern bool \u200B\u200C\u200C\u200B\u200E(byte* lpAddress, int dwSize, uint flNewProtect, out uint lpflOldProtect);
-
验证壳方法是否被修改:读取资源里的相应方法的方法体,与元数据的方法体是否一致,不一致,则调用 Environment.FailFast(null); 终止程序。
提取数据
// Token: 0x02000010 RID: 16
[NullableContext(1)]
[Nullable(0)]
internal static class \u200B\u200C\u200F\u200E\u200F\u200E\u200C\u200E
{
// Token: 0x06000028 RID: 40 RVA: 0x000048E8 File Offset: 0x000048E8
public static void \u200C\u200E\u200E\u200C\u200D\u200B\u200E\u200D()
方法主要功能:
-
找到资源"uwdchypjzvdi":通过判断资源数据的前四个字节为 CA FE BA BE,长度为 0x0000CF4F
-
解密资源:key 为 第4至7字节,即 0x8090B238,调用方法 0x0600003C 解密第8至末尾的字节。
-
解密后资源结构(第8至末尾):总数(4字节)、方法体Token(4)及字节摘要(4字节)、…………
0000 CA FE BA BE 38 B2 90 80 1C 00 00 00 01 00 00 06 ....8...........
0010 07 00 00 00 00 00 00 00 00 00 BE AC 00 00 20 87 .............. .
0020 33 B6 4D 20 87 34 B6 4D 61 8D 18 00 00 01 25 20 3.M .4.Ma.....%
0030 05 98 71 74 20 05 98 71 74 61 20 25 60 05 84 20 ..qt ..qta %`..
0040 70 60 05 84 61 9C 25 20 85 92 7F 0F 20 84 92 7F p`..a.% .... ...
0050 0F 61 20 38 0E 2D 7D 20 BC 0E 2D 7D 61 9C 25 20 .a 8.-} ..-}a.%
0060 75 46 F8 6C 20 77 46 F8 6C 61 20 5B 85 92 4D 20 uF.l wF.la [..M
0070 12 85 92 4D 61 9C 25 20 89 AB BD CE 20 8A AB BD ...Ma.% .... ...
0080 CE 61 20 BF AC 34 26 20 85 AC 34 26 61 9C 25 20 .a ..4& ..4&a.%
0090 E6 74 B6 32 20 E2 74 B6 32 61 20 FB F2 33 24 20 .t.2 .t.2a ..3$
00A0 DC F2 33 24 61 9C 25 20 48 C9 60 8D 20 4D C9 60 ..3$a.% H.`. M.`
00B0 8D 61 20 29 80 3F 22 20 83 80 3F 22 61 9C 25 20 .a ).?" ..?"a.%
magic CA FE BA BE
key 38 B2 90 80
cnt 1C 00 00 00
--------------------------------------------------------------------
最终分析结构:
00 00 00 00 token
00 00 00 00 未知(某种标记)
00 00 .... 局部变量签名长度、签名数据
00 00 00 00 异常处理块长度、异常处理块信息
00 00 00 00 方法体长度、方法体字节码
00 00 字符串
.......
--------------------------------------------------------------------
方法体:起始地址为 0xC
01 00 00 06 token
07 00 00 00 未知1,读的时候跳过了,+ 8
00 00 未知2
00 00 00 00 未知3
BE AC 00 00 方法体:0000020C00060300000000000000000067000000
地址计算:C + 8 + 未知2 + 2 + 未知3 + 4 + 0x0000ACBE + 4 = 0x0000ACDC
在地址 0x0000ACDC 处,读取2个字节(未知4), 0x0000ACDC + 2 = 0x0000ACDE
在地址 0x0000ACDE 处,此时就是下一个循环,这个一定是 token
未知2:如果不为0,则后面紧接着存储数据,
未知3:如果不为0,则后面紧接着存储数据,
未知4:如果不为0,则开始循环读取
--------------------------------------------------------------------
方法体:起始地址为 0x0000ACDE
02 00 00 06 token
03 00 00 00 未知1,读的时候跳过了,+ 8
00 00 未知2
00 00 00 00 未知3
67 00 00 00 方法体:000003000006030000
地址计算:0x0000ACDE + 8 + 未知2 + 2 + 未知3 + 4 + 0x00000067 + 4 = 0x0000AD57
在地址 0x0000AD57 处,读取2个字节(未知4), 0x0000AD57 + 2 = 0x0000AD59
在地址 0x0000AD59 处,此时就是下一个循环,这个一定是 token
--------------------------------------------------------------------
方法体:起始地址为 0x0000AD59
03 00 00 06 token
03 00 00 00 未知1,读的时候跳过了,+ 8
03 00 未知2:07 01 0E 疑似局部变量签名
00 00 00 00 未知3
1E 00 00 00 方法体:000003000006030000
地址计算: 0x0000AD59 + 8 + 3 + 2 + 未知3 + 4 + 0x0000001E + 4 = 0x0000AD8C
在地址 0x0000AD8C 处,读取2个字节(未知4), 0x0000AD8C + 2 = 0x0000AD8E
在地址 0x0000AD8E 处,此时就是下一个循环,这个一定是 token
--------------------------------------------------------------------
经过测试,在字节码跳转完了之后,读取的2个字节均为0。
-
存储结构:方法体的解密信息。
array:资源库解密之后的数据 "uwdchypjzvdi"
加密方法token列表
array3 {uint[0x0000001C]} uint[]
[0] 0x06000001 uint
[1] 0x06000002 uint
[2] 0x06000003 uint
[3] 0x06000004 uint
[4] 0x06000005 uint
[5] 0x06000006 uint
[6] 0x06000007 uint
[7] 0x06000008 uint
[8] 0x06000009 uint
[9] 0x0600000B uint
[10] 0x0600000C uint
[11] 0x0600000D uint
[12] 0x0600000E uint
[13] 0x0600000F uint
[14] 0x06000010 uint
[15] 0x06000011 uint
[16] 0x06000012 uint
[17] 0x06000013 uint
[18] 0x06000015 uint
[19] 0x06000016 uint
[20] 0x06000017 uint
[21] 0x06000018 uint
[22] 0x06000019 uint
[23] 0x0600001A uint
[24] 0x0600001B uint
[25] 0x0600001C uint
[26] 0x0600001D uint
[27] 0x0600001E uint
//加密方法的解密信息偏移地址
array4 {int[0x0000001C]} int[]
[0] 0x0000000C int
[1] 0x0000ACDE int
[2] 0x0000AD59 int
[3] 0x0000AD8E int
[4] 0x0000ADDE int
[5] 0x0000AE12 int
[6] 0x0000AE74 int
[7] 0x0000AEE2 int
[8] 0x0000AF4B int
[9] 0x0000AFA2 int
[10] 0x0000AFCA int
[11] 0x0000B4AA int
[12] 0x0000B689 int
[13] 0x0000BB32 int
[14] 0x0000C396 int
[15] 0x0000C442 int
[16] 0x0000C466 int
[17] 0x0000C740 int
[18] 0x0000C7D4 int
[19] 0x0000C96C int
[20] 0x0000CD2F int
[21] 0x0000CE6E int
[22] 0x0000CE9B int
[23] 0x0000CEC8 int
[24] 0x0000CEE3 int
[25] 0x0000CEFE int
[26] 0x0000CF19 int
[27] 0x0000CF34 int
num3 0x0000001C int
分析:此处执行之后,记录的数据会作为DynamicMethod方法还原之用
.... ConfuserEx 标准 AntiDump 保护逻辑,抹除 PE 导出表、DOS/NT 头、导入表关键标志,阻止工具内存转储分析
调试时,直接跳过执行。
// Token: 0x0200000F RID: 15
internal static class \u200B\u200E\u200F\u200D
{
// Token: 0x06000025 RID: 37 RVA: 0x00003FE0 File Offset: 0x00003FE0
public unsafe static void \u200C\u200D\u200E\u200C()
{
反调试
调试时,直接跳过执行。
// Token: 0x0200000E RID: 14
internal static class \u200F\u200E\u200B\u200F\u200E\u200F
{
// Token: 0x06000023 RID: 35 RVA: 0x00003F78 File Offset: 0x00003F78
public static void \u200D\u200F\u200C\u200D()
{
bool isAttached = Debugger.IsAttached;
if (isAttached)
{
Environment.Exit(1255763124 ^ -1255763125);
}
try
{
bool flag = \u200F\u200E\u200B\u200F\u200E\u200F.\u200D\u200B\u200D\u200E\u200C\u200C\u200F();
if (flag)
{
Environment.Exit(-309533851 ^ 309533850);
}
}
catch
{
}
}
// Token: 0x06000024 RID: 36
[DllImport("kernel32.dll", EntryPoint = "IsDebuggerPresent")]
private static extern bool \u200D\u200B\u200D\u200E\u200C\u200C\u200F();
}
解密一大堆数据:与字符串解密有关系,是解密的#US字符串。
挂接字符串解密函数
// Token: 0x02000001 RID: 1
internal class <Module>
{
// Token: 0x0600001F RID: 31 RVA: 0x000020A0 File Offset: 0x000020A0
private static void \u200E\u200F\u200D\u200D\u200C\u200E()
{
uint num = 464U;
uint num2 = 4144958357U;
uint[] array = new uint[]
{
Program静态构造方法
// CheckMe.Program
// Token: 0x0600000A RID: 10 RVA: 0x000039C0 File Offset: 0x000039C0
// Note: this type is marked as 'beforefieldinit'.
static Program()
{
int[] array = new int[21753686 ^ 21753692];
RuntimeHelpers.InitializeArray(array, fieldof(<PrivateImplementationDetails>.8242ECB75666623F6918BF63D14BA58912CDE2A4A82417B8FAD722CD12DB26C0).FieldHandle);
Program._keyEnc = array;
byte[] array2 = new byte[-890174908 ^ -890174911];
RuntimeHelpers.InitializeArray(array2, fieldof(<PrivateImplementationDetails>.AAE6C1A0CDEC645F6331A2757B98D69D5F22101FA2A8ABA06F52228F36A2EA26).FieldHandle);
Program._midEnc = array2;
}
//还原
static Program()
{
int[] array = new int[0xA];
RuntimeHelpers.InitializeArray(array, fieldof(<PrivateImplementationDetails>.8242ECB75666623F6918BF63D14BA58912CDE2A4A82417B8FAD722CD12DB26C0).FieldHandle);
Program._keyEnc = array;
byte[] array2 = new byte[0x5];
RuntimeHelpers.InitializeArray(array2, fieldof(<PrivateImplementationDetails>.AAE6C1A0CDEC645F6331A2757B98D69D5F22101FA2A8ABA06F52228F36A2EA26).FieldHandle);
Program._midEnc = array2;
}
直接定位读取
_keyEnc = [0x73, 0x77, 0x71, 0x94, 0x92, 0x98, 0x8A, 0x89, 0x9B, 0x97]
_midEnc = [0x5A, 0x55, 0x59, 0x4B, 0x51]
000250 73 00 00 00 77 00 00 00 71 00 00 00 94 00 00 00 s...w...q.......
000260 92 00 00 00 98 00 00 00 8A 00 00 00 89 00 00 00 ................
000270 9B 00 00 00 97 00 00 00 5A 55 59 4B 51 00 00 00 ........ZUYKQ...
DynamicMethod实现方法
全局类存储待解密信息
- private static byte[] byte_0;解密资源数据,存放方法体解密信息
- private static uint[] uint_0;加密方法体token列表
- private static int[] int_0;加密方法体解密数据的偏移地址列表
- private static DynamicMethod[] dynamicMethod_0;存储初次构造DM
- private static object object_0;未知
// Token: 0x0600002A RID: 42 RVA: 0x0000583C File Offset: 0x00003A3C
private static DynamicMethod \u200C\u200F\u200D\u200C\u200F(uint token, int pos)
{