模块 51 - 字符串哈希 (String Hashing)
恶意软件开发课程 - 字符串哈希 (String Hashing)
模块 51 - 字符串哈希 (String Hashing)#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
字符串哈希#
简介#
哈希 (Hashing, 散列) 是一种用于创建一段数据的固定大小表示的技术,称为哈希值或哈希码。哈希算法被设计为单向函数,这意味着从计算角度来看,根据哈希值确定原始输入数据是不可行的。哈希码通常尺寸更小,处理速度更快。在比较字符串时,使用哈希可以快速判断两个字符串是否相等,尤其是当字符串很长时,这比直接比较字符串本身要快得多。
在恶意软件开发中,字符串哈希是隐藏实现中所用字符串的一种非常有效的方法。因为明文字符串往往会被安全厂商用作指纹特征(Signatures)来检测恶意二进制文件。
💡 初学者提示:为什么要用哈希?
想象一下,杀毒软件就像是一个拿着“黑名单”的保安。如果你的代码里写着
CreateRemoteThread这样的明文,保安一眼就能认出来并由于它在名单上而拦截你。但是,如果你把这个名字变成了
0x7A1B2C3D(它的哈希值),保安看了一眼,发现它不在黑名单里。只有你的程序内部知道这个数字代表CreateRemoteThread。这就达到了隐匿意图的目的。
常见的字符串哈希算法#
本模块介绍了几种字符串哈希算法。需要明确的是,这些算法的输出通常是一个以十六进制格式表示的数字,因为它更整洁、更紧凑。本模块将讨论以下算法:
- Djb2
- JenkinsOneAtATime32Bit
- LoseLose
- Rotr32
除了本模块讨论的算法外,还有许多其他的算法,其中一些可以在 VX-API GitHub 仓库 ↗ 中找到。
Djb2#
Djb2 是一种简单且快速的哈希算法,主要用于为字符串生成哈希值,但也适用于其他类型的数据。它通过遍历输入字符串中的字符,并根据特定算法使用每个字符更新运行中的哈希值。
算法核心逻辑:
hash = ((hash << 5) + hash) + cc其中 hash 是当前哈希值,c 是输入字符串中的当前字符,<< 是按位左移运算符。
Djb2 以产生良好的哈希值分布而闻名,不同字符串之间发生哈希碰撞(即不同输入产生相同输出)的概率很低。
以下是来自 VX-API ↗ 的实现方案:
#define INITIAL_HASH 3731 // 随机初始值,增加随机性
#define INITIAL_SEED 7
// 为 ASCII 字符串生成 Djb2 哈希
DWORD HashStringDjb2A(_In_ PCHAR String)
{
ULONG Hash = INITIAL_HASH;
INT c;
while (c = *String++)
Hash = ((Hash << INITIAL_SEED) + Hash) + c;
return Hash;
}
// 为宽字符 (Wide-character) 字符串生成 Djb2 哈希
DWORD HashStringDjb2W(_In_ PWCHAR String)
{
ULONG Hash = INITIAL_HASH;
INT c;
while (c = *String++)
Hash = ((Hash << INITIAL_SEED) + Hash) + c;
return Hash;
}cJenkinsOneAtATime32Bit#
JenkinsOneAtATime32Bit 算法通过遍历输入字符串的字符并根据每个字符的值增量更新哈希值来工作。
算法核心逻辑:
hash += c;
hash += (hash << 10);
hash ^= (hash >> 6);c该算法产生的 32 位整数具有非常优秀的分布特性。
实现方案:
#define INITIAL_SEED 7
// 为 ASCII 字符串生成 Jenkins 哈希
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenA(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}cLoseLose#
LoseLose 算法通过遍历字符串中的每个字符并对每个字符的 ASCII 值求和来计算哈希值。正如其名,这是一个非常基础且容易发生碰撞的算法。但在恶意软件中,为了稍微增加随机性,通常会进行一些修改。
算法演进:
// 原始版本(极易碰撞)
hash += c;
// 改进版本(略微增加随机性)
hash += c;
hash *= c + 2; c实现方案:
#define INITIAL_SEED 2
DWORD HashStringLoseLoseA(_In_ PCHAR String)
{
ULONG Hash = 0;
INT c;
while (c = *String++) {
Hash += c;
Hash *= c + INITIAL_SEED; // 更新逻辑
}
return Hash;
}cRotr32#
Rotr32 (Rotate Right 32-bit) 算法在累加字符 ASCII 值的过程中运用了按位右移操作。
实现方案:
#define INITIAL_SEED 5
// 执行按位循环右移的辅助函数
UINT32 HashStringRotr32Sub(UINT32 Value, UINT Count)
{
DWORD Mask = (CHAR_BIT * sizeof(Value) - 1);
Count &= Mask;
#pragma warning( push )
#pragma warning( disable : 4146)
return (Value >> Count) | (Value << ((-Count) & Mask));
#pragma warning( pop )
}
INT HashStringRotr32A(_In_ PCHAR String)
{
INT Value = 0;
for (INT Index = 0; Index < lstrlenA(String); Index++)
Value = String[Index] + HashStringRotr32Sub(Value, INITIAL_SEED);
return Value;
}c栈字符串 (Stack Strings)#
在 C/C++ 中,字符串可以表示为字符数组,从而将各个字符彼此分离,这有助于规避基于字符串的检测。例如,字符串 “hello world” 可以表示为:
char string[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' };c如果在十六进制编辑器(如 HxD)中搜索 “hello world”,由于内存中字符是分散存储的,将搜索不到任何结果。

💡 初学者提示:栈字符串的局限性
虽然栈字符串能躲过简单的字符搜索,但高级调试器和分析工具(如 IDA Pro)通常有专门的插件来自动识别并合并这些字符。因此,字符串哈希 仍然是目前最通用且隐蔽的方案。
演示#
下面展示了使用本模块提到的算法对字符串 “MaldevAcademy” 进行哈希的结果。

🎯 总结#
在本模块中,我们学习了:
- 哈希的原理:理解了如何将长字符串转换为独一无二的数字标签。
- 规避技术:了解了哈希如何通过消除明文特征来对抗杀毒软件。
- 算法多样性:掌握了 Djb2, Jenkins 等几种经典的字符串哈希算法及其实现。
💡 关键要点
- 哈希是不可逆的:你可以从名字得到数字,但几乎无法从数字反推回名字。
- 在红队开发中,由于哈希算法多种多样,甚至可以自己稍微修改(如更改
INITIAL_SEED),这让防御者很难建立通用的哈希匹配库。
📚 下一步学习
下一个模块将介绍 IAT Hiding & Obfuscation - Introduction。我们将开始学习如何利用今天学到的字符串哈希技术,在内存中动态寻找函数地址,从而彻底在 PE 导入表中隐藏我们的意图!