本文首发于: https://xz.aliyun.com/t/14894
编译时对字符串常量加密,在产物中敏感字符串不以明文存储,所以在特征免杀中有较好的效果。
常见的方案
使用LLVM pass实现字符串加密
通常思路都是在pass中对字符串常量加密并添加解密函数,在初始化或者引用时解密字符串。
从稳定性上看,需要实现临界区或增加标志位,防止多线程竞争同时解密。
从免杀效果上看,由于字符串仍在原地解密,无法绕过内存查杀。
参考链接:
C++17 constexpr
https://github.com/JustasMasiulis/xorstr 是通过C++17 constexpr特性实现的字符串常量加密。
来看一个xorstr的例子
1 2 3
| int main() { std::puts(xorstr_("an extra long hello_world")); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| movabs rax, -4762152789334367252 push rbp mov rbp, rsp and rsp, -32 sub rsp, 64 mov QWORD PTR [rsp], rax mov rdi, rsp movabs rax, -6534519754492314190 mov QWORD PTR [rsp+8], rax ...... vpxor ymm0, ymm1, YMMWORD PTR [rsp+32] vmovdqa YMMWORD PTR [rsp], ymm0 vzeroupper call puts xor eax, eax leave ret
|
相对于llvm的方案而言优势在于将字符串常量解密在栈上
- 完成就没有线程安全问题了
- 栈上内存可能很快就被覆盖,一定程度上也解决了内存查杀问题
另外就是支持x86,arm下通过AVX、SSE、NEON等指令进行加速解密。
最大问题也非常突出: C++狗都不学 C++从学习难度,产物体积等完全没有优势
用zig实现编译时字符串常量加密
本文使用zig 0.11
zig也有类似于C++17 constexpr的特性,使用 comptime
关键字
官方文档在此: https://ziglang.org/documentation/0.11.0/#comptime
通过很简单的代码即可实现编译时字符串常量加密,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const std = @import("std"); const key = [16]u8{1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6}; const string = []const u8;
fn encrypt(comptime str: string) *[str.len]u8 { comptime var enstr: [str.len]u8 = undefined; for (0..str.len) |i| { enstr[i] = str[i] ^ key[i % key.len]; } return enstr[0..str.len]; } inline fn x(comptime str: string) []u8 { comptime var e = encrypt(str); var buf = e.*; for (0..buf.len) |i| { buf[i] ^= key[i % key.len]; } return buf[0..str.len]; } export fn main() void { var s1 = x("aaaabbbbccccddddaaaabbbbccccdddd123123123123123"); _ = std.io.getStdOut().write(s1) catch unreachable; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| main: vmovups ymm0, ymmword ptr [rip + .L__unnamed_1+15] vmovups ymm1, ymmword ptr [rip + .L__unnamed_1] xor eax, eax vmovups ymmword ptr [rsp - 41], ymm0 vmovups ymmword ptr [rsp - 56], ymm1 .LBB0_1: cmp rax, 47 je .LBB0_3 mov ecx, eax and ecx, 15 mov cl, byte ptr [rcx + .L__unnamed_2] xor byte ptr [rsp + rax - 56], cl inc rax jmp .LBB0_1 .LBB0_3: movabs rdi, 1 lea rsi, [rsp - 56] movabs rdx, 47 xor r8d, r8d .LBB0_4: mov rax, rdi syscall mov ecx, eax neg ecx cmp rax, -4095 cmovb ecx, r8d cmp cx, 4 je .LBB0_4 vzeroupper ret
.L__unnamed_1: .ascii "`cbegdejjcbag`ab`cbegdejjcbag`ab0005756::131266"
.L__unnamed_2: .ascii "\001\002\003\004\005\006\007\b\t\000\001\002\003\004\005\006"
|
可以点击此链接体验: https://zig.godbolt.org/z/fc3hcsseo
实现动态密钥
上面的例子的key是固定的,如果在zig使用中每次编译生成不通的key?
参考: https://ziggit.dev/t/how-to-implement-conditional-compilation-in-zig/379/3
在build.zig中添加,相当于定义了一个 option
模块,并添加了 key
的定义
1 2 3 4 5
| var options = b.addOptions(); var key: [16]u8 = undefined; std.os.getrandom(&key) catch unreachable; options.addOption([16]u8, "key", key); exe.addOptions("option", options);
|
并修改main.zig中key的定义
1
| const key = @import("option").key;
|
最终效果
![out](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAAaADAAQAAAABAAAAAQAAAADa6r/EAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII=)
完整代码
https://github.com/howmp/zigxorstr