10 · 资源加密:把 DLL 藏进保险箱
目录
- 为什么需要加密资源
- [SPN 三阶段加密算法](#SPN 三阶段加密算法)
- [xorshift128 PRNG:2^128-1 周期的高强度随机序列](#xorshift128 PRNG:2^128-1 周期的高强度随机序列)
- 加密流程完整实现
- [运行时解密 + AssemblyResolve](#运行时解密 + AssemblyResolve)
- 性能与内存考量
- 结语
1. 为什么需要加密资源
许多 .NET 程序将 DLL 作为 Embedded Resource。即使主程序被保护,攻击者解包后可以直接反编译这些裸 DLL。
2. SPN 三阶段加密算法
SPN(Substitution-Permutation Network)是 AES 的核心思想。我们实现三阶段:
阶段 1:SBox 代换
csharp
// 生成 256 字节随机 SBox
static byte[] GenerateSBox(uint seed)
{
var rng = new XorShift128(seed);
var sbox = new byte[256];
for (int i = 0; i < 256; i++) sbox[i] = (byte)i;
// Fisher-Yates 洗牌
for (int i = 255; i > 0; i--)
{
int j = (int)(rng.Next() % (i + 1));
(sbox[i], sbox[j]) = (sbox[j], sbox[i]);
}
return sbox;
}
// 代换
for (int i = 0; i < data.Length; i++)
data[i] = sbox[data[i]];
阶段 2:ARX 流密钥(Addition-Rotation-XOR)
csharp
// 使用 xorshift128 PRNG 生成流密钥
var rng = new XorShift128(seed);
for (int i = 0; i < data.Length; i++)
{
byte k = rng.NextByte();
data[i] = (byte)(data[i] + k); // 加法混合
data[i] = (byte)((data[i] << 3) | (data[i] >> 5)); // 循环左旋 3
data[i] ^= rng.NextByte(); // XOR 混合
}
阶段 3:字节置换
csharp
// Fisher-Yates 洗牌打乱位置
var rng2 = new XorShift128(permSeed);
for (int i = data.Length - 1; i > 0; i--)
{
int j = (int)(rng2.Next() % (i + 1));
(data[i], data[j]) = (data[j], data[i]);
}
三阶段后的加密数据与原始内容无任何可识别模式。
3. xorshift128 PRNG:2^128-1 周期
csharp
struct XorShift128
{
uint p0, p1, p2, p3;
public XorShift128(uint seed)
{
// 用简单的 LCG 从种子生成初始状态
p0 = (uint)(seed * 214013 + 2531011);
p1 = (uint)(p0 * 214013 + 2531011);
p2 = (uint)(p1 * 214013 + 2531011);
p3 = (uint)(p2 * 214013 + 2531011);
}
public uint Next()
{
uint t = p0 ^ (p0 << 11);
p0 = p1; p1 = p2; p2 = p3;
p3 = p3 ^ (p3 >> 19) ^ (t ^ (t >> 8));
return p3;
}
}
相比 ConfuserEx 的 xorshift32(周期 2^32-1),xorshift128 的状态空间提升了 2^96 倍,抗分析能力显著增强。
4. 加密流程完整实现
csharp
static byte[] Encrypt(byte[] data, out uint seed, out uint permSeed)
{
var rngCrypto = System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] seedBytes = new byte[8];
rngCrypto.GetBytes(seedBytes);
seed = BitConverter.ToUInt32(seedBytes, 0);
permSeed = BitConverter.ToUInt32(seedBytes, 4);
var result = (byte[])data.Clone();
// 1. SBox 代换
var sbox = GenerateSBox(seed);
for (int i = 0; i < result.Length; i++)
result[i] = sbox[result[i]];
// 2. ARX 流密钥
var rng = new XorShift128(seed);
for (int i = 0; i < result.Length; i++)
{
result[i] += rng.NextByte();
result[i] = (byte)((result[i] << 3) | (result[i] >> 5));
result[i] ^= rng.NextByte();
}
// 3. 字节置换
var rng2 = new XorShift128(permSeed);
for (int i = result.Length - 1; i > 0; i--)
{
int j = (int)(rng2.Next() % (i + 1));
(result[i], result[j]) = (result[j], result[i]);
}
return result;
}
存储格式:[0xC0 0xDE 0xFA 0xFE] + [seed 4B] + [origLen 4B] + [permSeed 4B] + [加密数据]
5. 运行时解密 + AssemblyResolve
csharp
internal static class ResourceEncryptor
{
static Dictionary<string, byte[]> _decryptedAssemblies = new();
public static void Initialize()
{
// 1. 扫描所有资源找魔术头 0xC0 0xDE 0xFA 0xFE
foreach (var resName in asm.GetManifestResourceNames())
{
if (buf[0] != 0xC0 || buf[1] != 0xDE ||
buf[2] != 0xFA || buf[3] != 0xFE) continue;
// 2. 读取 seed, origLen, permSeed
uint seed = BitConverter.ToUInt32(buf, 4);
uint origLen = BitConverter.ToUInt32(buf, 8);
uint permSeed = BitConverter.ToUInt32(buf, 12);
// 3. 解密(三阶段反向)
byte[] decrypted = Decrypt(buf, 16, origLen, seed, permSeed);
// 4. Assembly.Load 加载程序集
var loaded = Assembly.Load(decrypted);
_decryptedAssemblies[loaded.GetName().Name] = decrypted;
}
// 5. 挂接 AssemblyResolve 事件
AppDomain.CurrentDomain.AssemblyResolve += (_, args) =>
{
var name = new AssemblyName(args.Name).Name;
return _decryptedAssemblies.TryGetValue(name, out var bytes)
? Assembly.Load(bytes) : null;
};
}
// 解密:三阶段逆操作
static byte[] Decrypt(byte[] enc, int offset, uint origLen, uint seed, uint permSeed)
{
var data = new byte[origLen];
Array.Copy(enc, offset, data, 0, origLen);
// 逆置换
var rng2 = new XorShift128(permSeed);
for (int i = (int)origLen - 1; i > 0; i--)
{
int j = (int)(rng2.Next() % (i + 1));
(data[i], data[j]) = (data[j], data[i]);
}
// 逆 ARX
var rng = new XorShift128(seed);
for (int i = 0; i < origLen; i++)
{
data[i] ^= rng.NextByte();
data[i] = (byte)((data[i] >> 3) | (data[i] << 5)); // 循环右旋 3
data[i] -= rng.NextByte();
}
// 逆 SBox
var sbox = GenerateSBox(seed);
var invSBox = new byte[256];
for (int i = 0; i < 256; i++) invSBox[sbox[i]] = (byte)i;
for (int i = 0; i < origLen; i++)
data[i] = invSBox[data[i]];
return data;
}
}
6. 性能与内存
| 指标 | 未加密 | SPN 加密 |
|---|---|---|
| 程序集大小 | +0 | +0(等大小替换) |
| 启动时间 | 正常 | +50-200ms |
| 运行时 | 正常 | 无影响(解密仅一次) |
7. 结语
SPN 三阶段加密提供了远强于纯 XOR 的加密强度。攻击者解包后看到的是完全随机的字节,无法判断哪个资源是 DLL。
下一篇将讲解类型注入机制------保护引擎如何实现零外部 DLL 依赖。
本文由 TWSoft.AssemblyProtector 驱动。完整源码和工具请访问项目主页。