拿到一个来路不明的后门样本,第一反应肯定不是直接丢进虚拟机里双击运行。不管它是从邮件附件里翻出来的,还是从某台被攻陷的服务器上捞出来的,恶意软件分析的第一步永远是先“稳住阵脚”:把样本固定下来,把环境准备好,把能收集的情报先收集齐。这期实战记录就完整走一遍后门逆向的流程,从初始评估、静态分析、动态监控,到最后的代码级定位和配置还原,把我实际操作中踩过的坑和总结的经验一起放出来。这篇文章适合刚入门恶意样本分析、想系统了解后门分析流程的安全从业者,也适合那些已经做过一些分析但总觉得流程不够体系化的朋友参考。
分析的目标很明确:搞清楚这个后门是怎么工作的,它会连接哪里,上线之后能做什么,以及在主机上留了哪些痕迹。整个分析过程我会围绕样本本身的类型判断、静态信息提取、动态行为捕捉、关键代码定位和解密逻辑还原这几条主线展开,所有操作都在隔离的虚拟化环境中完成,不涉及任何真实生产系统。
1. 拿到样本后先别急着运行:第一步该做什么
1.1 样本来源与已知信息收敛
恶意软件分析的第一个动作不是打开反汇编器,而是先整理“我们已知什么、不知道什么”。以这次处理的样本为例,它是在某台Windows Server的临时目录里被安全设备拦截下来的,文件名看起来像正常的系统更新包,但哈希值从来没有出现过。样本来源决定了分析时的风险等级判断:如果是从真实被攻陷主机上提取的,那要额外注意它可能已经释放了其他组件、注册了计划任务或服务,分析时就要连这些主机痕迹一起考虑进去。
接下来要做的是把样本的哈希值记录下来。MD5、SHA-1、SHA-256三个都算一遍,这几个值在后续查询威胁情报、确认样本是否被公开标记、以及写分析报告的时候都必不可少。用系统自带的certutil或者Python脚本都能完成这个操作,我习惯用Python一次性输出三个哈希值,顺便把文件大小也固定下来。这个步骤虽然简单,但有些分析师图省事直接跳过,后面写报告时再回去翻哈希,往往就得重新从取证镜像里导一遍文件,浪费时间。
同时,还需要记录样本的时间属性。攻击者经常会修改文件的修改时间,也就是所谓的“时间戳伪造”,但创建时间和访问时间往往会被忽略。把这些时间信息固定下来,后面做时间线分析时能帮我们还原样本是何时被投放的,甚至能关联到同一批攻击活动。我处理这个样本时发现它的修改时间是伪造的,但创建时间比修改时间早了两天,这就说明文件被二次修改过,大概率是攻击者打包时改了属性。
1.2 隔离环境准备与哈希固定
分析恶意软件的环境要求不复杂但很严格:必须是隔离的虚拟机,快照要提前打好,网络要可控。
我这边用的是VMware Workstation,虚拟机装的是Windows 10企业版,配置了2核CPU、4G内存,硬盘40G。这里有个关键点:虚拟机必须与其他主机隔离,网络模式选“仅主机模式”或者自定义的虚拟网段,不能让样本直接接触真实网络。为了观察网络行为,我会在宿主机或者同一个虚拟网络里挂一台Ubuntu,跑Wireshark和INetSim。INetSim是一个模拟网络服务的工具,能让样本以为自己能连上真实的C2服务器,实际上所有流量都被录下来,这在后续动态分析里非常有用。
在快照这一点上我吃过亏。刚开始做分析的时候,虚拟机装好环境就急着跑样本,结果样本把系统文件给改了,后面想恢复到干净状态只能重装系统。现在我的习惯是:虚拟机装完系统、装好分析工具之后,先打一个“干净快照”,跑每个样本之前都恢复到快照状态,确保前后两次实验结果之间没有干扰。跑完一个样本,导出日志和抓包结果,再恢复快照继续下一个。
哈希固定在准备阶段也要完成。样本的哈希值不仅仅用于情报查询,在动态分析之后还需要重新计算一次哈希,确认运行过程中的样本没有自我修改。有些后门会释放新的文件到磁盘,把释放出来的文件也单独做个哈希,后续做同源分析时可以比对。
1.3 初步识别的几个关键信号
在静态分析工具打开之前,有几个快速信号能帮我们判断样本的大致类型。
第一个是用file命令或者PE查看工具确认文件格式。这次处理的样本是32位PE可执行文件,控制台子系统,这意味着它运行时不会弹出窗口,适合静默运行。32位这个信息很关键,因为32位样本和64位样本在后续逆向时的系统调用约定、API使用上都不同,分析工具也得对应调整。
第二个是看看编译时间。PE头里的TimeDateStamp字段记录了编译时间,虽然可以被伪造,但在大多数情况下能给一个参考。这个样本的编译时间显示为两年前的日期,而样本是最近才被捕获的,这说明要么是攻击者使用了老工具链,要么是样本经过了精心伪装。经验不足的分析师容易被这个字段误导,记住:编译时间只能作为参考线索,不能作为定论。
第三个是字符串里的语言特征。用简单的字符串提取工具扫一遍,如果能识别出中文、俄文、韩文等字符,往往能定位攻击者的大致来源。但要注意,样本里的字符串可能是编码过的或者加密过的,尤其是C2地址和URL,大多不会以明文形式出现。这个样本的字符串提取结果很干净,只有一些系统API的名称和少量看起来像是随机生成的字符,几乎没有自然语言文本,说明作者在做字符串混淆。
做完这些初步检查,就可以进入下一阶段了:静态分析。
2. 静态分析:在运行之前尽量榨干PE文件里的信息
2.1 导入表、字符串与可疑API的线索
静态分析的核心目标是“不运行样本就获取尽可能多的信息”。PE格式的导入表是一个极好的切入点。通过查看样本导入了哪些系统API,可以大致推测它的功能模块。
这个样本的导入表有几个值得注意的API组:
CreateProcess、ShellExecute、WinExec,这些进程创建类API说明它很可能具备执行命令的能力,也就是命令执行模块。RegOpenKeyEx、RegSetValueEx、RegCreateKey,注册表操作类API,说明有持久化安装的嫌疑,可能通过注册表自启动项来实现开机自启。InternetOpen、InternetConnect、HttpSendRequest、InternetReadFile,网络通信类API,说明它使用WinINet库进行HTTP通信。WinINet的好处是不需要自己实现TCP协议栈,而且能复用系统的代理设置,很多恶意软件作者喜欢用这个库。CreateService、OpenSCManager,服务管理类API,也指向持久化功能,可能注册成系统服务。
只靠导入表就能确认这个后门至少包含三个核心模块:命令执行、持久化、网络通信。接下来字符串查看器里的内容能进一步补充线索。使用strings工具扫描整个二进制文件,重点看这几类字符串:URL和IP地址、文件路径、注册表路径、互斥量名称、命令行参数提示。
这个样本里有几个可疑的字符串:
Mutex_Global_2344 AppInit_DLLs Software\Microsoft\Windows\CurrentVersion\Run update.exe http://互斥量名称尤其重要,恶意软件用它防止多开,同一个家族样本通常会使用固定的互斥量名。在威胁情报平台上搜索这个互斥量名,能关联到其他已知样本,从而推测家族归属。
URL相关的字符串出现的是http://前缀但完整的域名后面被截断了,这有两种可能:一是URL本身在二进制里被分段存储,拼接后才完整;二是字符串经过了加密处理,明文部分只是协议开头。这个疑点留到逆向阶段去解决。
2.2 加壳与伪装判断
确认PE有没有加壳,是静态分析里一个重要判断。加壳属于一种压缩或加密技术,用来隐藏程序真正的代码逻辑。判断工具首选DIE(Detect It Easy),它能从特征码层面识别出常见的加壳器和编译器工具链。
把这个样本丢进DIE,结果显示“UPX”的标记。UPX是一个非常常见的开源加壳器,很多恶意软件作者会用UPX加壳,但也有不少正规软件使用,所以“UPX”本身不代表恶意,只是说可执行文件入口点被压缩了,静态分析暂时看不到真正的逻辑。
识别出UPX壳之后,有两种处理方法:使用upx -d直接脱壳,或者手工在调试器里内存转储脱壳。UPX壳比较友好,大多数情况下直接执行upx -d sample.exe -o sample_unpacked.exe就能还原出原始代码。但是这个样本在尝试脱壳时出现了报错,提示“File is not packed or already unpacked”之类的信息,这说明样本可能对UPX做了魔改,头部标记被改过,或者壳的格式并不是标准的UPX。
既然直接脱壳这条路被堵住了,就只能通过调试器完成内存转储。过程大致是这样的:在x32dbg里加载样本,让它在入口点停下,然后运行到原始入口点(OEP)。UPX壳的特点是一段解压完成后会跳到真正的程序入口,在到达OEP时用进程转储功能把内存里的镜像导出,再用修复工具重建导入表。这里最常遇到的问题是导入表修复失败,因为有些API是通过延迟加载或者动态解析实现的,需要结合手动分析来处理。
这里插一句:魔改的UPX壳在后门样本里很常见,写壳的人往往只是简单改了几个字节,让自动脱壳工具失灵,但运行时逻辑和原来的UPX没有本质区别。所以只要掌握手工内存转储的思路,大多数情况下都能顺利脱离魔法笼子。
2.3 配置信息的静态提取
后门样本的配置信息通常包含C2地址、端口号、加密密钥、上线间隔等。这些信息有可能以明文存储,也有可能用简单的异或加密进行保护。在脱壳之后,重新扫描一次字符串,往往会得到比加壳版本多得多的明文配置内容。
这次脱壳后再扫描,发现了一个完整的URL:
hxxp://update.office-check[.]net:8080/api/login域名做了变种处理,把http改写成了hxxp、域名中的点改写成了[.],这是为了防止在报告中出现可点击的恶意链接。从域名和端口来看,这是一个典型的HTTP型C2,8080端口不是默认的80,说明作者希望避免一些只监控80端口的检测规则。
配置里还出现了三个长度不一的十六进制字符串,看起来像是密钥:
0x4B 0x7A 0x10 0x2F ... 0x6D 0x21 0x09 0x44 ...这种十六进制字节串在静态阶段无法直接判断用途,要等动态分析或者逆向时才清楚。
还发现了一个文件名c:\programdata\sysmon.dll。系统目录下的sysmon.dll和微软官方发布的Sysinternals工具集里的Sysmon虽然同名,但实际是完全不同的东西,恶意样本会在运行时把这个DLL释放出来的一个组件写到这个路径,作为持久化加载的载荷。这一步就在静态阶段锁定了样本释放文件的具体位置,后续动态分析时可以重点监控这个路径。
3. 动态分析:在隔离环境里让样本“跑起来”
3.1 行为监控三件套
静态信息收集完毕后,可以让样本在隔离虚拟机里运行,观察它的实际行为。这是恶意代码分析中最能直观看到攻击影响的阶段。
动态分析之前先开好三样监控工具:ProcMon、Wireshark和Regshot。这三个工具分别负责监控进程与文件操作、网络流量、注册表与系统状态变化。
ProcMon(Process Monitor)是Sysinternals套件里的工具,能够实时记录进程对文件系统、注册表、网络和进程/线程的操作。以管理员身份运行ProcMon,设置过滤条件只监控样本进程及其子进程的事件,然后启动样本,就能观察到完整的操作序列。这个样本运行后第一件事是修改了HKCU\Software\Microsoft\Windows\CurrentVersion\Run的注册表项,添加了指向C:\programdata\update.exe的键值,这就是典型的持久化安装行为。紧接着,它在C:\ProgramData下释放了update.exe和sysmon.dll两个文件。ProcMon对抓这类行为非常顺手,但在没有先开监控的情况下就运行样本,容易漏掉进程启动初期的行为。
Regshot的作用是记录系统注册表和文件的快照,在运行样本前打一次快照,运行后再打一次,然后对比差异。这个工具的优点是简洁直观,缺点是无法记录中途发生的短时操作,比如某些样本启动后马上自毁删除,Regshot就抓不到过程。所以Regshot是用在ProcMon的辅助定位上的,两者不是替代关系。
Wireshark在前面的虚拟网络上运行,抓取样本的所有网络流量。在配合INetSim的环境里,样本实际连上的是一台模拟服务器,Wireshark抓到的是模拟应答的结果,这能有效防止真实C2环境下的危害扩散。打开抓包后,样本一运行就发出了HTTP连接请求,目标域名正是静态阶段发现的update.office-check[.]net,路径是/api/login。INetSim返回了一个模拟的HTTP响应包,样本接收到响应后继续请求了/api/task,看起来是在获取服务端下发的任务指令。
3.2 网络行为与主机行为联动分析
动态分析的一个重要思路是不能只看行为,还要把“主机行为”和“网络行为”联动起来看。单独看ProcMon日志里创建了文件、写入注册表,或者单独看Wireshark里发了几次HTTP请求,都只是孤立的事实,只有当它们关联到一起时,才真正构成了后门的“行为链”。
回到这个样本的日志。ProcMon里可以看到样本先创建了互斥量,然后写入注册表Run键让自身开机自启,接着释放两个文件到ProgramData目录下,这两个文件分别是一个可执行文件和一个DLL。在之后的几十秒内,样本开始调用WinINet库函数发出HTTP请求,请求包里的User-Agent字段是Mozilla/5.0 (Windows NT 6.1),故意伪装成旧版浏览器的UA,也是常见的流量规避手段。
Wireshark里对应的封包显示:第一个TCP握手到update.office-check[.]net:8080,然后是一个HTTP POST请求,body内容是一段JSON结构的数据,里面有一个hwid字段,值为虚拟机的默认主机名和MAC地址拼接后的值。这就把主机身份信息和网络请求联系起来了:样本先收集受害主机的基本信息,然后发送给C2服务器,用于注册/标识“肉鸡”身份。
联动的价值在于:我们发现了一个关键的细节,它在单独观察两种日志时都可能被遗漏——样本在释放完sysmon.dll之后,动态分析主机上会新增一个指向C:\ProgramData\sysmon.dll的注册表项。而如果这是一个加载DLL的持久化路径,DLL里包含的代码就会是后门的主功能模块。这个推理链条完全是靠联动观察得到的。
3.3 反分析对抗的绕过思路
动态分析中经常碰到一个坎:样本检测到自己在虚拟机里运行,然后主动退出、不执行恶意行为,导致分析阶段一无所获。这种“反虚拟机”技巧在各种后门样本里极为常见,初步判断方式包括检查CPU是否是虚拟化品牌、检查特定硬件设备的存在、检查系统用户名是否为默认的管理员,以及检查窗口标题和进程列表。
这个样本运行后的进程树较为干净,没有直接触发虚拟机检测,但它使用了另一种常见的反调试技巧:检测自身是否处于调试状态并自动退出。在x32dbg里加载样本、打断点时,发现程序在一个高地址的循环里反复执行,迟迟不走到真正入口。这就是一种反调试的时机延迟手法,目的是让分析人员以为程序卡死了,从而失去耐心。
绕过的思路不复杂:先用x32dbg的“隐藏调试器”插件来隐藏调试痕迹,再在系统调用层做拦截。调试器隐藏插件主要修改PEB(进程环境块)中的BeingDebugged标志和NtGlobalFlag字段,让样本无法通过常规API得知自己被调试。然后,在内存中搜索指令特征来定位循环的位置,找到循环出口后打上断点,直接跳过延迟区域进入到真实逻辑。
此外,对于反虚拟机检测,最稳妥的办法是先跑一遍完整行为流程,记录下它干净运行时的进程链和日志,再去做对比分析。如果对比后发现样本在无调试环境下没有触发恶意行为,那么问题大半出在虚拟机的可检测性上。这时可以考虑使用真实硬件测试或者修改VMware的配置,比如去掉VMware Tools、修改虚拟显卡型号等,来降低虚拟化痕迹。不过这些操作要谨慎,保证整个过程都处于隔离可控的网络环境当中。
4. 逆向视角:定位核心功能与C2交互逻辑
4.1 用Ghidra/IDA定位关键函数
动态分析拿到了行为链,但还差最关键的“代码级”证据:程序内部是怎么实现这些行为的。接下来的工作就是把脱壳后的样本放进反编译器,梳理它的函数调用关系,找到核心功能模块。
我用的是Ghidra,因为它免费且对复杂文件的分析能力足够强,团队配合时也能统一环境。Ghidra加载脱壳后的样本后,会自动生成一个函数列表。大多数情况下,后门的入口函数并不叫main,而是entry,它先做一些初始化工作,然后跳转到主逻辑函数。找到主逻辑函数的办法,一个是看调用深度,找那些被多个地方引用的关键函数;另一个是从可疑API调用倒推,比如CreateProcess这个API被谁调用了,往上层追就能定位到命令执行模块的代码区域。
在这个样本里,CreateProcess的交叉引用指向了一个在0x00404A10地址附近的函数,这个函数在Ghidra的反编译视图里看起来非常清晰:它从C2服务器接收一段指令,根据指令里不同前缀分配合法行为。一个标准的后门命令分发函数逻辑通常是一串if-else分支,每个分支对应一个命令编号。我们在这个函数里找到了如下分支:
- 命令码
0x01:执行cmd.exe /c [收到的命令],并把标准输出回传给C2。 - 命令码
0x02:从指定的URL下载文件保存到本地。 - 命令码
0x03:上传指定路径的文件到C2。 - 命令码
0x04:修改自身持久化配置,包括重写注册表项和释放路径。
每个命令分支其实都不复杂,但它们组合起来就是一个完整的远程控制后门。这个后门在C2交互层面没有使用复杂的二进制协议,而是直接复用HTTP的关键字来区分指令,例如在HTTP请求的URL中加上action=login、action=command这样的参数,优点是开发实现简单,缺点是流量特征非常明显,Wireshark里一眼就能看出哪条是控制指令,哪条是结果回传。
4.2 字符串解密与配置还原
后门的C2地址、指令关键字、日志文件路径等信息,通常不会直接以明文出现。在代码里它们往往是经过某种简单加密处理,然后在运行时由一个解密函数还原成明文。
在Ghidra里定位到解密函数后,能清楚地看到这个样本使用的加密方式是“单字节异或循环”。原理很简单:配置块中的每个字节和一个密钥字节依次做异或运算,密钥从头到尾循环使用。例如密钥是0x4B,那么配置块第一字节异或0x4B,第二字节异或0x4B,以此类推。因为异或操作是无损可逆的,所以解密的代码逻辑在加密和解密时可以复用同一个函数。
这个样本的解密密钥经过确认就是静态分析阶段看到的那个十六进制字符串0x4B 0x7A 0x10 0x2F ...,不过它是循环使用多字节密钥的变体,而不是单字节。在代码里找到密钥之后,我直接写了一个小的Python脚本来还原整个配置块:
def decrypt_block(data, key): result = bytearray() key_len = len(key) for i, b in enumerate(data): result.append(b ^ key[i % key_len]) return bytes(result) encrypted_config = bytes.fromhex("...") # 从二进制中提取的加密配置 key = bytes([0x4B, 0x7A, 0x10, 0x2F, 0x6D, 0x21, 0x09, 0x44]) decrypted = decrypt_block(encrypted_config, key) print(decrypted)脚本还原出来的配置内容比静态阶段看到的丰富得多。除了C2的域名和端口,还有一组备用C2地址、两个文件的释放路径、一个整数型上线间隔参数(换算下来是450秒,即每7分钟左右回连一次),以及一个WhenLoaded的自定义日志标记。
这里有个容易被忽略的细节:同一份配置在恶意软件里可能嵌入多次。比如C2域名,静态扫描时看到了一次,动态流量里体现出来的也是同一个,但在配置块的解密里又出现了另一个备用地址。这说明这类后门为C2故障或域名失效做了应对,备用地址会在主地址连续连接失败指定次数后使用。分析报告里如果把备用地址漏掉了,那后续做情报延展时就会遗漏关键的追踪线索。
4.3 命令分发结构的梳理
后门的核心价值在于“失控”:攻击者通过C2服务器下发指令,控制受害主机执行任意操作。要彻底理解这个后门的危险程度,就必须梳理清楚它到底支持哪些指令、每条指令是怎么解析和执行的。
从反编译代码来看,命令分发流程大致是这样的:主循环收到C2下发的JSON数据,解析出type字段,根据不同的type值进入不同的处理函数。命令列表如下:
| type字段 | 功能 | 对应行为 |
|---|---|---|
ping | 心跳存活 | 回传online状态信息,保持上线标记 |
exec | 命令执行 | 调用CreateProcess执行传入字符串的shell命令,回显输出 |
download | 下载文件 | 通过URL从远程拉取文件保存到指定路径 |
upload | 上传文件 | 读取受害主机路径文件,POST到C2服务器 |
sleep | 修改回连周期 | 动态调整上线间隔,重新设定定时器 |
uninstall | 自毁卸载 | 删除注册表Run键,清理释放文件,结束自身进程 |
exec分支的实现细节让我印象深刻。攻击者并不是直接执行接收到的原始命令行,而是对内容做了一层base64解码。也就是说,攻击者在C2控制端下发指令时,需要先把命令做base64编码,后门收到后再解码,最后拼接到cmd.exe /c后面执行。这种设计主要是为了防止明文命令在流量审计中被轻易识别,也稍微增加了流量检测的难度。
uninstall分支也值得关注。很多后门样本根本没有自毁功能——攻击者要持续控制主机,为什么要自毁?而这类高端后门反而提供了卸载功能,它的真实用途是在攻击者完成使命后清理痕迹,或者在被发现后远程熔断。我们在实际应急响应中见到过多次“后门自动消失”的场景,事后复盘往往就是攻击者检测到安全设备告警后,主动下发自毁指令。所以,识别出一个后门是否具备自毁能力和自毁逻辑的触发条件,对于应急响应有直接的价值。
5. 常见问题与排查技巧实录
5.1 脱壳失败、字符串不完整等静态问题
在整个分析过程中,有几个问题是我反复遇到的,做个速查表出来可能对后来人帮助更大。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 用upx -d脱壳失败 | 壳的头部特征被修改,导致工具不识别 | 在调试器里跟踪入口点,手动内存转储并重建导入表 |
| 字符串扫描出来的URL不完整 | 字符串被分段存储或经过了加密 | 先脱壳再扫描;如果还不全,就追踪加密解密代码 |
| 导入表只有少量API,但行为明显复杂 | 导入被混淆,实际API调用通过动态解析完成 | 对照反汇编中的GetProcAddress调用点,还原真实的导入范围 |
| 样本在不同机器上的行为差异较大 | 存在反虚拟机/反沙箱逻辑,检测环境后改变行为 | 隐藏调试器、修改虚拟化特征,或者在两类环境下对比行为日志 |
| 动态监控时样本立即退出 | 检测到调试端口、异常的系统用户名或已知进程名 | 使用真实系统快照或修改虚拟机硬件信息后再试 |
第一个问题的处理细节很重要。手动内存转储的时候,正确的时序不是一加载样本就转储,而是要等壳把程序原始代码解压完毕、跳到OEP之后再做转储。如果转储过早,内存里的镜像还是壳代码,导入表也还没有被正确重建。判断OEP的一个简便方法是:在x32dbg里按F9运行到入口点,然后观察栈顶的返回地址。对于UPX壳,OEP处往往会出现一个pushad指令的对称逆操作,比如popad之后再跟着一个jmp跳转到真正的用户代码。在这个位置下断点,然后运行到断点跳转完成,再来做转储就稳了。
导入表重建的常见坑在于:很多后门在运行时通过LoadLibrary和GetProcAddress动态解析API,这些API不会出现在静态导入表里,转储后的导入表自然也没有它们。这是这类样本的普遍特征,不是转储流程本身的问题。遇到这种情况,不需要强行把每个API都修复完整,只要保证关键的逻辑函数能顺利反编译,剩下的动态调用点通过分析GetProcAddress的调用上下文来确定即可。
5.2 网络日志与样本行为对不上的排查思路
动态分析阶段,有一个非常典型的困惑:ProcMon里明明看到样本发起了HTTP请求,但Wireshark里却看不到对应的网络流量。排查思路要顺着调用链往下走。
首先检查样本是否调用了系统的代理设置。WinINet库默认会读取IE的代理配置,如果虚拟机的系统代理指向了一个不存在的地址或端口,请求就会在本地失败,Wireshark自然看不到数据包。解决办法是在虚拟机里把代理设置清空,或者在Wireshark里过滤http || tcp.port == 8080,同时观察是否有到代理端口的连接。
其次检查样本是否使用了非标准端口或者加密隧道。这个样本用的是8080端口,不算标准HTTP端口,容易在Wireshark默认的HTTP过滤规则下被忽略掉。如果样本用的是自定义TCP协议,那就得更仔细地查看抓包内容,按TCP端到端来追踪连接。
如果确认流量已经发出,但在宿主机侧的Wireshark看不到,那就要检查虚拟机的网络模式是否真的是“仅主机模式”或者NAT模式。如果是NAT模式,流量是经过宿主机的虚拟网卡出去的,抓包要在对应的虚拟接口上抓,而不是在物理网卡上抓。
这个样本分析过程中我遇到过这样一幕:ProcMon里看到TCP Send操作,但Wireshark里完全没有对应包。最后的定位是样本在发送数据前,故意检查了当前运行的进程名列表,如果发现“wireshark.exe”则稍作延迟再退出网络模块。这就是恶意软件针对分析工具的检测行为。解决的办法也很粗暴:把抓包工具改名运行,或者把Wireshark的进程名改成其他无害名称。从这以后,我在做动态分析时都会特别留意行为日志里的进程枚举操作和网络延迟操作。
5.3 反调试绕过的具体操作和细节
反调试技术是恶意样本分析中最耗时间的环节之一。这个后门没有上特别高深的反调试手段,但有一个自写的“时间差检测”,个人实战里很容易被甩开。
时间差检测的原理十分朴素:恶意代码检查自身从启动到当前位置的执行耗时。如果这个耗时异常大,说明代码在执行过程中被断点或单步跟踪拖慢了进度,也就是有人正在调试。正常程序完成初始化通常只需要几十毫秒,而调试器介入以后这个时间可能被拉长到几秒甚至更长。
绕过时间差检测的思路有很多种,我这次使用的方法是:不直接在程序启动的位置设断点,而是让程序在自由状态下运行到某个关键API,再在API入口处下断点。比如这个后门在请求C2前一定会调用WinINet的InternetConnect,那么在InternetConnect的入口处下断点,然后让样本从头运行时自动跑到这个断点处再停下来。
除此之外,x32dbg自带的HideDebugger插件几乎能规避绝大多数的常见检测点。它会清除PEB进程环境块中的调试标志、修改API返回的进程对象信息、隐藏调试器创建的线程等。配合插件,把时间差检测的阈值拉长,就能强行跳过运行时的调试检测段,直接进入到真正的逻辑分析。
实操总结里的几句体己话
反复分析了好几轮这个样本之后,我最大的感受是:恶意软件分析没有太多花哨的技巧,大部分时候都是在跟一个又一个细节上的对手博弈。能把静态分析的线索和动态分析的行为链接上,再把动态观察到的行为放回逆向代码里验证,这一套闭环走完,一个后门的面貌就基本交代得清清楚楚了。
针对这类HTTP型后门,我给几个实操建议。第一,动态分析时一定不要急着看抓包文件,先把ProcMon的完整进程树理清楚,恶意软件的执行顺序往往就藏着它的功能优先级;第二,配置还原不要只盯着一处加密,整个二进制里多搜几遍密钥或者配置块,备用C2和备用路径这类关键信息都在重复出现的片段里;第三,报告里记录C2地址和释放文件路径时,记得做无害化处理,防止读报告的人直接点进去。最后还有一个小习惯,每次分析完一个样本,把用过的分析思路和踩到的坑记录成个人知识库条目。积累的次数多了,再遇到同类问题,脑子里能立刻冒出对应的解法,这会比临时去查资料快很多。