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 CryptDecrypt、CryptHashData、CryptImportKey 等接口,形成完整的加密监控面板。开源工具 VMP_Hunter 就是基于这一思路实现的全量 Crypto API 拦截器GitHub。

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

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

5.1 Hook CryptImportKey 提取原始密钥

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

5.2 Hook CryptSetKeyParam 获取 IV

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

5.3 通过密钥句柄反查密钥

对于 CryptGenKey 生成的密钥,可以在 CryptEncrypt 被调用时,使用 CryptGetKeyParam 读取 KP_BLOCKLEN、KP_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 接口还可以通过 BCryptGenerateSymmetricKey 的 pbKeyObject 缓冲区直接提取密钥原始字节,比传统 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 入手,先确认明文密文对应关系,再逐步扩展到密钥生成、哈希计算与数据签名接口,最终还原完整的加密链路。

相关推荐
xyzzklk2 天前
在Open-WebUI中提升向量化速度
网络·人工智能·爬虫·ai·语言模型·自然语言处理·安卓
严肃小白兔2 天前
Cloudflare 开始区分 AI 爬虫与个人 Agent:网站访问控制应该怎样设计?
人工智能·爬虫
傻啦嘿哟5 天前
高德地图POI爬虫:爬取商户与景点数据,做LBS应用
爬虫
weixin199701080165 天前
官方授权通道和灰色爬虫,做数据服务该选哪条路(附 python 源码)
c++·爬虫·python
正飞信息技术6 天前
llms.txt部署指南:给AI爬虫一张清晰的网站地图
人工智能·爬虫
NeoGressAI外贸数字化8 天前
外贸独立站robots.txt怎么写:6个AI爬虫放行规则
人工智能·爬虫
OnlineProxy8 天前
移动应用数据爬取:通过模拟器与流量嗅探对 API 进行逆向工程
爬虫·python·httpx
傻啦嘿哟8 天前
分布式爬虫架构:Redis+Scrapy打造企业级爬虫系统
分布式·爬虫·架构
梯度下降者9 天前
零基础入门Python 爬虫
爬虫·python·数据分析·入门指南·webcrawler
2601_967979129 天前
0 到 1 学爬虫:用 Python 爬取网页数据指南一(附实战案例)
爬虫·python·实战案例·html解析·网页数据