sondt99/ObfuGuard

GitHub: sondt99/ObfuGuard

基于控制流平坦化和无用代码注入技术,为 Windows PE 二进制文件提供二进制级混淆保护的研究型工具。

Stars: 6 | Forks: 0

# ObfuGuard ## 要求 - 库:asmjit、zydis、keystone、capstone、lief ``` vcpkg install capstone:x64-windows vcpkg install keystone:x64-windows vcpkg install lief:x64-windows vcpkg install asmjit:x64-windows vcpkg install zydis:x64-windows ``` - 待混淆程序的 PE 文件和 PDB 文件。 ## 控制流平坦化 ### 技术 - 遍历函数的所有指令以创建 block。Block 从条件跳转指令的目标点开始构建。Block 的末尾将是自然终止点:要么是非函数调用的跳转指令,要么是 ret 指令,或者是条件跳转指令的目标点。 - 遍历所有 block,如果一个 block 的最后一条指令是条件跳转,则将该条件跳转所跳转到的 block 的位置设置到 block_dst 中。将当前 block 的 next_block 属性设置为 current_block_id+1。 - 打乱包含 block 的向量。使用 rax 作为状态变量,通过 dispatcher 转移流程,并创建控制结构以执行控制流平坦化。 ### PoC ![平坦化之前](/PoC/cff/before.png) - 在平坦化之前,该函数具有如上所示的基本流结构:A->B 或 C -> D。这种控制流平坦化的目标是创建一个控制结构,通过 dispatcher 在 block A、B、C、D 之间转移流程,而不是原来的线性流执行,同时保持效果不变。要实现的目标如下: ![目标](/PoC/cff/target.png) - 执行控制流平坦化后,将获取到的整个 PE 文件加载到 IDA 软件中以检查结果。 ![平坦化后的 CFG](/PoC/cff/CFG_after.png) - 根据获取到的控制流图(CFG),程序结构通过状态变量(即 rax 寄存器)进行了转换,并不断通过指令 `cmp eax,0`、`cmp eax,2`、`cmp eax,3`、`cmp eax,1` 转移流程。追踪与 eax 对应的流,如预期般指向了 block A、block B、block C 和 block D。 - 继续通过反编译获得的伪代码来验证程序的有效性: ``` __int64 __fastcall sub_1400292F5(char a1) { __int64 v5; // rax _QWORD *v6; // rbp __int64 v7; // rdi int v8; // eax _QWORD v15[26]; // [rsp-D0h] [rbp-1A8h] BYREF __int64 v16; // [rsp+0h] [rbp-D8h] char v17; // [rsp+10h] [rbp-C8h] v16 = v5; __asm { pushf } v8 = 0; while ( 1 ) { while ( v8 == 2 ) { __asm { popf } v16 = sub_140011217(); __asm { pushf } v8 = 3; } if ( v8 ) break; __asm { popf } v17 = a1; v16 = (__int64)v6; v15[25] = v7; v6 = v15; ((void (__fastcall *)(void *))sub_1400113F7)(&unk_140023076); ((void (__fastcall *)(_QWORD, const char *))sub_140011087)(std::cout, "Block A\n"); _CF = 0; _OF = 0; _ZF = v17 == 0; _SF = 0; if ( v17 ) { __asm { pushf } v8 = 1; } else { __asm { pushf } v8 = 2; } } if ( v8 == 3 ) { __asm { popf } return v16; } else { __asm { popf } return sub_1400112C1(); } } ``` - 根据获取到的伪代码,也很容易看出程序结构发生了变化。通过状态变量 v8(代表 rax),if 语句会检查状态变量的值,以将流程转移到 block A、sub_1400112c1 ()、sub_140011217() 并返回。沿着这个流程追踪,结果显示该程序结构通过变量 v8 如预期般将流程转移到了 block A、block B、block C 和 block D。 ## 无用代码注入 ### 技术 - 背景技术 - Trampoline:trampoline 技术通过间接跳转函数将程序流程转移到需要执行的源代码部分。 - 首先,为了给插入无用指令创造空间,程序将使用 trampoline 技术将所选函数的原始函数重定位到一个新创建的 section 中,以执行无用代码插入。将原始函数重定位到新的 section 将使程序能够更多地干预对函数的插入操作,而不会影响二进制文件其余部分的结构。 - 程序创建一个新的空 section 作为内存区域,待混淆的函数将被重定位到这里。将所选函数中的所有指令读入 buffer;将最初所选函数的内存区域转换为指向新创建 section 区域开头的间接跳转函数(trampoline 技术)。 - 程序遍历 buffer 并将代码从 buffer 写入新创建的 section 中。对于从 buffer 写入的每条指令,都会在其后写入一条或多条从程序无用代码集合中随机选择的无用代码指令。完成后,原始函数就被插入到了无用代码段之间。 ### PoC ![无用代码插入之前](https://static.pigsec.cn/wp-content/uploads/repos/cas/58/5817697cb4e807791f6771aa68b0199b4081940444b8d189db83dee6e73828f2.png) - 在执行无用代码插入之前,函数二进制文件位于 RVA 0x12380 处。该函数的所有指令都非常清晰且逻辑连续。无用代码插入的目的是,在插入无用代码后,该函数的原始二进制指令之间将被插入无用指令。这些指令会增加软件逆向工程的难度,但对整体程序逻辑完全无害。 - 执行无用代码插入后,将获取到的 PE 加载到 IDA 软件中以验证有效性: ![重定位原始 RVA 之后](https://static.pigsec.cn/wp-content/uploads/repos/cas/ae/aebbdca75c09783c52c326024d29b01d95e5c150948fb080fba49fde88c1a041.png) - 在所选函数的原始 RVA 处,可以看到该函数中的代码段仅仅是一条指向新 section 的跳转指令。根据 trampoline 技术,该函数的所有剩余部分都被 nop 指令修补了。追踪此 jmp 指令表明,原始函数代码已完全按照 trampoline 技术成功重定位到了新的 section 区域。 ![二进制文件之后](https://static.pigsec.cn/wp-content/uploads/repos/cas/82/82c642fdc7c028b1ce18ba0706edde946c463f3b2bdafcbcc96c17dbae16db7c.png) - 在原始函数被重定位到的 section 区域中,可以看到原始函数的二进制文件已经被插入到许多不影响整体程序逻辑的无用指令之间,例如 `mov rdx, rdx`、`lea rbx, rbx` 等。这些指令不影响程序逻辑,但会干扰分析人员对二进制代码的理解。获取到的文件的混淆效果符合预期。
标签:C++, PE文件处理, 二进制混淆, 代码保护, 控制流平坦化, 数据擦除, 逆向工程防御