Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
Mergen — 通过使用LLVM IR和解析汇编进行优化实现的反混淆。 | Kitploit
工具/GitHubGitHub/nac-l/mergen
静态分析动态分析 (沙盒)逆向工程二进制分析二进制利用
GitHubnac-l/mergen

Mergen

通过使用LLVM IR和解析汇编进行优化实现的反混淆。

查看仓库
87894364个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

项目概述:

Mergen是一个用于将汇编代码转换为LLVM中间表示(IR)的工具。 该工具旨在:

  • 对混淆的二进制代码进行反混淆或反虚拟化
  • 提升逆向工程流程,使其更高效、更有效,尤其适用于复杂软件系统。

构建与运行指南

要构建并运行该项目,请查看docs/BUILDING.md。

重写基准门控

重写工作应保持基准回归门控为绿色。门控会构建精选的PE样本,运行lifter,并验证提升后的IR输出。

  • 工作流文档:docs/REWRITE_BASELINE.md
  • 一键门控:scripts\\rewrite\\run.cmd

核心目标:

  • 反混淆

  • 反虚拟化

  • 优化

它是如何工作的?

我们对目标进行符号执行(或符号提升),这里的思路不是提升单个指令,而是提升整个函数。我们不期望每条指令或每个基本块每次都表现相同,而是将其视为每次可能且确实服务于不同目的。我们尽量使生成的IR保持简单并尽可能优化。我们还有与通常编译器不同的需求。我们利用分析来评估控制流。我们不能依赖LLVM完成所有分析,因为它们是为不同目标创建的,并且对我们的用例可能不是最优的。

image

示例

以下是实际示例,说明Mergen如何解决虚拟化程序的问题。

  1. VMProtect
  2. 分支/跳转表
  3. Themida 3.1.6.0 LION64 (Red)

示例 #1 (VMProtect)

这是我们的目标程序

root@kitploit:~
struct test {
    int a;
    int b;
    int c;
};

int maths(test a, int b, int c) {
        return a.a  + b - c;
}

image

image

VMProtect设置,所有选项均关闭,我们在超(ultra)设置下虚拟化该函数。(测试版本 3.4.0-3.6.0 3.8.1)

image

image

这里,我们运行mergen。第一个参数是文件名,第二个参数是函数的地址。看,运行起来多么简单。我们可以编译输出,以便使用我们最喜欢的反编译器进行探索。

image

root@kitploit:~
; ModuleID = 'my_lifting_module'
source_filename = "my_lifting_module"

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %0, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readonly %memory) local_unnamed_addr #0 {
entry:
  %stackmemory = alloca i128, i128 13758960, align 8
  %1 = trunc i64 %r8 to i32
  %2 = trunc i64 %rdx to i32
  %GEPLoadxd-5369456437- = getelementptr i8, ptr %memory, i64 %rcx
  %3 = load i32, ptr %GEPLoadxd-5369456437-, align 4
  %adc-temp-5370242400- = sub i32 %2, %1
  %realnot-5369532059- = add i32 %adc-temp-5370242400-, %3
  %stackmemory10243.sroa.55.1375304.insert.ext10255 = zext i32 %realnot-5369532059- to i64
  ret i64 %stackmemory10243.sroa.55.1375304.insert.ext10255
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }

编译后:

image

image

现在你可能会注意到寄存器有点不对。这是因为我们没有遵循调用约定,如果我们遵循调用约定,函数签名应该是这样的:

root@kitploit:~
define i64 @main(i64 %rcx, i64 %rdx, i64 %rdx, i64 %r8, i64 %r9 ...)

所以,我们只需调整函数签名使其看起来正常。如果你对这部分有更多疑问,建议你研究调用约定和ABI。

示例 #2 (分支/跳转表)

那么,假设我们有这段代码。虚拟机将把下面的代码转换为间接跳转,这对逆向工程师来说稍微有点不便。

root@kitploit:~
int maths(int a, int b, int c) {
    if (a > b)
        return a + b + c;
    else
        return a - b - c;
}
root@kitploit:~
next_handler = xxx;
if ( a-b > 0 )
  next_handler = yyy;
jump next_handler;

我们总是试图分析值并跟踪它们。这使我们能够理解控制流。 对于类似跳转表的分支 优化后的输出是一个简单的

root@kitploit:~
define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readnone %TEB, ptr nocapture readnone %memory) local_unnamed_addr #0 {
fake_ret:
  %0 = lshr i64 %rcx, 62
  %common.ret.op = and i64 %0, 2
  ret i64 %common.ret.op
}

未优化的输出。(为可读性进行了DCE)

root@kitploit:~
source_filename = "my_lifting_module"

define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr %TEB, ptr %memory) {
  %lsb = and i64 %rcx, 255
  %pf1 = mul i64 %lsb, 72340172838076673
  %pf2 = and i64 %pf1, -9205322385119247871
  %pf3 = urem i64 %pf2, 511
  %pf4 = and i64 %pf3, 1
  %pf5 = icmp eq i64 0, %pf4
  %0 = zext i1 %pf5 to i64
  %createrflag2 = shl i64 %0, 2
  %creatingrflag = or i64 2, %createrflag2
  %zeroflag = icmp eq i64 %rcx, 0
  %1 = zext i1 %zeroflag to i64
  %createrflag21 = shl i64 %1, 6
  %creatingrflag2 = or i64 %creatingrflag, %createrflag21
  %signflag = icmp slt i64 %rcx, 0
  %2 = zext i1 %signflag to i64
  %createrflag23 = shl i64 %2, 7
  %creatingrflag4 = or i64 %creatingrflag2, %createrflag23
  %GEPSTORE-5368713221- = getelementptr i8, ptr %memory, i64 1376032
  store i64 %creatingrflag4, ptr %GEPSTORE-5368713221-, align 4
  %realand-5368713229- = and i64 %creatingrflag4, 128
  %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7
  %3 = mul i64 %shr-lshr-5368713233-, 4
  %bvalue_indexvalue = add i64 5368713249, %3
  %4 = icmp eq i64 %bvalue_indexvalue, 5368713253
  %lolb- = select i1 %4, i64 5368713264, i64 5368713257
  %GEPSTORE-5368713248- = getelementptr i8, ptr %memory, i64 1376032
  store i64 %lolb-, ptr %GEPSTORE-5368713248-, align 4
  br i1 %4, label %real_ret, label %real_ret41

real_ret:                                         ; preds = %fake_ret
  %inc-5368713273- = add i64 %shr-lshr-5368713233-, 1
  ret i64 %inc-5368713273-

real_ret41:                                       ; preds = %fake_ret
  ret i64 %shr-lshr-5368713233-
}

注意这部分

root@kitploit:~
  %realand-5368713229- = and i64 %creatingrflag4, 128
  %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7

我们获取标志位,然后获取第7位,即符号标志(Sign Flag),然后使用符号标志来计算地址。通过分析,我们确定地址可能是两个值之一:5368713257或5368713264,然后将其转换为比较。如果地址是5368713257,走一个分支,否则走另一个。这样做时,用适当的值标记条件也很重要,因为稍后我们可能需要使用相同的值来计算另一个跳转。

尽管我们解决了间接跳转问题,但具有多于2个可能位置的跳转不受支持。这是因为针对它们的分析尚未实现。这使我们能够解决虚拟机风格的分支,但在处理现实中的跳转表时会遇到问题。

示例 #3 (Themida 3.1.6.0 LION64 (Red))

我们的目标程序:

image

Themida设置(我们目前只关心虚拟机):

image

image

image

虚拟化后:

image

运行Mergen:

image

输出代码:点击这里 那么,为什么我们的结果不如提升受VMP保护的程序那么成功呢?

Themida会主动写入.themida节区。与栈不同,我们不能忽略这些写入,因为这些值之后可能被其他东西读取。

但是,我们有一个临时解决方案。删除所有对.themida节区的存储操作。由于我们的程序不写入内存,我只是注释掉了所有的存储操作。现在我们剩下的是:

root@kitploit:~
source_filename = "my_lifting_module"

define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr writeonly %memory) local_unnamed_addr #0 {
  %trunc = trunc i64 %r8 to i32
  %trunc1 = trunc i64 %rdx to i32
  %trunc2 = trunc i64 %rcx to i32
  %realadd-5369771371- = add i32 %trunc1, %trunc2
  %realadd-5369582686- = add i32 %realadd-5369771371-, %trunc
  %trunc457139 = zext i32 %realadd-5369582686- to i64
  ret i64 %trunc457139
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) }

技术挑战

  • 循环
  • 自修改代码(尤其是有条件修改的代码)
  • 处于一个不存在"裁剪(outlining)"和"展开(unrolling)"优化的世界中。

联系我们

加入我们的Mergen Discord服务器交流想法或只是随便聊聊。

下载工具