PE装载器原理与实现

PE装载器作用:
会把保护检测做到PE装载器上,从而达到卸掉原程序的保护
 
PE模拟器装载的步骤
1 给PE装载器留出足够的空间来装载需要的可执行文件,这里需要手动修改装载的可执行文件名字和占用内存的大小,程序首选装载地址
2 检查加载的头部是否是PE的头文件格式
3 从原可执行文件中提取PE格式重要数据,具体数据在下面
4 因为提取到了可执行文件头部的大小,执行复制可执行文件的头部到PE装载器申请内存的首地址,注意这里要更改模块加载内存的地址属性为可读可写
5 复制区块表的相关数据到PE装载器申请内存的相应地址,注意:记得加模块偏移,头部包括区块已经复制过去了
6 读取导入表信息表的INT值(当INT的值不为空),读取出的值填入到IAT指向的偏移位置 注意:记得加模块偏移
7 跳转到加载文件的入口点
 
注意细节:
1 peLdr base 和 装载程序对应
2 peLdr sizeofimage 大于等于原装载程序
3 装载需要的数据有FILE_HEADER的可选头大小,节区表数量,需要OPTIONAL_HEADER32里AddressOfEntryPoint,SizeOfHeaders,SizeOfImage
 
.386
.model flat,stdcall
option casemap:none

include windows.inc
include msvcrt.inc
include user32.inc
include kernel32.inc

includelib msvcrt.lib
includelib user32.lib
includelib kernel32.lib

.data
IMAGE_SIZE equ 2000h  ;程序加载更改项

.const
g_fileName db "helloPe.exe",0 ;程序加载更改项

.code
org IMAGE_SIZE - 1000h ;这里减1000是程序本身具有头文件可以不拷贝,直接利用


checkPE proc lpAddress:LPVOID
	
	mov esi,lpAddress
	assume esi : ptr IMAGE_DOS_HEADER
	
	.if [esi].e_magic != 'ZM'
		mov eax,0
		ret
	.endif
	
	;指向PE文件头
	add esi,[esi].e_lfanew
	assume esi : ptr IMAGE_NT_HEADERS 
	.if[esi].Signature !='EP'
		mov eax,0
		ret
	.endif
	
	ret
checkPE endp

LoadPe proc uses esi ebx edi hMod:HMODULE
	LOCAL @hFile:HANDLE
	LOCAL @hMap:HANDLE
	LOCAL @lpAddress:LPVOID
	LOCAL @NumberOfSections:dword ;节区表数量
	LOCAL @SizeOfOptional:dword ;可选头大小
	LOCAL @AddrOfEntryPoint:dword;程序执行入口
	LOCAL @SizeOfImage:dword;所有头部加节表大小
	LOCAL @SizeOfHeaders:dword;头部占空间的大小
	LOCAL @dwOld:dword
	LOCAL @SectionAddr:dword
	LOCAL @importAddr:dword
	LOCAL @zeroIDD :IMAGE_DATA_DIRECTORY
	LOCAL @DllhMod:HMODULE
	
	invoke RtlZeroMemory, addr @zeroIDD,sizeof IMAGE_DATA_DIRECTORY ;设置全0结构体检测地址是否为0
	
	;读取文件
	invoke CreateFile,offset g_fileName,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
	.if eax==INVALID_HANDLE_VALUE
		jmp DISPOSE_ERROR
	.endif	
	mov @hFile,eax
	
	;创建内存位图
	invoke CreateFileMapping,@hFile,NULL,PAGE_READONLY,0,0,NULL
	.if eax==NULL
		jmp DISPOSE_ERROR
	.endif	
	mov @hMap,eax
	
	;位图映射到进程,复制可执行文件的头部到PE装载器申请内存的首地址
	invoke MapViewOfFile,@hMap,FILE_MAP_READ,0,0,0
	.if eax==NULL
		jmp DISPOSE_ERROR
	.endif	
	mov @lpAddress,eax
	
	;检查PE的头文件格式是否合法
	invoke checkPE,@lpAddress
	.if eax ==NULL
		jmp DISPOSE_ERROR
	.endif
	

	mov esi,@lpAddress
	assume esi : ptr IMAGE_DOS_HEADER
	
	;需要拿到的属性filehead 的可选头大小,节区表数量,需要optionhead里AddressOfEntryPoint
	;SizeOfHeaders
		
	add esi,[esi].e_lfanew
	assume esi : ptr IMAGE_NT_HEADERS 
		
	movzx eax,[esi].FileHeader.NumberOfSections
	mov @NumberOfSections,eax
	
	movzx eax,[esi].FileHeader.SizeOfOptionalHeader
	mov @SizeOfOptional,eax
	
	mov eax,[esi].OptionalHeader.AddressOfEntryPoint ;拿到程序入口点地址
	mov @AddrOfEntryPoint,eax
	mov eax,[esi].OptionalHeader.SizeOfImage ;拿到头部+节区表的总长度
	mov @SizeOfImage,eax
	mov eax,[esi].OptionalHeader.SizeOfHeaders
	mov @SizeOfHeaders,eax
	
	;这里获取导入表的地址,等于导入信息表的下标1*结构体的大小
	lea eax,[esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT * sizeof IMAGE_DATA_DIRECTORY]
	mov @importAddr,eax
	
	;拷贝可执行文件的头部到当前文件的模块载入地址
	
	;更改代码段的属性
	invoke VirtualProtect,hMod,@SizeOfImage,PAGE_EXECUTE_READWRITE,addr @dwOld

	invoke crt_memcpy, hMod, @lpAddress,@SizeOfHeaders
	.if eax==NULL
		jmp DISPOSE_ERROR
	.endif
		
	;5 复制区块表的相关数据到PE装载器申请内存的相应地址,注意:记得加模块偏移
	lea esi,[esi].OptionalHeader
	add esi,@SizeOfOptional
	
	;拿到了区块表的起始地址
	mov @SectionAddr,esi 
	
	;转换esi地址为IMAGE_SECTION_HEADER结构体
	mov esi,@SectionAddr
	assume esi:ptr IMAGE_SECTION_HEADER
	xor ecx,ecx
	
	.while ecx<@NumberOfSections
		.if [esi].SizeOfRawData!=0 ;首先判断文件偏移是否为0,为0是未初始化的节区
		mov eax,[esi].PointerToRawData ;文件的偏移加文件在内存中的基地址得到文件区块的起始位置
		add eax,@lpAddress ;在文件中的偏移加文件的基地址
		
		mov edi,[esi].VirtualAddress;文件在内存中映射的位置
		add edi,hMod ;在内存中的偏移加内存中的基地址
		
		invoke crt_memcpy ,edi,eax,[esi].SizeOfRawData
		.if eax ==NULL
			jmp DISPOSE_ERROR
		.endif
		
		.endif	
		inc ecx
		add esi,sizeof IMAGE_SECTION_HEADER
	.endw
	
	;加载导入表信息
	mov esi,@importAddr
	mov esi,dword ptr[esi]
	add esi,hMod
	assume esi:ptr IMAGE_IMPORT_DESCRIPTOR
	
	invoke crt_memcmp,esi,addr @zeroIDD,sizeof @zeroIDD
	.while eax!=0
		mov eax,[esi].FirstThunk
		add eax ,hMod
		.if dword ptr [eax] != 0 ;IAT表不为空

			mov ebx,[esi].OriginalFirstThunk
			.if ebx == 0
			 mov ebx,[esi].FirstThunk
			.endif
			
			add ebx,hMod ;不管是IAT表为空还是INT表为空,值都会保存在Ebx
			
			mov edi,[esi].FirstThunk
			add edi,hMod ;edi得到iat的位置
			
			mov eax,[esi].Name1
			add eax,hMod
			
			;获取模块的名称
			push ecx
			invoke LoadLibrary,eax
			pop ecx
			mov @DllhMod,eax
			
			mov ecx,0
			.while dword ptr[ebx+ecx*4] !=0
				mov eax,[ebx+ecx*4]
				.if eax&80000000h
					;序号导入
					and eax,0ffffh ;拿到了低4位的值
				
				.else
					;名称导入
					add eax,hMod
					assume eax:ptr IMAGE_IMPORT_BY_NAME
					lea eax,[eax].Name1
				.endif			
					;此时eax保存了序号或者函数名
					push ecx
					invoke GetProcAddress, @DllhMod,eax
					pop ecx
					mov [edi+ecx*4],eax
				    inc ecx
			.endw
			
		.endif
		
		add esi,sizeof IMAGE_IMPORT_DESCRIPTOR
		invoke crt_memcmp,esi,addr @zeroIDD,sizeof @zeroIDD
	.endw
	
	;跳转到程序入口点
	mov eax,hMod
	add @AddrOfEntryPoint,eax
	call @AddrOfEntryPoint
	
DISPOSE_ERROR:

	.if @lpAddress !=NULL
		invoke UnmapViewOfFile,@lpAddress
	.endif
	
	.if @hMap!=NULL
		invoke CloseHandle,@hMap
	.endif

	.if @hFile!=INVALID_HANDLE_VALUE
		invoke CloseHandle,@hFile
	.endif
	ret
LoadPe endp


START:
	;获取程序载入模块的确切地址
	invoke GetModuleHandle,NULL
	invoke LoadPe,eax
	invoke ExitProcess,0
end START

 

posted @ 2020-06-03 10:41  拂树若生花  阅读(773)  评论(0编辑  收藏  举报