0%
毅种循环

模块 57 - IAT 隐藏与混淆 - 编译时 API 哈希 (Compile Time API Hashing)

恶意软件开发课程 - IAT 隐藏与混淆 - 编译时 API 哈希 (Compile Time API Hashing)


模块 57 - IAT 隐藏与混淆 - 编译时 API 哈希 (Compile Time API Hashing)#

[!IMPORTANT] 本知识库声明

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

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

IAT 隐藏与混淆 - 编译时 API 哈希#

简介#

在之前的 API 哈希模块中,我们需要在将函数和模块添加到代码之前手动生成它们的哈希值。遗憾的是,这非常耗时。通过使用 编译时 API 哈希 (Compile Time API Hashing),我们可以完全避免这一繁琐步骤。

此外,在之前的模块中,哈希值是硬编码的。如果不在每次实现中手动更新,安全解决方案可能会将这些固定哈希值作为 IoC(入侵指标) 进行拦截。而使用编译时 API 哈希,每次编译二进制文件时都会自动生成全新的动态哈希值。

前提条件 (Caveat)#

此方法利用了 C++ 的 constexpr 关键字,因此 仅适用于 C++ 项目constexpr 运算符用于指示函数或变量可以在编译时求值。此外,它还能通过在编译期间执行计算来提高应用程序的运行效率,减少运行时的开销。

编译时哈希实现指南#

下面将逐步介绍实现编译时哈希所需的步骤。

1. 创建编译时哈希函数#

首先,需要使用 constexpr 运算符将现有的哈希函数转换为编译时函数。在这里,我们将修改 Djb2 算法。

其中 g_KEY 是一个全局 constexpr 变量,它是哈希计算的初始种子。为了确保每次编译产生的哈希都不同,我们将使用下文提到的 RandomCompileTimeSeed 函数生成它。

2. 生成随机种子值#

RandomCompileTimeSeed 利用 C++ 预定义的 __TIME__ 宏(格式为 HH:MM:SS)来提取当前时间的数字,并通过数学运算产生一个随机种子。由于每次编译时 __TIME__ 都在变化,因此生成的哈希也会随之改变。

// 在编译时生成一个随机键,作为哈希的初始种子
constexpr int RandomCompileTimeSeed(void)
{
	return '0' * -40271 +
		__TIME__[7] * 1 +
		__TIME__[6] * 10 +
		__TIME__[4] * 60 +
		__TIME__[3] * 600 +
		__TIME__[1] * 3600 +
		__TIME__[0] * 36000;
};

// 编译时随机种子
constexpr auto g_KEY = RandomCompileTimeSeed() % 0xFF;
cpp

3. 创建辅助宏#

我们需要两组宏:

  • 运行阶段宏 (RTIME_...):在程序运行过程中,为 GetProcAddressH 动态计算目标函数的哈希。
  • 编译阶段宏 (CTIME_...):利用编译器直接计算出预期的哈希常量,并定义为变量。
// 运行时计算哈希的宏
#define RTIME_HASHA( API ) HashStringDjb2A((const char*) API)
#define RTIME_HASHW( API ) HashStringDjb2W((const wchar_t*) API)

// 编译时生成常量的宏 (底层利用了 # 和 ## 运算符)
#define CTIME_HASHA( API ) constexpr auto API##_Rotr32A = HashStringDjb2A((const char*) #API);
#define CTIME_HASHW( API ) constexpr auto API##_Rotr32W = HashStringDjb2W((const wchar_t*) L#API);
cpp

💡 知识扩展:预处理器运算符

  • 字符串化运算符 (#):将宏参数包装成双引号字符串。例如 #MessageBoxA 会变成 "MessageBoxA"
  • 合并运算符 (##):将两个标记粘合在一起。例如 MessageBoxA ## _Rotr32A 会变成变量名 MessageBoxA_Rotr32A

宏展开演示#

调用 CTIME_HASHA(MessageBoxA) 后,编译器实际上会执行如下操作: constexpr auto MessageBoxA_Rotr32A = HashStringDjb2A("MessageBoxA"); 这将导致 MessageBoxA_Rotr32A 变量持有在编译时就已经计算好的、独特的哈希数字。

图片
图片

完整实现代码#

演示与验证#

特征检查#

使用 Strings.exe 搜索,发现二进制中没有任何 “MessageBox” 字符串。 使用 Dumpbin 检查 IAT,同样没有任何可疑导入。

动态哈希验证#

如下图所示,我们在同一份源码上进行两次编译。由于 __TIME__ 不同,生成的哈希值(g_KEY)完全不同。这让基于固定哈希特征的检测方案(如侦测特定的 Djb2 结果)彻底失效。

图片
图片
图片
图片


🎯 总结#

在本模块中,我们学习了:

  1. constexpr 的威力:理解了如何将繁重且危险的哈希计算提前到编译阶段完成。
  2. 动态哈希躲避:学会了利用时间戳作为种子,让每一份编译出的恶意软件都具有“独特”的数字指纹。
  3. 开发效率提升:不再需要任何外部脚本来计算哈希,极大地方便了大型项目的开发。

💡 关键要点

  • C++ 专用:请记住,如果你坚持使用纯 C 编译器(.c 文件),constexpr 将不可用。你需要将文件后缀改为 .cpp
  • 灵活性:你可以随意更换 RandomCompileTimeSeed 的算法,让哈希生成的规律更加难以捉摸。

📚 下一步学习

下一个模块将开启全新的篇章:API Hooking - Introduction。我们将学习如何从“躲避者”转变为“拦截者”,监控并修改系统的行为!