0%
毅种循环

模块 19 - Payload 加密 - AES 加密

恶意软件开发课程 - Payload 加密 - AES 加密


模块 19 - Payload 加密 - AES 加密#

[!IMPORTANT] 本知识库声明

本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示实践心得

  1. 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
  2. 版权归属:原始知识产权归原作者/官方所有。
  3. 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程
  4. 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。

Payload 加密 - AES 加密#

高级加密标准 (Advanced Encryption Standard)#

本模块讨论一种更安全的加密算法 —— 高级加密标准 (AES)。它是一种对称密钥算法,意味着加密和解密使用相同的密钥。AES 有几种类型,如 AES128AES192AES256,它们的区别在于密钥大小。例如,AES128 使用 128 位密钥,而 AES256 使用 256 位密钥。

此外,AES 可以使用不同的分组密码模式,如 CBCGCM。根据 AES 模式的不同,AES 算法除了加密密钥外,还需要一个称为初始化向量 (Initialization Vector, IV) 的附加组件。提供 IV 为加密过程提供了额外的安全层。

无论选择哪种 AES 类型,AES 始终需要 128 位输入并产生 128 位输出块。需要记住的重要一点是,输入数据应该是 16 字节的倍数 (128 位)。如果要加密的 payload 不是 16 字节的倍数,则需要填充 (padding) 来增加 payload 的大小,使其成为 16 字节的倍数。

本模块提供了两个使用 AES256-CBC 的代码示例。第一个示例通过 bCrypt 库(利用 Windows API)实现,第二个示例使用 Tiny AES Project。请注意,由于使用的是 AES256-CBC,代码使用 32 字节密钥16 字节 IV。如果使用不同的 AES 类型或模式,这些参数会有所不同。

[!IMPORTANT] 📚 知识扩展 - 为什么需要 IV (初始化向量)?

什么是 IV?

  • IV 是一个随机的 16 字节值
  • 每次加密都应该使用不同的 IV
  • IV 不需要保密,但必须是随机的

为什么需要 IV?

假设没有 IV:

情况 1: 加密 "Hello" with key "SecretKey123"
        → 密文: A3F2D1...

情况 2: 再次加密 "Hello" with key "SecretKey123"
        → 密文: A3F2D1... (相同!)
plaintext

问题: 攻击者可以通过重复的密文推断出重复的明文!

使用 IV 后:

情况 1: 加密 "Hello" with key + IV1
        → 密文: A3F2D1...

情况 2: 加密 "Hello" with key + IV2 (不同的 IV)
        → 密文: 7B9C4E... (完全不同!)
plaintext

结论: IV 确保相同的明文每次产生不同的密文,大大提高安全性!

[!WARNING] ⚠️ AES 填充 (Padding) 问题

为什么需要填充?

  • AES 是分组加密,每次处理固定的 16 字节
  • 如果数据不是 16 的倍数,最后一个块会不够 16 字节
  • 需要”填充”来补齐到 16 字节

示例:

原始数据: "Hello" (5 字节)
问题: 5 不是 16 的倍数,无法直接加密!

解决方案: 填充到 16 字节
填充后: "Hello" + 11 个填充字节 = 16 字节 ✓
plaintext

常见填充方案:

  1. PKCS#7 填充 (最常用)

    原始: [H][e][l][l][o]                    (5 字节)
    填充: [H][e][l][l][o][11][11][11]...[11]  (16 字节)
           ↑原始数据        ↑填充 11 个 0x0B (11 的十六进制)
    plaintext

    规则: 填充字节的值 = 填充的数量

  2. 零填充

    原始: [H][e][l][l][o]                    (5 字节)
    填充: [H][e][l][l][o][0][0][0]...[0]     (16 字节)
    plaintext

    问题: 无法区分原始的零和填充的零

本模块使用 BCRYPT_BLOCK_PADDING 标志自动处理填充!


使用 Windows API 的 AES (bCrypt 库)#

有多种方法可以实现 AES 加密算法。本节利用 bCrypt 库 (bcrypt.h) 执行 AES 加密。本节将解释代码,完整代码可在模块框的右上角下载。

AES 结构#

首先,创建一个 AES 结构,其中包含执行加密和解密所需的数据。

typedef struct _AES {

	PBYTE	pPlainText;         // 明文数据的基地址
	DWORD	dwPlainSize;        // 明文数据的大小

	PBYTE	pCipherText;        // 加密数据的基地址
	DWORD	dwCipherSize;       // 加密数据的大小 (如果有填充,可能会与 dwPlainSize 不同)

	PBYTE	pKey;               // 32 字节密钥 (AES-256)
	PBYTE	pIv;                // 16 字节 IV

} AES, *PAES;
c

SimpleEncryption 包装函数#

SimpleEncryption 函数有六个参数,用于初始化 AES 结构。结构初始化后,函数将调用 InstallAesEncryption 执行 AES 加密过程。请注意,其中两个参数是 OUT 参数,因此该函数返回以下内容:

  • pCipherTextData - 指向新分配的堆缓冲区的指针,其中包含密文数据。
  • sCipherTextSize - 密文缓冲区的大小。

如果 InstallAesEncryption 成功,函数返回 TRUE,否则返回 FALSE

SimpleDecryption 包装函数#

SimpleDecryption 函数也有六个参数,行为类似于 SimpleEncryption,区别在于它调用 InstallAesDecryption 函数并返回两个不同的值。

  • pPlainTextData - 指向新分配的堆缓冲区的指针,其中包含明文数据。
  • sPlainTextSize - 明文缓冲区的大小。

如果 InstallAesDecryption 成功,函数返回 TRUE,否则返回 FALSE

Cryptographic Next Generation (CNG)#

密码学下一代 (Cryptographic Next Generation, CNG) 提供了一组加密函数,供操作系统的应用程序使用。CNG 为加密操作提供了标准化接口,使开发人员更容易在其应用程序中实现安全功能。InstallAesEncryptionInstallAesDecryption 函数都使用 CNG。

有关 CNG 的更多信息可在这里找到。

[!IMPORTANT] 📚 知识扩展 - Windows 加密 API 的演变

Windows 提供了多代加密 API:

API全称引入时间状态特点
CryptoAPICryptographic APIWindows 2000⚠️ 已弃用老旧,不推荐使用
CNGCryptographic Next GenerationWindows Vista✅ 当前标准现代,模块化,推荐使用

CNG 的优势:

  • 🔹 模块化设计 - 支持自定义加密提供程序
  • 🔹 更好的性能 - 支持硬件加速
  • 🔹 更安全 - 符合 FIPS 140-2 标准
  • 🔹 更灵活 - 支持更多算法和密钥长度

为什么本模块使用 CNG?

  • ✅ 是 Windows 官方推荐的加密 API
  • ✅ 所有现代 Windows 版本都支持 (Vista 及以后)
  • ✅ 性能优秀,安全性高
  • ✅ 文档完善,社区支持好

InstallAesEncryption 函数#

InstallAesEncryption 是执行 AES 加密的函数。该函数有一个参数 PAES,它是指向已填充的 AES 结构的指针。函数中使用的 bCrypt 库函数如下所示。

使用的 bCrypt API:

API 函数功能说明
BCryptOpenAlgorithmProvider加载 BCRYPT_AES_ALGORITHM CNG 提供程序以启用加密函数的使用
BCryptGetProperty第一次调用获取 BCRYPT_OBJECT_LENGTH 的值,第二次获取 BCRYPT_BLOCK_LENGTH 属性标识符的值
BCryptSetProperty用于初始化 BCRYPT_OBJECT_LENGTH 属性标识符
BCryptGenerateSymmetricKey从指定的输入 AES 密钥创建密钥对象
BCryptEncrypt用于加密指定的数据块。此函数被调用两次,第一次获取加密数据的大小以分配该大小的堆缓冲区。第二次调用加密数据并将密文存储在分配的堆中
BCryptDestroyKey通过销毁使用 BCryptGenerateSymmetricKey 创建的密钥对象来清理
BCryptCloseAlgorithmProvider通过关闭先前使用 BCryptOpenAlgorithmProvider 创建的算法提供程序的对象句柄来清理

如果成功加密 payload,函数返回 TRUE,否则返回 FALSE