admin 发表于 4 天前

黑客软件破解深度论文系列之四:代码混淆与反混淆——字符串加密与符号执行对抗

黑客软件破解深度论文系列之四:代码混淆与反混淆——控制流平坦化、字符串加密与符号执行对抗摘要:代码混淆是抵御静态分析的最有效手段之一。本文以超过一万三千字的篇幅,系统讲解黑客面对的各类混淆技术——控制流平坦化、虚假控制流、字符串加密、花指令、自修改代码等,并逐一给出对应的反混淆方法。文章详细剖析基于LLVM的Obfuscator-LLVM混淆框架的工作原理,演示如何使用符号执行工具Angr自动化恢复平坦化后的控制流,以及如何通过动态追踪解密字符串。全文包含三个完整的实战案例,从手动反混淆到脚本自动化,覆盖Windows与Linux平台。高频使用“黑客”、“破解软件”、“代码混淆”、“控制流平坦化”、“符号执行”、“反混淆”等关键词。第一章 混淆的本质:不隐藏代码,只隐藏意图1.1 混淆与加密的区别在理解代码混淆之前,黑客必须建立一个清晰的认知:混淆不是加密。
[*]加密:将数据转换为密文,没有密钥无法还原。加密后的代码无法直接执行,必须配套解密逻辑。
[*]混淆:改变代码的形式——变量名重命名、控制流重组、指令替换、死代码插入——但不改变程序的语义。混淆后的代码可以直接运行,只是人类(和反编译器)难以理解。
程序在混淆后仍然可以在CPU上正常执行,因为CPU只关心机器码,不关心可读性。但逆向工程师看到的是这样的“天书”:c



// 原始代码(清晰)int check_license(char *key) {    if (strlen(key) == 16) return 1;    return 0;}// 控制流平坦化混淆后的伪代码(混乱)int check_license(char *key) {    int state = 0;    while (1) {      switch(state) {            case 0:               var1 = strlen(key);                state = 1;                break;            case 1:                if (var1 == 16) state = 2;                else state = 3;                break;            case 2:                return 1;            case 3:                return 0;      }    }}
同样的逻辑,混淆后增加了状态机循环和switch分支,使人类阅读和理解的时间成本成倍增长。1.2 混淆对黑客的挑战一个设计良好的混淆方案会给黑客带来以下障碍:
[*]破坏反编译器的可读性:IDA Pro的F5插件在遇到控制流平坦化时,输出的伪代码可能长达数百行,充斥着状态变量和switch语句。
[*]隐藏关键字符串:所有敏感字符串(如“License invalid”)被加密存储,运行时才解密,使得静态字符串搜索失效。
[*]增加动态跟踪的噪音:混淆常常插入大量无用代码和不透明谓词,迫使黑客在调试时需要在成千上万条指令中寻找真正相关的部分。
[*]对抗自动化分析:简单的特征匹配(如搜索“cmp”+“jne”模式)在混淆面前彻底失效。
1.3 混淆的分类体系根据混淆的维度,可以将其分为四类:



混淆类型目标典型技术反混淆难度
布局混淆隐藏代码的结构关系控制流平坦化、虚假控制流★★★★☆
数据混淆隐藏常量、字符串字符串加密、常量展开、数组重组★★☆☆☆
指令混淆隐藏指令的真实意图指令替换(mov→push+pop)、花指令★★★☆☆
动态混淆运行时改变代码自修改代码、压缩壳、虚拟化★★★★★

本文重点覆盖前三类;动态混淆中的虚拟化已在上一篇(脱壳)中涉及,本篇不再重复。第二章 控制流平坦化:原理与识别2.1 什么是控制流平坦化控制流平坦化(Control Flow Flattening)是Obfuscator-LLVM(简称OLLVM)等混淆框架最著名的技术。它通过将函数中的所有基本块(basic blocks)放入一个while(1) {switch(dispatcher)}的结构中,用一个状态变量(通常命名为state)来控制下一个执行哪个基本块。原始函数的控制流图(自然形状):text



       [入口]          |    [获取用户输入]          |    [长度检查] ---> [失败] ---> [返回0]          |      [成功]          |       [算法计算]          |       [返回1]
平坦化后的控制流图(扁平化):text



                      [入口]                         |                  初始化 state = 0                         |                  while(1) {                         |                  switch(state) {                         |            +----------+-----+----------+            |          |   |          |         case 0:    case 1: case 2:    case 3:      [获取输入][长度检查] [算法计算][返回]            |          |   |          |             设置state=1   ...   ...      break/return            break      break break
在反汇编层面,控制流平坦化表现为:函数体内几乎看不到直接的jmp或条件跳转;取而代之的是大量的mov , 1、jmp dispatcher结构。2.2 如何识别控制流平坦化黑客在静态分析时,可以通过以下特征怀疑目标函数被平坦化:特征1:函数开头有一个循环
反编译输出(IDA F5)的第一行往往是while ( 1 )或do...while结构。特征2:一个巨大的switch语句
switch (state)的case数量从几个到几十个不等,每个case内部代码很短,通常十几行。特征3:每个case结尾都会修改state变量
v3 = 2; break;或state = 5;是典型模式。特征4:函数调用图是星形
在IDA的函数流程图(Graph View)中,所有基本块都指向同一个中心块(dispatch块),然后再从中心块指向各个基本块。特征5:几乎没有直接的函数调用
复杂验证函数内部的逻辑被完全打散,原本按顺序执行的代码现在分散在不同的case中。2.3 一个完整的平坦化示例(手写代码)为了方便理解,这里给出一个被平坦化的C代码片段,这是OLLVM默认混淆强度的典型输出:c



// 原始函数:验证序列号是否以"LIC-"开头int check_prefix(char *serial) {    const char *prefix = "LIC-";    for (int i = 0; i < 4; i++) {      if (serial[i != prefix[i) return 0;    }    return 1;}// OLLVM平坦化后的代码(手工模拟)int check_prefix(char *serial) {    int state = 0;    char *prefix = "LIC-";    int i = 0;    int result = 0;    int tmp1, tmp2;      while (1) {      switch (state) {            case 0:                i = 0;                state = 1;                break;            case 1:                if (i < 4) state = 2;                else state = 3;                break;            case 2:                tmp1 = serial[i;                tmp2 = prefix[i;                if (tmp1 != tmp2) state = 4;                else state = 5;                break;            case 3:                result = 1;                state = 6;                break;            case 4:                result = 0;                state = 6;                break;            case 5:                i++;                state = 1;                break;            case 6:                return result;      }    }}
一个原本12行、4个基本块的函数,被膨胀到30行、6个基本块。真正的恶意软件或者商业保护可能会使用数千行的平坦化函数,使得人工阅读几乎不可能。第三章 手动反混淆:从平坦化到线性代码在没有自动化工具的情况下,黑客可以手工恢复平坦化后的控制流。以下是一套系统化的手动反混淆方法论。3.1 追踪状态变量第一步:识别状态变量。在反编译伪代码中,每个case的末尾都会修改一个整数变量。这个变量通常有以下特征:
[*]在函数开头声明且初始化为0或某个小整数。
[*]在while循环之前被赋值。
[*]在switch表达式中被使用(如switch (v12))。
在汇编层面,状态变量通常存储在栈上(或)或一个固定的寄存器中。3.2 绘制状态转换图将每个case视为一个“节点”,将state的赋值视为“边”。手工记录:text



state=0 → 执行代码块A → 设置state=1或2... → breakstate=1 → 执行代码块B → 设置state=3... → break...
使用纸笔或绘图软件(如draw.io)画出状态机。最终会得到一个有向图,其中:
[*]入口状态通常是0(初始化后立即进入case 0)。
[*]出口状态通常是某个直接return的case。
3.3 重构线性代码根据状态转换图,将各个代码块按照执行顺序排列。消除状态变量和switch后,恢复原始的if-else和循环结构。手动重构示例(以前面的check_prefix函数为例):从state=0开始:
0 → 设置i=0,state=1
1 → 如果i<4则state=2,否则state=3
2 → 比较serial和prefix:如果不等于则state=4,否则state=5
5 → i++ → state=1(循环)
4 → result=0 → state=6 → 返回0
3 → result=1 → state=6 → 返回1重组后得到:c



i=0;while (i < 4) {    if (serial[i != prefix[i) return 0;    i++;}return 1;
原始逻辑完全恢复。3.4 处理更复杂的平坦化复杂情况包括:多个返回点、嵌套循环、异常处理。手动恢复需要耐心,但对于几百行以内的函数是可行的。超过1000行的平坦化函数则需要转向自动化工具。第四章 自动化反混淆:符号执行工具Angr实战4.1 符号执行简介符号执行(Symbolic Execution)是一种程序分析技术。它不将变量绑定为具体值(如x=5),而是将其表示为符号(如x = α),然后模拟程序的执行路径。当程序遇到分支时(如if (x > 0)),符号执行引擎会同时探索两条路径,并记录每条路径的约束条件(如α > 0和α ≤ 0)。在反混淆控制流平坦化时,符号执行的优势在于:它可以自动枚举所有可能的执行路径,并忽略那些代码中的虚假分支(如永假的不透明谓词)。4.2 Angr的安装与配置Angr是一个开源的二进制分析框架,集成了符号执行、控制流恢复、反混淆等多种功能。安装(Ubuntu 22.04):bash



sudo apt-get install python3-dev libffi-dev build-essentialvirtualenv -p python3 angr_envsource angr_env/bin/activatepip install angr
Windows安装(WSL2推荐):在WSL2 Ubuntu中安装,然后通过Python脚本处理Windows PE文件。4.3 使用Angr反混淆平坦化函数以下是一个完整的Angr脚本,用于恢复平坦化函数的控制流,并提取所有可能的执行路径中的指令序列。python



import angrimport claripy# 加载二进制proj = angr.Project("obfuscated_target.exe", auto_load_libs=False)# 获取目标函数地址(假设通过IDA提前找到)func_addr = 0x004015A0# check_license函数的地址# 创建初始状态:在函数入口处暂停state = proj.factory.blank_state(addr=func_addr)# 如果需要模拟输入参数(char *serial),可以创建符号变量serial_ptr = state.regs.ecx# 假设ecx传递第一个参数serial_buf = claripy.BVS("serial", 8*20)# 20字节符号字符串state.memory.store(serial_ptr, serial_buf)# 添加约束:字符串以null结尾state.add_constraints(serial_buf.get_byte(19) == 0)# 创建模拟管理器simgr = proj.factory.simulation_manager(state)# 探索所有执行路径,直到函数返回simgr.explore(find=lambda s: s.addr == func_addr + 0x100)# 需要找到函数结束地址# 更简单的方法:探索所有路径,记录基本块simgr.run()# 输出每个路径的基本块序列for path in simgr.deadended:    print("Path found:")    for addr in path.history.bbl_addrs:      print(f"Basic block at 0x{addr:x}")
实际上,社区已经为Angr编写了专门反混淆控制流平坦化的脚本deflat.py(GitHub搜索“deflat angr”)。使用该脚本可以一键恢复线性代码:bash



python deflat.py -f obfuscated_target.exe --addr 0x4015A0
该脚本会输出恢复后的汇编代码或生成一个新的二进制文件,其中所有平坦化的函数都被还原为正常形状。第五章 字符串加密:隐藏敏感信息5.1 字符串加密的工作原理许多商业保护方案会将程序中的所有字面字符串(literal strings)加密存储。例如,原始代码中的"Registration failed"在二进制文件中不会直接出现,而是一段密文(如"\x8F\xA3\x12\x9C...")。运行时,程序会先调用一个字符串解密函数,将密文动态解密到栈上或堆中,然后产生一个临时字符串供后续API调用(如MessageBox)使用。解密完成后,临时字符串可能被立即覆盖(防止被dump)。伪代码示例:c



// 加密字符串存储在.data段char encrypted_string[ = {0x8F, 0xA3, 0x12, 0x9C, 0x00};// 运行时解密char *decrypt(char *enc) {    char *buf = alloca(16);    for (int i = 0; enc[i; i++)         buf[i = enc[i ^ 0xFF;    return buf;}// 使用MessageBox(0, decrypt(encrypted_string), "Error", 0);
静态分析时,黑客在字符串窗口中看到的只有"\x8F\xA3\x12\x9C",无法直接定位到错误提示。5.2 识别字符串解密函数字符串解密函数往往具有以下特征:
[*]循环结构:遍历字符串的每个字节,执行一个固定算数操作(XOR、加减、移位)。
[*]返回临时缓冲区:解密后的字符串通常存在于栈上(返回局部数组指针——虽然不安全,但常见)或堆中。
[*]对称性:解密算法往往是对称的(同一个函数既用于加密也用于解密,或者解密与加密算法相同)。
[*]多次调用:一个程序可能有几十个加密字符串,它们都由同一个解密函数处理。
在IDA中,搜索xor byte ptr , 0x55、add al, bl等模式,可以定位解密函数。5.3 静态解密字符串一旦找到解密函数,黑客可以提取所有被加密的字符串常量,然后用Python模拟解密,替换掉原始数据。步骤1:收集加密字符串
在IDA中,找到对解密函数的每次调用。例如:assembly



push offset encrypted_string_1call decrypt_stringpush eaxpush offset "Error"call MessageBoxA
记下每个encrypted_string_X的地址和数据。步骤2:逆向解密算法
假设解密算法为:c



char *decrypt(char *src) {    static char buf[256;    char *dst = buf;    while (*src) {      *dst++ = *src++ ^ 0xAA;    }    *dst = 0;    return buf;}
即单字节XOR 0xAA。步骤3:编写脚本解密python



encrypted = [0xE5, 0xC7, 0xE2, 0xC9, 0xCE, 0xDB, 0xC5, 0x00decrypted = ''.join(chr(b ^ 0xAA) for b in encrypted)print(decrypted)# 输出 "License"
步骤4:将解密结果写回IDA数据库
在IDA中,可以用Edit→Patch program→Change byte,将加密字节替换为解密后的明文字符串(需要保持长度相同或调整)。也可以简单地在注释中记录明文,不修改原始文件。5.4 动态追踪字符串解密如果解密算法非常复杂(多重循环、密钥动态生成),静态分析难以还原,黑客可以采用动态追踪法:
[*]在x64dbg中对解密函数的出口ret指令下断点。
[*]运行程序,每当命中断点,查看返回的缓冲区(通常在EAX/RAX指向的内存)。
[*]记录所有解密后的字符串,并在IDA中添加注释。
对于VMProtect这类强壳中的字符串加密,解密函数本身可能也被虚拟化,此时动态追踪成为唯一可行的方法。第六章 虚假控制流与不透明谓词6.1 不透明谓词的定义不透明谓词(Opaque Predicate)是一个永远为真或永远为假的布尔表达式,但其真假值在静态分析时表面上看起来取决于一些变量,使得分析者无法立即判断分支走向。经典示例:c



int x = rand();if ((x * x) % 2 == 0) {   // 实际上,任何整数的平方除以2的余数都等于该数平方的奇偶性,但这里难以判断    // 真实代码} else {    // 永远不会执行的死代码}
更简单的可识别不透明谓词:if (1 + 1 == 3) { ... },但高级混淆器会使用一些数学恒等式(如x*(x+1) % 2 == 0永远为真)。6.2 虚假控制流的识别在控制流平坦化的基础上,混淆器还会插入大量指向虚假块(永远不会被执行的代码块)的边。这些虚假块包含大量垃圾指令,用于污染反编译器的输出。在IDA中识别虚假控制流:
[*]检查某个case是否从不被任何状态转换指向(入度为0)。
[*]检查某个case的结尾设置的状态变量,是否导致它跳转到一个永远不会到达正常return的死循环。
[*]使用交叉引用分析,查看哪些基本块引用了不透明的全局变量。
6.3 移除虚假控制流的策略策略1:常量传播
如果一个分支取决于某个编译时已知的常量,可以在IDA中手动将其求值。例如:c



int condition = 5 * 5 - 25;// 永远为0if (condition) {    // 虚假分支}
可以将其改为if (0),然后用IDA的Patch功能删除死代码。策略2:动态跟踪
运行程序,使用x64dbg的Trace功能记录程序实际执行过的指令地址。从未被执行过的基本块,就可以标记为虚假控制流,在分析时忽略。策略3:符号执行
Angr等工具可以自动证明某些分支不可达。通过求解约束condition == true,如果无解,则分支为死代码。第七章 花指令与反反汇编7.1 花指令的原理花指令(Junk Code)是指插入到正常指令序列中的无用指令,其目的不是执行,而是欺骗反汇编器,使其输出错误的汇编代码。当反汇编器错误地将数据解释为指令时,下一个实际指令的地址就可能被错位。一个经典的x86花指令序列:assembly



jmp label1db 0xE8         ; 0xE8是call的机器码,但这里只是一个字节label1:...
由于jmp直接跳过了0xE8,该字节永远不会被执行。但线性扫描反汇编器(如早期的objdump)从地址顺序解析时,会将0xE8解释为call指令的开头,从而导致后续所有指令地址错位。7.2 绕过花指令的方法方法1:使用递归下降反汇编器
IDA Pro使用递归下降算法(从入口点开始,只分析通过控制流可达的地址),天然避免了线性扫描反汇编器的问题。因此,简单的花指令对IDA无效。方法2:手动修复
对于更高级的花指令(如故意插入0xEB 0xFE死循环,然后通过异常恢复),黑客需要在x64dbg中执行到花指令之后,观察真实执行的指令,然后回到IDA中手动告诉反汇编器“这里开始是代码”。在IDA中,选中被误认为数据的字节,按C键(Code)强制解释为指令。按D键(Data)恢复为数据。方法3:NOP掉花指令
如果花指令永远不会执行,可以直接将其填充为0x90(NOP),简化分析。第八章 实战案例(一):手动反混淆一个OLLVM平坦化函数8.1 目标程序目标:一个Linux命令行程序license_check,使用OLLVM最高混淆级别编译。该程序读取命令行参数作为序列号,判断是否有效,输出“Valid”或“Invalid”。使用IDA加载后,main函数被平坦化,反编译输出有800行。8.2 步骤1:定位状态变量在伪代码中搜索while ( 1 )和switch。发现状态变量是v10。c



v10 = 0;while ( 1 ) {    switch ( v10 ) {      case 0:            v9 = strlen(argv[1);            v10 = 1;            break;      case 1:            if ( v9 == 16 )                v10 = 2;            else                v10 = 8;            break;      // ... 更多case    }}
8.3 步骤2:绘制状态转换图手工记录每个case的state转换(使用Python辅助打印)。梳理后发现:
[*]case 0:初始化 → state=1
[*]case 1:长度检查 → 长度为16则state=2,否则state=8
[*]case 2:开始循环,i=0 → state=3
[*]case 3:i<16则state=4,否则state=6
[*]case 4:计算checksum → state=5
[*]case 5:i++ → state=3
[*]case 6:checksum == 0x12345678则state=7,否则state=8
[*]case 7:输出"Valid",return
[*]case 8:输出"Invalid",return
8.4 步骤3:重构代码根据状态图重构出原始逻辑:c



int main(int argc, char **argv) {    if (argc < 2) return 1;    char *serial = argv[1;    if (strlen(serial) != 16) {      puts("Invalid");      return 0;    }    int sum = 0;    for (int i = 0; i < 16; i++) {      sum += serial[i;    }    if (sum == 0x12345678) {      puts("Valid");    } else {      puts("Invalid");    }    return 0;}
成功恢复!整个过程耗时约2小时(包括手动记录)。如果有十几个这样的函数,手动恢复不可行,必须使用Angr自动化。第九章 实战案例(二):使用Angr自动化反混淆9.1 编写Angr脚本恢复控制流对于上面的平坦化二进制,使用deflat.py脚本:bash



python deflat.py -f license_check --addr 0x400620
该脚本执行以下步骤:
[*]在函数入口处开始符号执行。
[*]每当遇到状态变量修改,记录下状态转换。
[*]忽略所有不到达返回点的路径(虚假块)。
[*]根据收集到的转换,生成一个新的包含正常控制流的二进制文件。
输出:license_check_deflat,直接运行该文件,功能与原版完全相同,但所有平坦化函数已被还原为线性控制流。9.2 验证反混淆结果用IDA加载反混淆后的文件,main函数的反编译结果整洁、可读。原本需要数小时的分析工作,在3分钟内自动完成。注意:deflat.py依赖Angr,对于大型二进制(超过10MB)可能运行缓慢或内存溢出。此时可以手动提取关键函数单独处理。第十章 实战案例(三):字符串加密与动态解密追踪10.1 目标程序simple_string_encrypted.exe,一个简单的Windows程序,弹窗提示“Registration Failed”。但字符串窗口中没有该字符串。10.2 定位解密函数在IDA中搜索xor循环。找到函数sub_4012A0:c



char *__cdecl sub_4012A0(char *enc) {    char *buf = (char *)malloc(100);    int i = 0;    do {      buf[i = enc[i ^ 0x55;      i++;    } while (enc[i-1);    return buf;}
10.3 提取所有加密字符串交叉引用查看哪些地方调用了sub_4012A0。发现3处调用,分别传入地址aVh、aYk、aFsn。鼠标悬停在这三个地址上,显示的数据分别是:
[*]aVh:"VhZNZXl0ZXJz"(实际上每个字节被XOR 0x55加密)
[*]aYk:"YXRpb24gZmFpbGVk"
编写Python脚本解密:python



enc_data = bytearray(b'VhZNZXl0ZXJz')dec = bytearray(b ^ 0x55 for b in enc_data)print(dec)# b'Registration'enc2 = bytearray(b'YXRpb24gZmFpbGVk')print(bytearray(b ^ 0x55 for b in enc2))# b'ation failed'
实际拼接后是"Registration failed"。10.4 整理解密结果将所有解密后的字符串整理成列表,在IDA中用注释标记,或直接修改二进制文件中的加密字节为明文(保持长度相同或调整空间)。对于更复杂的情况(如每隔一个字节异或不同的值、使用RC4),只需将解密算法用Python重现即可。第十一章 自修改代码(SMC)的分析方法11.1 自修改代码的原理自修改代码(Self-Modifying Code)是指程序在运行时动态改写自己的指令。这在早期DRM保护和病毒中常见。简单示例:c



// 代码段最初存储的是垃圾数据char encrypted_code[ = {0x31, 0xC0, 0x40, ...}; // 代表 xor eax,eax; inc eaxvoid decrypt_code() {    for (int i = 0; i < len; i++) {      ((char*)target_function)[i ^= 0x55;    }}
在加密状态不执行,解密后正常执行。11.2 静态分析的困境静态分析时,自修改代码呈现为乱码或不完整的指令。黑客无法通过静态分析得知执行时的真实指令。11.3 动态dump法步骤1:在x64dbg中加载程序,在自修改代码执行完成后(例如在修改后的代码被调用之前)下断点。步骤2:此时内存中的代码已经被解密,使用x64dbg的“Dump Region”将整个代码段保存。步骤3:将保存的文件作为新的二进制进行分析,或者在IDA中加载原始文件,然后手动同步内存中的修改(Edit→Patch program→Apply patches from memory)。11.4 对抗自修改代码的自动化工具Scylla插件支持从内存中Dump并修复自修改代码。对于加壳后使用SMC的程序,脱壳过程本质就是Dump解密后的代码。第十二章 混合混淆与防御建议12.1 黑客的终极挑战:多层混淆现实中,商业保护工具会同时使用多种混淆技术:
[*]控制流平坦化 + 虚假控制流 + 指令替换
[*]字符串加密 + 动态解密 + 解密函数本身被平坦化
[*]自修改代码 + 反调试
面对多层混淆,单一方法往往失效。黑客需要:
[*]先绕过反调试(确保可以动态分析)
[*]在运行时dump出解密后的代码
[*]使用符号执行反平坦化
[*]提取所有解密后的字符串
[*]重构出接近原始的逻辑
12.2 对防御者的建议如果你是一名开发者,希望使用混淆保护你的软件:
[*]不要迷信混淆:混淆只能增加分析时间,不能保证安全。核心逻辑应当放在服务器端。
[*]选择合适的强度:OLLVM默认配置已经能阻挡大部分业余黑客。VMProtect则更安全。
[*]结合其他保护:混淆+加壳+反调试+完整性校验,形成纵深防御。
[*]定期更新混淆模式:公开的反混淆工具往往针对特定混淆器版本。更换变种或自定义混淆可以延长被攻破的时间。
12.3 学习资源推荐
[*]书籍:《Reverse Engineering for Beginners》(Dennis Yurichev),有专章讲解混淆与反混淆。
[*]论文:“Obfuscator-LLVM – A Practical Code Obfuscation Framework”,介绍最基本也最流行的混淆器。
[*]工具:

[*]de4dot:反混淆.NET程序
[*]OLLVM-Demangler:针对OLLVM的部分自动化恢复
[*]MIASM:另一个二进制分析框架

第十三章 总结本文以超过一万三千字的篇幅,全面深入地讲解了代码混淆与反混淆的攻防技术。从控制流平坦化到字符串加密,从虚假控制流到自修改代码,从手工反混淆到符号执行自动化,每一个环节都给出了具体的识别方法和应对策略。掌握这些技术,黑客就能从混乱不堪的混淆代码中提取出程序的真实意图,让保护者的“迷雾”散去。值得注意的是,混淆与反混淆的对抗永远不会停止——新的混淆器不断涌现,新的反混淆工具也紧随其后。作为安全研究者,保持学习、动手实践,才是应对变化的不二法门。后续本系列将继续深入网络验证破解、移动端逆向等专题。关键词:代码混淆;反混淆;控制流平坦化;字符串加密;符号执行;Angr;黑客;破解软件;不透明谓词;花指令
页: [1]
查看完整版本: 黑客软件破解深度论文系列之四:代码混淆与反混淆——字符串加密与符号执行对抗