《Windows PE权威指南》学习之第22章 PE病毒提示器

本文深入讲解Qt样式表(QSS)的语法与用法,包括选择器类型、子控件样式化、伪状态及冲突解决等内容。

本章将探讨PE病毒提示器的编写技术。

本章采用两种方式开发PE病毒提示器:一种是程序加手工的半自动化方法,另一种是全补丁的自动化方法。

声明 该PE病毒提示器不具备病毒预警功能,也无法阻止PE病毒的入侵,只是对PE病毒起到提示作用,让用户知道当前电脑的安全状况。

22.1 基本思路

PE病毒指Windows操作系统下,以PE文件为感染目标的病毒,有时也称为Win32病毒,属于文件型病毒的一种。

扩展阅读 什么是文件型病毒

所有通过操作系统的文件进行感染的病毒都称为文件型病毒。其主要目标是操作系统中的可执行文件,但不排除其他类型的文件,如宏病毒感染的就是OFFICE系列文件。这些可执行文件主要是以扩展名为“.com”、“.exe”结尾的文件。本章所描述的文件型病毒专指以“.exe”为结尾的PE文件。

打造该病毒提示器的总体思路为:首先,将某个系统PE文件(志愿者)作为这次打造生成病毒提示器的源,给该志愿者打补丁;然后,通过分析嵌入到志愿者文件中的补丁代码,实现对入侵PE病毒的检测与提示。

首先了解一下志愿者的选择条件。

22.1.1 志愿者的选择条件

志愿者即感染病毒的对象。大部分感染PE文件的病毒在传染时,都会对目标文件的大小进行检测。通常小于某一个值时不去感染,因为这种文件有好多是病毒分析者设置的“蜜罐”,可以通过这种方法很容易获得嵌入到蜜罐里的病毒代码,从而制定有效的杀毒方案。所以,确定志愿者时,一定要选择一个文件相对较大的程序。例如,本章选择采用系统的记事本程序notepad.exe,它在Windows 2000下的标准大小为50960,在Windows XP SP3下的标准大小为66560。

此外,文件型病毒入侵以后,大部分都会感染硬盘上的可执行文件。如果病毒无限制地感染磁盘文件,势必会导致硬盘指示灯不停闪烁,造成CPU占用时间过高。这种外在的表现会被细心的用户发现病毒的威胁。 因此,现在大部分文件型病毒在感染时都采取一定的策略。比如,挟持运行程序的函数,当程序运行时才感染;或者有阶段、有步骤地对系统目录、程序文件夹等进行小批量的感染操作,这样,用户从表面上是很难发觉病毒的。

本章打造的病毒提示器正好运用病毒感染的这个原理,选择系统自带的文件(记事本程序notepad.exe)作为志愿者,并从以下两方面积极地拉近与病毒之间的物理距离:

❑ 将自己放到系统文件夹中,即%WINDIR%。

❑ 将自己添加到注册表项中,每次开机都运行一遍。

这样做的目的就是让自己充分暴露给病毒,告诉病毒:你来的时候一定通知我哦!

22.1.2 判断病毒感染的原理

绝大多数情况下,病毒提示器一旦被病毒感染,其文件头就会发生相应的变化(文件头部的数据结构包括DOS MZ HEADER、DOS STUB、IMAGE_FILE_HEADER、IMAGE_OPTIONAL_HEADER、IMAGE_SECTION_HEADER),文件头部的数据结构IMAGE_SECTION_HEADER记录了PE文件中存在的每个节的相关统计信息。如果病毒文件修改了文件头部,或者修改了节区内容,那么通过对文件头部数据的分析就能得出PE文件是否被修改的结论。一旦发现文件被修改,程序会发给用户一个友好的提示,告诉他可能有病毒入侵。

扩展阅读 如何实现自我修复功能

通过对本章应用的扩展,也可以实现PE文件感染病毒后的自身修复功能。因为文件型病毒要获得执行控制权,首先要劫持目标入口地址或入口代码,目前,常见的文件型病毒,要么修改头部字段IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint,要么修改该地址指向的指令字节码。所以,只要将这两部分内容和这两部分内容的校验码保存到补丁程序里,当发现PE被修改的时候,提示用户,并重新修正该地址的值,便能达到自我修复的目的。

22.2 手工打造PE病毒提示器清

楚了编程思路以及原理之后,我们首先通过半自动化的过程开发PE病毒提示器。

本补丁实例的目标是特定的PE文件——记事本。在程序设计部分先不使用嵌入补丁框架,目的是学习在补丁代码中如何使用目标程序导入表中提供的现有函数。

22.2.1 编程思路

补丁程序的设计大致分为以下三步:

步骤1 获取函数地址。要获取地址的函数在记事本原始的导入表中并不存在,必须通过调用LoadLibraryA加载相应的动态链接库,然后调用GetProcAddress获取这些函数的入口地址。这些函数包括:

-------------------设置注册表相关-----------------------------
RegCreateKeyA(以下函数在advapi.dll中,所以应该先加载该链接库)
RegSetValueExA
------------------------显示病毒提示相关----------------------
MessageBoxA(该函数在user32.dll中,所以应该先加载该链接库)
------------------------文件相关----------------------
CreateFileA(以下函数在kernel32.dll中)
GetFileSize
CreateFileMappingA
DeleteFileA
GetWindowsDirectoryA
GetModuleFileNameA
CopyFileA

步骤2 在注册表的以下位置增加新项,名称为note,类型为REG_SZ,值为virNote.exe。

HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\RUN

加入启动项的目的是每次开机都会运行这个补丁后的记事本程序virNote.exe,以便随时监测机器是否被文件型病毒感染。

步骤3 定位到该文件的头部,生成节表的校验值,并与virNote.exe文件头部4ch偏移处存放的值进行比较。如果一致,则表示文件没有被修改过,退出不显示提示;否则显示病毒入侵的提示信息。下面对目标文件进行简单分析。

22.2.2 分析目标文件的导入表

兵法有云:“知己知彼,百战不殆”,下面将使用PEInfo小工具对目标文件(notepad.exe)的导入表进行分析,查看它使用了哪些动态链接库的哪些函数,以便确定补丁代码中是否可以直接使用这些函数。

PEInfo小工具对notepad.exe导入表的分析结果显示如下:

-------------------------------------------------------------
导入表所处的节:.text
-------------------------------------------------------------

导入库:comdlg32.dll
-----------------------------

OriginalFirstThunk   00007990
TimeDateStamp         ffffffff
ForwarderChain        ffffffff
FirstThunk             000012c4
-----------------------------

00000015           PageSetupDlgW
00000006           FindTextW
00000018           PrintDlgExW
00000003           ChooseFontW
00000008           GetFileTitleW
00000010           GetOpenFileNameW
00000021           ReplaceTextW
00000004           CommDlgExtendedError
00000012           GetSaveFileNameW

导入库:SHELL32.dll
-----------------------------

OriginalFirstThunk   00007840
TimeDateStamp         ffffffff
ForwarderChain        ffffffff
FirstThunk             00001174
-----------------------------

00000031           DragFinish
00000035           DragQueryFileW
00000030           DragAcceptFiles
00000259           ShellAboutW

......

导入库:KERNEL32.dll
-----------------------------

OriginalFirstThunk   00007758
TimeDateStamp         ffffffff
ForwarderChain        ffffffff
FirstThunk             0000108c
-----------------------------

00000318           GetCurrentThreadId
00000468           GetTickCount
00000660           QueryPerformanceCounter
00000362           GetLocalTime
00000472           GetUserDefaultLCID
00000320           GetDateFormatW
00000470           GetTimeFormatW
00000504           GlobalLock
00000511           GlobalUnlock
00000346           GetFileInformationByHandle
00000081           CreateFileMappingW
00000448           GetSystemTimeAsFileTime
00000842           TerminateProcess
00000315           GetCurrentProcess
00000822           SetUnhandledExceptionFilter
00000580         LoadLibraryA
00000374           GetModuleHandleA
00000430           GetStartupInfoA
00000500           GlobalFree
00000364           GetLocaleInfoW
00000590           LocalFree
00000586           LocalAlloc
00000952           lstrlenW
00000596           LocalUnlock
00000056           CompareStringW
00000592           LocalLock
00000234           FoldStringW
00000049           CloseHandle
00000946           lstrcpyW
00000678           ReadFile
00000082           CreateFileW
00000943           lstrcmpiW
00000316           GetCurrentProcessId
00000408         GetProcAddress
00000266           GetCommandLineW
00000937           lstrcatW
00000204           FindClose
00000211           FindFirstFileW
00000345           GetFileAttributesW
00000940           lstrcmpW
00000614           MulDiv
00000949           lstrcpynW
00000595           LocalSize
00000360           GetLastError
00000911           WriteFile
00000790           SetLastError
00000898           WideCharToMultiByte
00000593           LocalReAlloc
00000236           FormatMessageW
00000474           GetUserDefaultUILanguage
00000768           SetEndOfFile
00000130           DeleteFileW
00000246           GetACP
00000862           UnmapViewOfFile
00000615           MultiByteToWideChar
00000602           MapViewOfFile
00000859           UnhandledExceptionFilter
......

从以上所列内容(加黑部分)可以看出,记事本程序中使用了kernel32.dll中的两个最重要的函数:LoadLibraryA和GetProcAddress。

这对编写补丁代码来说是幸运的,因为使用这两个函数,其他任何动态链接库的任何函数地址都可以轻而易举地得到。这样,就免去了获取kernel32.dll基地址,以及通过其导出表获取这两个函数的工作。除此之外,记事本程序的原始导入表中还包含了补丁程序中要调用的几个函数:

❑ RegCloseKey

❑ MapViewOfFile

❑ UnmapViewOfFile

❑ CloseHandle

补丁代码调用以上这些函数的位置,可以直接使用invoke指令和原始函数名,无需再对这些函数的地址进行获取操作。下面来看补丁程序的源代码。

22.2.3 补丁程序的源代码

本实例的补丁代码将实现以下几项功能:

1)在注册表启动项中加入补丁后的记事本程序virNote.exe。

2)复制当前进程的所有字节码到临时文件中。

3)验证临时文件中的校验和是否正确。

4)根据校验和是否正确决定是否进行病毒提示。

补丁程序的源代码见代码清单22-1。

代码清单22-1 文件型病毒提示器补丁程序(chapter22\virWarn.asm)

  1    ;------------------------
  2    ; 功能:文件型病毒 提示器
  3    ;         关键代码将嵌入到notepad.exe文件节的间隙中
  4    ; 作者:戚利
  5    ; 开发日期:2010.7.1
  6    ;------------------------
  7         .386
  8         .model flat,stdcall
  9         option casemap:none
  10
  11    include     windows.inc
  12    include     kernel32.inc
  13    includelib kernel32.lib
  14    include     ADVAPI32.inc
  15    includelib ADVAPI32.lib
  16
  17
  18
  19         .code
  20    _ProtoRegCreateKey   typedef proto :dword,:dword,:dword
  21    _ProtoRegSetValueEx typedef proto :dword,:dword,:dword,:dword,:dword,:dword
  22    _ProtoMessageBox typedef proto :dword,:dword,:dword,:dword
  23    _ProtoGetWindowsDirectory      typedef proto :dword,:dword
  24    _ProtoGetModuleFileName   typedef proto :dword,:dword,:dword
  25    _ProtoCopyFile     typedef proto :dword,:dword,:dword
  26    _ProtoCreateFile   typedef proto :dword,:dword,:dword,:dword,:dword,:dword,:dword
  27    _ProtoGetFileSize             typedef proto :dword,:dword
  28    _ProtoCreateFileMapping     typedef proto :dword,:dword,:dword,:dword,:dword,:dword
  29    _ProtoDeleteFile              typedef proto :dword
  30
  31
  32    _ApiRegCreateKey                  typedef ptr _ProtoRegCreateKey
  33    _ApiRegSetValueEx                typedef ptr _ProtoRegSetValueEx
  34    _ApiMessageBox                    typedef ptr _ProtoMessageBox
  35    _ApiGetWindowsDirectory         typedef ptr _ProtoGetWindowsDirectory
  36    _ApiGetModuleFileName           typedef ptr _ProtoGetModuleFileName
  37    _ApiCopyFile                       typedef ptr _ProtoCopyFile
  38    _ApiCreateFile                    typedef ptr _ProtoCreateFile
  39    _ApiGetFileSize                   typedef ptr _ProtoGetFileSize
  40    _ApiCreateFileMapping           typedef ptr _ProtoCreateFileMapping
41    _ApiDeleteFile                    typedef ptr _ProtoDeleteFile
42
43
44    lpszKey                db    'SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\Run',0
45    lpszValueName         db    'note',0
46    lpszValue              db    'virNote.exe',0
47    hKey                    dd    ?
48    hFile                   dd    ?
49    hMapFile               dd    ?
50    lpMemory               dd    ?    ;内存中文件指针
51
52    hDllADVAPI32          dd    ?    ;存放advapi32.dll句柄
53    hDllUser32             dd    ?    ;存放user32.dll句柄
54    hDllKernel32          dd    ?    ;存放kernel32.dll句柄
55
56
57    @destFile              db    50h dup(0)
58    szBuffer               db    50h dup(0)
59    dwFileSize             dd    ?    ;存放文件大小
60    _dwSize                dd    ?
61
62    _RegCreateKey                _ApiRegCreateKey              ?
63    _RegSetValueEx               _ApiRegSetValueEx             ?
64    _MessageBox                   _ApiMessageBox                ?
65    _GetWindowsDirectory        _ApiGetWindowsDirectory     ?
66    _GetModuleFileName          _ApiGetModuleFileName        ?
67    _CopyFile                     _ApiCopyFile                   ?
68    _CreateFile                   _ApiCreateFile                ?
69    _GetFileSize                  _ApiGetFileSize               ?
70    _CreateFileMapping          _ApiCreateFileMapping        ?
71    _DeleteFile                   _ApiDeleteFile                ?
72
73
74    szADVAPI32                   db   'ADVAPI32.dll',0
75    szUser32                     db   'USER32.dll',0
76    szKernel32                   db   'KERNEL32.dll',0
77    szRegCreateKey              db   'RegCreateKeyA',0;该方法在ADVAPI32.dll中
78    szRegSetValueEx             db   'RegSetValueExA',0;该方法在ADVAPI32.dll中
79    szMessageBox                db   'MessageBoxA',0   ;该方法在USER32.dll中
80    szGetWindowsDirectory     db  'GetWindowsDirectoryA',0 ;以下方法在KERNEL32.dll中
81    szGetModuleFileName        db   'GetModuleFileNameA',0
82    szCopyFile                   db   'CopyFileA',0
83    szCreateFile                db   'CreateFileA',0
84    szGetFileSize               db   'GetFileSize',0
85    szCreateFileMapping        db   'CreateFileMappingA',0
86    szDeleteFile                db   'DeleteFileA',0
87
88    lpszTitle           db   '文件病毒提示器-by qixiaorui',0
89    lpszMessage        db  '请注意!您的机器在上一次使用时可能已经感染了文件型病毒!',0
90    lpszNewName         db   '\virNote_Bak.exe',0
91
92
93    start:
94         call @F
95    @@:
96         pop ebp
97         sub ebp,offset @B
98
99        ;首先获取ADVAPI32.dll、kernel32.dll和user32.dll的基地址
100
101         lea eax,[ebp+szADVAPI32]
102         push eax
103        call LoadLibrary                 ;!!!!!!!!!需要修正
104         mov [ebp+hDllADVAPI32],eax
…
115
116         ;获得几个函数的内存地址
117         lea eax,[ebp+szRegCreateKey]
118         push eax
119         mov eax,[ebp+hDllADVAPI32]
120         push eax
121        call GetProcAddress             ;!!!!!!!!!需要修正
122         mov [ebp+_RegCreateKey],eax
…
180         lea eax,[ebp+szDeleteFile]
181         push eax
182         mov eax,[ebp+hDllKernel32]
183         push eax
184        call GetProcAddress             ;!!!!!!!!!需要修正
185         mov [ebp+_DeleteFile],eax
186
187
188         ;将值写入注册表
189         lea eax,[ebp+hKey]
190         push eax
191         lea eax,[ebp+lpszKey]
192         push eax
193         push HKEY_LOCAL_MACHINE
194         call [ebp+_RegCreateKey]
195         mov eax,0Ch
196         push eax
197         lea eax,[ebp+lpszValue]
198         push eax
199         mov eax,REG_SZ
200         push eax
201         xor eax,eax
202         push eax
203         lea eax,[ebp+lpszValueName]
204         push eax
205         mov eax,[ebp+hKey]
206         push eax
207         call [ebp+_RegSetValueEx]
208         mov eax,[ebp+hKey]
209         push eax
210        call RegCloseKey                ;!!!!!!!!!需要修正
211
212         ;获取进程所在的目录
213         mov eax,50h
214         push eax
215         lea eax,[ebp+szBuffer]
216         push eax
217         call [ebp+_GetWindowsDirectory]
218
219         mov esi,0
220         mov edi,0
221         .while TRUE
222              mov al,byte ptr [ebp+szBuffer+esi]
223              .break .if al==0
224              mov byte ptr [ebp+@destFile+edi],al
225              inc esi
226              inc edi
227         .endw
228         mov esi,0
229         .while TRUE
230              mov al,byte ptr [ebp+lpszNewName+esi]
231              .break .if al==0
232              mov byte ptr [ebp+@destFile+edi],al
233              inc esi
234              inc edi
235         .endw
236         mov byte ptr [ebp+@destFile+edi],0
237
238         ;获取当前运行程序的完整路径C:\windows\virNote.exe
239         mov eax,50h
240         push eax
241         lea eax,[ebp+szBuffer]
242         push eax
243         xor eax,eax
244         push eax
245         call [ebp+_GetModuleFileName]
246
247         ;将当前程序运行文件szBuffer复制到系统目录@destFile
248         mov eax,FALSE
249         push eax
250         lea eax,[ebp+@destFile]
251         push eax
252         lea eax,[ebp+szBuffer]
253         push eax
254         call [ebp+_CopyFile]
255
256         ;打开命名后的新文件@destFile
257         push NULL
258         mov eax,FILE_ATTRIBUTE_ARCHIVE
259         push eax
260         mov eax,OPEN_EXISTING
261         push eax
262         push NULL
263         mov eax,FILE_SHARE_READ or FILE_SHARE_WRITE
264         push eax
265         mov eax,GENERIC_READ
266         push eax
267         lea eax,[ebp+@destFile]
268         push eax
269         call [ebp+_CreateFile]
270
271         mov [ebp+hFile],eax    ;将文件句柄送入相应变量
272
273         push NULL
274         push eax
275         call [ebp+_GetFileSize]
276         mov [ebp+dwFileSize],eax
277
278         ;建立内存映射
279         xor eax,eax
280         push eax
281         push eax
282         push eax
283         mov eax,PAGE_READONLY
284         push eax
285         xor eax,eax
286         push eax
287         mov eax,[ebp+hFile]
288         push eax
289         call [ebp+_CreateFileMapping]
290         mov [ebp+hMapFile],eax
291
292         ;将文件映射到内存
293         xor eax,eax
294         push eax
295         push eax
296         push eax
297         mov eax,FILE_MAP_READ
298         push eax
299         mov eax,[ebp+hMapFile]
300         push eax
301        call MapViewOfFile           ;!!!!!!!!!需要修正
302         mov [ebp+lpMemory],eax      ;获取文件在内存映像的起始位置
303
304         mov esi,[ebp+lpMemory]
305         add esi,3ch
306         mov esi,dword ptr [esi]
307         add esi,[ebp+lpMemory]
308         push esi
309         pop edi                         ;esi和edi都指向PE头
310
311         movzx ecx,word ptr [esi+6h] ;获取节的数量
312         mov eax,sizeof IMAGE_NT_HEADERS
313         add edi,eax                    ;edi指向节表
314
315         ;计算节表数据的总长度
316         mov eax,sizeof IMAGE_SECTION_HEADER
317         xor edx,edx
318         mul ecx
319         xchg eax,ecx                   ;ecx中为节表数据的总长度
320
321         ;计算从edi指向的ecx个长度的字节的校验和
322    _calcCheckSum:
323
324         mov [ebp+_dwSize],ecx
325         push esi
326         shr ecx,1
327         xor ebx,ebx
328         mov esi,edi
329
330         cld
331    @@:
332         lodsw
333         movzx eax,ax
334         add ebx,eax
335         loop @B
336         test [ebp+_dwSize],1
337         jz @F
338         lodsb
339         movzx eax,al
340         add ebx,eax
341    @@:
342         mov eax,ebx
343         and eax,0ffffh
344         shr ebx,16
345         add eax,ebx
346         not ax
347         pop esi
348
349
350         mov bx,word ptr [esi+4ch]    ;此处存放着原始的校验和
351         sub ax,bx
352         jz _ret
353
354         ;显示提示信息
355         xor eax,eax
356         push eax
357         lea eax,[ebp+lpszTitle]
358         push eax
359         lea eax,[ebp+lpszMessage]
360         push eax
361         push NULL
362         call [ebp+_MessageBox]
363
364    _ret:
365         ;关闭文件
366         mov eax,[ebp+lpMemory]
367         push eax
368        call UnmapViewOfFile        ;!!!!!!!!!需要修正
369
370         mov eax,[ebp+hMapFile]
371         push eax
372        call CloseHandle             ;!!!!!!!!!需要修正
373
374         mov eax,[ebp+hFile]
375         push eax
376        call CloseHandle             ;!!!!!!!!!需要修正
377
378         ;删除文件
379         lea eax,[ebp+@destFile]
380         push eax
381         call [ebp+_DeleteFile]
382
383         ret
384        ;此处已无用,不需要程序,可存放数据
385         mov eax,12345678h
386         org $-4
387    OldEIP   dd   00001000h
388         add eax,12345678h
389         org $-4
390    ModBase dd   00400000h
391         jmp eax
392         end start

以上代码使用了重定位技术和动态加载技术。因为这次打补丁的目标文件很明确,即记事本程序,所以,动态加载仅限于对那些在记事本程序导入表中不存在的函数。

阅读以上补丁代码时,需要特别注意那些已经在记事本导入表中存在的,同时补丁代码中也会调用的函数;记事本程序一旦运行,这些函数的地址就会被操作系统加载器解析出来并存储在IAT中。所以,后面的手工部分会将对这些函数的调用指令操作数进行修正,使其指向IAT中函数对应的地址。

行20~41定义了那些补丁代码中用到的、但在记事本程序导入表中不存在的函数。

行99~185通过两个已知函数LoadLibraryA和GetProcAddress加载所有用到的动态链接库,并获取那些自定义函数的地址。

行188~210将补丁后的记事本程序加入注册表的启动项。

行212~217获取操作系统根目录,即环境变量%windir%的路径,在“我的电脑”中显示为“C:\windows”。

行221~227将获取的目录复制给变量@destFile。

行229~236将临时文件的文件名追加到变量@destFile后,此时@destFile的值为字符串:“C:\windows\virNote_Bak.exe”。

行238~245获取当前运行的补丁后的记事本程序的绝对路径存储在变量szBuffer中。本例的值为“C:\windows\virNote.exe”。

行247~254将virNote.exe的字节码复制到指定的临时文件virNote_Bak.exe中。

行256~302打开virNote_Bak.exe并将其映射为内存文件。

行304~347计算virNote_Bak.exe节表的校验和。因为virNote_Bak.exe复制自当前进程virNote.exe,所以,无论此时电脑是否已感染PE文件病毒,该校验和都是针对virNote.exe最新的。

行350~351将当前计算出的校验和与为记事本程序打补丁时存储在记事本程序文件头部的原始校验和进行比较。如果还是原来的校验和,表示进程未被感染,则断定电脑暂时没有PE病毒,跳转到记事本原始入口地址运行;如果两者不一致,则表示有人或者程序更改了打过补丁的记事本程序的节表,显示病毒入侵提示,直接退出,不再运行记事本程序。

行364~381关闭所有打开的映射文件和相关句柄,并删除创建的临时文件。

行384~391使用了一种很特别的指令操作数的构造技术。此处使用了当前指令地址$减去某个数的方式将后续的字节码提前,这部分代码等价于以下代码:

mov eax,OldEIP
add eax,ModBase
jmp eax

由于有些位置的代码复用了记事本导入表中存在的函数,所以这些位置标示出的函数地址必须得到修正。这些位置包括行103、行121、行184、行210、行301等,这些位置在源代码中以注释“;!!!!!!!!!需要修正”重点标出。下

面来分析编译链接该补丁源码后,最终生成的字节码。

22.2.4 补丁程序的字节码

补丁程序的字节码如下:

00000200    53 4F 46 54 57 41 52 45   5C 4D 49 43 52 4F 53 4F    SOFTWARE\MICROSO
00000210    46 54 5C 57 49 4E 44 4F   57 53 5C 43 55 52 52 45    FT\WINDOWS\CURRE
00000220    4E 54 56 45 52 53 49 4F   4E 5C 52 75 6E 00 6E 6F    NTVERSION\Run.no
00000230    74 65 00 76 69 72 4E 6F   74 65 2E 65 78 65 00 00    te.virNote.exe..
…
00000310    00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
00000320    00 00 00 00 00 00 00 00   00 00 00 41 44 56 41 50    ...........ADVAP
00000330    49 33 32 2E 64 6C 6C 00   55 53 45 52 33 32 2E 64    I32.dll.USER32.d
00000340    6C 6C 00 4B 45 52 4E 45   4C 33 32 2E 64 6C 6C 00    ll.KERNEL32.dll.
00000350    52 65 67 43 72 65 61 74   65 4B 65 79 41 00 52 65    RegCreateKeyA.Re
00000360    67 53 65 74 56 61 6C 75   65 45 78 41 00 4D 65 73    gSetValueExA.Mes
00000370    73 61 67 65 42 6F 78 41   00 47 65 74 57 69 6E 64    sageBoxA.GetWind
00000380    6F 77 73 44 69 72 65 63   74 6F 72 79 41 00 47 65    owsDirectoryA.Ge
00000390    74 4D 6F 64 75 6C 65 46   69 6C 65 4E 61 6D 65 41    tModuleFileNameA
000003A0    00 43 6F 70 79 46 69 6C   65 41 00 43 72 65 61 74    .CopyFileA.Creat
000003B0    65 46 69 6C 65 41 00 47   65 74 46 69 6C 65 53 69    eFileA.GetFileSi
000003C0    7A 65 00 43 72 65 61 74   65 46 69 6C 65 4D 61 70    ze.CreateFileMap
000003D0    70 69 6E 67 41 00 44 65   6C 65 74 65 46 69 6C 65    pingA.DeleteFile
000003E0    41 00 CE C4 BC FE B2 A1   B6 BE CC E1 CA BE C6 F7    A.文件病毒提示器
000003F0    2D 62 79 20 71 69 78 69   61 6F 72 75 69 00 C7 EB    -by qixiaorui.
00000400    D7 A2 D2 E2 A3 A1 C4 FA   B5 C4 BB FA C6 F7 D4 DA    注意!您的机器在
00000410    C9 CF D2 BB B4 CE CA B9   D3 C3 CA B1 BF C9 C4 DC    上一次使用时可能
00000420    D2 D1 BE AD B8 D0 C8 BE   C1 CB CE C4 BC FE D0 CD    已经感染了文件型
00000430    B2 A1 B6 BE A3 A1 00 5C   76 69 72 4E 6F 74 65 5F    病毒!.\virNote_
00000440    42 61 6B 2E 65 78 65 00   E8 00 00 00 00 5D 81 ED   Bak.exe.?...]
00000450    4D 12 40 00 8D 85 2B 11   40 00 50 E8 42 03 00 00    M.@.崊+.@.P鐱...
00000460    89 85 4F 10 40 00 8D 85   38 11 40 00 50 E8 30 03    墔O.@.崊8.@.P?.
…
00000760    4A 00 00 00 8B 85 47 10   40 00 50 E8 26 00 00 00    J...媴G.@.P?...
00000770    8B 85 43 10 40 00 50 E8   1A 00 00 00 8D 85 5B 10    媴C.@.P?...崊[.
00000780    40 00 50 FF 95 27 11 40   00 C3 B8 00 10 00 00 05    @.P ?.@.酶.....
00000790    00 00 40 00 FF E0 FF 25   18 20 40 00 FF 25 14 20    ..@. ?%. @. %.
000007A0    40 00 FF 25 10 20 40 00   FF 25 08 20 40 00 FF 25    @. %. @. %. @. %
000007B0    0C 20 40 00 FF 25 00 20   40 00                          . @. %. @.

以上所示为补丁字节码的代码段内容。显示的所有字节码共需要空间大小为05B9h个字节,而记事本程序的.rsrc节一共有0C700-0B800=0F00个字节的剩余空间可以使用,所以,基本确定补丁代码可以嵌入到记事本程序的.rsrc节中。

注意 补丁字节码加框部分为补丁代码开始处,而下划线部分则是代码中需要修正的地址。

22.2.5 修正函数地址

由于在补丁程序中复用了记事本程序导入表中已存在的几个函数,补丁后的代码地址必须予以修正。从反汇编代码中获得这几个需要修正的地址如表22-1所示。

表22-1 补丁程序需要修正的地址

表中3~8项是在notepad.exe导入表中存在的API函数,这些函数也被补丁代码直接使用。那么它们的地址该如何计算呢?以函数RegCloseKey为例,计算其VA的具体步骤如下:

步骤1 从notepad.exe导入表中得到该函数的导入部分描述。

具体描述如下所示:

-----------------------------------
导入库:ADVAPI32.dll
-----------------------------

OriginalFirstThunk   00006704
TimeDateStamp        ffffffff
ForwarderChain       ffffffff
FirstThunk           00001000
-----------------------------

00000264           IsTextUnicode
00000394           RegCreateKeyW
00000424           RegQueryValueExW
00000435           RegSetValueExW
00000413           RegOpenKeyExA
                   00000423           RegQueryValueExA
                   00000388           RegCloseKey

步骤2 计算该函数的RVA。

如上所示,FirstThunk指向了该块的RVA,而下面的每个引入API函数则是以4个字节(dword)对齐的,因此,

RegCloseKey的RVA=FirstThunk+(序号-1)*4=00001000h+(7-1)*4=00001018h

步骤3 计算该函数的VA。将第2步计算得出的RVA加上基地址01000000h就是函数的入口地址VA了,所以:

RegCloseKey的VA=01000000+00001018=01001018h

表22-1中标示的其他函数也采用这样的运算方法。将计算好的所有地址进行更正,更正以后的字节码如下:

0000BD70   8B 85 43 10 40 00 50 E8  1A 00 00 00 8D 85 5B 10   媴C.@.P?...崊[.
0000BD80   40 00 50 FF 95 27 11 40  00 C3 B8 20 64 00 00 05   @.P ?.@.酶 d...
0000BD90   00 00 00 01 FF E0 FF 25  34 11 00 01 FF 25 C4 10   .... ?%4... %?
0000BDA0   00 01 FF 25 F4 10 00 01  FF 25 18 11 00 01 FF 25   .. %?.. %.... %
0000BDB0   30 11 00 01 FF 25 18 10  00 01                     0... %....

接下来,使用FlexHex手工对notepad.exe执行如下操作:

步骤1 文件偏移244h处将.rsrc节的属性更改为0e0000040h(也就是将节标记为可读/可写/可执行)。

步骤2 文件偏移100h处修改新的入口地址为0a000h+(0B800-7200h)+248h=0E848h。

步骤3 将修正以后的5B9h个字节码覆盖到文件0B800h开始的地方。

步骤4 算出文件节表的校验和为0AF16h(virNote的为74ddh),将其填入文件偏移124h(virNote的为104h,即PE头开始的4ch)处。注意,校验和为一个字。

执行到这里,文件型病毒提示器就完成了,将该提示器改个名字(例如virNote.exe)并复制到操作系统的系统目录下,然后进行以下测试。

22.2.6 测试运行

模拟病毒手动修改经过改造的notepad.exe的节表,使virNote.exe的节表发生变化。例如,在文件偏移0x01d5处(.text节的名称处)将00h修改为30h,然后保存。重新运行virNote.exe程序,发现文件型病毒提示器的对话框接着就弹出来了,弹出对话框如图22-1所示。

图22-1 被感染后的提示框

将virNote.exe复制到Windows操作系统的系统目录下(如C:\windows),先运行一次,系统将不会有任何提示,但注册表的启动项中将会增加一个note项。正常情况下,每次开机都会运行virNote.exe程序,因为该程序的节表未被修改或破坏,校验和不变;也就是说,它与存储在文件头部4ch偏移处的原始校验和是一致的,所以并不会出现任何提示。一旦virNote.exe被文件型病毒感染了,它就会弹出对话框提醒用户,该电脑已经被文件型病毒感染了。

22.3 补丁版的PE病毒提示器

上一节使用半自动化的补丁程序,通过手工修改实现了PE病毒提示器。本节将使用全自动化的方法实现该病毒提示器。

通过对志愿者打补丁的方法来实现入侵病毒提示的功能。

补丁程序必须具备以下两个功能:第一,将补丁程序本身写入系统启动项;第二,能够检测特定位置的校验和是否被修改。下面分别介绍这两部分代码的编写。

22.3.1 将提示器写入启动项

为了能实现每次开机运行该提示器,以增加被感染的几率,补丁代码需要将补丁后的目标程序写入注册表的启动位置。

注册表启动位置:SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\Run
新添加键:note
新添加键的键值:virNote.exe

由于键值中没有带路径,所以补丁代码还必须将病毒提示器复制到默认系统搜索路径中,比如Windows系统目录。以下是将提示器写入启动项的相关代码:

;将值写入注册表
lea eax,[ebx+hKey]
push eax
lea eax,[ebx+lpszKey]
push eax
push HKEY_LOCAL_MACHINE
call [ebx+_RegCreateKey]
mov eax,0Ch
push eax
lea eax,[ebx+lpszValue]
push eax
mov eax,REG_SZ
push eax
xor eax,eax
push eax
lea eax,[ebx+lpszValueName]
push eax
mov eax,[ebx+hKey]
push eax
call [ebx+_RegSetValueEx]
mov eax,[ebx+hKey]
push eax
call [ebx+_RegCloseKey]

代码首先调用函数RegCreateKey创建键,然后使用RegSetValueEx设置键的值,最后调用函数RegCloseKey关闭新创建的键。

22.3.2 检测特定位置校验和

本实例将PE病毒提示器放置在C:\windows子目录中,名称为virNote.exe。每次开机即运行,运行时先通过函数CopyFile将自己复制到临时文件C:\windows\virNote_Bak.exe中;然后,通过内存映射函数MapViewOfFile获取操作句柄,检查PE文件头部开始4ch处的校验和与计算生成的值是否一致。如果一致,表示未被修改;否则提示感染病毒信息,并显示记事本程序。相关代码见代码清单22-2。

代码清单22-2 补丁程序校验和检测函数_doCheck(chapter22\patch.asm)

1    ;----------------------------
2    ; 将当前文件复制到系统目录,并写入注册表
3    ; 返回   0 表示未被病毒感染
4    ;        1 表示已经被病毒感染
5    ;----------------------------
6    _doCheck   proc    _base
7         local @ret
8         pushad
9         mov ebx,_base
10
11         ;将值写入注册表
…
34
35         ;获取系统所在目录
36         mov eax,50h
37         push eax
38         lea eax,[ebx+szBuffer]
39         push eax
40         call [ebx+_GetWindowsDirectory]
41
42         mov esi,0 ;构造目标文件绝对路径=目录名+"\virNote_Bak.exe"
43         mov edi,0
44         .while TRUE
45              mov al,byte ptr [ebx+szBuffer+esi]
46              .break .if al==0
47              mov byte ptr [ebx+@destFile+edi],al
48              inc esi
49              inc edi
50         .endw
51         mov esi,0
52         .while TRUE
53              mov al,byte ptr [ebx+lpszNewName+esi]
54              .break .if al==0
55              mov byte ptr [ebx+@destFile+edi],al
56              inc esi
57              inc edi
58         .endw
59         ;@destFile中存放了目标文件的绝对路径
60         mov byte ptr [ebx+@destFile+edi],0
61
62         ;取当前程序运行路径C:\windows\virNote.exe
63         mov eax,50h
64         push eax
65         lea eax,[ebx+szBuffer]
66         push eax
67         xor eax,eax
68         push eax
69         call [ebx+_GetModuleFileName]
70
71         ;将当前程序运行文件szBuffer复制到系统目录@destFile
72         mov eax,FALSE
73         push eax
74         lea eax,[ebx+@destFile]
75         push eax
76         lea eax,[ebx+szBuffer]
77         push eax
78         call [ebx+_CopyFile]
79
80         ;打开命名后的新文件@destFile
81         push NULL
82         mov eax,FILE_ATTRIBUTE_ARCHIVE
83         push eax
84         mov eax,OPEN_EXISTING
85         push eax
86         push NULL
87         mov eax,FILE_SHARE_READ or FILE_SHARE_WRITE
88         push eax
89         mov eax,GENERIC_READ
90         push eax
91         lea eax,[ebx+@destFile]
92         push eax
93         call [ebx+_CreateFile]
94
95         mov [ebx+hFile],eax    ;将文件句柄送入相应变量
96
97         push NULL
98         push eax
99         call [ebx+_GetFileSize]
100         mov [ebx+dwFileSize],eax
101
102         ;建立内存映射
103         xor eax,eax
104         push eax
105         push eax
106         push eax
107         mov eax,PAGE_READONLY
108         push eax
109         xor eax,eax
110         push eax
111         mov eax,[ebx+hFile]
112         push eax
113         call [ebx+_CreateFileMapping]
114         mov [ebx+hMapFile],eax
115
116         ;将文件映射到内存
117         xor eax,eax
118         push eax
119         push eax
120         push eax
121         mov eax,FILE_MAP_READ
122         push eax
123         mov eax,[ebx+hMapFile]
124         push eax
125         call [ebx+_MapViewOfFile]
126         ;获取文件在内存映像的起始位置
127         mov [ebx+lpMemory],eax
128
129         mov esi,[ebx+lpMemory]
130         add esi,3ch
131         mov esi,dword ptr [esi]
132         add esi,[ebx+lpMemory]
133         push esi
134         pop edi                         ;esi和edi都指向PE头
135
136         movzx ecx,word ptr [esi+6h] ;获取节的数量
137         mov eax,sizeof IMAGE_NT_HEADERS
138         add edi,eax                    ;edi指向节表
139
140         ;计算节表数据的总长度
141         mov eax,sizeof IMAGE_SECTION_HEADER
142         xor edx,edx
143         mul ecx
144         xchg eax,ecx    ;ecx中为节表数据的总长度
145
146         ;计算从edi指向的ecx个长度字节的校验和    0F34Bh
147    _calcCheckSum:
148
149         mov [ebx+_dwSize],ecx
150         push esi
151         shr ecx,1
152         xor edx,edx
153         mov esi,edi
154
155         cld
156    @@:
157         lodsw
158         movzx eax,ax
159         add edx,eax
160         loop @B
161         test [ebx+_dwSize],1
162         jz @F
163         lodsb
164         movzx eax,al
165         add edx,eax
166    @@:
167         mov eax,edx
168         and eax,0ffffh
169         shr edx,16
170         add eax,edx
171         not ax
172        pop esi     ;到此为止,ax中存放了新的校验和
173
174
175         mov dx,word ptr [esi+4ch] ;此处存放着原始的校验和
176         sub ax,dx
177        jz _ret       ;校验和一致,表示未被修改
178
179        ;如果不一致,则显示提示信息
180         xor eax,eax
181         push eax
182         lea eax,[ebx+lpszTitle]
183         push eax
184         lea eax,[ebx+lpszMessage]
185         push eax
186         push NULL
187         call [ebx+_MessageBox]
188         mov @ret,1
189         jmp _ret1
190    _ret:
191         mov @ret,0
192    _ret1:
193         ;关闭文件
194         mov eax,[ebx+lpMemory]
195         push eax
196         call [ebx+_UnmapViewOfFile]
197
198         mov eax,[ebx+hMapFile]
199         push eax
200         call [ebx+_CloseHandle]
201
202         mov eax,[ebx+hFile]
203         push eax
204         call [ebx+_CloseHandle]
205
206         ;删除临时文件
207         lea eax,[ebx+@destFile]
208         push eax
209         call [ebx+_DeleteFile]
210         popad
211         mov eax,@ret
212         ret
213    _doCheck   endp

以上代码与22.2.3节介绍的代码基本一致,不同之处是对记事本程序导入表中已经存在的函数的处理:为了免去手工操作,即对记事本程序导入表中已经存在的函数的地址修正,在代码中对这些函数全部采用动态加载技术。如代码行116~127对函数MapViewOfFile的调用、代码行203~204对函数CloseHandle的调用等。另外,补丁代码还使用了嵌入补丁框架结构。

22.3.3 测试运行

使用第17章的补丁工具为记事本打补丁。因为节表的校验和还没有写入特定位置,所以,第一次运行生成的bindD.exe弹出了对话框提示。在病毒提示器正式工作之前,需要手动将校验和写入其文件头部开始的4ch处。

校验和是通过OD的单步调试过程获得的,图22-2是获得校验和时的OD运行界面图:

图22-2 获得校验和时的程序运行状态

如图所示,esi指向的数据区刚好是PE文件头的PE标志,从这个地方开始往下找4ch偏移,读出的字为0000。而当前EIP所处的位置刚好是动态获取到的校验和与该处值相减的指令,此时,指示eax的值为0F34BH。这个值就是bindD.exe的节数据的校验和。

获取该校验和后,将该值写入PE文件头的PE标志开始的4ch偏移处,作为记事本程序原始的校验和。

依次生成补丁程序patch.exe,补丁工具程序bind.exe(取自第17章生成的补丁工具)。用bind.exe对notepad.exe打补丁,最终生成patch_notepad.exe。

第一次运行patch_notepad.exe,因为校验和未写入,所以会显示病毒提示,该提示是不真实的。将校验和0F34Bh手动写入patch_notepad.exe文件PE头部4ch处,并更改文件名为virNote.exe。再次运行virNote.exe时不会有任何提示,至此,PE病毒提示器完成。

将virNote.exe复制到C:\windows目录中,PE病毒提示器就处于工作状态了。

注意 在测试时不要把防病毒软件打开,因为用这种方法生成的virNote.exe会被杀毒软件误认为是病毒而被拦截。例如,杀毒软件360是这样记录这个文件的:

你也可以参考本书第21章介绍的免杀技术,将virNote.exe实施加密,就不会出现这样的提示了。

假设PE文件病毒侵犯了我们的电脑,提示器会再次弹出提示窗口,此时虽无法杜绝病毒的运行,但可以明确提醒:您的电脑已经受到PE病毒的攻击了。如果想测试该提示器的运行效果,可以找到virNote.exe,将特定位置的校验和修改一下,或者把节表中的任何一处进行修改,都能达到测试目的。例如,以下是修改节.text名称的字节码:

000001D0                           2E 74 65 78 74 45 00 00          .textE..
000001E0   48 77 00 00 00 10 00 00 00 78 00 00 00 04 00 00   Hw.......x......
000001F0   00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60   ............ ..`
00000200   2E 64 61 74 61 00 00 00 A8 1B 00 00 00 90 00 00   .data.........
00000210   00 08 00 00 00 7C 00 00 00 00 00 00 00 00 00 00   .....|..........
00000220   00 00 00 00 40 00 00 C0 2E 72 73 72 63 00 00 00   ....@...rsrc...

如上所示,将节.text更名为.textE后,即可引发病毒提示器,并且显示提示信息。

22.4 小结

本章采用两种开发方式实现了PE病毒提示器,该程序可以在电脑感染PE病毒后提醒用户注意。原理是在PE文件头部存储一个节表信息校验和,由补丁程序计算当前校验和,并与存储的校验和进行对比,以判断文件是否被修改。由于本提示器是在病毒执行以后再执行,所以它不具备病毒的预警功能,只具备提示功能。

大家也可以扩展本章的程序,使用补丁对PE文件进行自修复。当发现电脑被病毒感染时,重新修复入口地址和代码段的起始N个字节,并提醒用户。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值