問:1.1
我知道程序中最重要的段是text段,請告訴我text 段在哪?
答:1.1
text 段在文件偏移0x400處,大小0x200字節,該區可運行,可讀取,包含代碼。
該區在內存中RVA 0x1000處,大小0x1000.
問:1.2
我用urtraedit 打開hello.exe 看了,在0x400處-0x600處,大部分都是0,為什么這樣呢。
答:1.2
pe 格式大部分文件都是這樣,這是對齊所要求的,文件對齊為0x200, 內存對齊為0x1000
你可以在NT_Option_Header 的Section_Alignment, File_Alignment 域中看到這兩個數據。
//
// Optional header format.
//
ty
pedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorO
peratingSystemVersion;
WORD MinorO
peratingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;z
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
問:1.3
慢點,別一下子貼那么多東西,我還沒有找到 _IMAGE_OPTIONAL_HEADER 的位置呢,
告訴我怎樣找
答:1.3
貼上那個_IMAGE_OPTIONAL_HEADER結構好說話,它的位置緊跟在 MAGE_FILE_HEADER 之后
告訴你個小技巧,那個Magic對NT x86來講總是010B,在頭文件找到那個010b,就是IMAGE_OPTIONAL_HEADER32 結構的地址。
問:1.4
問題越來越多了。
_IMAGE_OPTIONAL_HEADER 還沒有說清呢,又出來一個IMAGE_FILE_HEADER。先不管IMAGE_FILE_HEADER
先按你的小技巧,在頭部找到010b, 因為是little endial, 在ultraedit 中要找0b 01.
好,找到了,離那個 50 45 00 00 (ascii PE)相距不遠,在偏移D8處,按你所說SectionAlignment和FileAlignment 應該在結構第9個,第10個DWORD 處。
好,找到了,在f8處有00001000, FC處為00 00 02 00 (我已經考慮了endian,以后不用提醒了)。
答:1.4
呀,進步不小嗎?這樣一下子你就把IMAGE_OPTIONAL_HEADER32 中所有的東西都找出來了。
問:1.5
是的,我可以把Optional header中所有東西都找出來,但我現在除了剛才介紹的第9個DWORD為內存對齊大小,第10個DWORD為文件對齊大小,其它我都不知道是干什么的?
答:1.5
別著急,其實還是很容易理解的,從字面意義就能猜大概。不過我們現在還不是通讀Optional header的時候,還是揀我們最關心的問題插手吧。
問:1.6
還是回到text 段上來吧,剛才你對text段大小,位置,屬性分析的頭頭是到
你是從那看出來的?
答:1.6
是從section header 中看出來的,每一個section, 都有一個section header 描述其位置,大小,屬性。
section header 的結構是這樣定義的
#define IMAGE_SIZEOF_SHORT_NAME 8
ty
pedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
問:1.7
呦,慢點,怎么又往外甩結構,我很菜! 哦,不太多,還行吧。
不過你還是告訴我具體位置在哪吧,我好拿結構和數據對對號。
答:1.7
好,正是這種
學習方法。你一定能學會的。
節表頭是一個數組,它把所有節的位置,長度,屬性放在了一起
緊跟在option header 之后,所以你從文件頭部往下找就可以了。
看到IMAGE_SECTION_HEADER結構的第一個成員了嗎,它是
BYTE Name【8】
這是節名稱,你要找的text 段名字就是 .text, 你看ultraedit
ascii 碼區離文件開始不遠的地方,有一個.text, 對應的二進制
數據是2E 74 65 78 74, 這就是text 端IMAGE_SECTION_HEADER處
問:1.8
原來玄機在這里呀。我試試看。哦,看見了,在1B8處。 前8個
字節是節名稱。后面的00 00 00 28 到底是物理地址還是虛擬大小,
(偷偷的,虛擬大小,表示內存中只有0x28個字節有效,其它全是0),在后面00 00 10 00 是虛擬相對地址 俗稱RVA, 就是在內存中相對與起始地址的偏移。再后面00 00 02 00 為SizeOfRawData, 就是文件中大小,再后面 00 00 04 00 是
PointerToRawData,是文件的偏移 后面有三個DWORD 全是0,他們
是重定位信息和行號,很好,EXE文件可以不用管這些。最后一個
60 00 00 20 代表屬性可讀,可寫,是代碼。好,我終于理解你的第一句話了。
不解釋一下,我怎么能一下子聽的懂呢! 謝謝你。
那么我又有問題了。那程序針真是搜索這個.text字符串找到Text 節表頭嗎?
答:1.8
不是。前面說過,節表頭緊隨Optional header 之后。
問:1.9
Optional header 結構變量太多,我數了一下都沒數清,到底占多少個字節呢?
答:1.9
正等著你這一問呢?是啊,數都數不清,縱是現在記住了將來也容易忘。
估計微軟也想到了這一點,他把OPTION header 的大小放到了 _IMAGE_FILE_HEADER 的一個變量中,
下面是_IMAGE_FILE_HEADER 的定義
ty
pedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
SizeOfOptionalHeader 一般總是0xE0
問:1.10
我今天已經學了不少東西了,看樣子后面還很多的樣子。再問最后一個問題。
FILE_HEADER 在文件什么位置呢。
答:1.10
這個簡單,就在PE標識符后面。看到了嗎,在C0處,ascii 是PE. 二進制是50 45 00 00
代學生:
哦,看到了,今天10個問題已經滿了,我還想學,可是有點累了。。。
代老師:
今天就到這里吧,好好休息一下。
引言: 上一次以hello.exe 為例,介紹了
pe 文件頭,節表和導入表。
這一次我們以 count.dll 為例,介紹導出表和重定位表
count.dll 是羅云斌win32匯編編程中的例子程序,因其短小,故被選中。
問5.1:dll 為什么叫動態連接庫,與平常的靜態連接有什么不同。
答5.1:靜態連接庫在編譯連接時由link 程序把庫文件直接添加到運行程序中。
動態連接庫在編譯連接時只是把插樁加到代碼里。運行時由加載器載入
內存,修改插樁代碼使指向正確的地址。這個過程在上一講中已經說過了。
問5.2:既然是庫函數,就會有一堆函數構成,那么是否每個函數都可以被外邊調用呢?
答5.2:庫函數可分為三類,一個是庫入口函數。
一類為可被外部調用,叫導出庫函數。
一類不能被外部調用,我們叫它私有函數吧。因為它沒有向外提供接口。
問5.3:導出函數是怎樣向外提供接口的呢? 或者說我們怎樣才能使用導出函數呢?
答5.3: 這個問題是我們的重點,我們結合實例來吧,慢慢把它講清楚。
用ultraedit 打開這個dll 文件。
ultraedit看到的是最原始的文件,其它眾多的
pe 分析軟件都是從原始文件分析得到的。哦,當然,這話說的多余了。
大概瀏覽一下這個文件。
區分一下dos頭, PE 頭, 節表, 有幾個塊組成。這些都是很明顯的。上一講中已經說過了。
哦,上一講講的是exe, 這里是dll, 不過它們都是PE 文件, 格式是一樣的。
順便復習一下上次內容,這可以說是上次4講的精華了,卻是以count.dll 為例,同樣通俗易懂:
dos 頭: 標記 "MZ"
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ?........ ..
00000010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ?......@.......
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 C0 00 00 00 ............?..
PE 頭: 標記 "PE"
000000C0 50 45 00 00 4C 01 04 00 F6 34 EB 3C 00 00 00 00 PE..L...??....
000000D0 00 00 00 00 E0 00 0E 21 0B 01 05 0C 00 02 00 00 ....?.!........
從dos頭 0x3c 處也能看出PE 頭位置。
節表: 有明顯的字符串標記,此處是".text"
000001B0 2E 74 65 78 74 00 00 00 .text...
000001C0 70 00 00 00 00 10 00 00 00 02 00 00 00 04 00 00 p...............
000001D0 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..`
000001E0 2E 72 64 61 74 61 00 00 BC 00 00 00 00 20 00 00 .rdata..?... ..
000001F0 00 02 00 00 00 06 00 00 00 00 00 00 00 00 00 00 ................
00000200 00 00 00 00 40 00 00 40 2E 64 61 74 61 00 00 00 ....@..@.data...
00000210 04 00 00 00 00 30 00 00 00 00 00 00 00 00 00 00 .....0..........
00000220 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0 ............@..?
00000230 2E 72 65 6C 6F 63 00 00 2C 00 00 00 00 40 00 00 .reloc..,....@..
00000240 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00 ................
00000250 00 00 00 00 40 00 00 42 ....@..B........
數一數節表有4個
從PE 頭 0xc7處也能看出來。
大致瀏覽一下后面的數據塊劃分,塊與塊之間很容易識別,因為每一塊之間都有很多0,
它們是以512字節對齊填充的。
咦! 怎么只看到了3塊, 節表頭中不是說4塊嗎?
再仔細對照一下節表頭:跟我一塊找找。
00000400 55 8B EC B8 01 00 00 00 C9 C2 0C 00 55 8B EC 6A U嬱?...陜..U嬱j
00000410 01 FF 75 10 FF 75 0C FF 75 08 E8 4B 00 00 00 C9 . . . .鐺...?
00000420 C2 0C 00 55 8B EC FF 05 00 30 00 10 FF 35 00 30 ?.U嬱 .0.. .0
00000430 00 10 FF 75 0C FF 75 08 E8 CF FF FF FF A1 00 30 .. . .柘 0
00000440 00 10 C9 C2 08 00 55 8B EC FF 0D 00 30 00 10 FF ..陜..U嬱 .0.. br /> 00000450 35 00 30 00 10 FF 75 0C FF 75 08 E8 AC FF FF FF 5.0.. . .瓔 br /> 00000460 A1 00 30 00 10 C9 C2 08 00 CC FF 25 00 20 00 10 ?0..陜..?%. ..
00000470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
這段是 text 段,因為節表已經說了,text 段內存地址0x1000,大小0x70
在文件中處于偏移0x400, 占用文件大小0x200 字節。
00000600 38 20 00 00 00 00 00 00 30 20 00 00 00 00 00 00 8 ......0 ......
00000610 00 00 00 00 48 20 00 00 00 20 00 00 00 00 00 00 ....H ... ......
00000620 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000630 38 20 00 00 00 00 00 00 27 02 53 65 74 44 6C 67 8 ......'.SetDlg
00000640 49 74 65 6D 49 6E 74 00 55 53 45 52 33 32 2E 64 ItemInt.USER32.d
00000650 6C 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ll..............
00000660 00 00 00 00 F6 34 EB 3C 00 00 00 00 9C 20 00 00 ....??....?..
00000670 01 00 00 00 02 00 00 00 02 00 00 00 88 20 00 00 ............?..
00000680 90 20 00 00 98 20 00 00 46 10 00 00 23 10 00 00 ?..?..F...#...
00000690 A8 20 00 00 B2 20 00 00 00 00 01 00 43 6F 75 6E ?..?......Coun
000006A0 74 65 72 2E 64 6C 6C 00 5F 44 65 63 43 6F 75 6E ter.dll._DecCoun
000006B0 74 00 5F 49 6E 63 43 6F 75 6E 74 00 00 00 00 00 t._IncCount.....
000006C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
這段是 rdata 段,因為節表已經說了,rdata 段內存地址0x2000,大小0xbc
在文件中處于偏移0x600, 占用文件大小0x200 字節。
00000800 00 10 00 00 18 00 00 00 28 30 2E 30 3E 30 4B 30 ........(0.0>0K0
00000810 51 30 61 30 6C 30 00 00 00 00 00 00 00 00 00 00 Q0a0l0..........
00000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
這段是 reloc 段,因為節表已經說了,reloc 段內存地址0x4000,大小0x20
在文件中處于偏移0x800, 占用文件大小0x200 字節。
哦!也!文件分析完了。
呦,不是說看看丟了哪一塊嗎? 是data 段丟了。看看節表怎么說:
節表已經說了,data 段內存地址0x4000,大小0x4
在文件中處于偏移0x0, 占用文件大小0x0 字節。
怪不得文件中找不到它的蹤影,原來它不存在。但內存中還是給它留了位置。
不過這里是個特例,一般文件都會有data 段的存在。
(小聲說)別高興太早了,這只是劃分了各個塊,把每塊的具體內容分析完才算完
哦,也.
下面我們再詳細分析一下各個段功能。
text 段為核心,其它段都是為它服務的。
text 段
即代碼段,由指令集構成。你可以反匯編出這部分內容,就知道它們的功能了。
為了本帖的完整性,我把它貼過來,并不長。
10001000 EntryPoint:
10001000 55 push ebp
10001001 8BEC mov ebp,esp
10001003 B801000000 mov eax,00000001h
10001008 C9 leave
10001009 C20C00 retn 000Ch
;----------------------------------------------------------------------------------------------------
1000100C SUB_L1000100C:
1000100C 55 push ebp
1000100D 8BEC mov ebp,esp
1000100F 6A01 push 00000001h
10001011 FF7510 push [ebp+10h]
10001014 FF750C push [ebp+0Ch]
10001017 FF7508 push [ebp+08h]
1000101A E84B000000 call jmp_USER32.dll!SetDlgItemInt
1000101F C9 leave
10001020 C20C00 retn 000Ch
;----------------------------------------------------------------------------------------------------
10001023 _IncCount:
10001023 55 push ebp
10001024 8BEC mov ebp,esp
10001026 FF0500300010 inc [L10003000]
1000102C FF3500300010 push [L10003000]
10001032 FF750C push [ebp+0Ch]
10001035 FF7508 push [ebp+08h]
10001038 E8CFFFFFFF call SUB_L1000100C
1000103D A100300010 mov eax,[L10003000]
10001042 C9 leave
10001043 C20800 retn 0008h
;----------------------------------------------------------------------------------------------------
10001046 _DecCount:
10001046 55 push ebp
10001047 8BEC mov ebp,esp
10001049 FF0D00300010 dec [L10003000]
1000104F FF3500300010 push [L10003000]
10001055 FF750C push [ebp+0Ch]
10001058 FF7508 push [ebp+08h]
1000105B E8ACFFFFFF call SUB_L1000100C
10001060 A100300010 mov eax,[L10003000]
10001065 C9 leave
10001066 C20800 retn 0008h
;----------------------------------------------------------------------------------------------------
10001069 CC Align 2
1000106A jmp_USER32.dll!SetDlgItemInt:
1000106A FF2500200010 jmp [********] //故意把名字隱含了
;----------------------------------------------------------------------------------------------------
代碼分三類:
第一類與地址無關,它們二進制代碼已經定下來了
第二類與地址有關,它們二進制代碼也定下來了,如果dll 加載到它的默認地址,代碼不用修改
第三類是代碼還沒有確定,用插樁來表示。如:
jmp [********], 它的插樁是 10002000 地址
先來解決插樁問題吧,這就是hello.exe 講座中的導入表問題。
1. 內存地址 10002000-10000000(image_base) = 2000(RVA)
說實話,image_base 我記不清位置,也沒有明顯標記,每次要查結構偏移。
好在dll 通常是10000000,exe 通常是400000,不查也沒有問題。
我又查了一邊,偏移是PE 標識后(不包括PE00標識)第13 個DWORD 偏移處。加深點印象,跟IAT在目錄項偏移一樣
2. RVA -> offset: 從節表知 RVA 0x2000== offset 0x600:
3. [0x600] == 2038, RVA 2038==offset 638, [0x638]== "0207 setDlgItemInt", 前面是導出序號,后面是導出名稱
; ---------------------------------------------------------------------------
loader 解決插樁的問題是從導入表開始的。導入表是目錄項的第二項:
00000140 08 20 00 00 28 00 00 00 . ..(...
導入表指向導入函數庫數組。
RVA 2008 == offset 608, length=0x28 (一個導入表結構為5個DWORD-0x14, 故0x28為兩項,一個有效項,一個全0尾標識)
第一項:
originFirstThunk == 2030, RVA 2030==offset 630 (IAT 的備份,供你看的)
Name = 2048, RVA 2048 = offset 648 == "USER32.dll"
FirstThunk == 2000, RVA 2000 == offset 600 (IAT loader 加載時會更改這一部分,以完成插樁)
導入表給出了"USER32.dll",插樁處2038 給出了函數名"setDlgItemInt", loader 根據這些信息完成插樁。
; ---------------------------------------------------------------------------
至此我們已經復習了hello.exe 中講過的東西了。
問5.4:其實話說的越多越不清楚,越少越容易扼要,把導入表部分再概括一下把。
答5.4:導入表在rdata 域。
第一部分為導入地址表,這部分loader 在加載時會修改其數值完成插樁。
第二部分為導入表。導入表是為IAT 服務的,loader 要修改IAT, 必須要知道導入函數的名稱。
這由導入表提供。導入表同時還提供,該函數名負責IAT 表中哪一個區間。
所以,導入表是導入函數庫數組。每個結構有5個DWORD 變量,2個沒用。
3個變量全是RVA, 一個指向函數名,一個指向IAT, 另一個也指向IAT,在結構最前面,但這個IAT loader 不會更改它。
第三部分按地理位置劃分是IAT 的備份表,就是導入表中Origin_FirstThunk指向的部分。
第四部分是導入函數名表,前面是序號導出,后面緊跟名稱導出。修改插樁當然要用這些信息
第五部分為函數庫名稱區域。因為導入表只提供RVA, RVA指向的是這個區域。
問5.5:好,這樣一看rdata 的上半部分意義就明白了,那下面還有一部分內容呢?
答5.5:這就是還沒開始講的導出表了。導出表比導入表簡單。因為導入表可能從好幾個庫中導入,
而導出表只是將自己的相關函數導出。
問5.6:為什么要把導出表和導入表放到一起呢。
答5.6:因為它們的屬性相同。看節表rdata. 0x40000040,兩個屬性,我查了一下文檔,前面那個1代表可讀,
后面那個1代表代碼包含初始化數據。
問5.7: 具體導出表要包含那些內容才算把函數導出了呢?
答:呦,拖課了! 今天主要是復習了一下前邊講的內容,下一課我們再講導出表!
問5.7: dll 和 exe 文件都是PE 文件, 在PE 文件中是怎樣區分的呢?
答5.7:有一些小差別。
例如, exe 通常被加載到0x400000, 而dll 默認加載是0x10000000
exe 通常不含reloc 段,而dll 包含。
exe 通常不含export 段, 而dll 包含。
exe 屬性010f, 最后1bit 說它沒有重定位信息
dll 屬性210e. 最高位的2 說它是dll, 這個好像是最關鍵差別了吧。
問5.8:exe 的 entrypoint 是程序執行的起始點,dll 的 entrypoint 是什么呢?
答5.8:這個地方是dll 的入口函數地址,它是不可以省略的。
dll 在加載,卸載以及線程加載,卸載時都會
到這里執行程序。 它的通用結構是這樣的。
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved)
{
switch(fswReason)
case DLL_PROCESS_ATTACH
......
case DLL_THREAD_ATTACH
.....
case DLL_THREAD_DETACH
.....
case DLL_PROCESS_DETACH
....
return (TRUE or False) // true , 成功,false 失敗, loader 會把它從內存卸掉。
}
count.dll 中沒有按這種結構,它只是簡單的返回一個TRUE,因為它不需要申請內存和釋放內存等初始化操作。
count.dll entrypoint 是10001000, PE 標識后第10 DWORD 地址,記不住用工具查。
問5.9:前面說過dll 有入口函數, 導出函數和非導出函數。又回到上次未講的問題
導出表是怎樣把函數導出的。
答5.9:我們先猜一猜導出函數的關鍵要素吧。
1. 導出庫名稱
2. 函數導出序號。(提供序號導出)
3. 導出函數名 (提供函數名導出)
4. 序號或函數名對應的地址。
它向系統報告這些信息已經足夠了。
問5.10: 結合例子和結構定義具體說一下吧。
答5.10: 看count.dll 目錄項第一項
00000130 60 20 00 00 5C 00 00 00 ` ..\...
位置 RVA 2060 == offset 660
大小 0x5c
00000660 00 00 00 00 F6 34 EB 3C 00 00 00 00 9C 20 00 00 ....??....?..
00000670 01 00 00 00 02 00 00 00 02 00 00 00 88 20 00 00 ............?..
00000680 90 20 00 00 98 20 00 00 46 10 00 00 23 10 00 00 ?..?..F...#...
00000690 A8 20 00 00 B2 20 00 00 00 00 01 00 43 6F 75 6E ?..?......Coun
000006A0 74 65 72 2E 64 6C 6C 00 5F 44 65 63 43 6F 75 6E ter.dll._DecCoun
000006B0 74 00 5F 49 6E 63 43 6F 75 6E 74 00 t._IncCount.
那么這是一個什么樣的數據結構呢?我們先猜猜看,這里也叫逆向學習方法吧。
首先函數名稱:count.dll(offset 69c), 函數名稱_DecCount(offset 6a8), _IncCount(6b2) 都已經看到了。
轉換為RVA. offset 69c =RVA 209c
offset 6a8 =RVA 20a8
offset 6b2 =RVA 20b2
很高興在數據中找到了9c 20, a8 20, b2 20. 關注一下這些地址。
從660 在濾一下,感覺后面的00000002 可能是個數吧。
后邊的2088, 2090,2098
RVA 2088 == offset 688 [688] == 46 10 00 00 23 10 00 00 // 像函數地址,趕緊確認一下(在上一篇),正是。
RVA 2090 == offset 690 [690] == A8 20 00 00 B2 20 00 00 // 函數名稱地址
RVA 2098 == offset 690 [690] == 00 00 01 00 // 像hint
這樣劃分后,看不懂的就不多了,學習數據結構是時候了。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
我們分析的是對的,那個base 是什么東西 ?是導出函數的基序號。所以導出函數序號不是0,1,而是1,2 了
很明顯,1指的是1046地址,2指的是1023地址
問5.11 再總結一下導出表吧。
答5.11 導出表有兩種導出方法,一種是按名稱導出,一種是按序號導出。其中序號導出的個數總是大于等于名稱導出的個數。
導出的函數地址按4字節一字排開。每一個函數索引號就是+Base 值就是導出序號。
序號不直觀,所以有些函數用名稱導出,名稱導出最終還是要找到函數序號。所以把名稱所在的名稱數組的位置為索引
去從名稱序號數組中拿到序號(此為索引號),由索引號取到函數地址。
問5.12 假如loader 要插樁本函數 _IncCount 地址,它是怎樣操作的。
答5.12
1. 它首先要加載我們的dll. 用loadlibrary
2. 找到我們的導出表。
3. 再找到導出表中AddressOfNames。
4. 遍歷該表找到_IncCount 函數,記下它的索引
5. 從AddressOfNameOrdinals數組中,取到該索引對應的函數地址序號
6. 從導出表中找到AddressOfFunctions, 用得到的序號去取到它的地址。
7. 將該地址去填充到IAT 的對應位置上。
這樣插樁就完成了。哇!這個小插樁要經過這么多步驟哇,有沒有辦法簡化一下啦... 等著你去實現呢!
問5.13 count.dll 中還有一個reloc 節,講講它是怎樣構成的。
答5.13 reloc 也是為text 段服務的,前面說過,若dll 加載到它默認位置,可以不用reloc 段。
當不能加載到默認地址時, 某些于地址有關的指令需要重新定位。就是說要修改指令中地址
使其指向正確的地址。
看目錄項中reloc 表,第6個表(索引號為5):
00000160 00 40 00 00 18 00 00 00 .@......
RVA 4000 = offset 800, size=0x18
哦,縱使不用reloc 目錄項,直接目視也看見它了。這個程序很小,是這樣的。
00000800 00 10 00 00 18 00 00 00 28 30 2E 30 3E 30 4B 30 ........(0.0>0K0
00000810 51 30 61 30 6C 30 00 00 00 00 00 00 00 00 00 00 Q0a0l0..........
我們也像export 表一樣,先猜猜reloc 結構應該有那些重要元素。
1. 內存地址, 4byte, 我們要知道對哪的指令進行重定位。即where 問題
2. 替換方法。 是替換一字節,2字節還是4字節, 是how 的問題,估計有幾個bit 就夠了。
3. 用什么替換。 是一個what 的問題。 這個問題就不用考慮了,這個what,就是加載地址與默認地址偏移。
這樣看起來一個reloc 項至少也要 5 bytes 了。如果有很多項,那這很多項就構成一個數組。
我這里介紹的方法是一種逆向的學習方法,或者是原始的思考方法。因為我想最初設計這個PE 結構的人也會
這么想。
現在使用的PE 結構在這個想法的基礎上進行了優化,使得reloc表占用較少的字節,
具體說是它讓一個reloc 項占用2byte,下面看它的方法:
1.
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
它的意思是說,它要重定位VirtualAddress=1000這塊區域, 該區域所占的重定位信息大小為SizeOfBlock=0x18
后面緊跟的每2 個bytes 構成一個重定位項, 其中低12 bit為重定位地址, 高4bits 為重定位類型。
我這里把重定位類型copy 過來,其中有的在x86 上是用不到的。
//
// Based relocation types.
//
#define IMAGE_REL_BASED_ABSOLUTE 0
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MIPS_JMPADDR 5
#define IMAGE_REL_BASED_SECTION 6
#define IMAGE_REL_BASED_REL32 7
#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
#define IMAGE_REL_BASED_IA64_IMM64 9
#define IMAGE_REL_BASED_DIR64 10
#define IMAGE_REL_BASED_HIGH3ADJ 11
地址只有12為意味著你只能管理4K 范圍,是的,如果超過了4K范圍,我們重新定義一個IMAGE_BASE_RELOCATION
變量就可以了,它又能管理下一個4K的范圍。后續沒16bit 為一個reloc 項, 最尾部以0000 結尾。當然,由于
重定位塊大小有定義,縱使不用0000標識結尾也沒有問題,把這里的冗余姑且叫雙保險吧,就是浪費了2bytes
count.dll 中,我們先算算有幾個重定位項。哦,不用算,有7個,一查就查出來了。
但當程序大的時候還是要計算的。(0x18-8)/2-1=7 個。
計算公式:(SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/2-1
問:5.14 那這7個重定位項到底是怎樣重定位的呢?還是給出具體結果更徹底。
答:5.14 好,做事做到底,現在我們就開始吧。
第一項:28 30 其內容為 0x3028 重定位類型為3, 地址為0x28
其它重定位類型都是3, 地址不同而已。
#define IMAGE_REL_BASED_HIGHLOW 3
那么這個BASE_HIGHLOW 是什么意思呢?我們先看看0x28處的指令。
10001026 FF0500300010 inc [L10003000]
假如我們的dll 不是被加載到0x10000000,而是加載到0x30000000, 即向上提高了0x20000000
我們只要把0x28地址處也加上0x20000000,就可以了。這樣這條指令就變成了
10001026 FF0500300030 inc [L30003000]
看明白了吧! 窗戶紙就是這樣被捅破的
好了,本帖就到這里結束吧!