当前位置: 首页 > news >正文

.NET 程序保护实战系列 02 · 字符串加密:让反编译器的搜索功能失效

02 · 字符串加密:让反编译器的搜索功能失效

目录

  1. 反编译器的"核武器":字符串搜索
  2. 整体流程:从 Ldstr 到 GetString
  3. 编码器模式:三种加密策略
  4. XOR 块加密与 xorshift32 密钥演化
  5. ID 编码:让索引看起来像随机数
  6. 不可见 Unicode 命名:注入代码的隐身术
  7. 整数常量加密:顺手保护数值
  8. 运行时 Constant 类:占位符驱动的动态模板
  9. 相比 ConfuserEx 的改进
  10. 结语

1. 反编译器的"核武器":字符串搜索

使用 dnSpy 打开一个未保护的 .NET 程序,按 Ctrl+F 搜索 “license”、“http”、“password”——几乎所有的关键信息都藏在字符串里。字符串加密是保护的第一道防线,目的就是让攻击者无法通过字符串搜索快速定位关键代码。


2. 整体流程:从 Ldstr 到 GetString

StringEncryptionStep 的核心逻辑

publicclassStringEncryptionStep:IProtectionStep{publicstringName=>"字符串加密";publicintOrder=>10;publicboolIsEnabled(ProtectOptionsoptions)=>options.EncryptStrings;publicvoidInject(ProtectionContextcontext){// 1. 注入 Constant 运行时类型到目标模块// 2. 创建编码器(根据 StringMode: DelegateProxy/XorWithJunk/MultiLayer)// 3. 替换 Mutation.Crypt 占位符为真实解密 IL// 4. 内联 ID 解码到 GetString 方法// 5. 在 <Module>.cctor 开头插入 Constant.Initialize() 调用// 6. 用不可见 Unicode 重命名所有注入成员// 7. 标记注入成员为不可重命名}publicvoidExecute(ProtectionContextcontext){// 1. 扫描所有方法,收集 Ldstr 指令的字符串// 2. 编码字符串到加密缓冲区// 3. XOR 块加密缓冲区// 4. 替换每个 Ldstr 为 GetString(encodedId) 调用// 5. 可选:加密整数常量}}

3. 编码器模式:三种加密策略

// DelegateProxy 模式 — 在解密和调用者之间插入委托层typeof(Func<uint,string>).GetConstructor(new[]{typeof(object),typeof(IntPtr)})// 静态分析只能看到 Invoke 委托,无法追踪到 DecodeString// XorWithJunk 模式 — 追加随机垃圾字节// 解密时返回正确子串,提取工具会被伪造内容误导// MultiLayer 模式 — 双层加密// 外层 XOR 块加密 + 内层字节置换,使用不同的密钥

4. XOR 块加密与 xorshift32 密钥演化

// 生成 16 个 uint 的解密密钥staticuint[]GenerateKey(uintseed){varkey=newuint[0x10];uintn=seed;for(inti=0;i<0x10;i++){n^=n>>12;n^=n<<25;n^=n>>27;key[i]=n;}returnkey;}// 加密字符串数据staticuint[]Encrypt(uint[]data,uintseed){varkey=GenerateKey(seed);// 从种子生成密钥intn=0;for(inti=0;i<data.Length;i++){// 每 16 个 uint 更换一次密钥uintk=key[n++%key.Length];key[n%key.Length]=(data[i]^k);// XOR 加密data[i]^=k;}returndata;}

核心特点:

  • xorshift32 周期 2^32-1,性能极高
  • 每次保护使用不同的种子(随机选择某个方法入口点的 RVA)
  • 相同字符串在不同保护中产生完全不同的密文

5. ID 编码:让索引看起来像随机数

// 为每个 decoder 生成唯一的 (k1, k2) 编码密钥对intk1=random.Next(1,int.MaxValue);intk2=random.Next();// 编码:encodedId = (realId * k1 + k2) & 0x7FFFFFFFuintEncode(intid)=>(uint)((id*k1+k2)&0x7FFFFFFF);// 解码逻辑被内联到 GetString 方法中:// realId = (encodedId - k2) * modInverse(k1) & 0x7FFFFFFF

没有 ID 编码的GetString(0)GetString(1)GetString(2)… 会让攻击者轻易推断出字符串的顺序。编码后变成GetString(0x8F3A2B1C)GetString(0x2E1D5C9A)…——看起来全是随机值。


6. 不可见 Unicode 命名:注入代码的隐身术

// 使用零宽 Unicode 字符作为注入成员的名称// U+200B: Zero Width Space// U+200C: Zero Width Non-Joiner// U+200D: Zero Width Joiner// U+2060: Word Joiner// 注入后将类的原始名称替换为不可见字符组成的字符串// 在 dnSpy 类视图中,这些成员看起来是"空"的varinvisibleName=GenerateInvisibleName(random);typeDef.Name=invisibleName;methodDef.Name=invisibleName;

同时标记注入成员为不可重命名,防止被后续混淆步骤改名后无法调用:

// 标记:此成员不可被改名MarkAsNotRenameable(clonedMethod);// ObfuscationStep 中检查boolCanRename(IDnlibDefdef)=>!IsMarkedAsNotRenameable(def);

7. 整数常量加密:顺手保护数值

// ldc.i4 12345 → 拆分为:// ldc.i4 (12345 ^ key)// ldc.i4 key// xorforeach(varinstrinbody.Instructions){if(instr.OpCode==OpCodes.Ldc_I4){intval=(int)instr.Operand;intkey=random.Next();instr.Operand=val^key;// 加密值// 在下一行插入 key 和 xorInsertAfter(instr,Instruction.Create(OpCodes.Ldc_I4,key),Instruction.Create(OpCodes.Xor));}}

8. 运行时 Constant 类:占位符驱动的动态模板

运行时Constant类的核心 —— 利用Mutation占位符机制,在保护时动态替换关键值:

internalstaticclassConstant{// 占位符 —— 保护时会被 MutationHelper 替换为真实值和 IL// Mutation.KeyI0 → 替换为 buffer 长度// Mutation.KeyI1 → 替换为 seed// Mutation.Placeholder(...) → 替换为数组初始化 IL// Mutation.Crypt(...) → 替换为真实解密循环 ILstaticFunc<uint,string>_fn;// 委托代理层staticbyte[]_buf;// 解密后的字符串缓冲区publicstaticvoidInitialize(){// 以下占位符在保护时被替换为真实值uintlen=(uint)Mutation.KeyI0;// → 真实数据长度uintseed=(uint)Mutation.KeyI1;// → 真实种子uint[]enc=Mutation.Placeholder(newuint[len]);// → 真实密文数组uint[]key=GenerateKey(seed);for(inti=0;i<len;i++)enc[i]=Mutation.Crypt(enc[i],key[i%16]);// → XOR 解密循环// 将解密后的数据复制到字节缓冲区_buf=newbyte[enc.Length*4];Buffer.BlockCopy(enc,0,_buf,0,_buf.Length);_fn=DecodeString;// 委托绑定}// 公开入口 — 非泛型签名,模糊特征publicstaticstringGetString(uintid){return_fn(id);// 通过委托间接调用解码}// 实际解码 — 根据 id 从缓冲区提取字符串staticstringDecodeString(uintid){// id 首先经过编码解码:realId = (id - k2) * invK1uintrealId=Mutation.Placeholder(id);// → 真实解码 IL// 从 _buf[offset] 读取 UTF-8 字符串intoffset=BitConverter.ToInt32(_buf,(int)realId*4);intlength=BitConverter.ToInt32(_buf,offset);returnEncoding.UTF8.GetString(_buf,offset+4,length);}}

Mutation占位符类:

publicstaticclassMutation{// 16 个 key 占位字段 — 保护时替换为 ldc.i4 <真实值>publicstaticintKeyI0,KeyI1,KeyI2,KeyI3,KeyI4,KeyI5,KeyI6,KeyI7,KeyI8,KeyI9,KeyI10,KeyI11,KeyI12,KeyI13,KeyI14,KeyI15;// 占位方法 — 保护时替换为数组初始化指令序列publicstaticTPlaceholder<T>(Tval)=>val;// 占位方法 — 保护时替换为 Crypt IL 实现publicstaticuintCrypt(uintval,uintkey)=>0;}

9. 相比 ConfuserEx 的改进

特性ConfuserEx我们的实现
压缩LZMA 压缩直接 XOR 加密(更轻量,更快)
GetString 方法泛型Get<T>(int)— 签名特征明显非泛型GetString(uint)— 更隐蔽
注入成员命名保留原始名称 “Constant”不可见 Unicode 名称
ID 编码无 — 直接传递索引(k1, k2) 编码密钥对
委托代理DelegateProxy 模式
整数常量加密支持 Ldc_I4/I8 XOR 拆分
安全检查验证调用方程序集
参数类型保护支持 I4/I8/U4/U8/R4/R8

10. 结语

字符串加密是最基础但最有效的第一道防线。它让攻击者无法通过 Ctrl+F 定位关键代码,配合不可见 Unicode 命名,进一步隐藏保护代码的存在。Mutation 占位符机制是整个保护引擎的核心设计模式——运行时模板带占位符,保护时动态替换——后续的方法体加密、虚拟化、防篡改都依赖同样的模式。

下一篇将讲解符号混淆——如何让类名、方法名变成天书。


本文由 TWSoft.AssemblyProtector 驱动。完整源码和工具请访问项目主页。

http://www.cnnetsun.cn/news/3201928.html

相关文章:

  • WebApi 和 gRPC 还分不清?一个快递例子帮你彻底搞懂
  • 3分钟搞定Windows安卓驱动:智能ADB Fastboot一键安装工具完整指南
  • 好用的平衡机源头厂家
  • 终极指南:如何用哔咔漫画下载器快速打造个人漫画库
  • 终极方案:告别Windows软件兼容性困扰的3步解决指南
  • 【万字深度长文】不止是加密!计算机安全核心:信息隐藏技术全景解读与实战指南
  • Visual C++运行库终极修复指南:一键解决DLL缺失问题的完整方案
  • 终极指南:如何用OpenRocket免费软件设计你的第一枚模型火箭 [特殊字符]
  • OBS AI背景移除插件:虚拟绿幕与低光增强的完整实用指南
  • Si5351A时钟发生器与PIC18F24K50的精准时钟系统设计
  • Cursor不是VS Code套壳:AI IDE的架构本质与实战范式
  • 马里兰大学研究团队揭秘:机器人AI的“语言大脑“到底有多臃肿?
  • 摩德纳大学:AI的“眼睛“和“大脑“为什么总是鸡同鸭讲?
  • Mos:3步让你的Mac鼠标滚动丝滑如触控板,告别卡顿与方向混乱
  • 板材贴面场景中,如何用QMS+MES把含水率接入工单流转
  • 终极窗口管理方案:让老旧游戏在现代显示器上焕发新生
  • LTX-2.3 V1.6:8G显存AI视频生成工具实战评测与优化指南
  • 手把手教你做一个指纹浏览器:从原理到实现
  • 十万个why:为什么 RAG 系统向量库解决不了的难题,用知识图谱轻松解决?
  • GVDP 整车开发流程 7 大主机厂对比:大众/丰田/奔驰等 6 类项目周期与关键节点解析
  • 如何让macOS音乐播放拥有完美歌词:LyricsX智能歌词同步终极指南
  • PeaZip密码管理器:本地AES-256加密与离线密码管理实践
  • AI时代重新定义优质员工:把目标讲清楚,会成为最重要的能力。
  • ARM NEON 向量优化在图像处理中的应用:降低 CPU 开销,提升模式切换稳定性
  • 动态武器目标分配 (DWTA) 问题建模与求解:基于Python的3阶段马尔可夫决策过程仿真
  • 上下文腐烂:为什么AI Agent工作时间越长,表现越差
  • 大模型时代的流量调度与架构演进:解构基于语义的路由策略
  • EB Garamond 12:经典字体复兴与现代学术引用的完美融合
  • 2027财年XBOX将裁员3200人,多方面重置开启新征程!
  • PotPlayer变身多语言观影神器:三步搞定免费字幕翻译插件![特殊字符]