PE
病毒是指所有感染Windows下PE
檔案格式檔案的病毒.
PE病毒大多數採用Win32彙編編寫.
PE病毒對於一個熱衷於病毒技術的人來說,是必須
掌握的.
只有在PE病毒中,我們才能真正感受到高超的病毒技術.
編寫Win32病毒的幾個關鍵
需要自己尋找api函式的地址,然後直接調用該地址
一點背景:在PE Loader裝入我們的程式啟動後堆疊頂的地址是是程式的返回地址,肯定在Kernel中! 因此我們可以得到這個地址,然後向低地址縮減驗證一直到找到模組的起始地址,驗證條件為PE頭不能大於4096bytes,PE header的ImageBase值應該和當前指針相等.
病毒沒有.data段,變數和數據全部放在.code段
編寫Win32病毒的幾個關鍵
偏移地址的重定位
Call delta
delta: pop ebp
sub ebp,offset delta
那么變數var1的真正偏移地址為:var1+ebp
對PE檔案格式的了解
編寫Win32病毒的幾個關鍵
病毒如何感染其他檔案
在檔案中添加一個新節
該新節中添加病毒代碼和病毒執行後的返回Host程式的代嗎
修改檔案頭中代碼開始執行位置(AddressOfEntryPoint)指向新添加的節,以便程式運行後先執行病毒代碼.
PE病毒感染其他檔案的方法還有很多,譬如PE病毒還可以將自己分散插入到每個節的空隙中等等,這裡不在一一敘述.
PE檔案格式一覽
Section n
Section ...
Section 2
Section 1
Section table
PE header
DOS stub
DOS MZ header
PE header
Pe header 由三部分組成
字串 "PE\0\0"(Signature)
映像檔案頭(FileHeader)
可選映像頭(OptionalHeader)
字串 "PE\0\0"
Signature 一dword類型,值為50h,45h,00h,00h(PE\0\0). 本域為PE標記,我們可以此識別給定檔案是否為有效PE檔案.
這個字串在檔案中的位置(e_lfanew),可以在DOS程式頭中找到它的指針,它占用四個位元組,位於檔案開始偏移3CH位元組中.
映像檔案頭
該結構域包含了關於PE檔案物理分布的信息,比如節數目,檔案執行機器等.
它實際上是結構IMAGE_FILE_HEADER的簡稱.
映像檔案頭結構
IMAGE_FILE_HEADER STRUCT
___ Machine WORD
___ NumberOfSections WORD
___ TimeDateStamp dd
___ PointerToSymbolTable dd
___ NumberOfSymbols dd
___ SizeOfOptionalHeader WORD
___ Characteristics WORD
IMAGE_FILE_HEADER ENDS
映像檔案頭的基本信息
關於檔案信息的標記,比如檔案是exe還是dll
2
Characteristics *
7
可選頭的大小
2
SizeOfOptionalHeader
6
符號數目
4
NumberOfSymbols
5
COFF符號表的偏移
4
PointerToSymbleTable
4
生成該檔案的時間
4
TimeDataStamp
3
檔案中節的個數
2
NumberOfSection **
2
機器類型,x86為14ch
2
Machine *
1
描述
大小(位元組)
名字
順序
可選映像頭
optional header 結構是 IMAGE_NT_HEADERS 中的最後成員.包含了PE檔案的邏輯分布信息.該結構共有31個域,一些是很關鍵,另一些不太常用.這裡只介紹那些真正有用的域.
這兒有個關於PE檔案格式的常用術語: RVA
舉例說明,如果PE檔案裝入虛擬地址(VA)空間的400000h處,且進程從虛址401000h開始執行,我們可以說進程執行起始地址在RVA 1000h.每個RVA都是相對於模組的起始VA的.
可選映像頭
檔案中節對齊的粒度.
FileAlignment
記憶體中節對齊的粒度.
SectionAlignment
PE檔案的優先裝載地址.比如,如果該值是400000h,PE裝載器將嘗試把檔案裝到虛擬地址空間的400000h處.若該地址區域已被其他模組占用,那PE裝載器會選用其他空閒地址.
ImageBase
PE裝載器準備運行的PE檔案的第一個指令的RVA.若要改變整個執行的流程,可以將該值指定到新的RVA,這樣新RVA處的指令首先被執行.
AddressOfEntryPoint *
描述
名字
可選映像頭
NT用來識別PE檔案屬於哪個子系統.
Subsystem
一IMAGE_DATA_DIRECTORY 結構數組.每個結構給出一個重要數據結構的RVA,比如引入地址表等.
DataDirectory
所有頭+節表的大小,也就等於檔案尺寸減去檔案中所有節的尺寸.可以以此值作為PE檔案第一節的檔案偏移量.
SizeOfHeaders
記憶體中整個PE映像體的尺寸.
SizeOfImage
win32子系統版本.若PE檔案是專門為Win32設計的,該子系統版本必定是4.0否則對話框不會有3維立體感.
MajorSubsystemVersion
MinorSubsystemVersion
描述
名字
DataDirectory數據目錄
一個IMAGE_DATA_DIRECTORY數組,裡面放的是這個執行檔的一些重要部分的RVA和尺寸,目的是使執行檔的裝入更快,數組的項數由上一個域給出.IMAGE_DATA_DIRECTORY包含有兩個域,如下:
IMAGE_DATA_DIRECTORY
VitualAddress DD
Size DD
IMAGE_DATA_DIRECTORY ENDS
節表
節表其實就是緊挨著 PE header 的一結構
數組.該數組成員的數目由 file header (IMAGE_FILE_HEADER) 結構中 NumberOfSections 域的域值來決定.節表結構又命名為 IMAGE_SECTION_HEADER.
結構中放的是一個節的信息,如名字,地址,長度,屬性等.
IMAGE_SECTION_HEADER
本節原始數據在檔案中的位置
4
PointerToRawData *
5
本節的原始尺寸
4
SizeOfRawData *
4
這個值+映像基地址=本節在記憶體中的真正地址.OBJ中無意義.
4
Virtual *
3
OBJ檔案用作表示本節物理地址EXE檔案中表示節的真實尺寸
4
PhysicalAddress或VirtualSize
2
節名
8
Name *
1
描述
大小(位元組)
名字
順序
IMAGE_SECTION_HEADER
節屬性
4
Characteristics *
10
本節在行號表中的行號數目
2
NumberOfLinenumbers
9
本節要重定位的數目
2
NumberOfRelocations
8
行號偏移
4
PointerToLinenumbers
7
OBJ中表示該節重定位信息的偏移EXE檔案中無意義
4
PointerToRelocations
6
描述
大小(位元組)
名字
順序
節
"節(Section)"跟在節表之後,一般PE檔案都有幾個"節".比較常見的有:
代碼節
已初始化的數據節
未初始化的數據節
資源節
引入函式節
引出函式節
代碼節
代碼節一般名為.text或CODE,該節含有程式的可執行代碼.
每個PE檔案都有代碼節
在代碼節中,還有一些特別的數據,是作為調用映入函式之用.如:
Call MessageBoxA的調用,反彙編後該指令被換為call 0040101A,而地址0040101A仍在.text中,它放有一個跳轉指令jmp dword ptr[0040304c],即這條跳轉指令的目的地址處於.idata節中的0040304C處,其中放的才是MessageBoxA的真正地址,如下圖:
已初始化的數據節
這個節一般取名為.data或DATA
已初始化的數據節中放的是在編譯時刻就已確定的數據.如Hello World 中的字元串"Hello World!".
未初始化的數據節
這個節的名稱一般叫.bbs.
資源節
資源節一般名為.rsrc
這個節放有如圖示,對話框等程式要用到的資源.
資源節是樹形結構的,它有一個主目錄,主目錄下又有子目錄,子目錄下可以是子目錄或數據.
都是一個IMAGE_RESOURCE_DIRECTORY結構.結構如下:
IMAGE_RESOURCE_DIRECTORY 結構
以ID標識的資源數
2
NumberOfldEntries
6
以名字標識的資源數
2
NumberOfNamedEntries
5
次版本號
2
MinorVersion
4
主版本號
2
MajorVersion
3
資源生成時間
4
TimeDateStamp
2
通常為0
4
Characteritics
1
描述
大小(位元組)
名字
順序
引入函式節
一個引入函式是被某模組調用的但又不在調用者模組中的函式
這個節一般名為.idata,也叫引入表.
它包含從其它(系統或第三方寫的)DLL中引入的函式,例如user32.dll,gdi32.dll等.
它的開始是一個IMAGE_IMPORT_DESCRIPTOR數組.這個數組的長度不定,但他的最後一項是全0,可以以此判斷數組的結束.
引出函式節
什麼是引出函式節
引出函式節是用來向系統提供導出函式的名稱,序號和入口地址等信息,以便Windows裝載器通過這些信息來完成動態連結的過程.
了解引出函式節對於學習病毒來說,是極為重要的.
引出函式節
通過Api函式名查找其地址
⑴定位到PE檔案頭
⑵從PE檔案頭中的課選檔案頭中取出數劇目錄表的第一個數據目錄,得到導出表的地址.
⑶從導出表的NumberOfNames欄位得到以命名函式的總數,並以這個數字做微循環的次數來構造一個循環.
⑷從AddressOfNames欄位指向的函式名稱地址表的第一項開始,在循環中將每一項定義的函式名與要查找的函式名比較,如果沒有任何一個函式名符合,說明檔案中沒有指定名稱的函式.
⑸如果某一項定義的函式名與要查找的函式名符合,那么記住這個函式名在字元串地址表中的索引值,然後在AddressOfNameOrdinals指向的數組中以同樣的索引值去除數組項的值,假如該值為m.
⑹以m值作為索引值,在AddressOfFunctions欄位指向的函式入口地址表中獲取的RVA就是函式的入口地址,當函式被裝入記憶體後,這個RVA值加上模組實際裝入的基址(ImageBase),就得到了函式真正的入口地址