Hancitor装载器样本分析:从投递链到C2协议还原实战

发布时间:2026/10/17 12:47:41
Hancitor装载器样本分析:从投递链到C2协议还原实战 上午刚到工位内网邮件网关就弹了告警某供应商邮箱发来一封带Word附件的发票邮件标题是常见的“Invoice #23-887”。附件落下来一测本地样本库直接命中一个老熟人——Hancitor。这类装载型木马在攻击链里的定位相当特殊它本身不偷密码也不加密文件更像“快递员”把真正干活的下一阶段恶意代码送到受害主机顺便搭好一条长期回连通道。这轮分析我从样本落地一路做到C2协议还原中间踩了几个不算少见的坑把过程完整翻出来适合刚入门恶意代码分析的人参考也适合做威胁狩猎的蓝队同学拿来对思路。写这篇文章之前先说明白分析目标是搞清楚它是什么、怎么进来、在机器上干了什么、还会连去哪里而不是单纯为了生成一个“Hancitor家族”的标签。安全行业里有个很不好的习惯拿到哈希查几眼关联报告就急着下结论。真正的价值在于把静态特征、动态行为和网络协议连成一条完整的攻击链最后能产出可复用的检测规则这才叫分析。1. 项目概述与设计思路1.1 装载器在攻击链中的角色很多朋友混淆过装载器Loader和下载器DownloaderHancitor就是个典型例子。它通常通过钓鱼邮件投递附件是带宏的Office文档文档打开后诱导用户启用宏宏再释放并执行一段VBScript或直接释放一个PE。这个PE就是第一个真正的核心载荷也就是我们这次要拆的样本本体。它的任务很单纯侦查当前环境、判断是否在虚拟机里、通过WMI查一下有没有杀软、然后连C2拉取后续阶段。换句话说它负责给“箱子”开锁箱子里装的是窃密木马还是勒索组件取决于攻击者当时投放的战役日期。理解这层关系很重要因为你看到Hancitor的C2流量时不能用“窃密”或“勒索”去概括它它只是运输层真正的目的在第二阶段。从学习角度来说Hancitor是一个非常好的入门样本。它不像某些APT样本那样堆满花指令和混淆壳但也保留了完整的反沙箱检测、运行时字符串拼接、进程注入、C2心跳机制难度刚好卡在“看得懂”和“要动手”的交界线上。拆完这一个样本后面再去碰更复杂的商业远程访问木马心智负担会小很多。1.2 分析目标与路线图动手之前我给自己定了三条目标。第一确认投递链和载荷类型搞清楚宏文档到最终PE之间到底过手了几层。第二还原C2协议能拿到域名、URI路径、UA头、POST体格式这些关键要素。第三输出可执行的检测逻辑不管是YARA规则、流量特征还是行为规则都行。分析路线我习惯按“先静后动再挖微观”来走。静态侦察用file、sha256sum、pefile快速确认文件类型和PE结构动态分析进隔离虚拟机用进程监控和网络抓包记录行为最后回到调试器里做脱壳和汇编级的协议还原。顺序不能乱很多人一上来就开调试器结果壳都还没找着样本已经退出了浪费时间。这里有个经验动态行为观察阶段我建议先不急着看单条API而是把进程树截图、文件落盘、注册表写操作、网络请求放到一张时间轴上。很多家族都有自己固定的行为序列比如Hancitor典型的“先查杀软再连C2”光凭这一条顺序就能在无文件攻击场景里把它和普通办公宏病毒区分开。2. 样本落地与静态侦察2.1 样本获取与哈希锁定从告警邮件里提取附件时先不要直接用办公软件打开。Windows上默认的“预览窗格”都有可能触发宏逻辑正确做法是把附件下载到隔离的Linux分析机里先用命令行看一眼文件头。$ file invoice_23-887.doc invoice_23-887.doc: Composite Document File V2, Microsoft Word document $ sha256sum invoice_23-887.doc 4c1e2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2确认是OLE格式之后把哈希往本地威胁情报平台和开源沙箱里查了一遍。查询结果里确实挂了“Hancitor”的家族标签但说实话这个信息只能当参考不能当证据。恶意代码分析里有个常见误区同一个壳或同一个加载器可能被好几个家族借用哈希标签经常张冠李戴。哈希锁定后的第一件事是保存原始样本的几份副本放在带写保护的目录里。分析过程中样本可能自毁或伪装留一份初始副本是后面所有调试的基础。我见过有人分析到一半样本被覆盖前面的静态证据全部作废只能从头再来。2.2 区段、导入表与熵值三个快速信号用pefile快速跑一下这个样本的PE结构import pefile pe pefile.PE(invoice_23-887.doc_extracted.exe, fast_loadFalse) for sec in pe.sections: print(sec.Name.decode(errorsignore), hex(sec.SizeOfRawData), round(sec.get_entropy(), 2))输出显示.text熵值约6.4.data区段熵值高达7.9。看到这个数字基本可以判断代码本身没加特别复杂的壳但数据区里塞过加密或压缩后的载荷。通常一个干净的PE数据区熵在5左右超过7.0就要高度警惕。再翻导入表函数列表很短集中在kernel32的CreateFileW、VirtualAlloc、VirtualProtect还有wininet的InternetOpenW、InternetReadFileadvapi32里能看到CryptStringToBinary。这几个函数拼在一起就是典型的加载器特征分配内存、联网下载、然后对拿到的数据做一次解码。用Detect It Easy之类的开源查壳工具扫了一遍没有报出UPX、ASP等知名壳但这不代表没有混淆。再看strings输出里面能看到很多不成句的碎片比如http后面接着奇怪字符oint和tech.com拆成三段放在不同偏移位置。这就说明C2地址大概率是运行时动态拼接的静态提取域名这步在早期注定拿不到完整结果。2.3 字符串层面发现的关键TTP先用最朴素的手法筛一遍字符串strings -n 8 invoice_23-887.doc_extracted.exe | grep -Ei http|SELECT|Run|cmd|powershell|WMI结果里最扎眼的是这句WMI查询SELECT * FROM AntiVirusProduct这是典型的杀软探测行为。木马在回连之前先问一句“这台机器装了哪些保安”如果查询结果为空或者数量异常它就知道自己大概率在分析环境里直接退出。很多人在分析Hancitor变种时遇到“跑了十几秒就消失”的情况原因往往就是这个。除了WMI查询字符串里还能看到两个注册表路径一个指向CurrentVersion\Run另一个指向CurrentVersion\RunOnce。这说明样本有常规的持久化意图只不过具体执行时机可能要等到后续载荷落地之后。RunOnce这个点很多人会忽略它比Run更加隐蔽因为程序执行后键值会被系统自动删除取证时容易被当成“没有写启动项”。这一步拿到的东西已经足够支撑动态分析的方向重点关注WMI查询行为、联网时机、以及%APPDATA%或%TEMP%下的新落盘文件。3. 动态行为与进程链追踪3.1 分析环境自建隔离Windows虚拟机动态分析不是把样本丢进公共沙箱就完事的公共沙箱的结果只能用来交叉验证真正还原行为还得靠自己的环境。我用的是快照式虚拟机方案Windows 10 x642个逻辑CPU4GB内存磁盘40GB安装完常用工具后立即打一个干净快照。分析机上的网络配置是整个环境的关键。我的做法是虚拟机网络选择“仅主机模式”宿主机上跑一个虚拟DNS和一个模拟HTTP服务。虚拟DNS把样本回连时解析出来的域名全部指向127.0.0.1模拟HTTP服务则负责接收样本的请求并返回预设响应。这样既能让样本正常“走完流程”又不会把真正的恶意C2流量放到真实外网。工具方面我会提前装好三件套调试器、进程监控工具、网络抓包工具。进程监控方面我个人习惯用Sysinternals的Process Monitor网络抓包用Wireshark的轻量模式调试器看目标架构再选。有一个细节容易翻车一定要先关闭Windows自带的自动防护不然样本刚释放出第二层载荷就被隔离了后面全白干。3.2 进程行为画像样本双击运行之后Process Monitor立刻给了完整的时间线。开场动作是短时间循环调用GetCursorPos大约10秒内没有检测到鼠标移动就触发ExitProcess。这是非常经典的反沙箱手法很多自动化沙箱不具备模拟鼠标移动的能力。鼠标检测通过之后样本开始查询WMI紧接着在%TEMP%下释放了一个文件命名伪装成了sysinfo.dat。随后它没有直接唤起新的可执行文件窗口而是先断开自身用创建挂起进程的方式拉起一个具备白名单身份的常规进程把释放出来的数据写入这个进程的内存空间最后通过APC或远程线程触发执行。进程树大致长这样invoice_23-887.doc_extracted.exe └─ cmd.exe └─ powershell.exe -nop -w hidden -enc BASE64... └─ rundll32.exe看到PowerShell一条线先别急着判断为无文件攻击。这个样本的PowerShell只是用来中转一部分载荷逻辑真正的加载动作还是在本地落地文件中完成的。把进程树、文件落点和网络请求放在一张时间轴上就能清楚看到每一层之间的传递关系。3.3 注入链与关键API还原用调试器附加到目标进程在几个关键API上打端点场景就很清晰了。这个样本在注入手法上不算特别高明但逻辑完整。阶段典型API作用申请内存VirtualAlloc/VirtualAllocEx分配具备可读可写可执行属性的内存块写入载荷WriteProcessMemory将解码后的shellcode或PE写入目标进程调整权限VirtualProtect执行前去掉写权限改为可执行触发执行QueueUserAPC/CreateRemoteThread在目标进程内拉起载荷运行这段链路里最容易忽略的是VirtualProtect。恶意代码经常先申请RWX内存这一步在多数安全产品里已经是高危信号所以现在很多木马会拆成两步先用VirtualProtect把内存属性从 RWX 改成 RX或者反过来先申请RW内存写入后再改成RX。分析时看到VirtualProtect调用参数中的内存地址基本就能找到刚才写入载荷的位置顺着地址转储出来的数据就是第二层恶意代码。内存转储之后记得马上计算哈希并且单独保存。这一步得到的第二阶段载荷哈希比原始样本哈希更有溯源价值因为它已经脱掉了第一层投递逻辑更接近后续阶段。4. 网络回连与C2协议解析4.1 先切断真实链路再做“假服务器”分析C2协议有个铁律绝不能让样本在你的分析环境里连上真实的C2服务器。一旦连上对面就可能下发一个反制指令或者把当前机器作为跳板风险不可控。我采用的方式是双管齐下第一层宿主机上写临时DNS记录把样本静态字符串里所有疑似域名全部解析到127.0.0.1第二层起一个极简的Python HTTP服务专门用来接收样本在本地80端口发出来的请求。from http.server import BaseHTTPRequestHandler, HTTPServer class FakeC2(BaseHTTPRequestHandler): def log_message(self, fmt, *args): return def do_GET(self): print([GET], self.path, flushTrue) self.send_response(404) self.end_headers() def do_POST(self): length int(self.headers.get(Content-Length, 0)) body self.rfile.read(length) print([POST], self.path, self.headers, body, flushTrue) self.send_response(200) self.end_headers() HTTPServer((127.0.0.1, 80), FakeC2).serve_forever()别小看这段代码。它打印的内容直接告诉你三件事请求路径、请求头、POST体。样本的C2域名是运行中拼接出来的静态分析阶段拿不到但只要它在代码里调用了InternetOpenW和HttpSendRequestW这个假服务器就能把它想看的东西完整记录下来。4.2 C2协议和流量特征假服务器显示的请求长这样[POST] /php/status.php?rand73491 User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1) Content-Type: application/x-www-form-urlencoded id4421ver3.2dataU2FsdGVkX1...POST体里的data字段是一段Base64编码的密文解码后能看到一段JSON结构包含机器名、操作系统版本、CPU型号和反病毒产品列表。这是样本在向C2“报到”告诉服务器这台机器是什么环境、装了哪些安全软件。响应部分更有意思。假服务器返回一个200状态码和一段看似乱码的文本样本收到后并不会直接运行而是先走一遍CryptStringToBinary做Base64解码再用一个单字节异或循环对结果做处理最后才判断这段数据能否被当成后续载荷加载。我提取了这段处理逻辑发现它的异或密钥就藏在主模块的常量区里动态调试时设置断点在异或循环的末尾往EAX寄存器里盯一眼就能看到。流量特征上也有几个值得注意的点UA头是一个很老的IE版本字符串这种UA在正常业务环境里几乎绝迹请求路径集中在/php/目录下POST体里的参数名固定是id、ver、data。这些特征组合起来写流量告警规则时可以直接锁定。4.3 多阶段Payload落地链样本拿到C2响应后会根据响应内容里的一个标志位决定下一步动作。如果标志位是“下载模块”它就会再发一次GET请求去拉取一段经过双重编码的DLL文件。落盘后的模块名没有固定格式这次我看到的是伪名为mscoree.dll的一个动态库文件放在%APPDATA%\Roaming下。这里必须提一个区别第一阶段的C2回连和后续载荷的连接不是同一个通道。第一阶段的信道主要用来传信令和一小段配置第二阶段模块内部还会带有自己独立的C2地址。所以分析时如果只盯着第一阶段那个域名很容易漏掉真正的长期驻留地址。正确做法是把第一阶段解锁后的模块继续做静态分析提取它内部的连接点信息再落到假服务器上继续跟踪。第二阶段模块的功能依然是“装载”并不负责最终窃密这符合Hancitor家族的惯例一条链路可以在这个阶段插入窃密模块也可以换成勒索组件。分析到这一步已经足够给检测规则提供素材了。5. 深入逆向加壳层与核心逻辑还原5.1 脱壳找OEP和内存转储第一层样本没有强壳但后续释放的DLL模块做了处理外壳逻辑虽然不算复杂却足以让静态分析工具看不出入口点。我使用调试器把模块装载起来在VirtualProtect上打一个条件断点条件是地址属于模块主体区段。壳层在做完内存解压后会调用VirtualProtect把当前内存页属性从可写改为可执行这就是“最后一步”的信号。此时看调用栈返回地址直接指向原始入口点OEP。在OEP位置下断点等程序跑到这里用调试器的批量转储功能把当前进程内存镜像抓下来再配合自动修复导入表工具就能得到一个基本可静态分析的PE文件。脱壳不是终点只是二次分析的起点。转储后文件依然可能带有很多无效数据和被抽空的导入表直接用IDA打开会看到一堆解析不出来的引用。所以转储后第一步永远是修复导入表让反编译器能够把API调用关系映射出来。5.2 从汇编还原URL构造器脱壳后在IDA里找到对InternetConnectA的交叉引用很快就能定位到一个专门负责拼URL的函数。这个函数的逻辑比较有代表性它不是简单地把字符串首尾相连而是先从一个结构体里读取三段碎片再用一个自定义字母表的Base64解码器做一次转换最后才拼出完整的域名。伪造的伪代码大致是这样char* build_url() { char* frag1 get_config_value(d1); char* frag2 get_config_value(d2); char* frag3 get_config_value(d3); char* raw concat(frag1, /, frag2, /, frag3); decode_custom_base64(raw); // 换过字母表的base64 xor_decode(raw, 0x4C); // 单字节异或 return raw; }如果你拿到样本后直接拿Python标准库的base64.b64decode去解这段字符串大概率会得到一堆乱码。原因就是这里用了自定义字母表。在IDA里搜索64字节的常量表与标准Base64字母表逐位对比很容易发现差异。这种“看似标准但不标准”的编码在恶意代码里非常常见静态提取时一定要留意解码器实现。还原URL构造器之后把它跑一遍就能得到真实的回调域名。这个域名再配上前面的假DNS规则就能在完全隔离的环境里继续跑后续阶段。5.3 命令分发与状态机第二阶段模块的C2回连逻辑是一个典型的状态机。主循环读入C2响应按响应体第一个字节作为命令号跳进对应的switch分支执行命令值动作0x01更新心跳间隔0x02下载并执行新的载荷0x03执行系统命令并回传结果0x04上传指定文件到C2这个结构几乎是所有装载器的通用模板。实际分析时不需要把每个分支的细节都读完优先看0x02下载分支和0x03命令执行分支就够了它们体现了攻击者最可能用到的能力。0x01心跳间隔分支简单记录一下即可0x04上传分支需要关注它读取的文件路径那往往暗示攻击者对目标数据的位置有预判。在汇编层面还原状态机时我的经验是先找主循环再用交叉引用确认所有分支的入口都汇聚在一个比较指令附近。从比较指令往上看能得到命令号对应的比较值直接做成上面这张表。不要把时间花在梳理每一行汇编上抓住结构再对照哈希值效率最高。6. 常见问题与排查技巧实录6.1 反沙箱和“假死”怎么破分析Hancitor时遇到最多的现象就是“跑了十几秒然后消失”。原因往往是样本的鼠标移动检测或屏幕分辨率检测。解决方案分两步一是给虚拟机配置2个以上CPU、4GB以上内存、屏幕分辨率设置到1024x768以上二是在宿主上跑一个鼠标模拟脚本每隔几秒发送一次鼠标移动事件让GetCursorPos检测到变化。如果断点在调试器里看到了ExitProcess调用也可以直接在入口处修改比较跳转条件跳过反沙箱分支。不过改跳转条件有个副作用样本后续可能依赖某个被跳过的环境变量容易造成行为缺失。优先用环境模拟改代码只是备选方案。6.2 C2总是解析不出来怎么办有时假服务器等了五分钟却一个请求都没收到原因基本都在样本里。这个家族习惯用运行时拼接的方式生成域名静态字符串根本看不出完整地址。此时不要急着开Wireshark乱抓而是在调试器里直接对InternetConnectA和InternetOpenA打断点断下来后读栈上的参数完整URL立刻出现在眼前。如果你已经拿到了域名但样本不回连检查一下假DNS配置是否指向了正确的IP。很多Windows API对URL解析会同时查IPv4和IPv6导致样本先走IPv6走了半天没出结果。把宿主机IPv6临时关闭问题通常直接消失。6.3 样本识别别太依赖公开标签公共沙箱和情报平台给的“Hancitor”标签仅供参考不能当作分析结论。我见过同一个加载器被冠上四五种家族名的案例因为很多恶意代码都复用公开的构建套件或加壳模块。正确做法是收集本次样本独有的证据集WMI查询特征、C2 URI路径、POST体结构、异或密钥、第二阶段模块哈希把这些组合成一个“行为指纹”再判断它到底属于哪个家族。如果只看标签你可能会把分析重点放在某个窃密模块上而这个样本根本不会拉取窃密模块。方向偏了后面全盘皆输。6.4 分析结果归档的必备清单一次完整的装载器分析结束后归档清单至少包含五类内容原始样本及其哈希、提取出来的网络标识域名、URI、UA、POST格式、进程注入链、第二阶段模块哈希、以及检测规则。我习惯把它们整理成一个表格后续不管是写报告还是做规则落地都能直接提取。阶段关键证据工具静态高熵data区段、导入表、WMI字符串pefile、DIE、floss行为WMI查询、%TEMP%释放、进程注入Process Monitor、x64dbg网络/php/目录、POST体格式、UA头Wireshark、假C2服务器逆向自定义Base64、异或密钥、命令分发表IDA、调试器最后分享一个实操习惯分析这类“快递员”型样本时我会在第二阶段模块落地之后单独再跑一轮完整的行为分析而不是只盯着第一阶段。真正的财产损失往往藏在第二层甚至第三层载荷里装载器本身的行为只是“前菜”。多花半小时跟踪第二层的落地和执行就等于替后面的应急响应团队提前踩了一遍雷。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询