应用安全 --- apk加固 之 OLLVM
c++代码混淆之王
核心定义
OLLVM 的全称是 Obfuscator LLVM。它是一个开源的代码混淆工具,基于著名的 LLVM 编译器框架。
简单来说,它的核心作用是:将你的源代码(尤其是 C/C++)编译成功能完全一样,但反编译后极其难以阅读和理解的程序。 它通过在编译器的中间表示层(LLVM IR)插入各种混淆变换来达到这个目的。
核心原理:为什么它这么有效?
要理解 OLLVM 的强大之处,需要明白传统编译和它工作的区别:
-
传统编译流程:
源代码 (C/C++)
->编译器前端 (生成中间表示 IR)
->编译器优化器
->编译器后端 (生成机器码/汇编)
-
OLLVM 的编译流程:
源代码 (C/C++)
->编译器前端 (生成 LLVM IR)
->OLLVM 混淆器 (对 IR 进行混淆变换)
->编译器优化器
->编译器后端 (生成极其混乱的机器码/汇编)
关键点在于: OLLVM 不是在源代码级别(容易绕过)或最终的机器码级别(极其复杂)进行混淆,而是在 LLVM IR 这个中间层进行操作。
-
LLVM IR 是一种既保留高级语言结构(如函数、循环、分支)又接近机器码的中间语言。
-
在这个层面进行混淆,变换可以非常深入和彻底,并且因为 LLVM 支持多种平台(x86, ARM, MIPS等),所以一次混淆,就能为所有这些平台生成被混淆的代码,实现了“一次编写,到处混淆”。
主要混淆技术(Passes)
OLLVM 提供了几种经典的混淆技术,可以单独或组合使用:
-
控制流扁平化
-
这是 OLLVM 最著名、最标志性的功能。
-
作用:打破代码中原有的自然分支和循环结构,将所有基本块(Basic Block)放到一个巨大的 switch-case 结构(或状态机)中,然后通过一个“状态变量”来控制下一个要执行哪个基本块。
-
效果:反编译后,你看到的代码不再是清晰的 if-else 或 for/while 循环,而是一堆顺序排列的代码块和一个巨大的分发器,逻辑流程变得极其难以追踪。
- 举例:
例如,原始代码如下
int calculate(int x) { if (x > 0) { return x * 2; } else { return -x; } }
平坦化后的代码
int calculate(int x) { int dispatcher = 0; int result = 0; while (1) { switch (dispatcher) { case 0: // 起始块 if (x > 0) { dispatcher = 1; // 跳转到正数处理 } else { dispatcher = 2; // 跳转到负数处理 } break; case 1: // 正数处理块 result = x * 2; dispatcher = 3; // 跳转到结束块 break; case 2: // 负数处理块 result = -x; dispatcher = 3; // 跳转到结束块 break; case 3: // 结束块 return result; default: return -1; // 错误路径 } } }
-
-
指令替换
-
作用:将简单的算术或逻辑运算(如
ADD
,SUB
,AND
,OR
)替换为一系列功能等价但更复杂的指令序列。 -
例子:
a = b + c
可能被替换成a = (b & c) + (b | c)
这样更绕的形式。 -
效果:增加逆向时理解代码具体操作的难度,但通常会对性能有一定影响。
- 例如:
可以看到 add eax, [rsp+38h+var_C] 指令已经被替换成
xor ecx, ecx ; 使用异或指令将 ecx 清零 (ecx = 0) sub ecx, edx ; 计算 ecx = 0 - edx (此时 ecx = -1) sub eax, ecx ; 计算 eax = eax - ecx,即 eax = 1 - (-1) = 2
-
-
虚假控制流
-
作用:在正常的代码流程中插入永远为真或永远为假的条件跳转。这些跳转永远不会被执行,但它们的存在会欺骗反编译器,使其生成错误的反编译图。
-
效果:在控制流图中制造大量的“噪音”和虚假分支,干扰逆向分析人员的判断。
-
-
字符串加密
-
作用:将程序中的明文字符串(如密码、密钥、调试信息)在编译时进行加密存储,然后在程序运行时动态解密使用。
-
效果:防止攻击者通过简单的字符串搜索快速定位到关键代码位置(例如,搜索 “Login Failed” 字符串来找到认证函数)。
-
用途和应用场景
-
软件保护与防逆向工程:这是最主要的目的。保护核心算法(如加密算法、游戏反作弊逻辑、独家业务规则)不被竞争对手或黑客轻易分析和窃取。
-
增加盗版和破解难度:让软件破解者难以通过修改关键跳转或验证逻辑来制作盗版软件。
-
移动应用(Android/iOS)保护:Android NDK 和 iOS 开发都使用 Clang/LLVM,因此 OLLVM 被广泛用于加固 so 库和 iOS 应用,防止针对原生代码的逆向。
现状与影响
-
开源与分支:原始版本的 OLLVM 已经多年未更新。目前社区有许多活跃的分支和改进版本,例如 Hikari、Armariris 等,它们修复了原始版本的 Bug,并增加了新的混淆技术和对抗反混淆的方法。
-
商业化:许多商业的软件保护壳和加固方案(如国内的几大安卓加固厂商)的核心混淆技术都基于或借鉴了 OLLVM 的思想。
-
攻防对抗:OLLVM 的出现极大地推动了软件保护技术的发展,同时也催生了许多反混淆技术的研究。如今,高级的反编译器(如 IDA Pro、Ghidra)和专门的反混淆工具(如 deFlat)都在努力尝试还原被 OLLVM 混淆的代码,这是一场持续的“军备竞赛”。
总结
特性 | 描述 |
---|---|
全称 | Obfuscator LLVM |
本质 | 基于 LLVM 编译框架的代码混淆工具 |
核心优势 | 在编译器中间表示层(IR)进行混淆,与平台无关,混淆效果深入且强大 |
关键技术 | 控制流扁平化、指令替换、虚假控制流、字符串加密 |
主要用途 | 保护软件核心逻辑、防止逆向工程、增加破解难度 |
现状 | 开源项目,社区有多个改进分支,是软件保护领域的事实标准之一 |
总而言之,OLLVM 是软件安全领域的一个里程碑式的工具,它将以编译技术为基础的代码保护手段提升到了一个新的高度。
原版地址:
OLLVM 项目地址:https://github.com/obfuscator-llvm/obfuscator
OLLVM 文档:https://github.com/obfuscator-llvm/obfuscator/wiki/Features
开源地址:https://github.com/CYRUS-STUDIO/LLVM
参考:
注意上面都是没有编译的第三方开发者移植版,我将带有混淆参数的编译了的版本,不知道如何使用请查看readme。
https://gitee.com/null_465_7266/ollvm-compile
举例说明
#include <iostream> int main() { int a = 15; int b = 27; int sum = a + b; std::cout << "Sum: " << sum << std::endl; return 0; }
ida反汇编
.text:00000001400016D0 ; =============== S U B R O U T I N E ======================================= .text:00000001400016D0 .text:00000001400016D0 ; Attributes: bp-based frame .text:00000001400016D0 .text:00000001400016D0 ; int __fastcall main(int argc, const char **argv, const char **envp) .text:00000001400016D0 public main .text:00000001400016D0 main proc near ; CODE XREF: __tmainCRTStartup+1E7↑p .text:00000001400016D0 ; DATA XREF: .pdata:000000014000709C↓o .text:00000001400016D0 .text:00000001400016D0 var_10 = dword ptr -10h .text:00000001400016D0 var_C = dword ptr -0Ch .text:00000001400016D0 var_8 = dword ptr -8 .text:00000001400016D0 var_4 = dword ptr -4 .text:00000001400016D0 .text:00000001400016D0 push rbp .text:00000001400016D1 sub rsp, 30h .text:00000001400016D5 lea rbp, [rsp+30h] .text:00000001400016DA call __main .text:00000001400016DF mov [rbp+var_4], 0 .text:00000001400016E6 mov [rbp+var_8], 0Fh .text:00000001400016ED mov [rbp+var_C], 1Bh .text:00000001400016F4 mov eax, [rbp+var_8] .text:00000001400016F7 add eax, [rbp+var_C] .text:00000001400016FA mov [rbp+var_10], eax .text:00000001400016FD mov rcx, cs:_refptr__ZSt4cout .text:0000000140001704 lea rdx, aSum ; "Sum: " .text:000000014000170B call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::ostream &,char const*) .text:0000000140001710 mov rcx, rax .text:0000000140001713 mov edx, [rbp+var_10] .text:0000000140001716 call _ZNSolsEi ; std::ostream::operator<<(int) .text:000000014000171B mov rcx, rax .text:000000014000171E lea rdx, _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ ; std::endl<char,std::char_traits<char>>(std::ostream &) .text:0000000140001725 call _ZNSolsEPFRSoS_E ; std::ostream::operator<<(std::ostream & (*)(std::ostream &)) .text:000000014000172A xor eax, eax .text:000000014000172C add rsp, 30h .text:0000000140001730 pop rbp .text:0000000140001731 retn .text:0000000140001731 main endp .text:0000000140001731 .text:0000000140001731 ; --------------------------------------------------------------------------- .text:0000000140001732 algn_140001732: ; DATA XREF: .pdata:000000014000709C↓o .text:0000000140001732 align 4 .text:0000000140001734 ; [00000006 BYTES: COLLAPSED FUNCTION std::operator<<<std::char_traits<char>>(std::ostream &,char const*). PRESS CTRL-NUMPAD+ TO EXPAND] .text:000000014000173A align 4 .text:000000014000173C ; [00000006 BYTES: COLLAPSED FUNCTION std::endl<char,std::char_traits<char>>(std::ostream &). PRESS CTRL-NUMPAD+ TO EXPAND] .text:0000000140001742 align 4 .text:0000000140001744 ; [00000006 BYTES: COLLAPSED FUNCTION std::ostream::operator<<(int). PRESS CTRL-NUMPAD+ TO EXPAND] .text:000000014000174A align 4 .text:000000014000174C
gemini反编译
#include <iostream> int main() { // 在栈上为局部变量预留空间 // 在汇编代码中,这些变量位于基指针(rbp)的偏移量 -8(a)、-12(b) 和 -16(sum) 处 // .text:00000001400016E6 mov [rbp-8], 0Fh int a = 15; // 十六进制 0xF 等于十进制 15 // .text:00000001400016ED mov [rbp-12], 1Bh int b = 27; // 十六进制 0x1B 等于十进制 27 // .text:00000001400016F4 mov eax, [rbp-8] // .text:00000001400016F7 add eax, [rbp-12] // .text:00000001400016FA mov [rbp-16], eax int sum = a + b; // 计算结果(42)并存储 // 以下代码行对应于 std::cout 的链式调用 // .text:00000001400016FD ... 调用打印 "Sum: " // .text:0000000140001713 ... 调用打印 'sum' 的值 // .text:000000014000171E ... 调用打印换行符(std::endl) std::cout << "Sum: " << sum << std::endl; // .text:000000014000172A xor eax, eax return 0; // 返回值为0表示程序成功执行 }
sub混淆
#include <iostream>
int main() {
// mov [rbp+var_8], 0Fh -> (hex 0F is decimal 15)
int a = 15;
// mov [rbp+var_C], 1Bh -> (hex 1B is decimal 27)
int b = 27;
// The assembly performs the calculation: eax = a - (0 - b), which simplifies to a + b.
// mov eax, a
// mov edx, b
// xor ecx, ecx ; ecx = 0
// sub ecx, edx ; ecx = -b
// sub eax, ecx ; eax = a - (-b) => a + b
int sum = a + b; // 15 + 27 = 42
// The subsequent calls print the string "Sum: ", the value of the 'sum' variable,
// and a newline character to the console.
std::cout << "Sum: " << sum << std::endl;
// xor eax, eax -> sets the return value to 0
return 0;
}
这两个代码片段的功能结果完全相同,都是计算 15 + 27,然后将结果 42 打印出来。它们最核心的区别在于执行加法计算的方式不同,这通常是由编译器的优化级别不同导致的。 主要区别:计算方式 第一个代码片段(复杂版): 使用了五条指令,通过 xor 和 sub(减法)的组合来完成加法。 第二个代码片段(简洁版): 使用了一条 add(加法)指令直接完成计算。 代码分析 让我们来详细看看计算部分。 第一个代码片段 (未优化/Debug模式) Fragmento de código .text:00000001400016F4 mov eax, [rbp+var_8] ; eax = 15 .text:00000001400016F7 mov edx, [rbp+var_C] ; edx = 27 .text:00000001400016FA xor ecx, ecx ; ecx = 0 .text:00000001400016FC sub ecx, edx ; ecx = 0 - 27 => ecx = -27 .text:00000001400016FE sub eax, ecx ; eax = 15 - (-27) => eax = 15 + 27 = 42 .text:0000000140001700 mov [rbp+var_10], eax ; 将结果 42 存入变量 这里的计算逻辑是 a - (-b),这在数学上等同于 a + b。这种代码显得很冗长,但它可能非常忠实地反映了未经优化的中间代码。 第二个代码片段 (优化后/Release模式) Fragmento de código .text:00000001400016F4 mov eax, [rbp+var_8] ; eax = 15 .text:00000001400016F7 add eax, [rbp+var_C] ; eax = 15 + 27 => eax = 42 .text:00000001400016FA mov [rbp+var_10], eax ; 将结果 42 存入变量 这里的计算非常直接明了,用一条 add 指令就完成了任务。这是典型的优化编译器会生成的代码,因为它更短、更快、更高效。
字符串混淆
.text:00000001400016D0 ; =============== S U B R O U T I N E ======================================= .text:00000001400016D0 .text:00000001400016D0 ; Attributes: bp-based frame .text:00000001400016D0 .text:00000001400016D0 ; int __fastcall main(int argc, const char **argv, const char **envp) .text:00000001400016D0 public main .text:00000001400016D0 main proc near ; CODE XREF: __tmainCRTStartup+1E7↑p .text:00000001400016D0 ; DATA XREF: .pdata:000000014000709C↓o .text:00000001400016D0 .text:00000001400016D0 var_10 = dword ptr -10h .text:00000001400016D0 var_C = dword ptr -0Ch .text:00000001400016D0 var_8 = dword ptr -8 .text:00000001400016D0 var_4 = dword ptr -4 .text:00000001400016D0 .text:00000001400016D0 push rbp .text:00000001400016D1 sub rsp, 30h .text:00000001400016D5 lea rbp, [rsp+30h] .text:00000001400016DA call __main .text:00000001400016DF mov [rbp+var_4], 0 .text:00000001400016E6 mov [rbp+var_8], 0Fh .text:00000001400016ED mov [rbp+var_C], 1Bh .text:00000001400016F4 mov eax, [rbp+var_8] .text:00000001400016F7 add eax, [rbp+var_C] .text:00000001400016FA mov [rbp+var_10], eax .text:00000001400016FD lea rcx, unk_1400090A0 .text:0000000140001704 lea rdx, unk_140005050 .text:000000014000170B add rdx, 1Eh .text:000000014000170F call _Lgoron_decrypt_string_0 .text:0000000140001714 mov rcx, cs:_refptr__ZSt4cout .text:000000014000171B lea rdx, unk_1400090A0 .text:0000000140001722 call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::ostream &,char const*) .text:0000000140001727 mov rcx, rax .text:000000014000172A mov edx, [rbp+var_10] .text:000000014000172D call _ZNSolsEi ; std::ostream::operator<<(int) .text:0000000140001732 mov rcx, rax .text:0000000140001735 lea rdx, _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ ; std::endl<char,std::char_traits<char>>(std::ostream &) .text:000000014000173C call _ZNSolsEPFRSoS_E ; std::ostream::operator<<(std::ostream & (*)(std::ostream &)) .text:0000000140001741 xor eax, eax .text:0000000140001743 add rsp, 30h .text:0000000140001747 pop rbp .text:0000000140001748 retn .text:0000000140001748 main endp .text:0000000140001748 .text:0000000140001748 ; --------------------------------------------------------------------------- .text:0000000140001749 algn_140001749: ; DATA XREF: .pdata:000000014000709C↓o .text:0000000140001749 align 10h .text:0000000140001750 .text:0000000140001750 ; =============== S U B R O U T I N E ======================================= .text:0000000140001750 .text:0000000140001750 .text:0000000140001750 _Lgoron_decrypt_string_0 proc near ; CODE XREF: main+3F↑p .text:0000000140001750 ; DATA XREF: .pdata:00000001400070A8↓o .text:0000000140001750 .text:0000000140001750 var_24 = dword ptr -24h .text:0000000140001750 var_20 = qword ptr -20h .text:0000000140001750 var_18 = qword ptr -18h .text:0000000140001750 var_10 = qword ptr -10h .text:0000000140001750 var_4 = dword ptr -4 .text:0000000140001750 .text:0000000140001750 sub rsp, 28h .text:0000000140001754 mov [rsp+28h+var_20], rdx .text:0000000140001759 mov [rsp+28h+var_18], rcx .text:000000014000175E add rdx, 17h .text:0000000140001762 mov [rsp+28h+var_10], rdx .text:0000000140001767 xor eax, eax .text:0000000140001769 cmp cs:dword_1400090A8, 1 .text:0000000140001770 mov [rsp+28h+var_4], eax .text:0000000140001774 jz loc_1400017D2 .text:000000014000177A .text:000000014000177A loc_14000177A: ; CODE XREF: _Lgoron_decrypt_string_0+72↓j .text:000000014000177A mov eax, [rsp+28h+var_4] .text:000000014000177E mov rcx, [rsp+28h+var_18] .text:0000000140001783 mov rdx, [rsp+28h+var_10] .text:0000000140001788 mov [rsp+28h+var_24], eax .text:000000014000178C movsxd r8, eax .text:000000014000178F mov r8b, [rdx+r8] .text:0000000140001793 mov r9d, 17h .text:0000000140001799 xor edx, edx .text:000000014000179B div r9d .text:000000014000179E mov eax, [rsp+28h+var_24] .text:00000001400017A2 mov r9d, edx .text:00000001400017A5 mov rdx, [rsp+28h+var_20] .text:00000001400017AA movsxd r9, r9d .text:00000001400017AD xor r8b, [rdx+r9] .text:00000001400017B1 movsxd rdx, eax .text:00000001400017B4 mov [rcx+rdx], r8b .text:00000001400017B8 add eax, 1 .text:00000001400017BB cmp eax, 6 .text:00000001400017BE mov [rsp+28h+var_4], eax .text:00000001400017C2 jnz loc_14000177A .text:00000001400017C8 mov cs:dword_1400090A8, 1 .text:00000001400017D2 .text:00000001400017D2 loc_1400017D2: ; CODE XREF: _Lgoron_decrypt_string_0+24↑j .text:00000001400017D2 add rsp, 28h .text:00000001400017D6 retn .text:00000001400017D6 _Lgoron_decrypt_string_0 endp .text:00000001400017D6 .text:00000001400017D6 ; --------------------------------------------------------------------------- .text:00000001400017D7 algn_1400017D7: ; DATA XREF: .pdata:00000001400070A8↓o .text:00000001400017D7 align 8 .text:00000001400017D8 ; [00000006 BYTES: COLLAPSED FUNCTION std::operator<<<std::char_traits<char>>(std::ostream &,char const*). PRESS CTRL-NUMPAD+ TO EXPAND] .text:00000001400017DE align 20h .text:00000001400017E0 ; [00000006 BYTES: COLLAPSED FUNCTION std::endl<char,std::char_traits<char>>(std::ostream &). PRESS CTRL-NUMPAD+ TO EXPAND] .text:00000001400017E6 align 8 .text:00000001400017E8 ; [00000006 BYTES: COLLAPSED FUNCTION std::ostream::operator<<(int). PRESS CTRL-NUMPAD+ TO EXPAND] .text:00000001400017EE align 10h .text:00000001400017F0 .text:00000001400017F0 ; =============== S U B R O U T I N E =======================================
_Lgoron_decrypt_string_0 函数和一些看似是加密字符串的数据。让我们来分解一下。 分析 _Lgoron_decrypt_string_0: 这个函数接受两个参数: rcx (传入 var_18): 这是解密字符串将存储的目标缓冲区(来自 unk_1400090A0 的 main)。 rdx(传递给 var_20):这是加密字符串数据的指针(来自 unk_140005050 + 1Eh 的 main)。 让我们追踪逻辑: 序章: sub rsp, 28h 设置了栈帧。 var_20 存储了 rdx(加密源),而 var_18 存储了 rcx(解密目标)。 add rdx, 17h: 这会修改 rdx(原始加密源指针) 原始 ,通过向其添加 0x17(十进制的 23)并把这个新地址存储在 var_10 中。这意味着 var_10 指向 unk_140005050 内的某个偏移量,这很可能是 密钥 或加密数据的不同部分。 xor eax, eax and mov [rsp+28h+var_4], eax: 初始化 var_4(一个索引 i)为 0。 cmp cs:dword_1400090A8, 1 and jz loc_1400017D2: 这是一种检查,用于确认字符串是否已经被解密。如果 dword_1400090A8 是 1,它将跳转到末尾,实际上只解密一次字符串。这是字符串解密例程中常见的优化方法。 解密循环(loc_14000177A): mov eax, [rsp+28h+var_4]: eax 获取当前循环索引 i mov rcx, [rsp+28h+var_18]: rcx 获取目标缓冲区地址。 mov rdx, [rsp+28h+var_10]: rdx 获取"密钥"材料的地址(来自 unk_140005050 + 0x17)。 mov [rsp+28h+var_24], eax: 将 i 存储到 var_24 中 movsxd r8, eax: 将 i 符号扩展到 r8 mov r8b, [rdx+r8]: 从"密钥"材料(rdx)的偏移量 i 处加载一个字节到 r8b mov r9d, 17h: r9d gets 0x17 (23 decimal), which is likely the key length or a modulo value. xor edx, edx, div r9d: This calculates i % 23. The remainder is in edx. mov eax, [rsp+28h+var_24]: eax gets i again. mov r9d, edx: r9d gets i % 23. mov rdx, [rsp+28h+var_20]: rdx 获取 实际加密字符串 的地址(来自 unk_140005050 + 0x1E)。 movsxd r9, r9d: 将 i % 23 进行符号扩展到 r9 xor r8b, [rdx+r9]: 这是核心解密操作! 它将加载自"密钥"(位于 rdx+i)的字节与来自 加密数据 (位于 rdx + (i % 23))的字节进行异或操作。 等等,xor r8b, [rdx+r9] 正在使用 rdx(指向 unk_140005050 + 0x1E)和 r9(即 i % 0x17)。这意味着用于异或操作的加密字符串字节取自 同一块 的 unk_140005050,但通过 i % 0x17 相对于 加密数据 的基址进行索引。这看起来像是一种重复密钥异或密码。 movsxd rdx, eax: 将 i 符号扩展到 rdx mov [rcx+rdx], r8b: 将解密后的字节(r8b)存储到目标缓冲区(rcx)的偏移量 i 处 add eax, 1: 自增 i cmp eax, 6: 比较 i 与 6。 mov [rsp+28h+var_4], eax: 存储更新的 i jnz loc_14000177A: 如果 i 不是 6,则再次循环。 mov cs:dword_1400090A8, 1: 将标志设置为表示解密完成。 尾声: add rsp, 28h, retn 清理堆栈并返回。 解密算法概述: 解密似乎是一种重复密钥 XOR 密码 ,具有特定的结构: 该函数解密6个字节。 用于 XOR 操作的"密钥"位于 unk_140005050 + 0x17 实际加密数据从 unk_140005050 + 0x1E 开始 对于每个字节 i(从 0 到 5): 从 unk_140005050 + 0x17 + i 的位置取出一个字节 K_i 从 E_i 中取出一个字节 unk_140005050 + 0x1E + (i % 0x17)。(这个 i % 0x17 有点不寻常;通常情况下,对于重复密钥,它会是 key_index % key_length)。 解密后的字节 D_i = K_i XOR E_i 这个 D_i 存储在目标缓冲区 destination_buffer + i 的位置 让我们尝试解密 "Sum: ": 首先,让我们识别数据: unk_140005050 从 0x140005050 开始: 76 F8 8C 22 C8 62 A3 02 56 F4 69 06 CC 61 17 7C B6 0E BF 04 B4 81 2B 89 AC D3 0C 84 E3 AF 9F 8A 70 3B B3 0F E6 70 67 7E B6 E4 49 6B B1 DB BD 0A B1 65 7C 0E 25 CC FF 1D 01 93 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 所以 var_20(加密源指针)是 unk_140005050 + 0x1E 所以 var_10(键指针)是 unk_140005050 + 0x1E + 0x17 = unk_140005050 + 0x35 让我们列出相关字节: 9F 8A 70 3B B3 0F(这是前 6 个字节) CC FF 1D 01 93 0F(这是循环中使用的密钥材料的前 6 个字节) 现在,让我们对每个字符执行 XOR 操作: 操作是:DecryptedByte[i] = KeyByte[i] XOR EncryptedByte[i % 0x17] xor r8b, [rdx+r9] -> r8b(KeyMaterial[i])与 EncryptedData[i % 0x17] 进行异或 所以公式是 DecryptedByte[i] = KeyMaterial[0x35 + i] XOR EncryptedData[0x1E + (i % 0x17)] 让我们调整模运算符(0x17 = 23)。由于我们只循环 6 次,i % 0x17 将只是 i 所以,DecryptedByte[i] = (unk_140005050 + 0x35 + i) XOR (unk_140005050 + 0x1E + i) 让我们重新提取字节: 用于 XOR 的关键材料字节(来自 unk_140005050 + 0x35): CC FF 1D 01 93 0F 用于异或的加密数据字节(来自 unk_140005050 + 0x1E): 9F 8A 70 3B B3 0F 现在,进行异或操作: i = 0: CC XOR 9F = 33 ('3') i = 1: FF XOR 8A = 75 ('u') i = 2: 1D XOR 70 = 6D ('m') i = 3: 01 XOR 3B = 3A (':') i = 4: 93 XOR B3 = 20 (' ') i = 5: 0F XOR 0F = 00 (空终止符) 解密后的字符串是 "Sum: "。 这证实了 _Lgoron_decrypt_string_0 成功解密了混淆数据中的字符串 "Sum: "。 这是解密函数的概念性 C++代码: code C++ #include <iostream> #include <string> #include <vector> // Assuming these are external arrays of bytes from the .rdata section extern unsigned char unk_140005050[]; // Contains both encrypted data and key material extern int dword_1400090A8; // A flag to check if already decrypted void _Lgoron_decrypt_string_0(char* destination_buffer, const unsigned char* encrypted_source_base) { // Optimization: if already decrypted, just return if (dword_1400090A8 == 1) { return; } // encrypted_source_base corresponds to unk_140005050 + 0x1E // The key material starts at unk_140005050 + 0x35 const unsigned char* key_material_base = unk_140005050 + 0x35; // Derived from rdx+0x17 in assembly for (int i = 0; i < 6; ++i) { // Loop 6 times unsigned char key_byte = key_material_base[i]; // Byte from key material unsigned char encrypted_byte = encrypted_source_base[i]; // Byte from encrypted data (i % 0x17 simplifies to i for 0-5) destination_buffer[i] = key_byte ^ encrypted_byte; } destination_buffer[6] = '\0'; // Null-terminate the string dword_1400090A8 = 1; // Mark as decrypted } // Example usage (assuming the data from .rdata is available) // This is illustrative and won't compile without the actual data /* unsigned char unk_140005050[] = { 0x76, 0xF8, 0x8C, 0x22, 0xC8, 0x62, 0xA3, 0x02, 0x56, 0xF4, 0x69, 0x06, 0xCC, 0x61, 0x17, 0x7C, 0xB6, 0x0E, 0xBF, 0x04, 0xB4, 0x81, 0x2B, 0x89, 0xAC, 0xD3, 0x0C, 0x84, 0xE3, 0xAF, 0x9F, 0x8A, // 0x1E is here (9F) 0x70, 0x3B, 0xB3, 0x0F, 0xE6, 0x70, 0x67, 0x7E, 0xB6, 0xE4, 0x49, 0x6B, 0xB1, 0xDB, 0xBD, 0x0A, 0xB1, 0x65, 0x7C, 0x0E, 0x25, 0xCC, 0xFF, 0x1D, 0x01, 0x93, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x35 is here (CC) // ... rest of the data }; char unk_1400090A0[7]; // Buffer for "Sum: " + null terminator int dword_1400090A8 = 0; // Initialize as not decrypted int main() { // ... setup variables ... int var_8 = 0xF; int var_C = 0x1B; int var_10_result = var_8 + var_C; // 42 // Call the decryption function _Lgoron_decrypt_string_0(unk_1400090A0, unk_140005050 + 0x1E); std::cout << unk_1400090A0 << var_10_result << std::endl; // Prints "Sum: 42" return 0; } */
0x9F, 0x8A, 0x70, 0x3B, 0xB3 和 0xCC, 0xFF, 0x1D, # 偏移0x30-0x37 0x01, 0x93 异或这两组数据
异或结果(0x53, 0x75, 0x6D, 0x3A, 0x20),这些十六进制值转换为ASCII字符后如下:
-
0x53 → 'S'
-
0x75 → 'u'
-
0x6D → 'm'
-
0x3A → ':'
-
0x20 → 空格
因此,完整的ASCII字符串是:"Sum: "。
因此我们知道,解密的核心算法是xor,程序会将加密后的数据和密钥保存在一起,随机存放。在运行时用偏移取出对应的数据。
解密脚本
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ OLLVM字符串解密工具 - 精简版 专门用于解密OLLVM混淆的字符串数据 """ def decrypt_ollvm_string(): """ 解密OLLVM加密的字符串"Sum: " """ # 完整的加密数据 - 从汇编代码unk_140005050提取的原始字节数据 full_encrypted_data = bytes([ 0x76, 0xF8, 0x8C, 0x22, 0xC8, 0x62, 0xA3, 0x02, # 偏移0x00-0x07 0x56, 0xF4, 0x69, 0x06, 0xCC, 0x61, 0x17, 0x7C, # 偏移0x08-0x0F 0xB6, 0x0E, 0xBF, 0x04, 0xB4, 0x81, 0x2B, 0x89, # 偏移0x10-0x17 0xAC, 0xD3, 0x0C, 0x84, 0xE3, 0xAF, 0x9F, 0x8A, # 偏移0x18-0x1F 0x70, 0x3B, 0xB3, 0x0F, 0xE6, 0x70, 0x67, 0x7E, # 偏移0x20-0x27 0xB6, 0xE4, 0x49, 0x6B, 0xB1, 0xDB, 0xBD, 0x0A, # 偏移0x28-0x2F 0xB1, 0x65, 0x7C, 0x0E, 0x25, 0xCC, 0xFF, 0x1D, # 偏移0x30-0x37 0x01, 0x93, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, # 偏移0x38-0x3F ]) # main函数传递unk_140005050+0x1E给解密函数,所以加密数据从偏移0x1E开始 encrypted_data = full_encrypted_data[0x1E:] print(f"encrypted_data: {encrypted_data}") # 解密函数内执行add rdx, 0x17,所以密钥从传入数据的0x17偏移开始 key_data = encrypted_data[0x17:0x17 + 23] # 提取23字节密钥 print(f"key_data: {key_data}") # 加密字符串数据就在传入的数据开头(偏移0x1E处) encrypted_string = encrypted_data[:5] # "Sum: "共5字节 # 执行XOR解密算法 decrypted_chars = [] # 存储解密后的字符 for i in range(5): # 遍历5个字节 key_byte = key_data[i] # 获取对应位置的密钥字节 encrypted_byte = encrypted_string[i] # 获取对应位置的加密字节 decrypted_byte = key_byte ^ encrypted_byte # XOR运算解密 decrypted_chars.append(chr(decrypted_byte)) # 转换为字符并添加到结果 # 拼接解密后的字符串 decrypted_string = ''.join(decrypted_chars) return decrypted_string if __name__ == "__main__": # 执行解密并输出结果 result = decrypt_ollvm_string() # 调用解密函数 print(f"解密结果: {result}") # 输出解密得到的字符串
开启所有的混淆参数
; =============== S U B R O U T I N E ======================================= .text:00000001400016D0 .text:00000001400016D0 ; Attributes: bp-based frame .text:00000001400016D0 .text:00000001400016D0 ; int __fastcall main(int argc, const char **argv, const char **envp) .text:00000001400016D0 public main .text:00000001400016D0 main proc near ; CODE XREF: __tmainCRTStartup+1E7↑p .text:00000001400016D0 ; DATA XREF: .pdata:000000014000709C↓o .text:00000001400016D0 .text:00000001400016D0 var_30 = qword ptr -30h .text:00000001400016D0 var_28 = qword ptr -28h .text:00000001400016D0 var_20 = qword ptr -20h .text:00000001400016D0 var_18 = qword ptr -18h .text:00000001400016D0 var_10 = qword ptr -10h .text:00000001400016D0 var_8 = qword ptr -8 .text:00000001400016D0 .text:00000001400016D0 push rbp .text:00000001400016D1 sub rsp, 30h .text:00000001400016D5 lea rbp, [rsp+30h] .text:00000001400016DA sub rsp, 20h .text:00000001400016DE call __main .text:00000001400016E3 add rsp, 20h .text:00000001400016E7 mov eax, cs:x .text:00000001400016ED mov ecx, cs:y .text:00000001400016F3 mov edx, eax .text:00000001400016F5 sub edx, 1 .text:00000001400016F8 imul eax, edx .text:00000001400016FB and eax, 1 .text:00000001400016FE cmp eax, 0 .text:0000000140001701 setz al .text:0000000140001704 cmp ecx, 0Ah .text:0000000140001707 setl cl .text:000000014000170A or al, cl .text:000000014000170C test al, 1 .text:000000014000170E jnz loc_140001719 .text:0000000140001714 jmp loc_140001987 .text:0000000140001719 ; --------------------------------------------------------------------------- .text:0000000140001719 .text:0000000140001719 loc_140001719: ; CODE XREF: main+3E↑j .text:0000000140001719 ; main:loc_140001987↓j .text:0000000140001719 mov eax, cs:x .text:000000014000171F mov ecx, cs:y .text:0000000140001725 mov edx, eax .text:0000000140001727 sub edx, 1 .text:000000014000172A imul eax, edx .text:000000014000172D and eax, 1 .text:0000000140001730 cmp eax, 0 .text:0000000140001733 setz al .text:0000000140001736 cmp ecx, 0Ah .text:0000000140001739 setl cl .text:000000014000173C or al, cl .text:000000014000173E test al, 1 .text:0000000140001740 jnz loc_14000174B .text:0000000140001746 jmp loc_140001987 .text:000000014000174B ; --------------------------------------------------------------------------- .text:000000014000174B .text:000000014000174B loc_14000174B: ; CODE XREF: main+70↑j .text:000000014000174B mov eax, cs:x .text:0000000140001751 mov ecx, cs:y .text:0000000140001757 mov edx, eax .text:0000000140001759 sub edx, 1 .text:000000014000175C imul eax, edx .text:000000014000175F and eax, 1 .text:0000000140001762 cmp eax, 0 .text:0000000140001765 setz al .text:0000000140001768 cmp ecx, 0Ah .text:000000014000176B setl cl .text:000000014000176E or al, cl .text:0000000140001770 test al, 1 .text:0000000140001772 jnz loc_14000177D .text:0000000140001778 jmp loc_14000186A .text:000000014000177D ; --------------------------------------------------------------------------- .text:000000014000177D .text:000000014000177D loc_14000177D: ; CODE XREF: main+A2↑j .text:000000014000177D ; main:loc_140001982↓j .text:000000014000177D mov eax, 10h .text:0000000140001782 mov [rbp+var_10], rax .text:0000000140001786 call ___chkstk_ms .text:000000014000178B sub rsp, rax .text:000000014000178E mov rax, [rbp+var_10] .text:0000000140001792 mov r8, rsp .text:0000000140001795 call ___chkstk_ms .text:000000014000179A sub rsp, rax .text:000000014000179D mov rax, [rbp+var_10] .text:00000001400017A1 mov rcx, rsp .text:00000001400017A4 call ___chkstk_ms .text:00000001400017A9 sub rsp, rax .text:00000001400017AC mov rax, [rbp+var_10] .text:00000001400017B0 mov rdx, rsp .text:00000001400017B3 call ___chkstk_ms .text:00000001400017B8 sub rsp, rax .text:00000001400017BB mov rax, rsp .text:00000001400017BE mov [rbp+var_8], rax .text:00000001400017C2 mov dword ptr [r8], 0 .text:00000001400017C9 mov dword ptr [rcx], 0Fh .text:00000001400017CF mov dword ptr [rdx], 1Bh .text:00000001400017D5 mov ecx, [rcx] .text:00000001400017D7 mov edx, [rdx] .text:00000001400017D9 sub ecx, 0ACD86B94h .text:00000001400017DF add ecx, edx .text:00000001400017E1 add ecx, 0ACD86B94h .text:00000001400017E7 mov [rax], ecx .text:00000001400017E9 mov rcx, cs:_refptr__ZSt4cout .text:00000001400017F0 lea rdx, aSum ; "Sum: " .text:00000001400017F7 sub rsp, 20h .text:00000001400017FB call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::ostream &,char const*) .text:0000000140001800 add rsp, 20h .text:0000000140001804 mov rcx, rax .text:0000000140001807 mov rax, [rbp+var_8] .text:000000014000180B mov edx, [rax] .text:000000014000180D sub rsp, 20h .text:0000000140001811 call _ZNSolsEi ; std::ostream::operator<<(int) .text:0000000140001816 add rsp, 20h .text:000000014000181A mov rcx, rax .text:000000014000181D lea rdx, _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ ; std::endl<char,std::char_traits<char>>(std::ostream &) .text:0000000140001824 sub rsp, 20h .text:0000000140001828 call _ZNSolsEPFRSoS_E ; std::ostream::operator<<(std::ostream & (*)(std::ostream &)) .text:000000014000182D add rsp, 20h .text:0000000140001831 mov eax, cs:x .text:0000000140001837 mov ecx, cs:y .text:000000014000183D mov edx, eax .text:000000014000183F sub edx, 1 .text:0000000140001842 imul eax, edx .text:0000000140001845 and eax, 1 .text:0000000140001848 cmp eax, 0 .text:000000014000184B setz al .text:000000014000184E cmp ecx, 0Ah .text:0000000140001851 setl cl .text:0000000140001854 or al, cl .text:0000000140001856 test al, 1 .text:0000000140001858 jnz loc_140001863 .text:000000014000185E jmp loc_14000186A .text:0000000140001863 ; --------------------------------------------------------------------------- .text:0000000140001863 .text:0000000140001863 loc_140001863: ; CODE XREF: main+188↑j .text:0000000140001863 xor eax, eax .text:0000000140001865 mov rsp, rbp .text:0000000140001868 pop rbp .text:0000000140001869 retn .text:000000014000186A ; --------------------------------------------------------------------------- .text:000000014000186A .text:000000014000186A loc_14000186A: ; CODE XREF: main+A8↑j .text:000000014000186A ; main+18E↑j .text:000000014000186A mov eax, cs:x .text:0000000140001870 mov ecx, cs:y .text:0000000140001876 mov edx, eax .text:0000000140001878 sub edx, 1 .text:000000014000187B imul eax, edx .text:000000014000187E and eax, 1 .text:0000000140001881 cmp eax, 0 .text:0000000140001884 setz al .text:0000000140001887 cmp ecx, 0Ah .text:000000014000188A setl cl .text:000000014000188D or al, cl .text:000000014000188F test al, 1 .text:0000000140001891 jnz loc_14000189C .text:0000000140001897 jmp loc_14000198C .text:000000014000189C ; --------------------------------------------------------------------------- .text:000000014000189C .text:000000014000189C loc_14000189C: ; CODE XREF: main+1C1↑j .text:000000014000189C ; main+370↓j .text:000000014000189C mov eax, 10h .text:00000001400018A1 mov [rbp+var_20], rax .text:00000001400018A5 call ___chkstk_ms .text:00000001400018AA sub rsp, rax .text:00000001400018AD mov rax, [rbp+var_20] .text:00000001400018B1 mov r8, rsp .text:00000001400018B4 call ___chkstk_ms .text:00000001400018B9 sub rsp, rax .text:00000001400018BC mov rax, [rbp+var_20] .text:00000001400018C0 mov rcx, rsp .text:00000001400018C3 call ___chkstk_ms .text:00000001400018C8 sub rsp, rax .text:00000001400018CB mov rax, [rbp+var_20] .text:00000001400018CF mov rdx, rsp .text:00000001400018D2 call ___chkstk_ms .text:00000001400018D7 sub rsp, rax .text:00000001400018DA mov rax, rsp .text:00000001400018DD mov [rbp+var_18], rax .text:00000001400018E1 mov dword ptr [r8], 0 .text:00000001400018E8 mov dword ptr [rcx], 0Fh .text:00000001400018EE mov dword ptr [rdx], 1Bh .text:00000001400018F4 mov ecx, [rcx] .text:00000001400018F6 mov edx, [rdx] .text:00000001400018F8 sub ecx, 0ACD86B94h .text:00000001400018FE add ecx, edx .text:0000000140001900 add ecx, 0ACD86B94h .text:0000000140001906 mov [rax], ecx .text:0000000140001908 mov rcx, cs:_refptr__ZSt4cout .text:000000014000190F lea rdx, aSum ; "Sum: " .text:0000000140001916 sub rsp, 20h .text:000000014000191A call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::ostream &,char const*) .text:000000014000191F add rsp, 20h .text:0000000140001923 mov rcx, rax .text:0000000140001926 mov rax, [rbp+var_18] .text:000000014000192A mov edx, [rax] .text:000000014000192C sub rsp, 20h .text:0000000140001930 call _ZNSolsEi ; std::ostream::operator<<(int) .text:0000000140001935 add rsp, 20h .text:0000000140001939 mov rcx, rax .text:000000014000193C lea rdx, _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ ; std::endl<char,std::char_traits<char>>(std::ostream &) .text:0000000140001943 sub rsp, 20h .text:0000000140001947 call _ZNSolsEPFRSoS_E ; std::ostream::operator<<(std::ostream & (*)(std::ostream &)) .text:000000014000194C add rsp, 20h .text:0000000140001950 mov eax, cs:x .text:0000000140001956 mov ecx, cs:y .text:000000014000195C mov edx, eax .text:000000014000195E sub edx, 1 .text:0000000140001961 imul eax, edx .text:0000000140001964 and eax, 1 .text:0000000140001967 cmp eax, 0 .text:000000014000196A setz al .text:000000014000196D cmp ecx, 0Ah .text:0000000140001970 setl cl .text:0000000140001973 or al, cl .text:0000000140001975 test al, 1 .text:0000000140001977 jnz loc_140001982 .text:000000014000197D jmp loc_14000198C .text:0000000140001982 ; --------------------------------------------------------------------------- .text:0000000140001982 .text:0000000140001982 loc_140001982: ; CODE XREF: main+2A7↑j .text:0000000140001982 jmp loc_14000177D .text:0000000140001987 ; --------------------------------------------------------------------------- .text:0000000140001987 .text:0000000140001987 loc_140001987: ; CODE XREF: main+44↑j .text:0000000140001987 ; main+76↑j .text:0000000140001987 jmp loc_140001719 .text:000000014000198C ; --------------------------------------------------------------------------- .text:000000014000198C .text:000000014000198C loc_14000198C: ; CODE XREF: main+1C7↑j .text:000000014000198C ; main+2AD↑j .text:000000014000198C mov eax, 10h .text:0000000140001991 mov [rbp+var_30], rax .text:0000000140001995 call ___chkstk_ms .text:000000014000199A sub rsp, rax .text:000000014000199D mov rax, [rbp+var_30] .text:00000001400019A1 mov r8, rsp .text:00000001400019A4 call ___chkstk_ms .text:00000001400019A9 sub rsp, rax .text:00000001400019AC mov rax, [rbp+var_30] .text:00000001400019B0 mov rcx, rsp .text:00000001400019B3 call ___chkstk_ms .text:00000001400019B8 sub rsp, rax .text:00000001400019BB mov rax, [rbp+var_30] .text:00000001400019BF mov rdx, rsp .text:00000001400019C2 call ___chkstk_ms .text:00000001400019C7 sub rsp, rax .text:00000001400019CA mov rax, rsp .text:00000001400019CD mov [rbp+var_28], rax .text:00000001400019D1 mov dword ptr [r8], 0 .text:00000001400019D8 mov dword ptr [rcx], 0Fh .text:00000001400019DE mov dword ptr [rdx], 1Bh .text:00000001400019E4 mov ecx, [rcx] .text:00000001400019E6 mov edx, [rdx] .text:00000001400019E8 sub ecx, 0ACD86B94h .text:00000001400019EE add ecx, edx .text:00000001400019F0 add ecx, 0ACD86B94h .text:00000001400019F6 mov [rax], ecx .text:00000001400019F8 mov rcx, cs:_refptr__ZSt4cout .text:00000001400019FF lea rdx, aSum ; "Sum: " .text:0000000140001A06 sub rsp, 20h .text:0000000140001A0A call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::ostream &,char const*) .text:0000000140001A0F add rsp, 20h .text:0000000140001A13 mov rcx, rax .text:0000000140001A16 mov rax, [rbp+var_28] .text:0000000140001A1A mov edx, [rax] .text:0000000140001A1C sub rsp, 20h .text:0000000140001A20 call _ZNSolsEi ; std::ostream::operator<<(int) .text:0000000140001A25 add rsp, 20h .text:0000000140001A29 mov rcx, rax .text:0000000140001A2C lea rdx, _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ ; std::endl<char,std::char_traits<char>>(std::ostream &) .text:0000000140001A33 sub rsp, 20h .text:0000000140001A37 call _ZNSolsEPFRSoS_E ; std::ostream::operator<<(std::ostream & (*)(std::ostream &)) .text:0000000140001A3C add rsp, 20h .text:0000000140001A40 jmp loc_14000189C .text:0000000140001A40 main endp ; sp-analysis failed .text:0000000140001A40 .text:0000000140001A40 ; --------------------------------------------------------------------------- .text:0000000140001A45 algn_140001A45: ; DATA XREF: .pdata:000000014000709C↓o .text:0000000140001A45 align 8 .text:0000000140001A48 ; [00000006 BYTES: COLLAPSED FUNCTION std::operator<<<std::char_traits<char>>(std::ostream &,char const*). PRESS CTRL-NUMPAD+ TO EXPAND] .text:0000000140001A4E align 10h .text:0000000140001A50 ; [00000006 BYTES: COLLAPSED FUNCTION std::endl<char,std::char_traits<char>>(std::ostream &). PRESS CTRL-NUMPAD+ TO EXPAND] .text:0000000140001A56 align 8 .text:0000000140001A58 ; [00000006 BYTES: COLLAPSED FUNCTION std::ostream::operator<<(int). PRESS CTRL-NUMPAD+ TO EXPAND] .text:0000000140001A5E align 20h .text:0000000140001A60 #include <iostream> using namespace std; // 全局变量声明 int x; int y; int main() { // 主循环条件:((x * (x - 1)) & 1) == 0 || y < 10 while (((x * (x - 1)) & 1) == 0 || y < 10) { // 内层循环条件:((x * (x - 1)) & 1) == 0 || y < 10 while (((x * (x - 1)) & 1) == 0 || y < 10) { // 第三层循环条件:((x * (x - 1)) & 1) == 0 || y < 10 while (((x * (x - 1)) & 1) == 0 || y < 10) { // 检查条件,如果满足则执行计算和输出 if (((x * (x - 1)) & 1) == 0 || y < 10) { // 在栈上分配局部变量 int var1 = 0; int var2 = 15; // 0Fh int var3 = 27; // 1Bh // 执行加密/解密计算 // sub ecx, 0ACD86B94h; add ecx, edx; add ecx, 0ACD86B94h // 这实际上等价于:result = var2 + var3 int result = var2 + var3; // 输出结果 cout << "Sum: " << result << endl; // 检查退出条件 if (((x * (x - 1)) & 1) == 0 || y < 10) { return 0; } } // 检查是否继续第三层循环 if (!(((x * (x - 1)) & 1) == 0 || y < 10)) { break; } } // 检查是否继续内层循环 if (!(((x * (x - 1)) & 1) == 0 || y < 10)) { break; } } // 外层循环的另一个分支 if (!(((x * (x - 1)) & 1) == 0 || y < 10)) { // 在栈上分配局部变量 int var1 = 0; int var2 = 15; // 0Fh int var3 = 27; // 1Bh // 执行相同的加密/解密计算 int result = var2 + var3; // 输出结果 cout << "Sum: " << result << endl; // 继续循环 continue; } } return 0; } /* 反编译说明: 1. 原汇编代码包含复杂的嵌套循环和条件判断 2. 核心条件是:((x * (x - 1)) & 1) == 0 || y < 10 - x * (x - 1) & 1 检查x是否为偶数或x-1是否为偶数 - y < 10 检查y是否小于10 3. 程序在多个位置执行相同的计算:15 + 27 = 42 4. 使用了字符串加密技术,通过减去和加上0ACD86B94h来混淆计算,但实际效果是不变的 5. 程序使用std::cout输出"Sum: 42"和换行符 6. 程序结构表明这可能是一个混淆过的简单计算程序 */
基于对反编译代码的分析,这个程序使用了多种LLVM混淆技术:
### 1. 控制流平坦化 (Control Flow Flattening)
- 特征 :复杂的嵌套循环和条件判断
- 表现 :三层嵌套的while循环,所有使用相同的条件
- 目的 :将原本简单的线性执行流程转换为复杂的控制流图
### 2. 虚假控制流 (Bogus Control Flow)
- 特征 :多个永远不会执行的代码分支
- 表现 :
```
if (!(((x * (x - 1)) & 1) == 0 || y < 10)) {
// 这个分支永远不会执行,因为条件始终为真
}
```
- 目的 :增加代码复杂度,误导逆向分析
### 3. 指令替换 (Instruction Substitution)
- 特征 :使用复杂的数学运算替代简单操作
- 表现 :
```
// 原始汇编中的加密操作:
// sub ecx, 0ACD86B94h; add ecx, edx; add ecx, 0ACD86B94h
// 实际等价于:result = var2 + var3
```
- 目的 :用恒等变换隐藏真实的计算逻辑
### 4. 常量加密 (Constant Encryption)
- 特征 :使用魔数进行加密/解密操作
- 表现 :魔数 0xACD86B94 用于混淆常量值
- 原理 : (value - key) + other + key = value + other
- 目的 :隐藏程序中的常量值
### 5. 死代码插入 (Dead Code Insertion)
- 特征 :插入不影响程序逻辑的无用代码
- 表现 : int var1 = 0; 变量被声明但从未使用
- 目的 :增加代码体积,干扰分析
### 6. 循环展开混淆 (Loop Unrolling Obfuscation)
- 特征 :将简单的执行逻辑包装在复杂的循环结构中
- 表现 :本来只需执行一次的代码被放在多层循环中
- 目的 :隐藏程序的真实执行次数和逻辑
### 对应的LLVM Pass:
1. 1.
-fla (Control Flow Flattening)
2. 2.
-bcf (Bogus Control Flow)
3. 3.
-sub (Instruction Substitution)
4. 4.
-sobf (String Obfuscation)
5. 5.
-split (Basic Block Splitting)
### 混淆效果评估:
- 复杂度 :★★★★☆ (高度复杂的控制流)
- 隐蔽性 :★★★★☆ (有效隐藏了简单的加法运算)
- 性能影响 :★★☆☆☆ (轻微的性能开销)
- 逆向难度 :★★★★☆ (需要仔细分析才能理解真实逻辑)
总结:这是一个典型的LLVM-Obfuscator处理过的代码,主要使用了控制流平坦化和指令替换技术,将简单的 15 + 27 = 42 计算隐藏在复杂的控制流和加密操作中。