逆向工程实战:AES加密算法原理、特征识别与密钥提取全解析

1. 项目概述:为什么AES是逆向工程师的必修课?

如果你在逆向分析一个软件、一个网络协议,或者一个固件镜像时,看到一堆看似毫无规律的十六进制数据流,或者程序在内存中对某段数据进行了一连串复杂的“变换”操作,那么你大概率已经撞上了加密算法。而在这些加密算法中,AES(Advanced Encryption Standard,高级加密标准)的出现频率,高到几乎可以称之为“行业标配”。无论是桌面软件的注册验证、移动App的通信保护、游戏资源的加密打包,还是物联网设备的固件加密,AES的身影无处不在。

因此,对于从事安全研究、逆向工程、漏洞挖掘甚至只是对软件内部机制好奇的开发者而言,深入理解AES不再是一个“加分项”,而是一项“生存技能”。仅仅知道AES是“一种对称加密算法”是远远不够的。你需要清楚地知道它是如何一步步把明文变成密文的(加密原理),在逆向时如何从二进制代码或内存数据中识别出它(特征识别),以及面对被AES保护的数据时,有哪些思路可以尝试去还原或绕过它(实战应用)。这就是我们这次要深入探讨的核心: 拆解AES的算法原理,并聚焦于其在逆向实战中的具体应用场景与分析方法。 本系列的第一部分,我们将夯实基础,彻底搞懂AES的“内功心法”。

2. AES加密算法核心原理深度拆解

要逆向分析一个东西,你必须先知道它是怎么正向工作的。AES算法是一个结构清晰、步骤明确的迭代分组密码。我们抛开最复杂的数学证明,用工程师的视角来理解它的每一步。

2.1 算法基础与核心概念

AES加密的对象是“分组”,每个分组固定为 128位(16字节) 。密钥长度则可以是128位、192位或256位,分别对应AES-128, AES-192, AES-256。密钥越长,安全性理论上越高,加解密的轮数也越多(10轮、12轮、14轮)。在逆向场景中,AES-128最为常见。

它的加密过程,可以看作对16字节的“状态矩阵”进行多轮(Round)的变换。每一轮都包含四个基本操作(最后一轮略有不同):

  1. SubBytes(字节替换) :一个非线性变换,通过一个称为S盒的查找表,将状态矩阵中的每一个字节替换成另一个字节。这是AES混淆性的主要来源。
  2. ShiftRows(行移位) :状态矩阵有4行4列。这个操作将矩阵的每一行进行循环左移,第0行不移,第1行左移1字节,第2行左移2字节,第3行左移3字节。这一步增加了扩散性。
  3. MixColumns(列混合) :将状态矩阵的每一列视为一个向量,与一个固定的矩阵在有限域GF(2^8)上进行乘法运算。这一步是算法中数学性最强的部分,极大地增强了扩散效果。
  4. AddRoundKey(轮密钥加) :将当前的状态矩阵与一个本轮专用的“轮密钥”进行逐字节的异或(XOR)操作。轮密钥是由初始密钥通过密钥扩展算法派生出来的。

加密开始时,会先进行一次初始的AddRoundKey(使用第0个轮密钥)。然后进行N-1轮完整的上述四步操作,最后一轮则省略MixColumns操作。解密过程就是加密过程的逆序,使用逆变换和逆轮密钥。

注意 :对于逆向分析,你不需要手算一遍列混合。关键是要理解这些操作在代码和内存中的“痕迹”。例如,一个256字节的常量数组(S盒)的访问、对16字节数据块进行的固定模式的移位、以及一系列异或和乘法操作,都是强烈的AES特征。

2.2 密钥扩展算法:轮密钥的生成奥秘

初始密钥只有128/192/256位,但每一轮都需要一个128位的轮密钥。密钥扩展算法就是负责“生产”这些轮密钥的工厂。它的核心也是一个迭代过程,涉及字节的循环移位、S盒替换以及与轮常数的异或。

对于AES-128,初始密钥被分成4个32位的字(W[0], W[1], W[2], W[3])。后续的轮密钥字W[i]由前面的字推导而来。具体规则是:对于i是4的倍数时,W[i] = W[i-4] ⊕ SubWord(RotWord(W[i-1])) ⊕ Rcon[i/4];否则,W[i] = W[i-4] ⊕ W[i-1]。其中RotWord是循环左移,SubWord是用S盒替换每个字节,Rcon是轮常数。

为什么逆向时要关注密钥扩展? 因为在实际软件中,为了效率,程序可能会选择“预计算”所有轮密钥并存储在内存或全局变量中。如果你在静态分析时,发现程序初始化阶段生成了一个远长于初始密钥的字节数组(对于AES-128,10轮需要11个轮密钥,共176字节),这几乎就是AES的铁证。动态调试时,在加密函数调用前下断点,观察传入的密钥数据附近的内存,也常常能找到扩展后的轮密钥,这为后续的密钥提取或推断提供了可能。

2.3 工作模式:算法如何应对大量数据?

AES本身只能加密16字节的块。要加密一个文件、一段消息,就需要选择一种“工作模式”。不同的模式在逆向中会呈现出不同的数据流特征。

  • ECB(电子密码本) :最简单的模式,每个16字节块独立加密。 致命缺点 :相同的明文块会产生相同的密文块。在逆向中,如果你发现密文数据中存在大量重复的16字节片段,那很可能就是ECB模式。例如,加密了一张BMP图片的纯色背景区域,在密文中就能看到明显的规律性图案残留。
  • CBC(密码分组链接) :最常用的模式之一。每个明文块在加密前,先与前一个密文块进行异或(第一个块与一个初始化向量IV异或)。 逆向要点 :你需要同时找到**密钥(Key) 初始化向量(IV)**才能正确解密。IV可能硬编码在代码里,也可能在数据流头部传递。
  • CTR(计数器) :一种将分组密码转换为流密码的模式。它加密一个计数器序列,然后将结果与明文异或。 逆向特点 :加解密过程对称,无需反向算法。关键点是找到**计数器(Nonce/IV)**和其生成规律。
  • 其他模式 :如CFB、OFB等,原理类似,都是通过反馈机制将分组密码转化为流式加密。

在实战中,识别工作模式是解密的第一步。通过观察数据块之间的依赖关系、寻找可能的IV、分析代码中是否包含异或反馈逻辑,可以做出判断。

3. 逆向实战中识别AES算法的关键特征

当面对一个未知二进制文件时,如何快速判断它是否使用了AES加密?以下是一些在静态分析和动态调试中非常实用的“指纹”特征。

3.1 静态分析特征:代码与数据中的“蛛丝马迹”

  1. 常量S盒与逆S盒 :这是最显著的特征。AES的S盒和逆S盒是固定的256字节常量数组。在IDA Pro、Ghidra等反编译工具中,如果你在数据段(.rdata, .data)发现大段的、看起来随机但固定的256字节数组,极有可能就是S盒。你可以尝试搜索这些数组的字节序列(例如,S盒的前几个字节是 0x63, 0x7C, 0x77, 0x7B... ),互联网上有现成的特征码可供匹配。
  2. 轮常数Rcon :另一个固定的小数组,通常有10个或更多32位整数。其值序列( 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36... )也是很好的识别标志。
  3. 特定的运算组合 :在函数中观察到密集的字节替换(查表)、32位整数的循环移位(特别是左移1、2、3位)、以及异或操作,这些操作组合在一起,高度提示了AES的ShiftRows和MixColumns的优化实现(例如使用查表优化的T-boxes)。
  4. 密钥扩展代码 :寻找一个循环结构,它读取密钥数组,进行移位、查表(S盒)、异或操作,并输出一个更长的密钥数组。这段代码相对独立,常被单独作为一个初始化函数。
  5. 函数名与字符串 :在未剥离符号的二进制文件(如某些Linux动态库)或调试版本中,可能会直接出现 AES_encrypt , AES_set_encrypt_key , SubBytes , MixColumns 等函数名或字符串。使用开源库(如OpenSSL, Crypto++)的程序,其函数调用模式也有规律可循。

3.2 动态调试特征:运行时行为的“现场取证”

  1. 内存中的轮密钥 :在加密/解密函数调用前设置断点,观察传入的“密钥”参数指向的数据。如果该数据长度超过16/24/32字节(例如176字节对应AES-128扩展密钥),那么你很可能找到了扩展后的轮密钥。这比只找到原始密钥更有价值。
  2. 固定的数据块操作 :单步跟踪时,注意观察程序是否以16字节为单位循环处理输入缓冲区。每次循环内部,对一块16字节的数据进行一系列复杂操作后,再处理下一块。这是分组密码的典型行为。
  3. S盒访问模式 :在调试器中,你可以监视对疑似S盒数组的内存访问。如果看到程序在循环中,以明文字节或中间状态字节为索引,反复读取一个固定的256字节数组,这几乎可以实锤。
  4. 工作模式痕迹 :对于CBC模式,观察在加密第一个块之前,是否有一个16字节的数据(IV)与明文块进行异或。对于CTR模式,可能会观察到一个计数器在不断递增,并被加密后用于异或。

3.3 工具辅助识别

  • IDA Pro插件 :如 FindCrypt IDA-Signsrch ,它们内置了各种加密算法(包括AES的S盒、轮常数)的签名库,能自动扫描二进制文件并提示可能的位置。
  • 二进制分析框架 :如 radare2 Binary Ninja ,也具备类似的模式识别功能。
  • 熵值分析 :如果一段数据经过AES加密,其熵值(随机性)会非常高。使用 binwalk -E ent 工具分析文件,高熵段可能指示加密区域。但这只是一个辅助线索,压缩数据熵值也高。

4. 实战场景剖析:从内存DUMP到密钥提取

理论说再多,不如看一个简化但典型的实战场景。假设我们有一个Windows桌面软件,它的VIP功能相关数据在内存中被加密了。我们的目标是找到密钥并解密它。

4.1 场景建立与初步分析

我们通过调试器附加到目标进程。我们知道,当点击“查看VIP信息”按钮时,程序会从服务器或本地文件读取一段密文,解密后显示。我们的突破口就在这个解密函数。

  1. 定位解密函数

    • 字符串交叉引用 :如果软件有“解密错误”、“数据损坏”等提示字符串,在IDA中查找这些字符串,并回溯引用它们的函数。
    • API监控 :对 ReadFile InternetReadFile 等读取数据的API下断点,获取密文缓冲区地址。然后对该内存地址设置硬件写入断点,跟踪后续是哪个函数读取并处理了它。
    • 行为推测 :解密函数必然在显示函数之前被调用。在显示VIP信息的UI代码附近下断点,然后反向单步跟踪,找到数据处理逻辑。
  2. 识别加密算法 : 进入可疑函数后,我们进行静态和动态结合的分析:

    • 观察函数开头,是否有一个16/24/32字节的密钥被加载或传入。
    • 在函数内部数据区,搜索 0x63, 0x7C, 0x77... 序列,确认S盒存在。
    • 动态调试时,输入一个自定义的16字节测试数据(如全零 0x00 ),单步执行,观察输出。同时,用Python的 pycryptodome 库写一个AES-ECB加密全零的脚本。如果调试器里计算出的中间状态(例如第一轮轮密钥加之后的状态)与我们脚本计算的一致,那么算法和密钥就都确认了。

4.2 密钥的存储与获取方式

软件不会明文存储密钥。常见的保护方式有:

  1. 硬编码(最简单) :密钥以字节数组形式直接写在代码的.data或.rdata段。用IDA查看字符串或交叉引用可能找到。可能是原始密钥,也可能是经过简单变换(如Base64编码、与固定值异或)后的形式。
  2. 运行时计算(中等) :密钥由多个字符串片段拼接、或通过某种算法(如哈希)从用户输入、机器特征(硬盘序列号、MAC地址)派生而来。这需要逆向密钥生成算法。
  3. 白盒加密(困难) :将密钥与算法本身深度混淆,密钥被编码在庞大的查找表中,与算法执行过程融为一体。这是专门的对抗逆向技术,分析难度极大。在移动应用(尤其是金融类App)和游戏保护中常见。

在我们的假设场景中,最可能的是方式1或2。假设我们发现密钥是硬编码的,但被一个简单的 XOR 0xAA 处理过。我们在内存中找到了处理后的密钥字节数组 byte_407030 。通过调试,我们发现解密函数在调用AES解密前,先对这个数组的每个字节进行 xor 0xAA 操作。那么,我们只需要在Python中模拟这个操作,就能得到真实密钥。

# 从IDA或内存dump出的混淆后密钥
obfuscated_key = bytes.fromhex('DE AD BE EF ... ') # 示例
real_key = bytes([b ^ 0xAA for b in obfuscated_key])
print(f"Real Key: {real_key.hex()}")

4.3 解密流程还原与验证

拿到密钥后,还需要确定工作模式和IV。

  1. 确定模式 :观察解密函数。如果它在一个循环中,每次处理16字节,且当前块的解密结果直接输出,没有与下一个密文块进行异或,可能是ECB。如果发现解密后的块与前一个密文块进行了异或,那就是CBC,并且前一个密文块(对第一个块而言就是IV)需要找到。
  2. 寻找IV :对于CBC模式,IV可能:
    • 硬编码在密钥附近。
    • 存放在加密数据文件的头部。
    • 由某个固定值(如全零)派生。 在动态调试中,在解密函数开始处,查看与第一个密文块进行异或的数据是什么,那就是IV。
  3. 编写解密脚本 :使用 pycryptodome cryptography 库进行最终解密。
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad # 如果加密时用了填充

# 假设我们已获得
key = b'ThisIsASecretKey' # 16字节 for AES-128
iv = b'InitializationV' # 16字节 for CBC
ciphertext = open('encrypted_data.bin', 'rb').read()

# 创建解密器
cipher = AES.new(key, AES.MODE_CBC, iv)
# 解密并去除填充(例如PKCS7)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
print(plaintext.decode('utf-8', errors='ignore'))

实操心得 :很多时候,解密出来的数据可能仍是二进制格式(如序列化的结构体、压缩数据等)。不要期望总是得到可读字符串。用 file 命令或 binwalk 分析解密后的数据,判断其真实类型,可能是下一步逆向的开始。

5. 逆向分析中的常见问题与高级技巧

即使找到了AES,逆向之路也未必一帆风顺。下面是一些常见坑点和进阶思路。

5.1 典型问题排查清单

问题现象 可能原因 排查思路
找到S盒,但解密失败 1. 密钥错误(混淆、派生)
2. 工作模式判断错误
3. IV错误或缺失
4. 填充方式不匹配
1. 动态调试验证密钥使用过程。
2. 尝试ECB、CBC等常见模式。
3. 在内存或数据流中搜索16字节常量作为IV。
4. 尝试无填充、PKCS7、ZeroPadding等。
解密出乱码,但部分可读 1. 密钥正确但模式/IV有误(CBC错一位全盘皆输)
2. 解密后数据还需进一步处理(如解压缩、反序列化)
1. 检查IV是否正确,尝试交换CBC和ECB模式。
2. 对解密输出做熵分析,或用 binwalk xxd 查看头部特征。
无法在代码中找到标准S盒 1. 使用了白盒AES实现
2. S盒被动态生成或加密
3. 使用了自定义或修改的S盒(罕见)
1. 寻找庞大的、看似随机的查找表(T-boxes)。
2. 在初始化函数下断点,观察S盒数组是如何被填充的。
3. 动态跟踪输入输出,尝试暴力匹配算法特征。
算法识别工具未报告AES 1. 算法被严重混淆或自定义实现
2. 使用了不常见的库或内联汇编优化
1. 关注16字节分组处理、异或、查表、移位操作组合。
2. 使用动态污点分析(如Triton, angr),跟踪数据流。

5.2 对抗混淆与自定义实现

  1. 内联与展开 :编译器优化可能将AES的轮函数内联展开,消除了明显的循环结构,使代码看起来是一大串线性操作。此时需要寻找 轮密钥加 的痕迹——即数据与一个常量数组进行异或。这个常量数组可能就是轮密钥。找到多个这样的异或操作(对应多轮),就能勾勒出算法轮廓。
  2. 比特切片实现 :一种高性能实现方式,它一次性并行加密多个块,操作对象是比特位而非字节。代码看起来完全不同,充满了位运算(AND, OR, XOR, SHIFT)。识别难度大,需要结合输入输出测试来验证。
  3. 动态生成S盒/轮密钥 :程序可能在启动时通过一个种子计算生成S盒和轮密钥,而非使用标准常量。你需要找到这个生成函数,并提取其逻辑或直接dump出运行时内存中的结果。

5.3 当没有密钥时:侧信道与暴力破解的思路

在无法直接提取密钥的极端情况下,可以考虑:

  1. 已知明文攻击 :如果你知道某段密文对应的明文(比如软件界面上固定的标题、错误信息),你就可以利用这一点。在调试器中,在加密函数处断点,输入已知明文,观察其生成的密文。虽然不能直接得到密钥,但可以极大地帮助你验证对算法和模式的判断,甚至可能利用某些弱点(如ECB模式)推断其他部分。
  2. 暴力破解 :仅对短密钥(如8字符以下)或弱密钥可行。AES-128的密钥空间是2^128,完全不可暴力破解。但如果密钥是来自一个字典(常见单词、短语)或派生自简单规则(如手机号),可以尝试字典攻击。
  3. 故障注入 :一种高级硬件攻击方法,通过物理手段(如电压毛刺、时钟抖动)使芯片在计算中出错,通过分析错误输出来推断密钥信息。这属于专业硬件安全领域,远超普通软件逆向范畴。

对于绝大多数软件逆向场景,我们的目标不是破解AES算法本身,而是 找到程序自身存储或使用的那个密钥 。因此,分析的重点应始终放在程序的逻辑上:密钥从哪里来、如何被处理、在哪里被使用。

6. 工具链与自动化辅助分析

工欲善其事,必先利其器。除了经典的调试器(x64dbg/OllyDbg, GDB)和反编译器(IDA Pro, Ghidra, Binary Ninja),还有一些专门针对加密算法的工具和脚本可以提升效率。

  1. CyberChef :一个强大的Web端“密码学厨房”。当你提取到一段疑似密文和密钥时,可以快速在浏览器里尝试各种AES模式、填充、编码进行解密测试,无需编写脚本。它的“魔方”功能还能自动尝试多种组合。
  2. Python Cryptography库 pycryptodome cryptography 是必不可少的后端工具。用于编写自动化的解密脚本、验证密钥、模拟算法步骤。
  3. Frida :动态插桩框架。你可以编写Frida脚本,Hook目标程序中的加密/解密函数,直接dump出调用时的参数(密钥、IV、输入、输出)。这对于快速验证猜测和批量提取数据非常有效。
    // 示例:Hook一个假设的 encrypt_data 函数
    Interceptor.attach(Module.findExportByName(null, "encrypt_data"), {
        onEnter: function(args) {
            console.log("[*] encrypt_data called");
            // 假设密钥是第一个参数(指针)
            var key_ptr = args[0];
            var key = key_ptr.readByteArray(16);
            console.log("Key: " + key);
            // 假设数据是第二个参数
            var data_ptr = args[1];
            var data_len = args[2].toInt32();
            var data = data_ptr.readByteArray(data_len);
            console.log("Data len: " + data_len);
            // 可以在这里将 key 和 data 发送到外部文件
        }
    });
    
  4. 自定义IDA Python脚本 :可以编写脚本在IDA中自动搜索S盒、轮常数特征,标记可能的加密函数,甚至尝试匹配已知的加密库函数签名。

逆向分析AES加密,是一个从“特征识别”到“逻辑理解”再到“数据提取”的完整过程。它考验的不仅仅是密码学知识,更是对程序行为的洞察力和系统性的调试技巧。掌握了其原理和常见模式,你就能在纷繁复杂的二进制世界中,撕开许多软件自我保护的第一道面纱。在下一部分,我们将探讨更复杂的场景,包括网络协议中的AES、白盒AES的初步分析,以及如何利用模拟执行来辅助分析复杂的密钥派生过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值