初学者如何分析真实的恶意样本?Hancitor 实战全流程记录

https://xz.aliyun.com/news/91835

恶意软件分析目标

在分析二进制文件时我们要寻找什么?

这个问题很重要,因为在分析恶意软件时有许多可能的目标和方面需要考虑。然而,在实际调查中,还有其他重要领域与恶意软件分析相关,当然,我们在分析的所有阶段都应该考虑它们:

a. 内存分析:这是一种极其强大的技术,在过去十年中已证明其无限价值,并且在调查期间用作了解恶意软件感染事件、其后果、副作用的首要方法,还能够获取大量可能难以从磁盘或任何其他来源收集的证据。

b. 网络分析:这是一种非常有用的资源(例如pcap文件),通过流量分析来理解和检测未经授权的通信(C2 – 命令与控制通道),并进行工件收集(例如二进制文件、恶意文档和Cobalt Strike信标)。

c. 文件系统/磁盘分析:任何调查的最后前沿,我们可以在其中分析和检测对手入侵、违规、欺诈、泄露以及当然还有恶意软件感染的副作用。

样本文件信息

(SHA256) 8ff43b6ddf6243bd5ee073f9987920fa223809f589d151d7e438fd8cc08ce292

样本下载:MalwareBazaar | Download malware samples

微步情报中心可以搜索到,这是2021年的恶意样本了,时间也很长了

d2b5ca33bd20260409210547

目标恶意软件似乎来自 Hancitor 家族。

它使用 EnumerateProcesses() 函数,因此了解是否有任何特殊原因(例如代码注入);

WriteProcessMemory() 的触发方式与我们通常在解包过程和代码注入中看到的那样,因此这里没有消息是个好消息。

当我们再去了解这个家族基本能确定就是 c2 类型远控木马了。
使用PE-bear查看区段,发现.data 段中的原始大小和虚拟大小相差很大,可能存在加壳。

d2b5ca33bd20260409210710

使用DIE查看信息熵可以看到,也是有可能存在加壳的。

d2b5ca33bd20260409210728

由于这个恶意软件样本是一个DLL,所以我们可以了解它导出的函数,第一个导出函数Callrun,应该是自定义的主要函数,既然知道了这个DLL文件中有些内容可能是加壳了,所以使用调试器去调试这个DLL文件找到解压出来的数据进行dump

d2b5ca33bd20260409210749

d2b5ca33bd20260409210759

因此,使用x32dbg(这个二进制文件是32位的),我们可以通过打开rundll32.exe并将命令行(文件更改命令行)更改为

"C:\Windows\SysWOW64\rundll32.exe" C:\Users\Administrador\Desktop\sample_1.bin,#1 文件路径改一下

来运行它。

d2b5ca33bd20260409210930

更改后,重新启动/重新加载调试会话,并在到达入口点后在几个经典函数上设置以下断点。在恶意软件分析和脱壳过程中,这三个函数被称为经典断点,因为它们是恶意软件在内存中展开其真实 Payload 时最常调用的 Windows API。

  1. VirtualAlloc (在退出点设置断点)

作用: VirtualAlloc 用于在当前进程的地址空间中保留或提交内存区域。

绝大多数加壳恶意软件(Packer)在启动后,其原始代码是加密或压缩的。为了运行真实的恶意代码,它必须先申请一块新的、干净的内存空间,然后将解密后的代码写入其中。

当在调用一个函数时,Windows 会通过寄存器(在 x86/x64 中通常是 EAXRAX)返回结果。

VirtualAlloc 的返回值就是新申请到的内存起始地址

如果在函数开始时中断,你还不知道内存会被分配在哪里;如果在退出点(即执行完 RET 指令时)中断,你可以直接查看 EAX/RAX 的值。

拿到这个地址后,通常会在调试器中监视这块内存。随后你会看到解密后的 PE 文件或 Shellcode 被一点点写入这块空间。

  1. VirtualProtect

作用: 更改现有内存区域的保护属性(例如,将“只读”改为“可执行”)。

出于安全考虑,现代操作系统通常遵循 DEP(数据执行保护) 原则,即一块内存不能同时拥有可写和可执行权限。恶意软件在解密阶段需要写权限。解密完成后,为了执行这些代码,它必须调用 VirtualProtect 将权限更改为 PAGE_EXECUTE_READPAGE_EXECUTE_READWRITE

当调试器在 VirtualProtect 处中断时,它通常预示着恶意软件已经完成了代码的解密,准备好跳转到“真实入口点(OEP)”去执行了。

  1. ResumeThread

作用: 恢复一个挂起的线程。这通常与 进程镂空(Process Hollowing) 技术紧密相关。

恶意软件经常使用一种技术:先启动一个合法的系统进程(如 svchost.exeexplorer.exe),但将其设为“挂起(Suspended)”状态。接着,恶意软件会掏空该合法进程的内存,注入自己的恶意代码。最后,它会调用 ResumeThread。一旦这个函数被执行,恶意代码就会在合法进程的掩护下正式运行。

ResumeThread 处设置断点是拦截恶意软件尝试“借尸还魂”的最后防线。此时,你可以检查目标进程的内存,获取注入后的最终样本。

d2b5ca33bd20260409211022

在VirtualAlloc快返回的指令下断点和VirtualProtect 开始的指令下断点,F9,停在VirtualAlloc 函数的断点时查看,eax 寄存器的值,因为这个就是使用VirtualAlloc申请内存区域的起始地址。

d2b5ca33bd20260409211039

经过三次VitualAlloc函数调用,最后一次F9会断在VirtualProtect函数的位置,如图:

d2b5ca33bd20260409211056

d2b5ca33bd20260409211137

如图所示,ASCII表示中的前几个字符是“M8Z”,这表明它使用了aPLib压缩。这时候右键–>转到内存布局–>内存转到文件,dump下来解压缩的数据。

d2b5ca33bd20260409211153

之后将dump 下来的文件放进010editor,搜索标准的PE文件头,然后将之前的数据全部删除,就得到一个正常的PE文件。

d2b5ca33bd20260409211207

修复 dump 文件后,可以看到导入表中已经出现 WININET.dll 等与网络通信相关的 API,说明当前样本已不再是最初的壳层代码,而是具备实际功能的解包后载荷。因此接下来转入 IDA 对其主逻辑与配置解密流程进行静态分析。

d2b5ca33bd20260409211221

IDA静态分析

主要逻辑在 sub_100013F6() 这个函数,主要内容如下,这是一个任务轮询器,每轮结束后休眠约 120 秒。

d2b5ca33bd20260409211358

sub_0x1000153C,该函数负责构造上线信息并向服务端发送请求,查看函数详细实现

d2b5ca33bd20260409211418

sub_10001CFF调用了sub_10001652() ,这个函数的具体操作为:调用 GetAdaptersAddresses(AF_INET, …)来遍历所有网络适配器

  • 每个适配器取 PhysicalAddress,也就是 MAC 地址
  • 把 MAC 地址拷到一个 8 字节变量 v3
  • 再把这些值异或累积到 v1
  • 释放缓冲区
  • 调用 sub_10001BDC() 做附加处理
  • 返回累积结果

sub_10001652() 用网卡 MAC 地址计算一个机器指纹,sub_10001CFF() 负责只计算一次,并缓存结果,最终这个值被当作受害主机的 GUID 上报给 C2。

sub_10002406(DWORD nSize)函数,这个函数主要是构造用于 C2 上报的受害主机身份字符串,格式为 计算机名 @ 域名\用户名;其中用户名不是当前进程用户,而是通过枚举 explorer.exe 并读取其 token 得到的当前交互式桌面用户。

d2b5ca33bd20260409211516

sub_10001C45 的功能是:通过访问 http://api.ipify.org 获取受害机公网 IP,并把结果缓存到全局缓冲区;如果获取失败,则返回并输出 0.0.0.0。

d2b5ca33bd20260409211533

sub_10001CB7()主要逻辑是从样本内置的静态密文区 unk_10004018 复制出一份 0x2000 字节的配置数据,使用基于 pbData 的 CryptoAPI 密钥派生逻辑进行原地解密,并把得到的明文配置缓存到全局堆缓冲区 dword_10006264,供后续函数读取 C2/URL 等配置项。

d2b5ca33bd20260409211547

主要的数据解密函数是sub_10002131,接下来详细分析解密过程,以方便我们手动解密。datacopy这个函数将&unk_10004018 数据copy到dword_10006264,长度是0x200,所以, sub_10002131第一个参数的内容应该是unk_10004018中存放的数据。

d2b5ca33bd20260409211647

初始化 Crypto 上下文CryptAcquireContextA(&phProv, NULL, NULL, PROV_RSA_FULL(1), CRYPT_VERIFYCONTEXT(0xF0000000)) —— 不使用持久密钥容器,临时会话。

创建 SHA1 哈希对象CryptCreateHash(phProv, CALG_SHA1(0x8004), 0, 0, &phHash)

对 pbData 做 SHA1 哈希CryptHashData(phHash, pbData, dwDataLen, 0) —— pbData 就是“种子密钥材料”。

pbData中存放的数据为 [0xC5, 0x8B, 0x00, 0x15, 0x7F, 0x8E, 0x92, 0x88]

d2b5ca33bd20260409211710

从哈希派生 RC4 密钥CryptDeriveKey(phProv, CALG_RC4(0x6801), phHash, 0x280011u, &phKey)关键细节

  • 0x280011 的高 16 位 0x28 = 40 位(5 字节) RC4 密钥长度(微软文档要求高位指定位长)。
  • 低 16 位 0x11 = CRYPT_EXPORTABLE | CRYPT_NO_SALT。 所以最终 RC4 密钥只有 前 5 字节 的 SHA1 哈希值

**执行 RC4 解密(原地)**CryptDecrypt(phKey, 0, TRUE, 0, a1, &pdwDataLen) —— 把 a1 中的密文直接解成明文,长度不变(RC4 是流密码)

总结就是:将 pbData 中的数据进行 SHA1 加密 → 结果当作 RC4 的密钥。

解密

d2b5ca33bd20260409211729

d2b5ca33bd20260409211823

得到:

1910_nsw http://newnucapi.com/8/forum.php|http://gintlyba.ru/8/forum.php|http://stralonz.ru/8/forum.php|

解密出来的是该木马的通信基础设施配置

 v9 = sub_10001CB7(nSize, DomainCount, String1, v16, v10);
 wsprintfA(String, "GUID=%I64u&BUILD=%s&INFO=%s&EXT=%s&IP=%s&TYPE=1&WIN=%d.%d(x32)", (_DWORD)v4, v15, v9);

wsprintfA(String, “GUID=…&BUILD=%s&INFO=%s&EXT=%s&IP=%s&TYPE=1&WIN=%d.%d(x32)”, …)把主机信息拼成一条 POST body,准备发给 C2。(x32) 表示当前走的是 32 位系统分支。

最后String参数会传入

d2b5ca33bd20260409211842

这个拼接出来的 String 就是这个函数里的 lpString,作为 HTTP POST 请求体 发送到 C2。这整个函数在做的事可以概括为:

  1. 解析 lpszUrl(域名、路径、协议)
  2. 建立 WinINet 连接:InternetConnectA。
  3. 创建 POST 请求:HttpOpenRequestA(…, “POST”, …)
  4. 发送请求头 szHeaders + 请求体 lpString:HttpSendRequestA
  5. 检查 HTTP 状态码是否 200:HttpQueryInfoA
  6. 读取响应数据到 lpBuffer,并补 \0:InternetReadFile
  7. 返回 Buffer == 200(成功为 1,失败为 0)

总结

第一次写,写的不好请见谅。

从初始样本情报入手,确认该 DLL 样本疑似属于 Hancitor 家族;随后结合区段特征与动态调试,完成第一阶段脱壳与内存 dump;在修复 PE 后,通过 IDA 静态分析定位到主轮询逻辑、主机信息收集逻辑与配置解密逻辑;最终确认样本使用内置 pbData 经过 SHA1 派生 RC4 密钥,对配置区进行解密,并将主机标识、用户信息、公网 IP 等内容拼接后上报至 C2

请登录后发表评论

    请登录后查看回复内容