Win32匯編 - PE結構解析器


PE格式是Windows系統下最常用的可執行文件格式,有些應用必須建立在了解PE文件格式的基礎之上,如可執行文件的加密與解密,文件型病毒的查殺等,熟練掌握PE文件結構,有助於軟件的分析.

在PE文件中,代碼,已初始化的數據,資源和重定位信息等數據被按照屬性分類放到不同的Section(節區/或簡稱為節)中,而每個節區的屬性和位置等信息用一個IMAGE_SECTION_HEADER結構來描述,所有的IMAGE_SECTION_HEADER結構組成了一個節表(Section Table),節表數據在PE文件中被放在所有節數據的前面.

在Win32系統中,當我們執行了可執行文件之后,可執行文件會被映射到內存,並且以4kb的粒度進行對齊,這個4kb也就是一個頁面的大小,而每個頁面又分別具有,可執行,可讀寫等屬性.

PE格式中的DOS部分由MZ格式的文件頭和可執行代碼部分組成,可執行代碼被稱為DOS塊(DOS stub).MZ格式的文件頭由IMAGE_DOS_HEADER結構定義,以下就是DOS頭部分的關鍵屬性.

		mov esi,lpMemory
		assume esi:ptr IMAGE_DOS_HEADER
		movzx eax,[esi].e_magic         ; 讀取DOS的頭部
		movzx eax,[esi].e_ss            ; DOS代碼段的初始堆棧段
		movzx eax,[esi].e_sp            ; DOS代碼段的初始堆棧指針
		movzx eax,[esi].e_cs            ; DOS代碼的入口地址
		movzx eax,[esi].e_ip            ; DOS代碼的入口IP
		movzx eax,[esi].e_lfanew        ; 指向了PE文件的開頭(重要)

第一個字段e_magic被定義為MZ,標志着DOS文件的開頭部分,最后一個字段e_lfanew則指明了PE文件的開頭位置,現在來說除了第一個字段和最后一個字段有些用處,其他的字段幾乎已經廢棄了,這里也不再介紹了.

解析PE頭結構

從DOS文件頭的e_lfanew字段(文件頭偏移003ch),PE文件格式排列在DOS頭的后面,也就是e_lfanew指針所指向的地址,而PE文件的第一個字節就是PE這兩個字符,有了這些信息,我們就可以寫一個小工具,來檢測指定一個程序是否是可執行文件啦.

.data
	szFileName db "lyshark.exe",0h
	hFile dd ?
	hMapFile dd ?
	lpMemory dd ?
	szText db "這是一個PE可執行文件 !",0h
.code
	main PROC
	; 打開文件,並創建內存映射鏡像
		invoke	CreateFile,addr szFileName,GENERIC_READ,FILE_SHARE_READ or \
			FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
		mov hFile,eax
		invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL
		mov hMapFile,eax
		invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
		mov lpMemory,eax
; -------------------------------------------------------------------
	; 檢測PE文件是否有效,是否是一個正常的PE
		mov esi,lpMemory
		assume esi:ptr IMAGE_DOS_HEADER
		; 判斷是否為DOS文件頭部
		.if [esi].e_magic == IMAGE_DOS_SIGNATURE
			add esi,[esi].e_lfanew              ; 遞增指針
			assume esi:ptr IMAGE_NT_HEADERS
			; 判斷是否為PE可執行文件
			.if [esi].Signature == IMAGE_NT_SIGNATURE
				invoke MessageBox,NULL,addr szText,0,MB_OK
			.endif
		.endif
; -------------------------------------------------------------------
		invoke UnmapViewOfFile,addr lpMemory
		invoke ExitProcess,NULL
	main ENDP
END main

上面的核心代碼原理也非常的簡單,過程:讀入文件,判斷第一個字符是不是MZ,如果是MZ,則在判斷e_lfanew指針指向的地址是不是PE如果是,則說明這是PE文件.

解析各區塊信息

下面的代碼,則用於讀取PE文件的一些關鍵區塊信息.

	.386
	.model flat,stdcall
	option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include masm32.inc
includelib masm32.lib

.data
	szFileName db "lyshark.exe",0h
	hFile dd ?
	hMapFile dd ?
	lpMemory dd ?
	lpBuffer db 2048 dup(?)
.const
	szMsg	db	"----------------------------------------",0dh,0ah
		db	"運行平台:           0x%04X",0dh,0ah
		db	"節區數量:           %d",0dh,0ah
		db	"文件屬性:           0x%04X",0dh,0ah
		db	"時間標記:            %d",0dh,0ah
		db	"鏡像裝入基址:       0x%08X",0dh,0ah
		db	"程序的入口RVA:      0x%08X",0dh,0ah
		db	"代碼節起始RVA:      0x%08X",0dh,0ah
		db	"數據節起始RVA:      0x%08X",0dh,0ah
		db	"----------------------------------------",0dh,0ah,0
.code
	main PROC
	; 打開文件,並創建內存映射鏡像
		invoke	CreateFile,addr szFileName,GENERIC_READ,FILE_SHARE_READ or \
			FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
		mov hFile,eax
		invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL
		mov hMapFile,eax
		invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
		mov lpMemory,eax
		mov esi,lpMemory

		assume	esi:ptr IMAGE_DOS_HEADER
		add esi,[esi].e_lfanew
		assume esi:ptr IMAGE_NT_HEADERS
		invoke wsprintf,addr lpBuffer,addr szMsg,\
			[esi].FileHeader.Machine, \                 ; 運行平台
			[esi].FileHeader.NumberOfSections, \        ; 節區數目
			[esi].FileHeader.Characteristics, \         ; 文件屬性
			[esi].FileHeader.TimeDateStamp, \           ; 時間標記
			[esi].OptionalHeader.ImageBase, \           ; 鏡像基址
			[esi].OptionalHeader.AddressOfEntryPoint, \ ; 入口RVA地址
			[esi].OptionalHeader.BaseOfCode, \          ; 代碼節起始RVA
			[esi].OptionalHeader.BaseOfData
		invoke StdOut,addr lpBuffer
		invoke ExitProcess,NULL
	main ENDP
END main

解析節與節表

系統裝載可執行文件並不等同於內存映射,內存映射是將整個磁盤文件原封不動的搬到內存中去,而PE的加載則會處理一些其他數據,例如預處理,重定位等,裝入以后頁面位置,偏移等都會隨之發生改變,Windows裝載器在裝載DOS部分,PE文件頭部分和節表部分時不進行任何處理,而裝載節的時候將根據節的屬性做不同的處理.

	.386
	.model flat,stdcall
	option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include masm32.inc
includelib masm32.lib

.data
	szFileName db "lyshark.exe",0h
	hFile dd ?
	hMapFile dd ?
	lpMemory dd ?
	lpBuffer db 2048 dup(?)
.const
	szMsg	db	"----------------------------------------------------------",0dh,0ah
		db	"節區名稱  節區大小  虛擬地址  Raw_尺寸  Raw_偏移  節區屬性",0dh,0ah
		db	"----------------------------------------------------------",0dh,0ah,0
	szFmt	db	"%s  %08X  %08X  %08X  %08X  %08X",0dh,0ah,0
.code
	main PROC
		invoke	CreateFile,addr szFileName,GENERIC_READ,FILE_SHARE_READ or \
			FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
		mov hFile,eax
		invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL
		mov hMapFile,eax
		invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0
		mov lpMemory,eax
		mov esi,lpMemory

		assume	esi:ptr IMAGE_DOS_HEADER     ; 指向DOS開頭
		add esi,[esi].e_lfanew               ; 遞增指針到PE結構開頭
		assume esi:ptr IMAGE_NT_HEADERS
		
		invoke StdOut,addr szMsg                        ; 輸出提示信息
		movzx ecx,[esi].FileHeader.NumberOfSections     ; 取出節的數量,作為循環條件
		add esi,sizeof IMAGE_NT_HEADERS      ; 指向.text節
		assume esi:ptr IMAGE_SECTION_HEADER  ; 指向節中的SECTION
		.repeat
			push ecx      ; wsprintf影響ecx寄存器,所以這里必須壓棧保存數據
			mov eax,[esi].VirtualAddress
			
			invoke wsprintf,addr lpBuffer,addr szFmt,esi, \   ; 節區名稱
				[esi].Misc.VirtualSize, \                 ; 節區大小
				[esi].VirtualAddress, \                   ; 虛擬地址
				[esi].SizeOfRawData, \                    ; Raw_尺寸
				[esi].PointerToRawData, \                 ; Raw_偏移
				[esi].Characteristics                     ; 節區屬性
			invoke StdOut,addr lpBuffer                       ; 打印節區信息
			pop ecx
			add esi,sizeof IMAGE_SECTION_HEADER
		.untilcxz
		invoke ExitProcess,NULL
	main ENDP
END main


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM