ELF&PE 文件結(jié)構(gòu)分析

ELF&PE 文件結(jié)構(gòu)分析

說簡單點萧福,ELF 對應(yīng)于UNIX 下的文件,而PE 則是Windows 的可執(zhí)行文件窘疮,分析ELF 和 PE 的文件結(jié)構(gòu),是逆向工程冀墨,或者是做調(diào)試闸衫,甚至是開發(fā)所應(yīng)具備的基本能力。在進(jìn)行逆向工程的開端诽嘉,我們拿到ELF 文件蔚出,或者是PE 文件,首先要做的就是分析文件頭虫腋,了解信息骄酗,進(jìn)而逆向文件。不說廢話悦冀,開始分析:

ELF和PE 文件都是基于Unix 的 COFF(Common Object File Format) 改造而來趋翻,更加具體的來說,他是來源于當(dāng)時著名的 DEC(Digital Equipment Corporation) 的VAX/VMS 上的COFF文件格式盒蟆。我們從ELF 說起踏烙。

ELF

ELF 文件標(biāo)準(zhǔn)里把系統(tǒng)中采用ELF 格式的文件歸類為四種:

  • 可重定位文件,Relocatable File ,這類文件包含代碼和數(shù)據(jù)茁影,可用來連接成可執(zhí)行文件或共享目標(biāo)文件宙帝,靜態(tài)鏈接庫歸為此類,對應(yīng)于Linux 中的.o 募闲,Windows 的 .obj.
  • 可執(zhí)行文件步脓,Executable File ,這類文件包含了可以直接執(zhí)行的程序浩螺,它的代表就是ELF 可執(zhí)行文件靴患,他們一般沒有擴(kuò)展名。比如/bin/bash 要出,Windows 下的 .exe
  • 共享目標(biāo)文件鸳君,Shared Object File ,這種文件包含代碼和數(shù)據(jù)患蹂,鏈接器可以使用這種文件跟其他可重定位文件的共享目標(biāo)文件鏈接或颊,產(chǎn)生新的目標(biāo)文件。另外是動態(tài)鏈接器可以將幾個這種共享目標(biāo)文件與可執(zhí)行文件結(jié)合传于,作為進(jìn)程映像來運行囱挑。對應(yīng)于Linux 中的 .so,Windows 中的 DLL
  • 核心轉(zhuǎn)儲文件沼溜,Core Dump File平挑,當(dāng)進(jìn)程意外終止,系統(tǒng)可以將該進(jìn)程地址空間的內(nèi)容及終止時的一些信息轉(zhuǎn)存到核心轉(zhuǎn)儲文件。 對應(yīng) Linux 下的core dump通熄。

ELF 文件的總體結(jié)構(gòu)大概是這樣的:

ELF Header
.text
.data
.bss
... other section
Section header table
String Tables, Symbol Tables,..
  • ELF 文件頭位于最前端唆涝,它包含了整個文件的基本屬性,如文件版本唇辨,目標(biāo)機(jī)器型號廊酣,程序入口等等。
  • .text 為代碼段助泽,也是反匯編處理的部分啰扛,他們是以機(jī)器碼的形式存儲嚎京,沒有反匯編的過程基本不會有人讀懂這些二進(jìn)制代碼的嗡贺。
  • .data 數(shù)據(jù)段,保存的那些已經(jīng)初始化了的全局靜態(tài)變量局部靜態(tài)變量鞍帝。
  • .bss 段诫睬,存放的是未初始化的全局變量局部靜態(tài)變量,這個很容易理解消略,因為在未初始化的情況下添怔,我們單獨用一個段來保存当悔,可以不在一開始就分配空間,而是在最終連接成可執(zhí)行文件的時候亲澡,再在.bss 段分配空間。
  • 其他段纫版,還有一些可選的段床绪,比如.rodata 表示這里存儲只讀數(shù)據(jù), .debug 表示調(diào)試信息等等其弊,具體遇到可以查看相關(guān)文檔癞己。
  • 自定義段,這一塊是為了實現(xiàn)用戶特殊功能而存在的段梭伐,方便擴(kuò)展痹雅,比如我們使用全局變量或者函數(shù)之前加上 attribute(section('name')) 就可以吧變量或者函數(shù)放到以name 作為段名的段中。
  • 段表糊识,Section Header Table 绩社,是一個重要的部分,它描述了ELF 文件包含的所有段的信息赂苗,比如每個段的段名愉耙,段長度,在文件中的偏移哑梳,讀寫權(quán)限和一些段的其他屬性劲阎。

ELF Header

ELF 文件信息的查看利器在Linux 下是是objdump, readelf, 相關(guān)命令較多,可查鸠真。下面我們從ELF 文件頭說起悯仙。

文件頭包含的內(nèi)容很多龄毡,我們在Ubuntu 系統(tǒng)下使用 readelf 命令來查看ELF 文件頭:

我們以bash 這個可執(zhí)行文件為例,我們可以看到ELF 文件頭定義了ELF 魔數(shù)锡垄,文件機(jī)器字節(jié)長度沦零,數(shù)據(jù)存儲方式,版本货岭,運行平臺路操,ABI版本,ELF 重定位類型千贯,硬件平臺屯仗,硬件平臺版本,入口地址搔谴,程序頭入口和長度魁袜,段表的位置和長度,段的數(shù)量敦第。

ELF 文件頭的結(jié)構(gòu)和相關(guān)常數(shù)一般定義在了 /usr/include/elf.h 中峰弹,我們可以進(jìn)去查看一下:


除了第一個,其他都是一一對應(yīng)的芜果,第一個是一個對應(yīng)了Magic number, Class, Data, Version, OS/ABI, ABI version.

出現(xiàn)在最開始的ELF Magic number鞠呈, 16字節(jié)是用來標(biāo)識ELF 文件的平臺屬性,比如字長右钾,字節(jié)序蚁吝,ELF 文件版本。在加載的時候霹粥,首先會確認(rèn)魔數(shù)的正確性灭将,不正確的話就拒絕加載。

另一個重要的東西是段表(Section Header Table) ,保存了各種各樣段的基本屬性后控,比如段名庙曙,段長度,文件中的偏移浩淘,讀寫權(quán)限捌朴,段的其他屬性。而段表自己在ELF 文件中的位置是在ELF 頭文件 e_shoff 決定的张抄。

我們可以使用 objdump -h 的指令來查看ELF 文件中包含哪些段砂蔽,以bash 這個可執(zhí)行為例,其實除了我們之前說的哪些基本結(jié)構(gòu)署惯,他包含很多其他的結(jié)構(gòu):


同樣的左驾,我們使用readelf -S 的指令也可以進(jìn)行查看。

下面我們來看一下結(jié)構(gòu),還是到elf.h 中去查看诡右,他的結(jié)構(gòu)體名字叫 Elf32_Shdr安岂,64位對應(yīng)Elf64_Shdr,結(jié)構(gòu)如下:


以上結(jié)構(gòu)中,分別對應(yīng)于:

  • 段名
  • 段類型
  • 段標(biāo)志位
  • 段虛擬地址
  • 段偏移
  • 段長度
  • 段鏈接
  • 段對齊
  • 項帆吻,一些大小固定的項域那,如符號表等。

這些項目猜煮,在使用readelf -S 指令時一一對應(yīng)次员。

另外還有一個重要的表,叫重定位表王带,一般段名叫.rel.text淑蔚, 在上邊沒有出現(xiàn),鏈接器在處理目標(biāo)文件時辫秧,需要對目標(biāo)文件中的某些部位進(jìn)行重定位束倍,就是代碼段和數(shù)據(jù)段中那些對絕對地址引用的位置被丧,這個時候就需要使用重定位表了盟戏。

字符串表

為什么會有字符串表呢?其實這個也是在不斷發(fā)展改進(jìn)中找到的解決辦法甥桂,在ELF 文件中柿究,會用到很多的字符串,段名黄选,變量名等等蝇摸,但是字符串其本身又長度不固定,如果使用固定結(jié)構(gòu)來表示办陷,就會帶來空間上的麻煩貌夕。所以,構(gòu)造一個字符串表民镜,將使用的字符串統(tǒng)一放在那里啡专,然后通過偏移量來引用字符串,豈不美哉制圈。

需要使用的時候们童,只需要給一個偏移量,然后就到字符串該位置找字符串鲸鹦,遇到\0 就停止慧库。

字符串在ELF 文件中,也是以段的形式保存的馋嗜,常見的段名 .strtab齐板, .shstrtab 兩個字符串分別為字符串表和段表字符串,前者用來保存普通的字符串,后者保存段名甘磨。

在我們使用readelf -h 的時候听皿,我們看到最后一個成員,section header string table index 宽档,實際上他指的就是字符串表的下標(biāo)尉姨,bash 對應(yīng)的字符串表下標(biāo)為27,在使用objdump 的時候吗冤,實際上忽略了字符串表又厉,我們使用readelf ,就可以看到第27位即字符串表:



下面我們回顧一下椎瘟,這個ELF 構(gòu)造的精妙之處覆致,當(dāng)一個ELF 文件到來的時候,系統(tǒng)自然的找到他的開頭肺蔚,拿到文件頭煌妈,首先看魔數(shù),識別基本信息宣羊,看是不是正確的璧诵,或者是可識別的文件,然后加載他的基本信息仇冯,包括CPU 平臺之宿,版本號,段表的位置在哪苛坚,還可以拿到字符串表在哪比被,以及整個程序的入口地址。這一系列初始化信息拿到之后泼舱,程序可以通過字符串表定位等缀,找到段名的字符串,通過段表的初始位置娇昙,確認(rèn)每個段的位置尺迂,段名,長度等等信息涯贞,進(jìn)而到達(dá)入口地址枪狂,準(zhǔn)備執(zhí)行。

當(dāng)然宋渔,這只是最初始的內(nèi)容州疾,其后還要考慮鏈接,Import,Export 等等內(nèi)容皇拣,留待以后完善严蓖。

PE 文件

下面我們?nèi)タ纯锤鼮槌R姷腜E 文件格式薄嫡,實際上PE 與 ELF 文件基本相同,也是采用了基于段的格式颗胡,同時PE 也允許程序員將變量或者函數(shù)放在自定義的段中毫深, GCC 中attribute(section('name')) 擴(kuò)展屬性。

PE 文件的前身是COFF毒姨,所以分析PE 文件哑蔫,先來看看COFF 的文件格式,他保存在WinNT.h 文件中弧呐。

COFF 的文件格式和ELF 幾乎一毛一樣:

Image Header
SectionTable Image_SECTION_HEADER
.text
data
.drectve
.debug$S
... other sections
Symbol Table

文件頭定義在WinNT.h 中闸迷,我們打開來看一下:


我們可以看到,它這個文件頭和ELF 實際上是一樣的俘枫,也在文件頭中定義了段數(shù)腥沽,符號表的位置,Optional Header 的大小鸠蚪,這個Optional Header 后邊就看到了今阳,他就是PE 可執(zhí)行文件的文件頭的部分,以及段的屬性等茅信。

跟在文件頭后邊的是COFF 文件的段表盾舌,結(jié)構(gòu)體名叫 IMAGE_SECTION_HEADER :


屬性包括這些,和ELF 沒差:

  • 段名
  • 物理地址 PhysicalAddress
  • 虛擬地址 VirtualAddress
  • 原始數(shù)據(jù)大小 Sizeof raw data
  • 段在文件中的位置 File pointer to raw data
  • 該段的重定位表在文件中的位置 File pointer to relocation table
  • 該段的行號表在文件中的位置 File pointer to line number
  • 標(biāo)志位汹押,包括段的類型矿筝,對齊方式,讀取權(quán)限等標(biāo)志棚贾。

DOS 頭

在我們分析PE 的之前,還有另外一個頭要了解一下榆综,DOS 頭妙痹,不得不說,微軟事兒還是挺多的鼻疮。

微軟在創(chuàng)建PE 文件格式時怯伊,人們正在廣泛使用DOS 文件,所以微軟為了考慮兼容性的問題判沟,所以在PE 頭的最前邊還添加了一個 IMAGE_DOS_HEADER 結(jié)構(gòu)體耿芹,用來擴(kuò)展已有的DOS EXE。在WinNTFS.h 里可以看到他的身影挪哄。

DOS 頭結(jié)構(gòu)體的大小是40字節(jié)吧秕,這里邊有兩個重要的成員,需要知道迹炼,一個是e_magic 又見魔數(shù)砸彬,一個是e_lfanew颠毙,它只是了NT 頭的偏移。

對于PE 文件來說砂碉,這個e_magic蛀蜜,也就是DOS 簽名都是MZ,據(jù)說是一個叫 Mark Zbikowski 的開發(fā)人員在微軟設(shè)計了這種ODS 可執(zhí)行文件增蹭,所以...

我們以Windows 下的notepad++ 的可執(zhí)行文件為例滴某,在二進(jìn)制編輯軟件中打開,此類軟件比較多滋迈,Heditor 打開:


開始的兩個字節(jié)是4D5A壮池,e_lfanew 為00000108 注意存儲順序,小端杀怠。

你以為開頭加上了DOS 頭就完事了么椰憋,就可以跟著接PE 頭了么。為了兼容DOS 當(dāng)然不是這么簡單了赔退,緊接著DOS 頭橙依,跟的是DOS 存根,DOS stub硕旗。這一塊就是為DOS 而準(zhǔn)備的窗骑,對于PE 文件,即使沒有它也可以正常運行漆枚。


旁邊的ASCII 是讀不懂的创译,因為他是機(jī)器碼,是匯編墙基,為了在DOS 下執(zhí)行软族,對于notepad++ 來說,這里是執(zhí)行了一句残制,this program cannot be run in DOS mode 然后退出立砸。逗我= =,有新的人初茶,可以在DOS 中創(chuàng)造一個程序颗祝,做一些小動作。

NT頭

下面進(jìn)入正題恼布,在HEditor 上也看到了PE螺戳,這一塊就是正式的步入PE 的范疇。


這是32位的PE 文件頭定義折汞,64位對應(yīng)改倔幼。第一個成員就是簽名,如我們所說字支,就是我們看到的「PE」凤藏,對應(yīng)為50450000h奸忽。

這里邊有兩個東西,第一個就是我們之前看到的COFF 文件頭揖庄,這里直接放進(jìn)來了栗菜,我們不再分析。

看第二個蹄梢,IMAGE_OPTIONAL_HEADER 不是說這個頭可選疙筹,而是里邊有些變量是可選的,而且有一些變量是必須的禁炒,否則會導(dǎo)致文件無法運行:


有這么幾個需要重點關(guān)注的成員而咆,這些都是文件運行所必需的:

  1. Magic 魔數(shù),對于32結(jié)構(gòu)體來說是10B幕袱,對于64結(jié)構(gòu)體來說是20B.
  2. AddressOfEntryPoint 持有EP 的RVA 值暴备,之處程序最先執(zhí)行的代碼起始位置,也就是程序入口们豌。
  3. ImageBase 進(jìn)程虛擬內(nèi)存的范圍是0-FFFFFFFF (32位)涯捻。PE 文件被加載到這樣的內(nèi)存中,ImageBase 指出文件的優(yōu)先裝入位置望迎。
  4. SectionAlignment, FileAlignment PE 文件的Body 部分劃分為若干段障癌,F(xiàn)ileAlignment 之處段在磁盤文件中的最小單位,SectionAlignment指定了段在內(nèi)存中的最小單位辩尊。
  5. SizeOfImage 指定 PE Image 在虛擬內(nèi)存中所占的空間大小涛浙。
  6. SizeOfHeader PE 頭的大小
  7. Subsystem 用來區(qū)分系統(tǒng)驅(qū)動文件與普通可執(zhí)行文件。
  8. NumberOfRvaAndSizes 指定DataDirectory 數(shù)組的個數(shù),雖然最后一個值摄欲,指出個數(shù)是16轿亮,但實際上PE 裝載還是通過識別這個值來確定大小的。至于DataDirectory 是什么看下邊
  9. DataDirectory 它是一個由IMAGE_DATA_DIRECTORY 結(jié)構(gòu)體組成的數(shù)組蒿涎,數(shù)組每一項都有定義的值哀托,里邊有一些重要的值,EXPORT/IMPORT/RESOURCE, TLS direction 是重點關(guān)注的劳秋。

段頭

PE 的段頭直接沿用的COFF 的段頭結(jié)構(gòu),上邊也說過了胖齐,我們查看notepad++ 的段頭玻淑,可以獲得各個段名,以及其信息呀伙,這里补履,我們可以使用一些軟件查看,更加方便:


RVA to RAW

理解PE 最重要的一個部分就是理解文件從磁盤到內(nèi)存地址的映射過程剿另,做逆向的人員箫锤,只有熟練地掌握才能跟蹤到程序的調(diào)用過程和位置贬蛙,才能分析和尋找漏洞。

對于文件和內(nèi)存的映射關(guān)系谚攒,其實很簡單阳准,他們通過一個簡單的公式計算而來:


換算公式是這樣的:

RAW -PointToRawData = RVA - VirtualAddress

尋找過程就是先找到RVA 所在的段,然后根據(jù)公式計算出文件偏移馏臭。因為我們通過逆向工具野蝇,可以在內(nèi)存中查找到所在的RVA,進(jìn)而我們就可以計算出在文件中所在的位置括儒,這樣绕沈,就可以手動進(jìn)行修改。

看回我們剛才載入的nodepad++ 帮寻,其中的V Addr, 實際上就是VirtualAddress乍狐,R offset 就是PointerToRawData。


假如我們的RVA 地址是5000固逗,那么計算方法就是浅蚪,查看區(qū)段,發(fā)現(xiàn)在.text 中抒蚜,5000-1000+400 = 4400掘鄙,這就是RAW 00004400,而實際上嗡髓,因為我們的ImageBase 是00400000操漠,所以,我們在反編譯時候內(nèi)存中的地址是00405000.

接下來饿这,使我們的PE頭中的核心內(nèi)容浊伙,IAT 和 EAT,也就是 Import address table, export address table.

IAT

導(dǎo)入地址表的內(nèi)容與Windows 操作系統(tǒng)的核心進(jìn)程长捧,內(nèi)存嚣鄙,DLL 結(jié)構(gòu)有關(guān)。他是一種表格串结,記錄了程序使用哪些庫中的哪些函數(shù)哑子。

下面,讓我們把目光轉(zhuǎn)到DLL 上肌割,Dynamic Linked Library 支撐了整個 OS卧蜓。DLL 的好處在于,不需要把庫包含在程序中把敞,單獨組成DLL 文件弥奸,需要時調(diào)用即可,內(nèi)存映射技術(shù)使加載后的DLL 代碼奋早,資源在多個進(jìn)程中實現(xiàn)共享盛霎,更新庫時候只要替換相關(guān)DLL 文件即可赠橙。

加載DLL 的方式有兩種,一種是顯式鏈接愤炸,使用DLL 時候加載期揪,使用完釋放內(nèi)存。另一種是隱式鏈接摇幻,程序開始就一同加載DLL横侦,程序終止的時候才釋放掉內(nèi)存。而IAT 提供的機(jī)制與隱式鏈接相關(guān)绰姻,最典型的Kernel32.dll枉侧。

我們來看看notepad++ 調(diào)用kernel32.dll 中的CreateFileW, 使用PE 調(diào)試工具Ollydbg


我們看到填入?yún)?shù)之后,call 了35d7ffff 地址的內(nèi)容狂芋,然后我們?nèi)ump 窗口榨馁,找一下kernel.CreateFileW:

我們雙擊匯編窗口,啟動編輯帜矾,發(fā)現(xiàn)確實是call 的這個數(shù)值:


可是問題來了翼虫,上邊是E8 35D7FFFF,下邊地址卻是 00C62178屡萤。其實這是Win Visita, Win 7的ASLR 技術(shù)珍剑,主要就是針對緩沖溢出攻擊的一種保護(hù)技術(shù),通過隨機(jī)化布局死陆,讓逆向跟蹤者招拙,難以查找地址,就難以簡單的進(jìn)行溢出攻擊措译。不過還是可以通過跳板的方式别凤,找到溢出的辦法,這就是后話了领虹。

現(xiàn)在可以確定的是规哪,35D7FFFF 可以認(rèn)為保存的數(shù)值就是 CreateFileW 的地址。而為什么不直接使用CALL 7509168B 這種方式直接調(diào)用呢塌衰? Kernel32.dll 版本各不相同诉稍,對應(yīng)的CreateFileW 函數(shù)也各不相同,為了兼容各種環(huán)境最疆,編譯器準(zhǔn)備了CreateFileW 函數(shù)的實際地址均唉,然后記下DWORD PTR DS:[xxxxxx] 這樣的指令,執(zhí)行文件時候肚菠,PE 裝載器將CreateFileW 函數(shù)地址寫到這個位置。

同時罩缴,由于重定位的原因存在蚊逢,所以也不能直接使用CALL 7509168B 的方式层扶,比如兩個DLL 文件有相同的 ImageBase,裝載的時候烙荷,一個裝載到該位置之后镜会,另一個就不能裝載該位置了,需要換位置终抽。所以我們不能對實際地址進(jìn)行硬編碼戳表。

IMAGE_IMPORT_DESCRIPTOR


對于一個普通程序來說,需要導(dǎo)入多少個庫昼伴,就會存在多少個這樣的結(jié)構(gòu)體匾旭,這些結(jié)構(gòu)體組成數(shù)組,然后數(shù)組最后是以NULL 結(jié)構(gòu)體結(jié)束圃郊。其中有幾個重要的成員:

  • OriginalFirstThunk INT Import Name Table 地址价涝,RVA
  • Name 庫名稱字符串地址,RVA持舆,就是說該地址保存庫名稱
  • First Thunk IAT 地址 RVA
  • INT 中個元素的值是上邊那個IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)體指針色瘩。
  • INT 與 IAT 大小應(yīng)相同。

那么PE 是如何導(dǎo)入函數(shù)輸出到IAT 的:

  1. 讀取NAME 成員逸寓,獲取擴(kuò)名稱字符串
  2. 裝載相應(yīng)庫: LoadLibrary("kernel32.dll")
  3. 讀取OriginalFirstThunk成員居兆,獲取INT 地址
  4. 讀取INT 數(shù)組中的值,獲取相應(yīng)的 IMAGE_IMPORT_BY_NAME地址竹伸,是RVA地址
  5. 使用IMAGE_IMPORT_BY_NAME 的Hint 或者是name 項泥栖,獲取相應(yīng)函數(shù)的起始位置 GetProcAddress("GetCurrentThreadId")
  6. 讀取FistrThunk 成員,獲得IAT 地址佩伤。
  7. 將上面獲得的函數(shù)地址輸入相應(yīng)IAT 數(shù)組值聊倔。
  8. 重復(fù)4-7 到INT 結(jié)束。

這里就產(chǎn)生了一個疑惑生巡,OriginalFirstThunk 和 First Thunk 都指向的是函數(shù)耙蔑,為什么多此一舉呢?

首先孤荣,從直觀上說甸陌,兩個都指向了庫中引入函數(shù)的數(shù)組,魚C 畫的這張圖挺直觀:


OriginalFirstThunk 和 FirstThunk 他們都是兩個類型為IMAGE_THUNK_DATA 的數(shù)組盐股,它是一個指針大小的聯(lián)合(union)類型钱豁。
每一個IMAGE_THUNK_DATA 結(jié)構(gòu)定義一個導(dǎo)入函數(shù)信息(即指向結(jié)構(gòu)為IMAGE_IMPORT_BY_NAME 的家伙,這家伙稍后再議)疯汁。
然后數(shù)組最后以一個內(nèi)容為0 的 IMAGE_THUNK_DATA 結(jié)構(gòu)作為結(jié)束標(biāo)志牲尺。
IMAGE_THUNK_DATA32 結(jié)構(gòu)體如下:

因為是Union 結(jié)構(gòu),IMAGE_THUNK_DATA 事實上是一個雙字大小。
規(guī)定如下:

當(dāng) IMAGE_THUNK_DATA 值的最高位為 1時谤碳,表示函數(shù)以序號方式輸入溃卡,這時候低 31位被看作一個函數(shù)序號。

當(dāng) IMAGE_THUNK_DATA 值的最高位為 0時蜒简,表示函數(shù)以字符串類型的函數(shù)名方式輸入瘸羡,這時雙字的值是一個 RVA,指向一個 IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)搓茬。

我們再看IMAGE_IMPORT_BY_NAME 結(jié)構(gòu):


結(jié)構(gòu)中的 Hint 字段也表示函數(shù)的序號犹赖,不過這個字段是可選的,有些編譯器總是將它設(shè)置為 0卷仑。

Name 字段定義了導(dǎo)入函數(shù)的名稱字符串峻村,這是一個以 0 為結(jié)尾的字符串。

現(xiàn)在重點來了:

第一個數(shù)組(由 OriginalFirstThunk 所指向)是單獨的一項系枪,而且不能被改寫雀哨,我們前邊稱為 INT。第二個數(shù)組(由 FirstThunk 所指向)事實上是由 PE 裝載器重寫的私爷。

PE 裝載器裝載順序正如上邊所講的那樣雾棺,我們再將它講詳細(xì)一點:

PE 裝載器首先搜索 OriginalFirstThunk ,找到之后加載程序迭代搜索數(shù)組中的每個指針衬浑,找到每個 IMAGE_IMPORT_BY_NAME 結(jié)構(gòu)所指向的輸入函數(shù)的地址捌浩,然后加載器用函數(shù)真正入口地址來替代由 FirstThunk 數(shù)組中的一個入口,因此我們稱為輸入地址表(IAT).

繼續(xù)套用魚C 的圖工秩,就能直觀的感受到了:

所以尸饺,在讀取一次OriginalFirstThunk 之后,程序就是依靠IAT 提供的函數(shù)地址來運行了助币。

EAT

搞清楚了IAT 的原理浪听,EAT 就好理解了,目前這篇總結(jié)的有點長了眉菱,我長話短說迹栓。IAT 是導(dǎo)入的庫和函數(shù)的表,那么EAT 就對應(yīng)于導(dǎo)出俭缓,它使不同的應(yīng)用程序可以調(diào)用庫文件中提供的函數(shù)克伊,為了方便導(dǎo)出函數(shù),就需要保存這些導(dǎo)出信息华坦。

回頭看PE 文件中的PE頭我們可以看到IMAGE_EXPORT_DIRECTORY 結(jié)構(gòu)體以的位置愿吹,他在IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress 的值就是 IMAGE_EXPORT_DIREDCTORY 的起始位置。


IMAGE_EXPORT_DIRECTORY結(jié)構(gòu)體如下:

這里邊同樣是這么幾個重要的成員:

  • NumberOfFunctions 實際Export 函數(shù)的個數(shù)
  • NumberOfNames Export 函數(shù)中具名的函數(shù)個數(shù)
  • AddressOfFunctins Export 函數(shù)地址數(shù)組惜姐,數(shù)組個數(shù)是上邊的NOF
  • AddressOfNames 函數(shù)名稱地址數(shù)組犁跪,個數(shù)是上邊的NON
  • AddressOfNameOrdinals Ordinal 地址數(shù)組,個數(shù)等于上邊NON
  • Name 一個RVA 值,指向一個定義了模塊名稱的字符串耘拇。如即使Kernel32.dll 文件被改名為”Ker.dll”撵颊。仍然可以從這個字符串中的值得知其在編譯時的文件名是”Kernel32.dll”。
  • Base:導(dǎo)出函數(shù)序號的起始值惫叛,將AddressOfFunctions 字段指向的入口地址表的索引號加上這個起始值就是對應(yīng)函數(shù)的導(dǎo)出 序號。
    以kernel32.dll 為例逞刷,我們看一下:


從上邊這些成員嘉涌,我們實際上可以看出,是有兩種方式提供給那些想調(diào)用該庫中函數(shù)的夸浅,一種是直接從序號查找函數(shù)入口地址導(dǎo)入仑最,一種是通過函數(shù)名來查找函數(shù)入口地址導(dǎo)入。

先上一個魚C 的圖帆喇,方便理解:


上邊圖警医,注意一點,因為AddressOfNameOrdinals 的序號應(yīng)當(dāng)是從0開始的坯钦,不過圖中映射的是第二個函數(shù)指向的序號1预皇。

我們分別說一下兩種方式:

當(dāng)已知導(dǎo)出序號的時候

  1. Windows 裝載器定位到PE 文件頭,
  2. 從PE 文件頭中的 IMAGE_OPTIONAL_HEADER32 結(jié)構(gòu)中取出數(shù)據(jù)目錄表婉刀,并從第一個數(shù)據(jù)目錄中得到導(dǎo)出表的RVA 吟温,
  3. 從導(dǎo)出表的 Base 字段得到起始序號,
  4. 將需要查找的導(dǎo)出序號減去起始序號突颊,得到函數(shù)在入口地址表中的索引鲁豪,
  5. 檢測索引值是否大于導(dǎo)出表的 NumberOfFunctions 字段的值,如果大于后者的話律秃,說明輸入的序號是無效的用這個索引值在 AddressOfFunctions 字段指向的導(dǎo)出函數(shù)入口地址表中取出相應(yīng)的項目爬橡,這就是函數(shù)入口地址的RVA 值,當(dāng)函數(shù)被裝入內(nèi)存的時候棒动,這個RVA 值加上模塊實際裝入的基地址糙申,就得到了函數(shù)真正的入口地址

當(dāng)已知函數(shù)名稱查找入口地址時

  1. 從導(dǎo)出表的 NumberOfNames 字段得到已命名函數(shù)的總數(shù)赋焕,并以這個數(shù)字作為循環(huán)的次數(shù)來構(gòu)造一個循環(huán)
  2. 從 AddressOfNames 字段指向得到的函數(shù)名稱地址表的第一項開始男应,在循環(huán)中將每一項定義的函數(shù)名與要查找的函數(shù)名相比較但狭,如果沒有任何一個函數(shù)名是符合的疲迂,表示文件中沒有指定名稱的函數(shù)妹卿,如果某一項定義的函數(shù)名與要查找的函數(shù)名符合惹想,那么記下這個函數(shù)名在字符串地址表中的索引值缺厉,然后在 AddressOfNamesOrdinals 指向的數(shù)組中以同樣的索引值取出數(shù)組項的值桃犬,我們這里假設(shè)這個值是x
  3. 最后卜范,以 x 值作為索引值衔统,在 AddressOfFunctions 字段指向的函數(shù)入口地址表中獲取的 RVA 就是函數(shù)的入口地址

一般來說,做逆向或者是寫代碼都是第二種方法,我們以kernel32.dll 中的GetProcAddress 函數(shù)為例锦爵,其操作原理如下:

  1. 利用 AddressOfNames 成員轉(zhuǎn)到 『函數(shù)名稱數(shù)組』
  2. 『函數(shù)名稱數(shù)組』中存儲著字符串地址舱殿,通過比較字符串,查找指定的函數(shù)名稱险掀,此時數(shù)組所以為成為name_index
  3. 利用 AddressOfNameOrdinals 成員沪袭,轉(zhuǎn)到這個序號數(shù)組
  4. 在ordinal 數(shù)組中通過name_index 查找到相應(yīng)的序號
  5. 利用AddressOfFunctions 成員,轉(zhuǎn)到『函數(shù)地址數(shù)組』EAT
  6. 在EAT 中將剛剛得到的ordinal 作為索引樟氢,獲得指定函數(shù)的入口地址

寫了這么多冈绊,實際上算是對文件結(jié)構(gòu)有了一個入門的認(rèn)識,至少知道在程序運行過程中埠啃,系統(tǒng)是如何進(jìn)行操作和鏈接的死宣,而更加詳細(xì)的內(nèi)容注入運行時壓縮,DLL 注入碴开,API 鉤取等技術(shù)毅该,就需要在這個基礎(chǔ)之上繼續(xù)挖掘,所以PE 潦牛,ELF 文件結(jié)構(gòu)的分析是相當(dāng)重要的眶掌。

PS. 參考:
魚C 講解PE 文件格式之INT
《Windows PE 權(quán)威指南》
《逆向工程核心原理》
《程序員的自我修養(yǎng)-鏈接,裝載與庫》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末罢绽,一起剝皮案震驚了整個濱河市畏线,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌良价,老刑警劉巖寝殴,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異明垢,居然都是意外死亡蚣常,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門痊银,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抵蚊,“玉大人,你說我怎么就攤上這事溯革≌晟” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵致稀,是天一觀的道長冈闭。 經(jīng)常有香客問我,道長抖单,這世上最難降的妖魔是什么萎攒? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任遇八,我火速辦了婚禮,結(jié)果婚禮上耍休,老公的妹妹穿的比我還像新娘刃永。我一直安慰自己,他們只是感情好羊精,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布斯够。 她就那樣靜靜地躺著,像睡著了一般园匹。 火紅的嫁衣襯著肌膚如雪雳刺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天裸违,我揣著相機(jī)與錄音,去河邊找鬼本昏。 笑死供汛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的涌穆。 我是一名探鬼主播怔昨,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宿稀!你這毒婦竟也來了趁舀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤祝沸,失蹤者是張志新(化名)和其女友劉穎矮烹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罩锐,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡奉狈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涩惑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仁期。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竭恬,靈堂內(nèi)的尸體忽然破棺而出跛蛋,到底是詐尸還是另有隱情,我是刑警寧澤痊硕,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布赊级,位于F島的核電站,受9級特大地震影響寿桨,放射性物質(zhì)發(fā)生泄漏此衅。R本人自食惡果不足惜强戴,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挡鞍。 院中可真熱鬧骑歹,春花似錦、人聲如沸墨微。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翘县。三九已至最域,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锈麸,已是汗流浹背镀脂。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留忘伞,地道東北人薄翅。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像氓奈,于是被迫代替她去往敵國和親翘魄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容