Hook Crypto API 获取加密参数

在逆向分析、协议还原与恶意软件分析中,加密参数的提取往往是突破点。大量 Windows 程序依赖系统自带的 Crypto API 完成加解密、哈希计算与签名验证,通过 Hook 这些系统接口,可以在不破解算法的前提下,直接截获明文、密钥、IV 与加密结果,大幅提升逆向效率。

本文将从原理到实战,系统讲解如何 Hook Windows Crypto API 以获取关键加密参数,并提供可直接复用的实现方案。

一、Windows Crypto API 体系与核心函数

Windows 提供两代加密接口:传统 CryptoAPI(advapi32.dll)与新一代 CNG(Cryptography Next Generation,bcrypt.dll)。绝大多数老牌软件与加壳程序仍在使用传统接口,而较新的应用则倾向于 BCrypt 系列。

1.1 传统 CryptoAPI 关键函数(advapi32.dll)

表格

函数名 作用 可截获参数
CryptEncrypt 加密数据 明文、密文、密钥句柄、数据长度
CryptDecrypt 解密数据 密文、解密后明文
CryptGenKey 生成密钥 算法类型、密钥长度、密钥句柄
CryptDeriveKey 派生密钥 基础密码、算法、派生后密钥
CryptImportKey 导入密钥 原始密钥明文数据
CryptHashData 计算哈希 输入明文、哈希结果
CryptSetKeyParam 设置密钥参数 IV、填充模式、盐值

其中 CryptEncrypt 是逆向中最高频的 Hook 目标,函数原型如下:

复制代码
BOOL CryptEncrypt(
  HCRYPTKEY  hKey,       // 加密密钥句柄
  HCRYPTHASH hHash,      // 哈希对象句柄(可NULL)
  BOOL       Final,      // 是否最后一块数据
  DWORD      dwFlags,    // 标志位
  BYTE*      pbData,     // 输入明文,输出密文(原地覆盖)
  DWORD*     pdwDataLen, // 数据长度指针
  DWORD      dwBufLen    // 缓冲区总大小
);

1.2 CNG 接口关键函数(bcrypt.dll)

  • BCryptEncrypt / BCryptDecrypt:对称加解密
  • BCryptGenerateSymmetricKey:生成对称密钥
  • BCryptOpenAlgorithmProvider:打开算法提供者
  • BCryptHash:哈希计算

CNG 接口参数更清晰,Hook 后提取密钥与明文也更直接。

二、Hook 技术选型与对比

针对 Crypto API 的 Hook 主要有三种实现路线,各有适用场景:

2.1 IAT Hook(导入表 Hook)

通过修改 PE 导入地址表(IAT)中函数指针,将目标 API 指向自定义函数。优点是稳定性高、不易触发异常;缺点是只能拦截通过导入表调用的 API,对于动态加载(GetProcAddress)无效。

2.2 Inline Hook(内联 Hook)

直接在目标函数入口处改写机器码,写入跳转指令劫持执行流。优点是通用性强,无论静态导入还是动态加载均可拦截;缺点是需要处理指令对齐、DEP/NX、CFG 等系统保护,x64 下实现复杂度高于 x86。

2.3 Frida 动态插桩

基于 Frida 框架进行运行时 Hook,无需编写 DLL 注入代码,脚本化程度高,适合快速分析与原型验证。Windows、Android、iOS 多平台通用,是目前逆向分析的主流工具链。

三、实战一:Inline Hook 拦截 CryptEncrypt

下面给出 x86 环境下 Inline Hook CryptEncrypt 的完整实现,运行后可直接打印每次加密的明文与密文。

3.1 定义函数指针与全局变量

复制代码
#include <windows.h>
#include <stdio.h>

typedef BOOL (WINAPI *CryptEncrypt_t)(
    HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final,
    DWORD dwFlags, BYTE* pbData, DWORD* pdwDataLen, DWORD dwBufLen
);

CryptEncrypt_t g_OriginalCryptEncrypt = NULL;
BYTE g_OriginalBytes[5] = {0}; // 保存原函数前5字节

3.2 Hook 代理函数

复制代码
BOOL WINAPI HookedCryptEncrypt(
    HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final,
    DWORD dwFlags, BYTE* pbData, DWORD* pdwDataLen, DWORD dwBufLen)
{
    // 调用前打印明文
    printf("[CryptEncrypt] 调用前明文长度: %d\n", *pdwDataLen);
    printf("[CryptEncrypt] 明文(Hex): ");
    for (DWORD i = 0; i < *pdwDataLen; i++) {
        printf("%02X ", pbData[i]);
    }
    printf("\n");

    // 调用原始函数
    BOOL result = g_OriginalCryptEncrypt(
        hKey, hHash, Final, dwFlags, pbData, pdwDataLen, dwBufLen
    );

    // 调用后打印密文
    if (result) {
        printf("[CryptEncrypt] 加密后密文长度: %d\n", *pdwDataLen);
        printf("[CryptEncrypt] 密文(Hex): ");
        for (DWORD i = 0; i < *pdwDataLen; i++) {
            printf("%02X ", pbData[i]);
        }
        printf("\n------------------------\n");
    }

    return result;
}

3.3 安装 Inline Hook

复制代码
void InstallCryptEncryptHook()
{
    HMODULE hAdvapi = LoadLibraryA("advapi32.dll");
    g_OriginalCryptEncrypt = (CryptEncrypt_t)GetProcAddress(hAdvapi, "CryptEncrypt");

    DWORD oldProtect;
    VirtualProtect(g_OriginalCryptEncrypt, 5, PAGE_EXECUTE_READWRITE, &oldProtect);

    // 保存原始5字节
    memcpy(g_OriginalBytes, g_OriginalCryptEncrypt, 5);

    // 构造 JMP 指令: E9 + 相对偏移
    BYTE jmpCode[5] = { 0xE9, 0, 0, 0, 0 };
    DWORD offset = (DWORD)HookedCryptEncrypt - (DWORD)g_OriginalCryptEncrypt - 5;
    memcpy(jmpCode + 1, &offset, 4);

    // 写入跳转
    memcpy(g_OriginalCryptEncrypt, jmpCode, 5);

    VirtualProtect(g_OriginalCryptEncrypt, 5, oldProtect, &oldProtect);
}

将上述代码编译为 DLL,通过远程线程注入目标进程后,所有 CryptEncrypt 调用都会被截获,明文与密文直接输出到控制台。

注:x64 环境下不能使用 5 字节相对跳转,需改用 14 字节绝对跳转(mov rax, addr; jmp rax),并配合 Trampoline 跳板执行被覆盖的原始指令。

四、实战二:Frida Hook 一键提取加密参数

对于快速分析场景,Frida 是效率最高的方案。以下脚本可直接 Hook advapi32.dll 中的 CryptEncrypt,并打印明文、密文与密钥信息:

复制代码
// crypto_hook.js
const CryptEncrypt = Module.findExportByName("advapi32.dll", "CryptEncrypt");

Interceptor.attach(CryptEncrypt, {
    onEnter: function (args) {
        this.hKey = args[0];
        this.pbData = args[4];
        this.pdwDataLen = args[5];
        this.dataLen = this.pdwDataLen.readU32();

        console.log("\n[+] CryptEncrypt 被调用");
        console.log("    密钥句柄:", this.hKey);
        console.log("    明文长度:", this.dataLen);
        console.log("    明文(Hex):", hexdump(this.pbData, { length: this.dataLen }));
    },
    onLeave: function (retval) {
        if (retval.toInt32() !== 0) {
            const outLen = this.pdwDataLen.readU32();
            console.log("    密文长度:", outLen);
            console.log("    密文(Hex):", hexdump(this.pbData, { length: outLen }));
        }
    }
});

执行命令:

复制代码
frida -p <目标PID> -l crypto_hook.js

除了 CryptEncrypt,只需替换函数名即可批量 Hook CryptDecryptCryptHashDataCryptImportKey 等接口,形成完整的加密监控面板。开源工具 VMP_Hunter 就是基于这一思路实现的全量 Crypto API 拦截器GitHub。

五、进阶:密钥与 IV 的提取

只截获明文密文还不够,完整还原加密算法需要拿到密钥与 IV。

5.1 Hook CryptImportKey 提取原始密钥

当程序调用 CryptImportKey 导入密钥时,pbData 参数即为密钥明文(含 BLOB 头)。Hook 该函数可直接拿到原始密钥字节。

5.2 Hook CryptSetKeyParam 获取 IV

CryptSetKeyParamKP_IV 参数用于设置初始化向量。Hook 时判断 dwParam 值是否为 KP_IV(值为 1),即可捕获 IV 数据。

5.3 通过密钥句柄反查密钥

对于 CryptGenKey 生成的密钥,可以在 CryptEncrypt 被调用时,使用 CryptGetKeyParam 读取 KP_BLOCKLENKP_ALGID 等参数判断算法类型,配合 CryptExportKey 导出密钥 BLOB。

六、BCrypt(CNG)接口的 Hook

新一代 CNG 接口参数更规范,Hook 收益更高。以 BCryptEncrypt 为例:

复制代码
const BCryptEncrypt = Module.findExportByName("bcrypt.dll", "BCryptEncrypt");

Interceptor.attach(BCryptEncrypt, {
    onEnter: function (args) {
        this.hKey = args[0];
        this.pbInput = args[1];
        this.cbInput = args[2].toInt32();
        this.pbOutput = args[4];
        this.pcbResult = args[6];

        console.log("[BCryptEncrypt] 输入长度:", this.cbInput);
        console.log(hexdump(this.pbInput, { length: this.cbInput }));
    },
    onLeave: function (retval) {
        if (retval.toInt32() === 0) {
            const outLen = this.pcbResult.readU32();
            console.log("[BCryptEncrypt] 输出长度:", outLen);
            console.log(hexdump(this.pbOutput, { length: outLen }));
        }
    }
});

CNG 接口还可以通过 BCryptGenerateSymmetricKeypbKeyObject 缓冲区直接提取密钥原始字节,比传统 CryptoAPI 更加直观。

七、常见问题与对抗手段

7.1 Hook 不触发的原因

  • 程序没有走系统 API,而是自带了加密库(如 OpenSSL、mbedTLS)
  • 使用了动态 LoadLibrary + GetProcAddress,IAT Hook 失效
  • 程序在 Hook 安装前就完成了加密调用并缓存了结果
  • 目标程序运行在 WoW64 模式下,Hook 了错误位数的 DLL

7.2 反 Hook 与应对

部分保护壳会校验系统 API 入口处的哈希,检测到修改后直接退出。应对思路:

  • 改用深层 Hook,Hook CSP 提供者内部函数而非 advapi32 导出层
  • 使用硬件断点(Drx 寄存器)替代指令改写
  • Hook 底层内核接口(如 NtQueryInformationProcess 反调试对抗)
  • 使用 Frida 的 Interceptor.replace 配合 Stalker 追踪

八、总结

Hook Crypto API 是逆向分析中性价比极高的技术手段 ------ 不需要理解复杂算法,不需要还原混淆代码,只需要在系统接口层 "搭个偷听装置",就能拿到所有关键参数。

对于快速验证,优先使用 Frida 脚本,几分钟内即可搭建完整的加密监控环境;对于需要集成到工具中的稳定方案,推荐使用 Inline Hook 并做好 x86/x64 兼容;如果目标程序仅通过导入表调用 API,IAT Hook 是最稳定、最隐蔽的选择。

在实际逆向工作中,建议从 CryptEncrypt / BCryptEncrypt 入手,先确认明文密文对应关系,再逐步扩展到密钥生成、哈希计算与数据签名接口,最终还原完整的加密链路。

相关推荐
瓦学妹1 天前
Wikimon Scraper:用 Python 构建数据爬虫
开发语言·爬虫·python
小白学大数据2 天前
长周期爬虫的数据一致性:断点续爬 + 事务回滚保障采集质量
开发语言·爬虫·测试工具
跨境旺仔小拳头2 天前
Wikimon Scraper:用 Python 构建数据爬虫
数据库·爬虫·python
鬼手点金2 天前
Scrapy 网络爬虫框架
爬虫·python·scrapy·ajax·html·json·requsts
正在走向自律2 天前
WorkBuddy AI工具使用介绍完全指南
人工智能·爬虫·ai编程·workbuddy·ai的力量
雨晨源码(同名B站)2 天前
基于Python的网易云音乐评论数据情感化分析系统 音乐爬虫信息可视化 |SnowNLP评论情感分析
开发语言·hadoop·爬虫·python·信息可视化·毕业设计
鬼手点金2 天前
Scrapy + Playwright 完整示例(JS 动态渲染网页)
开发语言·javascript·爬虫·python·scrapy·html·json
zhougl9963 天前
Java Python 爬虫开发比较
java·爬虫·python
zhougl9963 天前
Java Spring Boot 爬虫技术全面介绍
java·spring boot·爬虫