Win32 PE病毒感染实验

发布于 2025-12-15  108 次阅读


一个简单的实验小作业

下面的病毒样本可以直接下载,后缀txt修改为exe即可,运行效果为感染同目录下文件,被感染文件运行后会创建三个txt文件,较无害

原始病毒样本

完善后病毒样本

病毒源码与完善后病毒源码(后缀txt修改为cpp即可编译)

一、实验目的:

从Win32病毒的原理了解其特性:

Win32病毒是代码片段(Shellcode);

Win32病毒具有自我复制的特性;

Win32病毒的功能不依赖于宿主对象的API函数;

为了实现通用性,Win32病毒常采用新加节的方式实现。

二、实验环境:

Win32病毒p3inf:感染同目录中所有PE文件;

已感染对象target.exe:被清除病毒的目标文件;

未感染对象RemovePeSign.exe

p3inf源码

二进制编辑器UltraEdit、

文件比对工具hexcomparison

PE文件查看器Stud_PE

三、实验原理

通过观察p3inf.exe感染文件的现象,结合课堂讲授的Win32病毒原理,手工完成对被感染对象target.exe的修复还原。

阅读p3inf的源码,完善病毒代码

(1)p3inf病毒会重复感染同一对象,修改使其只感染对象一次;

(2)p3inf病毒不具备递归感染的特性,修改使其完成递归复制的功能;

四、实验步骤

1.感染文件消毒

实验开始,我们需要对病毒功能进行测试(能否正常使用)

由于病毒的功能比较无害,创建一些简单的txt文件,我们直接在本机进行测试,创建实验目录

在运行p3inf-win10.exe之后,exe文件会被感染,下面是运行效果

可以看到多了三个txt文件,还有启动exe时对出现Haxxed的提示框

我们先将被感染的exe文件拖进StudPE中查看,可以看到区段中多了一个奇怪的东西.spooky(非正常命名)

为了修复该病毒感染的程序,我们需要删除.spooky区段,但是直接删除会导致程序进入错误的入口点,所以我们同时还得修改程序入口点

要找到程序的原入口点,我们使用ollydbg查看程序,打开后可以看到入口点和Stud_PE中一致,都是8000

在.spooky区段执行结束之后,会执行正常程序,所以我们的思路就是步进执行程序知道快要跳出.spooky区段,然后查看从该区段出来之后会到哪个位置,跳出的位置大概就是原始程序的入口点

这里我们步进执行到这一步停下,刚好也要到了.spooky的出口了。

我们尝试修改入口点为00402D90,并且删除.spooky区段

之后再打开程序就是正常执行了,可以预览效果

2.病毒加强

阅读p3inf的源码,完善病毒代码

(1)p3inf病毒会重复感染同一对象,修改使其只感染对象一次;

(2)p3inf病毒不具备递归感染的特性,修改使其完成递归复制的功能;

完成该任务前需要观察源代码

从代码结构和功能来看,这是一个典型的PE 文件感染型病毒(实验性质),主要通过修改其他 32 位 PE 文件的结构,注入恶意代码实现感染,该程序的主要行为是:遍历自身所在目录的所有非目录文件,对符合条件的 32 位 PE 文件进行感染 —— 在目标文件中添加一个新的代码节区,注入恶意代码,并修改程序入口点使其优先执行恶意代码,最后跳回原程序入口点。

程序的行为结构如下:

1. run() 函数:遍历文件并触发感染

获取自身路径:通过 GetModuleFileName 获取当前程序的路径,用于排除自身(避免重复感染)。

遍历目录文件:使用 FindFirstFile 和 FindNextFile 遍历当前目录下的所有非目录文件(过滤文件夹),收集文件名到 file_names 列表。

触发感染:对列表中的每个文件,若不是自身,则调用 infect 函数尝试感染,并输出感染结果。

2. infect() 函数:核心感染逻辑

这是病毒的核心,负责修改目标 PE 文件结构并注入恶意代码,步骤如下:

(1)验证目标文件是否为 32 位 PE 文件

读取文件数据到内存,解析 DOS 头(IMAGE_DOS_HEADER),通过 e_magic 验证是否为有效的 DOS 签名(MZ)。

解析 NT 头(IMAGE_NT_HEADERS),通过 OptionalHeader.Magic 判断是否为 32 位 PE 文件(0x10B,即 267),若为 64 位则终止感染。

(2)添加新的节区

在目标文件的节表(IMAGE_SECTION_HEADER)末尾添加一个新节区,名称为 .spooky(通过 section_name 参数传入)。

计算新节区的关键属性(基于 PE 文件对齐规则):

VirtualSize 和 VirtualAddress:按内存对齐(SectionAlignment)计算。

SizeOfRawData 和 PointerToRawData:按文件对齐(FileAlignment)计算。

节区属性 Characteristics 设为 0xE00000E0(包含可执行、可读、可写等权限,确保恶意代码可执行)。

(3)修改 PE 文件头信息

更新节区数量(NumberOfSections += 1)。

调整镜像大小(SizeOfImage),包含新节区的大小。

关闭 ASLR(地址空间布局随机化):通过 DllCharacteristics &= ~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 关闭动态基址,确保恶意代码地址固定。

(4)注入恶意代码并修改入口点

恶意代码生成:通过内联汇编生成一段代码,功能包括:

动态查找系统函数:通过遍历 PEB(进程环境块)中的导出表,获取 LoadLibraryA、GetProcAddress 等函数地址(避免直接使用硬编码地址,提高隐蔽性)。

加载系统库:通过 LoadLibraryA 加载 kernel32.dll 和 user32.dll。

弹窗行为:调用 MessageBoxA 显示消息(代码中可见字符串片段,如 "phh55_is_good_...",可能是实验性提示信息)。

跳回原入口点:通过硬编码标记 0xFADED420 定位跳转地址,运行完恶意代码后回到原程序入口,保证原程序能继续执行。

写入恶意代码:将汇编生成的恶意代码写入新节区的文件偏移(PointerToRawData)。

修改入口点:将原程序入口点(AddressOfEntryPoint)改为新节区的虚拟地址(VirtualAddress),使程序启动时优先执行恶意代码。

3. 辅助函数

alignment():根据 PE 文件的对齐规则(内存对齐 / 文件对齐)计算地址或大小,确保新节区符合 PE 格式规范。

char_to_lpcwstr():将 ANSI 字符串转换为宽字符串,适配 Windows API 的宽字符接口(如 CreateFile、FindFirstFile)。

下面我们需要分析核心修改

(1) 防止重复感染每次运行都新增 .spooky 节 → PE 结构损坏、体积膨胀✅ 遍历节表检查 .spooky 是否已存在<br>若存在则跳过感染infect() 函数开头:<br>for (int i = 0; i < file_header->NumberOfSections; i++)<br>if (strcmp(sec_name, section_name) == 0)
(2) 递归感染仅感染当前目录文件(FindFirstFile(dir\*))✅ 新增 infect_directory() 递归函数<br>✅ run() 改为调用递归入口infect_directory()<br>调用 FindFirstFile + if (DIR) infect_directory(sub)

那我们就针对制定问题进行修改

1.防重复感染:

在infect()中,使用IMAGE_FIRST_SECTION(nt_header)获取节表指针。

循环遍历NumberOfSections个节,提取节名(8字节),用strcmp比较是否为".spooky"。

如果已存在,打印提示并返回0(视为成功,不重复操作)。添加了内存清理(delete[] data)以防泄漏。

2.递归感染:

新增infect_directory(const std::string& dir_path)函数:使用FindFirstFile扫描目录,区分文件/目录。

对于子目录,递归调用自身(构建完整路径)。

run()简化:直接调用infect_directory于当前目录,自动处理递归。

排除.和..以防循环。原代码的路径构建逻辑优化为从GetModuleFileName提取目录。

然后我们再编译病毒文件,获得game.exe,然后重新安排实验环境,这次需要再test目录下再开一个test目录,测试我们的递归感染能力

运行game.exe后可以看到成功感染

证明我们的递归感染能力是没有问题的

然后我们再执行一次game.exe,可以看到不会出现两次Haxxed,证明我们也做到了防止重复感染

实验完毕


人生苦难处,正是修行时