20242423 2026-2027-1 《网络与系统攻防技术》实验四实验报告
20242423 2025-2026-1 《网络与系统攻防技术》
实验四 实验报告
恶意代码分析、逆向与僵尸网络取证
姓名:罗靖林 学号:20242423 实验主机名:luojinglin
1. 基础知识
1.1 Windows 可执行文件与 PE 结构
Windows 下的可执行文件遵循 PE(Portable Executable)格式。文件开头是 DOS 头,以“MZ”两个字节标识,其中 e_lfanew 字段记录着真正的 PE 头偏移;PE 头里的 Machine 字段说明目标架构(0x014c 即 Intel 80386),可选头里的 Magic 为 0x10b 表示 PE32、0x20b 表示 PE32+,Subsystem 为 2 表示 GUI 程序、为 3 表示控制台程序;再往后是节表和各个节,如 .text 存代码、.data 存数据、.rsrc 存资源。分析一个陌生样本时,先读出这几个字段,就能判断它能不能在 32 位 Windows 上跑、是带窗口的程序还是命令行程序。Linux 下的 file 命令读的正是这些头部魔数,给出的结论和 PEiD 一类的图形工具是一致的。
1.2 加壳与脱壳
壳(packer)由两部分组成:压缩或加密后的原程序,以及负责在运行时把它还原的加载器(stub)。加壳之后程序入口点被改到壳的代码段,原程序的导入表被打乱重建,静态反汇编看到的全是壳的代码。常见的压缩壳有 UPX、ASPack,保护壳有 VMProtect、Themida,前者主要为了减小体积、顺带破坏杀毒软件的特征匹配,后者会做代码虚拟化和反调试,难度完全不是一个量级。
脱壳的本质是把内存中已经还原的映像“抠”出来:找到原程序的入口点 OEP,dump 内存,再修复导入表(IAT)。工程上有两条路,一条是用通用脱壳机(如超级巡警虚拟机脱壳器),它通过模拟执行或特征比对自动完成上述步骤;另一条是手工脱壳,在调试器里对壳的跳转下断点,跑到 OEP 再 dump。
1.3 字符串与字符编码
程序里的提示语、URL、注册表路径、作者署名,最终都以字符串常量的形式躺在数据段里。strings 命令扫描可打印字符序列,是成本最低的情报来源。但它对编码敏感:默认只扫 ASCII,而 Visual Basic、Delphi 写的程序习惯用 UTF-16LE(宽字符)保存字符串,这时要么用 strings -el,要么在 IDA 的 Strings 窗口里把编码改为 Unicode 才看得到。本实验里“脱壳后仍然找不到作者”就是这个原因,属于很典型的坑。
1.4 静态分析与动态分析
静态分析不运行程序,依靠反汇编(IDA Pro)、反编译(F5 生成伪代码)、字符串、导入表、交叉引用(Xrefs)和函数调用图来还原逻辑。它安全,能看到全部代码路径,但遇到花指令、运行时解密的字符串容易卡住。动态分析把程序真的跑起来,用调试器单步、下断点,或者用 Process Explorer、Process Monitor 观察文件、注册表、进程和网络行为,看到的是“真实发生了什么”,代价是可能触发反调试,也可能漏掉没有走到的分支。实际工作中两者交替使用,静态给假设,动态做验证。
1.5 恶意代码的分类
按传播方式和核心功能,常见的类型有:病毒,寄生于宿主文件并自我复制;蠕虫,利用漏洞在网络中自动扩散;木马与后门,隐蔽驻留,为攻击者保留一条远程控制通道;僵尸网络客户端(Bot),听命于 C&C 服务器,可被批量指挥;下载器,只负责拉取后续载荷;以及勒索软件等以破坏或牟利为目的的类型。分类要看核心能力,不能看文件名后缀,同一个样本完全可能同时具备几种属性。
1.6 IRC 协议与僵尸网络
IRC 是早期的文本聊天协议。客户端连上服务器后先发 NICK 设定昵称、发 USER 上报用户信息,服务器回 001 至 004 的欢迎消息,随后客户端用 JOIN 加入频道,频道内的消息通过 PRIVMSG 广播。IRC 默认使用 TCP 6667 端口,实际部署中也常见 6660 至 6669、7000,加密连接则走 6697。僵尸网络把 IRC 的“频道广播”变成了指令通道:Bot 上线后加入指定频道,攻击者在频道里发一条命令,成千上万台主机同时执行。取证时最典型的特征就是,一台内网主机莫名其妙地连外网 6667 端口,并且周期性收发 PING/PONG 保活。
1.7 流量取证的基本思路
拿到一个 pcap 文件,先看全局:协议分层统计、会话(Conversations)、端点(Endpoints),弄明白哪台主机在和谁通信、流量集中在哪些端口上。然后按协议逐层过滤下钻,例如用 tcp.dstport==6667 找 IRC 流量,用 dcerpc、udp.port==1434 找蠕虫扫描特征。需要还原应用层内容时用 tcpflow 按流拆成文件,再配合 grep、sed、sort、wc 做统计。判断攻击是否成功不能只看有没有攻击包,要看后续有没有建立会话、有没有异常外连和文件传输,这一点在后文的任务四里体现得特别明显。
2. 实验内容
2.1 实验环境
主机 / 角色 | 系统 | 用途与主要工具 |
|---|---|---|
Kali 虚拟机(主机名 luojinglin) | Kali Linux | 主分析环境:file、md5sum、sha256sum、objdump、upx 4.2.4、strings、python3(pefile)、wine 10.0(运行 PE 样本)、tshark、capinfos、grep/sed/sort 管道 |
宿主机 | Windows 10 | 虚拟机管理、报告撰写与截图核对 |
样本与数据:RaDa.exe(恶意代码样本)、crackme1.exe、crackme2.exe(逆向练习程序)、botnet_pcap_file.dat(蜜罐主机 5 天的 Snort 抓包数据)。
2.2 实验任务
任务一,对 RaDa.exe 做文件类型识别、脱壳和字符串提取,查出样本作者;任务二,静态分析 crackme1.exe 与 crackme2.exe,找到能输出成功信息的输入;任务三,把 RaDa.exe 当作分析对象,围绕摘要、目的、特性、反分析技术、分类、同类工具、作者归因和检测方法写一份完整分析;任务四,对蜜罐流量做取证,还原蜜罐被攻破并加入僵尸网络的全过程。
2.3 总体方案
四个任务其实是一条由内向外的主线。前两步在文件层面把样本“扒开”:先辨认它是什么、被什么壳包着,脱掉壳之后再看里面的字符串和代码;任务二把注意力放在代码逻辑上,练习从反汇编里读出校验条件;任务三回到样本本身,把前面零散看到的现象归纳成一份能交给别人看的分析结论;任务四则把视角抬到网络层面,用一个真实的蜜罐流量包复盘入侵与失陷。前三个任务练的是“看一个文件”,第四个任务练的是“看一段历史”,两者合起来才是一次完整的攻防视角。
3. 实验过程
3.1 任务一:RaDa 的文件类型识别、脱壳与字符串提取
3.1.1 文件格式与类型识别
先把 RaDa.exe 放进 Kali 虚拟机(主机名 luojinglin)的 ~/lab4 目录,在命令行执行 file 命令,并顺手算出两个哈希值做样本登记:
file RaDa.exe
md5sum RaDa.exe
sha256sum RaDa.exe
RaDa.exe: PE32 executable for MS Windows 4.00 (GUI), Intel i386, 3 sections
MD5: caaa6985a43225a0b3add54f44a0d4c7
SHA256: 13c2379e9d9f679396e21a3391804cc834892f25691381b6d078d07b56d38f90
逐项读 file 的输出:PE32 说明它是 32 位 PE 格式,不是 64 位;MS Windows 4.00 是目标操作系统版本;GUI 表示它链接的是图形子系统,运行时会有窗口而不是纯命令行;Intel i386 是目标指令集;3 sections 说明节表只有三项。也就是说,这是一个在 32 位 Windows 上运行的图形界面程序,符合早期远程控制类木马的形态。MD5 与公开样本库中该样本的登记值一致,可以确认拿到的是挑战原始样本。

图 3-1 file 命令识别 RaDa.exe 的文件类型、运行平台与哈希登记
接着直接执行 strings RaDa.exe,屏幕上几乎全是不可读的乱码,正常的提示语、路径一条都看不到。这个现象基本可以断定样本被加壳了——字符串被压缩或加密,只有运行时才在内存里还原。先用 objdump -h 看节表:
objdump -h RaDa.exe
Idx Name Size VMA LMA File off Algn
0 JDR0 0000b000 00401000 00401000 00000400 2**2
1 JDR1 00004000 0040c000 0040c000 00000400 2**2
2 .rsrc 00000e00 00410000 00410000 00004400 2**2
节名不是正常的 .text/.data,而是 JDR0、JDR1——UPX 打出来的节应该叫 UPX0、UPX1,名字被改过。再用 upx -l 验证:
upx: RaDa.exe: CantUnpackException: file is modified/hacked/protected; take care!!!

图 3-2 节表异常与 UPX 校验报错证实样本被改过特征的 UPX 壳
两条证据合起来就能下结论:壳是 UPX(节布局、节尺寸与 UPX 的两段式压缩节完全一致),但作者把 UPX 的指纹抹掉了——节名从 UPX0/UPX1 改成 JDR0/JDR1,文件里本该出现三处的 UPX! 魔数也被改成了 JDR!,目的就是让 upx 工具和 PEiD 这类特征匹配的识别器认不出来。这正是典型的反分析手法:不改壳的算法,只改壳的“签名”。
3.1.2 脱壳处理
既然壳本体就是 UPX,只是签名被改了,那就不必动用内存 dump:把指纹改回去,让 upx 自己完成脱壳。用 python 把三处被篡改的特征还原——节名 JDR0、JDR1 改回 UPX0、UPX1,魔数 JDR! 改回 UPX!:
python3 -c "d=open('RaDa.exe','rb').read()
for a,b in [(b'JDR0',b'UPX0'),(b'JDR1',b'UPX1'),(b'JDR!',b'UPX!')]:
print(a.decode(),'>',b.decode(),':',d.count(a),'处'); d=d.replace(a,b)
open('RaDa_fix.exe','wb').write(d)"
JDR0 -> UPX0 : 1 处
JDR1 -> UPX1 : 1 处
JDR! -> UPX! : 1 处
再用修复后的文件执行 upx -d:
upx -d -o RaDa_unpacked.exe RaDa_fix.exe
57344 <-2099236.61%win32/peRaDa_unpacked.exeUnpacked 1 file.

图 3-3 修复 UPX 指纹后脱壳得到 RaDa_unpacked.exe
原文件 20992 字节,脱壳后 57344 字节,膨胀到 2.7 倍,这是解压后的正常现象。为了确认脱壳是否成功,对脱壳文件再执行一次 strings,此时能看到大量可读的路径、函数名和提示语,说明壳已经被去掉。

图 3-4 脱壳前后 strings 输出对比
3.1.3 字符串提取与作者信息
本以为任务到此结束,结果 strings RaDa_unpacked.exe 里翻了几遍也没找到像人名的内容。原因在 1.3 节已经提过:这个样本是 Visual Basic 写的,字符串以 UTF-16LE 存储,而 strings 默认按 ASCII 扫描,宽字符中间夹着 0x00,天然被切碎。加上 -el 参数按小端宽字符重新扫描,信息量立刻不一样了,能看到完整的工作目录、注册表路径,以及下面这一行:
strings -el RaDa_unpacked.exe | grep -iE 'author|siles|perez'
Scan Of The Month 32 (SotM) - September 2004
Copyright (C) 2004 Raul Siles & David Perez
--authors
Authors: Raul Siles & David Perez, 2004

图 3-5 Unicode 字符串中的作者信息
另一个更省事的验证办法是直接让样本自己说:字符串里有 --authors 参数,把样本放进 wine 里带参数运行,程序会把作者信息回显出来。两种方法结果一致,可以互相印证。
wine RaDa.exe --authors

图 3-6 样本 --authors 参数的作者回显
任务一的结论汇总如下:
项目 | 结果 |
|---|---|
文件格式 | PE32(32 位 Portable Executable) |
运行平台 | Intel 80386 系列 / 32 位 Windows,GUI 子系统 |
加壳工具 | UPX(节名与魔数被改为 JDR,指纹被抹除以逃避识别) |
编写作者 | Raul Siles 与 David Perez,2004 年 |
其他线索 | 字符串中含“Scan Of The Month 32 (SotM) - September 2004”,指向 Honeynet Project 2004 年 9 月的公开挑战样本 |
3.2 任务二:crackme1.exe 与 crackme2.exe 的逆向分析
3.2.1 crackme1.exe
把两个程序传进虚拟机后先不急着上反汇编器,直接在命令行乱敲几次,观察程序对错误输入的反馈,这一步能省很多事。不输参数时它提示缺少东西,随便输一个参数时它反问“你在说什么”,说明它既检查参数个数,也检查参数内容。
用 strings 扫一遍 crackme1.exe,能看到 “I know the secret” 这条明显的提示语。用 radare2 打开程序,列出字符串并定位引用它的函数:
r2 -q crackme1.exe
[0x00401000]> izz~secret
[0x00401000]> aaa; pdf @ main
反汇编输出的逻辑很清楚:
int __cdecl main(int argc, char **argv)
{
if (argc != 2) { puts("I think you are missing something."); return 1; }
if (strcmp(argv[1], "I know the secret")) { puts("Pardon? What did you say?"); return 1; }
puts("..."); // 校验通过后的成功提示
return 0;
}
也就是说,程序只接受一个参数,而且这个参数必须恰好是 I know the secret。crackme1 是控制台程序,直接放进 wine 里验证:
wine crackme1.exe "I know the secret"

图 3-7 crackme1 的校验逻辑(反汇编)
程序不再报错,输出了成功信息,说明输入正确。这里有个细节值得记:参数必须加引号,否则空格会把一个参数拆成四个,argc 变成 5,第一道判断就过不了。

图 3-8 crackme1 输入正确口令后的输出
3.2.2 crackme2.exe
crackme2 用同样的方法分析,字符串里同样有 “I know the secret”,但照着 crackme1 的做法输入之后仍然失败,多了一句 I have an identity problem.。回到 radare2 看反汇编,发现判断比 crackme1 多了一层,而且这一层判断的对象不是参数,是 argv[0]:
if (argc != 2) { ... }
if (strstr(argv[0], "crackmeplease") == NULL) { ... } // 检查自身文件名
if (strcmp(argv[1], "I know the secret")) { ... }
argv[0] 是程序被调用时的路径,所以程序其实在检查自己的文件名里是否包含 crackmeplease。把文件复制一份并改名为 crackmeplease.exe,再执行:
cp crackme2.exe crackmeplease.exe
wine crackmeplease.exe "I know the secret"
这次输出变成了:
We have a little secret: Chocolate

图 3-9 crackme2 中对自身文件名 argv[0] 的检查

图 3-10 crackme2 改名后输出成功信息
这个题目设计的用意挺明确的:crackme1 只考“看得懂比较逻辑”,crackme2 加了自身文件名校验,逼着人去想“程序能拿到的输入不止命令行参数”。真实恶意代码里类似的套路很多,比如靠自身所在路径、注册表里的安装目录、机器名来判断是不是在分析环境中,只看参数是永远过不去的。
3.3 任务三:RaDa 样本分析报告
(1)二进制文件摘要
样本是一个 32 位 Windows GUI 可执行程序,PE32 格式,内含 3 个节,链接器为较老的版本,编译时间落在 2004 年,与字符串里出现的 2004 年署名一致。文件经 UPX 压缩加壳,因此原始文件熵值偏高、导入表项很少。脱壳后可以看到这是 Visual Basic 6 编写的程序,依赖 msvbvm60.dll 运行库,字符串以 Unicode 存放。
识别项 | 内容 |
|---|---|
文件名 | RaDa.exe(脱壳后 RaDa_unpacked.exe) |
文件类型 | PE32 executable for MS Windows 4.00 (GUI), Intel i386, 3 sections |
文件大小 | 加壳 20992 字节;脱壳后 57344 字节(膨胀至 2.7 倍) |
MD5 / SHA256 | MD5 = caaa6985a43225a0b3add54f44a0d4c7;SHA256 = 13c2379e9d9f679396e21a3391804cc834892f25691381b6d078d07b56d38f90 |
编译器特征 | Visual Basic 6.0(msvbvm60.dll、宽字符字符串) |
加壳信息 | UPX 压缩壳,节名改为 JDR0/JDR1、魔数改为 JDR! 以抹除指纹 |
关键字符串 | C:\RaDa、C:\RaDa\tmp、RaDa_commands.html、http://10.10.10.10/RaDa、Software\Microsoft\Windows\CurrentVersion\Run、Wscript.Shell、Starting DDoS Smurf remote attack、Authors: Raul Siles & David Perez, 2004 |
说明:哈希值本身是识别同一个样本最省事的凭据,同一份样本换名字、换压缩包,哈希一定不变;反过来,只要有一个字节不同,哈希就完全不同,所以它只能用来“确认是同一份”,不能用来“判断是同家族”。实际比对时通常把哈希、文件大小、编译时间戳、关键字符串几项合起来看。
(2)二进制文件的目的
综合字符串和动态行为,这是一个以 HTTP 为控制通道的远程访问木马,同时带有下载器和 DDoS 客户端的成分。程序被运行后先在自己的工作目录(C:\RaDa 及其 bin、tmp 子目录)落位,然后通过 Wscript.Shell 对象向注册表 Run 键写入启动项,实现开机自启;之后周期性地向 http://10.10.10.10/RaDa 请求 RaDa_commands.html,把这个文件当作指令清单下载到本地 tmp 目录,解析之后执行对应动作,包括执行系统命令、上传下载文件、发起流量攻击等。换句话说,它给攻击者在受害主机上开了一条能长期使用的隐蔽通道,而不是一次性破坏。

图 3-11 wine 环境下观察 RaDa 的驻留与网络行为
(3)样本具有的不同特性
一是有持久化能力。通过注册表 Run 键而不是启动文件夹,做得比较隐蔽,普通用户不会去翻注册表。二是有独立的工作目录结构,C:\RaDa 下分 bin 与 tmp,说明它预期要长期存放工具和临时数据,这是“可持续运营”的设计,不是打完就跑。三是控制通道走 HTTP,混在正常的 80 端口流量里,比自建 TCP 通道更容易穿过出口防火墙。四是指令与程序分离,具体要干什么写在服务器下发的 RaDa_commands.html 里,本地程序只负责解析执行,这让基于静态特征的检测很难奏效——文件里根本没有下一步动作的痕迹。五是具备泛洪攻击与内网扫描能力,字符串里能看到对 192.168、172.16、10 三个私有网段的探测逻辑,说明它把被控主机当成进一步扩散的跳板。六是带虚拟环境判断,在 VMware 等环境中会改变行为甚至不动作,属于典型的反分析设计。
(4)防止被分析或逆向的技术
最直接的一层是 UPX 加壳。加壳之后入口点指向壳的节,导入表被打乱,静态反汇编只能读到壳的解压代码,用 strings 也看不到任何有效信息,这一步就把大部分“随手看看”的分析挡在外面。第二层是字符串采用 Unicode 编码,即便脱了壳,用默认参数跑 strings 仍然一无所获,必须知道去改编码设置,这是利用工具默认行为设置的障碍。第三层是反虚拟机检测,通过查询硬件特征、注册表键或特定驱动来判断自己是否跑在分析环境里,如果在,就选择休眠或表现正常,让分析人员看不到恶意行为。第四层是行为上的“延迟与分离”:真正恶意的动作要等远程指令下发才发生,样本本身不含攻击代码,沙箱短时间运行往往只能看到它安静地创建目录、写注册表。
对应的破解思路也一并记下来:加壳就用脱壳机或内存 dump,字符串编码问题就换编码或直接看内存,反虚拟机就修补检测函数的返回值或者换用物理机、改硬件特征,行为延迟就拉长沙箱观察时间并伪造可用的 C&C 服务端。
(5)样本分类及理由
归类为后门木马(Backdoor / RAT),兼具僵尸网络客户端与下载器的性质。判断依据是它的行为链条:写入注册表实现自启、建立隐藏的工作目录、主动外连固定地址获取指令、按指令执行任意操作。它没有感染其他可执行文件的代码,不具备病毒的自我复制能力;也没有利用漏洞主动扫描传播的模块,因此不属于蠕虫。它进入宿主的方式依赖外部投递——本次实验的语境里,蜜罐是被蠕虫和弱口令攻击打下来之后,攻击者再上传此类工具做长期控制的。这一点也解释了为什么它要做得这么“轻”:它是被人放进去的,不需要自己跑。
(6)具有相似功能的其他工具
功能上最接近的是各类国产远控木马,灰鸽子是典型代表,同样是服务端生成、客户端图形化管理、支持文件管理与屏幕控制;Gh0st RAT 与 Poison Ivy 也是同一类东西,后者以生成器和多上线方式著称;Bifrost、Beast 这些老一代 RAT 在设计思路上与 RaDa 几乎平行。往僵尸网络方向看,SDBot、Agobot、rBot 这些 IRC Bot 提供的指令集(DDoS、下载执行、扫描)与本样本高度重合,区别只是控制通道从 IRC 换成了 HTTP。攻击侧的工具链里,Metasploit 的 meterpreter 配合 msfvenom、Veil-Evasion 之类的生成器,也能产出功能等价、形态更现代的载荷。合规的运维工具如 PsExec、Radmin 在“远程执行”这一点上能力相似,区别在于它们有明确的授权与日志,而这类样本的一切设计都指向隐藏。
(7)开发作者的可追溯性
就本样本而言,可以追溯到署名层面,而且证据不止一处:脱壳后的字符串里有“Authors: Raul Siles & David Perez, 2004”,程序自身提供 --authors 参数会主动回显,字符串里还有“SotM 32 - September 2004”,指向 Honeynet Project 2004 年 9 月的公开挑战。三者互相印证,可以确定这份样本的作者署名是 Raul Siles 与 David Perez。
但必须把“署名”和“真人身份”分开看。字符串是可以随意伪造的,攻击者嫁祸他人是常规操作;即便署名属实,也只说明这两个名字与该程序有关,不等于能定位到现实中的某个自然人。要往前推进,需要额外的限定条件:一是要有独立来源的旁证,比如样本首次出现的渠道、发布帖、同一作者的其他作品在代码风格与编译环境上的一致性;二是要有技术侧的指纹,比如 PDB 路径、编译器的地区设置、代码里的语言习惯;三是要有司法层面的手段,比如通过邮箱、支付记录、服务器租用信息去关联,这已经超出个人分析的范围。可行的环境是断网的隔离虚拟机加公开威胁情报库(样本库的首次提交时间、哈希关联),在“样本归属”这个层级给出结论;再往上,只能给出可能性而不是结论。
(8)检测该恶意软件的方法
第一,基于哈希的特征匹配。对样本计算 MD5 与 SHA256,与杀毒软件、威胁情报平台的库比对。它的优点是零误报、极快,适合做文件准入和全网排查;缺点是只要样本被重新编译或改一个字节就失效,对加壳变种无能为力。工程上通常对原始样本和脱壳后的样本各维护一份哈希。
第二,基于 PE 结构与 YARA 规则的静态检测。不依赖完整哈希,而是匹配结构特征和字符串片段,例如节名同时出现 UPX0 与 UPX1、入口点位于最后一个节、节区大小与文件大小严重不符、导入表异常精简、存在 msvbvm60.dll 依赖,再加上 C:\RaDa、RaDa_commands.html、Software\Microsoft\Windows\CurrentVersion\Run 这些字符串的组合。YARA 规则可以把这些条件写成布尔表达式,既能抓住同族变种,又比纯哈希稳。难点在于规则要不断维护,写得过宽会误报正常程序。
第三,主机行为监控。这是最贴近实战的一层。监控注册表 Run 键的新增写入、C:\RaDa 及子目录的创建、进程对 wscript.shell COM 对象的调用、以及由 Office 或浏览器进程派生出的可疑子进程。这一层不关心样本长什么样,只看它做了什么,因此对加壳、改名、重新编译都免疫。代价是需要部署 EDR 或 Sysmon 之类的探针,并且要能忍受一定量的告警噪音。
第四,网络流量检测。样本的控制通道有很强的规律性:周期性向固定地址请求 RaDa_commands.html,URI 里带 RaDa 字样,还会访问 download.cgi、upload.cgi 这类接口。在出口设备或 IDS 上针对这些 URI 特征、访问周期、异常的 User-Agent 写规则,就能在它上线时抓到。再结合内网横向的行为特征(对 445、139 的大量连接、对三个私有网段的扫描)做关联,准确率会更高。局限是攻击者换掉域名和 URI 就失效,所以要和 DNS 日志、代理日志一起看。
第五,内存扫描与内存取证。加壳样本在磁盘上是加密的,在内存里一定是明文。对可疑进程做内存 dump,然后对 dump 文件跑 Unicode 字符串提取和 YARA 扫描,往往能直接命中作者信息、C&C 地址和指令关键字。这一方法专门克制压缩壳和保护壳,是目前对抗加壳恶意代码最有效的手段之一。前提是得有权限抓内存,且要赶在进程退出之前。
第六,沙箱动态分析与日志关联。把样本丢进断网或仿真网络的沙箱运行一段时间,记录文件、注册表、进程、网络四类行为,形成行为报告。由于本样本的恶意动作依赖远程指令,沙箱里需要伪造一个能返回指令文件的 HTTP 服务端,才能把后续行为激发出来。日志侧则把 DNS 请求、代理访问记录、进程创建日志按主机和时间做关联,找出“同一台主机先访问可疑 URI、随后出现异常外连”的模式,这是从单点检测上升到事件研判的关键一步。
3.4 任务四:Windows 2000 蜜罐被攻破并加入僵尸网络的取证分析
(1)IRC 与加入消息、常用端口
IRC(Internet Relay Chat)是互联网中继聊天协议,采用客户端—服务器模型,用户连接到服务器后加入频道进行群聊或私聊。客户端申请加入网络时,首先要向服务器发送 NICK 消息设定自己的昵称,紧接着发送 USER 消息上报用户名、主机名和真实姓名,服务器校验通过后返回 001 至 004 的欢迎消息,之后客户端再用 JOIN 加入具体频道。IRC 一般使用 TCP 6667 端口,6660 至 6669 这一段也常被使用,7000 端口在部分网络中出现,需要加密时则走 6697。
(2)僵尸网络及其用途
僵尸网络(Botnet)是攻击者通过命令与控制信道一对多控制的大量被感染主机集合,这些主机被称为“肉鸡”或 Bot。控制者(Botmaster)通过 C&C 服务器下发指令,Bot 自动执行。它的用途主要有:发起分布式拒绝服务攻击,用数量压垮目标带宽或连接数;批量发送垃圾邮件;作为扫描与爆破的平台,继续扩大感染范围;窃取受害主机的账号、文件等敏感数据;以及作为攻击者的代理跳板,隐藏真实来源。本实验中的蜜罐就是被植入了 IRC Bot 后加入某个频道,成为其中一员。
(3)蜜罐通信过的 IRC 服务器
IRC 使用 TCP 连接,所以要找它连过哪些服务器,只要找蜜罐主动向哪些地址的 6667 端口发过 SYN 即可。用 tshark 过滤:
tshark -r botnet_pcap_file.dat \
-Y 'ip.src==172.16.134.191 && tcp.dstport==6667 && tcp.flags.syn==1 && tcp.flags.ack==0' \
-T fields -e ip.dst | sort | uniq -c | sort -rn
结果指向 5 个地址:209.126.161.29、66.33.65.58、63.241.174.144、217.199.175.10、209.196.44.172。从时序上看,蜜罐是先连前四台,握手完成后不久就被断开或拒绝,直到第五台 209.196.44.172 才被接受并允许加入频道。这与 IRC Bot 常见的“备用服务器列表”设计一致:程序里按顺序尝试,连上哪台算哪台。

图 3-12 蜜罐主机发往 6667 端口的目的地址分布
(4)访问该僵尸网络的不同主机数
这一步用图形界面数不现实,改用 tshark 把 Bot 服务器 209.196.44.172 与 6667 端口之间的 TCP 载荷全部导出来,再用 python 还原成文本、从 IRC 的 353(NAMES)回复里统计去重的昵称数:
tshark -r botnet_pcap_file.dat -Y 'ip.addr==209.196.44.172 && tcp.port==6667' \
-T fields -e tcp.payload | python3 nick_count.py
统计结果为 3454 个不同的昵称,即观察期内至少有 3454 台主机在该服务器的频道里出现过。需要说明的是,服务器在欢迎消息里自报的全局用户数是 4752 人(上限 4765),这两个数字口径不同:353 回复的 NAMES 列表只覆盖当前频道内的成员,而服务器自报的是整台服务器的在线用户总数。另外按挑战官方说明,整个观察期累计进入该僵尸网络的主机共 15164 台。报告里取“频道内 NAMES 去重”的 3454 作为回答口径,并在截图里附上统计脚本,保证数字可复现。

图 3-13 353 NAMES 去重统计得到的主机数
(5)用于攻击蜜罐的 IP 地址
按源地址分组统计进入蜜罐的流量:
tshark -r botnet_pcap_file.dat -Y 'ip.dst==172.16.134.191' \
-T fields -e ip.src | sort | uniq -c | sort -rn | head
统计结果里,除 Bot 服务器 209.196.44.172 本身(正常 C&C 流量)外,攻击行为的分布很清楚:207.172.16.150,是蜜罐自己访问取证工具下载站的对象(非攻击);主要攻击源有 24.197.194.106(4549 个包),对 Web 服务做了大量扫描,尝试 FrontPage 扩展、默认脚本、目录遍历等;61.111.101.78(1433 个包),向 445 端口发送 Deloder 蠕虫的载荷;210.22.204.101(1165 个包),对 445 端口做口令尝试并成功上传远程管理工具;217.151.192.231(1028 个包),蜜罐下载 ZipCentral 的来源站点;129.116.182.239 与 195.36.247.77,同样打 445 并试图读取 SAM 文件;199.107.7.2,探测 31337 端口(Back Orifice 的默认端口);此外还有一大批源地址向 UDP 1434 发送 SQL Slammer 的探测包,以及向 139、137、111、1080、21 等端口做零星探测的主机。

图 3-14 攻击蜜罐的主要源地址
(6)攻击者尝试利用的安全漏洞
结合端口、载荷特征和漏洞利用的固定指纹,可以归纳出以下几类:IIS 与 Index Server 的 Web 类漏洞,包括 MS01-033(Index Server ISAPI 扩展缓冲区溢出)、.ida/.idq 脚本、FrontPage 扩展和目录遍历;Windows 2000 的 SMB 服务弱口令问题,攻击者直接对 TCP 445 尝试管理员账号,这正是 Deloder 蠕虫的传播方式;SQL Server 的两个漏洞 MS02-039 与 MS02-061,对应 UDP 1434 的 Slammer 与 SQLExp;SQLsnake 蠕虫针对 sa 账号空口令、SQL 服务以管理员权限运行的配置缺陷;此外还有 NetBIOS 共享探测、FTP 与代理端口探测、Back Orifice 后门端口探测、以及 UDP 28431 的 Hack'A'Tack 探测。
(7)成功的攻击及其成功方式
有两条攻击链是明确成功的。第一条来自 61.111.101.78:3 月 6 日 05:35:34 开始向蜜罐的 445 端口发送 W32/Deloder 蠕虫载荷,05:38:29 载荷传输结束,期间攻击者在目标上执行了一系列命令,把 inst.exe、Devlr32.exe 复制到 system32 并运行,同时删除 C$、D$、E$、ADMIN$ 等共享;6 分 50 秒后的 05:45:19,蜜罐主动向 66.33.65.58 的 6667 端口发起连接,开始了向五台 IRC 服务器依次尝试的过程,最终在 209.196.44.172 上完成注册并加入频道。这一连串时间点几乎是把“蠕虫落地—执行—植入 Bot—外连上线”四个阶段按顺序摆了出来。
第二条来自 210.22.204.101:3 月 5 日凌晨对 445 端口反复尝试管理员口令,随后上传并安装了 Remote Administrator Server 2.1,此后蜜罐的 4899 端口与该地址之间出现了持续的双向数据流,说明攻击者已经可以通过 Radmin 图形化控制这台主机。
两条链能成功,根子是同一个:蜜罐的管理员账号是空口令,且系统补丁缺失。445 端口上的口令校验形同虚设,蠕虫和人手都能直接进;进了之后,一个是自动化的 Bot,一个是人工操控的远控工具,殊途同归。反过来看,Web 扫描、Slammer、SQLsnake 这些尝试虽然数量巨大,但没有在流量里留下入侵成功的证据,属于“打了但没打进去”。这也说明一个道理:判断攻击是否成功,必须看后续有没有落地、有没有外连,光有攻击包不算数。

图 3-15 Deloder 蠕虫载荷与执行痕迹

图 3-16 蜜罐加入僵尸网络的上线过程
4. 问题及解决方案
问题 1:脱壳后用 strings 仍然找不到作者信息,一度怀疑脱壳失败。
问题 1 解决方案:脱壳文件用默认参数跑 strings 依然找不到人名,但输出里能看到大量形如“C”后跟空字节的碎片,判断是宽字符被 ASCII 扫描切碎了。改用 strings -el 按小端宽字符扫描后信息全部出现。这件事让我记住了:字符串提取不是“跑个命令”这么简单,编码是必须确认的前提。
问题 2:upx -d 直接脱壳报“file is modified/hacked/protected”,超级巡警一类的图形脱壳机在 Kali 的 wine 环境里又因中文乱码无法操作。
问题 2 解决方案:先判断壳的类型和被改的位置。objdump -h 看到节名是 JDR0/JDR1,对比 UPX 标准输出确定节布局没变、只是签名被改,于是用 python 把三处特征(JDR0、JDR1、JDR!)改回 UPX 的标准值,再交给 upx -d 顺利完成脱壳。这件事让我体会到,识别器认不出壳不代表壳变强了,先分清“算法变了”还是“签名被改”,后者一条 replace 就能解决。
问题 3:crackme1 输入正确口令仍然失败。
问题 3 解决方案:最初是直接输入 I know the secret 没加引号,空格把参数拆成了四个,argc 不等于 2,第一道判断就返回了。加上双引号后通过。后来在伪代码里看到了 argc != 2 的判断,才明白报错“I think you are missing something”的真正含义是参数个数不对,而不是内容不对。
问题 4:crackme2 用同样的口令一直不成功,反汇编里也找不到原因。
问题 4 解决方案:把伪代码从头到尾读了一遍,发现除了参数校验还有一处对 argv[0] 的字符串查找,比对的关键字是 crackmeplease。把程序复制改名为 crackmeplease.exe 后运行成功。这个坑让我意识到,逆向时不能只盯着“我输入的东西”,程序自身的信息(路径、文件名、环境变量)同样可能参与校验。
问题 5:想用 tcpflow 按 TCP 流拆分会话,但虚拟机没有外网,apt 安装不了 tcpflow。
问题 5 解决方案:改用已经装好的 tshark。-T fields -e tcp.payload 可以把符合条件的 TCP 载荷逐包导出,配合一段十几行的 python 脚本做十六进制还原和正则统计,同样能完成 tcpflow 加 grep、sort、wc 管道的全部工作,几秒钟就出结果。这件事说明工具不在于全,而在于会不会组合——tshark 负责取数据,python 负责加工,效果是一样的。
问题 6:统计僵尸网络主机数时,自己数出来 3454,但服务器欢迎消息里写的是 4752,不知道哪个对。
问题 6 解决方案:回去看 IRC 协议的消息语义。353 是 NAMES 回复,列出的是当前频道的成员;服务器欢迎消息里的数字是整台服务器的全局在线用户数。两者覆盖范围不同,不存在谁对谁错。报告里把几个数都写出来并注明口径,比强行取一个更准确。这个问题让我体会到取证结论必须交代“数字是怎么来的”。
问题 7:截图主机名不符合要求,返工了一次。
问题 7 解决方案:虚拟机默认主机名是随机字符串,需要提前改成姓名拼音。在“我的电脑—属性—计算机名”里修改后重启,之后所有截图都带上了正确的主机名;涉及自己编辑的文件,文件名统一加学号前缀,避免交上去之后对不上人。
5. 学习感悟、思考等
这次实验最大的感受是,恶意代码分析并不神秘,它更像一套有固定顺序的排查流程:先看它是什么,再看它被什么包着,脱掉壳看它说了什么,然后才谈得上读懂它想干什么。真正卡住人的往往不是高深的技术,而是一些很朴素的前提——编码没设对,字符串就在眼前也看不见;参数没加引号,口令再对也进不去。这些细节在课本上是不会写的,只有自己踩过才知道疼。
加壳这一层给我的印象很深。在没有脱壳之前,一个样本几乎是一块铁板:strings 全是乱码,反汇编只有几十行壳代码,导入表干净得可疑。可一旦脱掉壳,作者署名、C&C 地址、注册表路径、攻击网段全都摊在眼前。这也让我理解了为什么攻防双方在“壳”上投入这么多:防守方想看清,攻击方不想被看清,围绕这一层已经演化出压缩壳、加密壳、虚拟化保护一整套技术谱系。而内存取证之所以重要,就是因为不管磁盘上加了多少层壳,程序跑起来的那一刻必须是明文。
任务二的两个 crackme 让我第一次认真读了反汇编。以前觉得逆向就是“看懂汇编指令”,实际做下来发现,重点是抓判断点和数据流:找到字符串、顺着交叉引用回到比较的地方、看清比较的两个操作数分别来自哪里。crackme2 里那个对 argv[0] 的检查尤其有意思,它把“输入”的概念从命令行参数扩展到了程序自身的属性。这和真实恶意代码里的反分析手法是一回事——判断自己是不是在虚拟机里、是不是被调试,本质上都是在检查“环境”这个隐藏的输入。
任务四的取证是另一种训练。同一份流量,可以只回答“有哪些 IP”,也可以一路追到“这台机器几点几分被什么打穿、打穿之后干了什么、什么时候上的线”。区别在于有没有把时间线和因果链串起来。印象最深的是那 6 分 50 秒的间隔:蠕虫载荷执行完,到蜜罐去连 IRC 服务器,中间隔了不到七分钟。这个时间差就是“失陷”这件事的可测量证据。另外,统计数字对不上的那次也让我学到了一件事:遇到矛盾先怀疑口径,不要先怀疑数据。
如果让我给这门课的学习提一点自己的看法,我会说,工具会用只是及格线,知道工具为什么这么设计才算入门。识别器为什么认不出这个壳,因为它比的是 UPX 的特征串,而样本把特征串改了;strings 为什么会漏掉作者,因为 VB 用的是宽字符;沙箱为什么看不到行为,因为恶意动作在等远程指令。把这些“为什么”想清楚,换一个样本、换一个工具,思路仍然是通的。最后也是一句提醒自己的话:所有这些操作都必须放在断网的隔离环境里做,样本可以重来,主机中招了没法重来。
参考资料
[1] 诸葛建伟, 等. 网络攻防技术与实践[M]. 北京: 清华大学出版社.
[2] Michael Sikorski, Andrew Honig. 恶意代码分析实战[M]. 诸葛建伟, 等译. 北京: 电子工业出版社.
[3] The Honeynet Project. Scan of the Month 32: RaDa Analysis[EB/OL]. https://www.honeynet.org/.
[4] The Honeynet Project. Scan of the Month 27: Botnet Forensics[EB/OL]. https://honeynet.org/scans/scan27/.
[5] UPX Team. UPX: the Ultimate Packer for eXecutables[EB/OL]. https://upx.github.io/.
[6] radare2 Official Documentation[EB/OL]. https://radare2.io/.
[7] 谢希仁. 计算机网络(第8版)[M]. 北京: 电子工业出版社.

浙公网安备 33010602011771号