PE檔案結構解析3
0x0導讀
今天的內容是pe檔案結構的匯出表,和如何使用程式碼列印出匯出表。
0x1環境
編譯器:VirsualStudio2022
16進位制檢視工具:winhex
0x2匯出表
0x1匯出表是啥
匯出表就是當前的PE檔案有哪些函式可以被別人使用. 比如去飯店吃飯一般都會有一個選單來說這家飯店都有什麼菜,pe檔案相當於這家飯店,而匯出表就相當於選單,來說明這個pe檔案都有哪些函式可以被別人使用,注意並不是只有DLL才有匯出表,並不是只有DLL才可以提供函式給別人使用Exe也可以。
0x2匯出表結構
匯出表的定義
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;
Characteristics
保留,一般為0.
TimeDateStamp
時間戳,匯出表生成的時間。
MajorVersion
主版本號。
MinorVersion
次版本號
Name
一個rva,指向當前dll的名字。
Base
匯出函式起始序號
NumberOfFunctions
匯出函式的個數。
NumberOfNames
以名字匯出函式個數。
AddressOfFunctions
一個rva,指向匯出函式地址表,這個表裡面存的是函式地址,表中的值大小是4位元組
AddressOfNames
一個rva,指向匯出函式名稱表,這個表裡面放的是函式名字地址,是一個rva,表中的值大小是4位元組
AddressOfNameOrdinals
一個rva,指向匯出函式序號表,放的是函式的序號,透過這個序號可以找到函式地址,表中值大小是2位元組
0x3程式碼解析
大致思路:讀取檔案到記憶體中,定位到匯出表,定位匯出函式地址表,定位匯出函式序號表,定位匯出函式名字表,迴圈NumberOfSections
成員的值,拿這個值去和匯出函式序號表中的值比對,如果一樣就得到當前匯出函式序號的下標,拿這個下標去函式名字表中找到對應的地址,的到函式名,再拿匯出函式序號表的值加上base
成員得到匯出函式序號,在透過匯出函式序號的值得到函式地址。
#include <stdio.h> #include <Windows.h> #define path "C:\\Users\\blue\\Desktop\\CmdBar.dll" DWORD rtf(char* buffer, DWORD rva) { PIMAGE_DOS_HEADER doshd = (PIMAGE_DOS_HEADER)buffer; PIMAGE_NT_HEADERS nthd = (PIMAGE_NT_HEADERS)(buffer + doshd->e_lfanew); PIMAGE_FILE_HEADER filehd = (PIMAGE_FILE_HEADER)(buffer + doshd->e_lfanew + 4); PIMAGE_OPTIONAL_HEADER32 optionhd = (PIMAGE_OPTIONAL_HEADER32)(buffer + doshd->e_lfanew + 24); PIMAGE_SECTION_HEADER sectionhd = IMAGE_FIRST_SECTION(nthd); for (int i = 0; i < filehd->NumberOfSections; i++) { if (rva >= sectionhd[i].VirtualAddress && rva <= sectionhd[i].VirtualAddress + sectionhd[i].SizeOfRawData) { return rva - sectionhd[i].VirtualAddress + sectionhd[i].PointerToRawData; } } } void main() { FILE* fp = fopen(path, "rb"); fseek(fp, 0, SEEK_END); int size = ftell(fp); rewind(fp); char* ptr = (char*)malloc(size + 0x1000); memset(ptr, 0, size + 0x1000); fread(ptr, size, 1, fp); PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr; PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew); PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4); PIMAGE_OPTIONAL_HEADER Option = (PIMAGE_OPTIONAL_HEADER)(ptr + Dos->e_lfanew + 24); PIMAGE_DATA_DIRECTORY Data = Option->DataDirectory; PIMAGE_SECTION_HEADER Sec = IMAGE_FIRST_SECTION(Nt); PIMAGE_EXPORT_DIRECTORY Exp = (PIMAGE_EXPORT_DIRECTORY)(ptr + rtf(ptr, Data[0].VirtualAddress)); PBYTE DllName = (PBYTE)ptr + rtf(ptr, Exp->Name); printf("匯出表\n"); printf("Characteristics:%x\n", Exp->Characteristics); printf("TimeDateStamp:%x\n", Exp->TimeDateStamp); printf("MajorVersion:%x\n", Exp->MajorVersion); printf("MinorVersion:%x\n", Exp->MinorVersion); printf("Name_Rva:%x\n", Exp->Name); printf("Name:%s\n", DllName); printf("Base:%x\n", Exp->Base); printf("NumberOfFunctions:%x\n", Exp->NumberOfFunctions); printf("NumberOfNames:%x\n", Exp->NumberOfNames); printf("AddressOfFunctions:%x\n", Exp->AddressOfFunctions); printf("AddressOfNames:%x\n", Exp->AddressOfNames); printf("AddressOfNameOrdinals:%x\n", Exp->AddressOfNameOrdinals); PBYTE AddressOfFunctions = (PBYTE)(ptr + rtf(ptr, Exp->AddressOfFunctions)); PWORD AddressOfOrdinals = (PWORD)(ptr + rtf(ptr, Exp->AddressOfNameOrdinals)); PDWORD AddressOfNames = (PDWORD)(ptr + rtf(ptr, Exp->AddressOfNames)); BOOL flag = FALSE; for (int i = 0; i < Exp->NumberOfFunctions; i++) { int k = 0; for (; k < Exp->NumberOfNames; k++) { if (i == AddressOfOrdinals[k]) { flag = TRUE; break; } } if (flag == TRUE) { PBYTE FunName = (PBYTE)ptr + rtf(ptr, AddressOfNames[k]); printf("Function:[%s][%d][%x]\n", FunName, k + Exp->Base, AddressOfFunctions[AddressOfOrdinals[k]]); } } getchar(); }
因為讀取檔案的程式碼和rva轉換foa的程式碼,定位頭的程式碼已經講過了,關於這一部分程式碼的解釋請移步我的上一篇文章,如果有不懂的可以評論,現在我們們直接看下面的程式碼。
PIMAGE_EXPORT_DIRECTORY Exp = (PIMAGE_EXPORT_DIRECTORY)(ptr + rtf(ptr, Data[0].VirtualAddress)); PBYTE DllName = (PBYTE)ptr + rtf(ptr, Exp->Name); printf("匯出表\n"); printf("Characteristics:%x\n", Exp->Characteristics); printf("TimeDateStamp:%x\n", Exp->TimeDateStamp); printf("MajorVersion:%x\n", Exp->MajorVersion); printf("MinorVersion:%x\n", Exp->MinorVersion); printf("Name_Rva:%x\n", Exp->Name); printf("Name:%s\n", DllName); printf("Base:%x\n", Exp->Base); printf("NumberOfFunctions:%x\n", Exp->NumberOfFunctions); printf("NumberOfNames:%x\n", Exp->NumberOfNames); printf("AddressOfFunctions:%x\n", Exp->AddressOfFunctions); printf("AddressOfNames:%x\n", Exp->AddressOfNames); printf("AddressOfNameOrdinals:%x\n", Exp->AddressOfNameOrdinals); PBYTE AddressOfFunctions = (PBYTE)(ptr + rtf(ptr, Exp->AddressOfFunctions)); PWORD AddressOfOrdinals = (PWORD)(ptr + rtf(ptr, Exp->AddressOfNameOrdinals)); PDWORD AddressOfNames = (PDWORD)(ptr + rtf(ptr, Exp->AddressOfNames)); BOOL flag = FALSE; for (int i = 0; i < Exp->NumberOfFunctions; i++) { int k = 0; for (; k < Exp->NumberOfNames; k++) { if (i == AddressOfOrdinals[k]) { flag = TRUE; break; } } if (flag == TRUE) { PBYTE FunName = (PBYTE)ptr + rtf(ptr, AddressOfNames[k]); printf("Function:[%s][%d][%x]\n", FunName, k + Exp->Base, AddressOfFunctions[AddressOfOrdinals[k]]); } }
PIMAGE_EXPORT_DIRECTORY Exp = (PIMAGE_EXPORT_DIRECTORY)(ptr + rtf(ptr, Data[0].VirtualAddress));
定義一個PIMAGE_EXPORT_DIRECTORY
型別的結構體指標指向,基址+資料目錄第一項的VirtualAddress轉換為Foa的值從而定位到匯出表開始的地方,Data[0].VirtualAddress就是取匯出表第一項的地址。
PBYTE DllName = (PBYTE)ptr + rtf(ptr, Exp->Name);
定義一個指標用來指向當前DLL的名字,因為上面說過了匯出表的Name
成員是一個rva所以把它轉換成foa加上基址即可,下面的printf就不看了,主要就是列印出匯出表的成員,直接來到這一部分。
PBYTE AddressOfFunctions = (PBYTE)(ptr + rtf(ptr, Exp->AddressOfFunctions));
因為上面說過匯出表成員AddressOfFunctions
是一個rva,這個rva指向匯出函式地址表,所以要先把AddressOfFunctions
轉換成foa在加上基址即可定位到匯出函式地址表。
PWORD AddressOfOrdinals = (PWORD)(ptr + rtf(ptr, Exp->AddressOfNameOrdinals));
定義一個PWORD
型別的指標指向匯出函式序號表,因為上面說過匯出表成員AddressOfNameOrdinals
是一個rva,這個rva指向匯出函式序號表,所以要先把AddressOfFunctions
轉換成foa在加上基址即可定位到匯出函式序號表。
PDWORD AddressOfNames = (PDWORD)(ptr + rtf(ptr, Exp->AddressOfNames));
定義一個PDWORD
型別的指標指向匯出函式名稱表,因為上面說過匯出表成員AddressOfNames
是一個rva,這個rva指向匯出函式名稱表,所以要先把AddressOfNames
轉換成foa在加上基址即可定位到匯出函式名稱表。
我們先大概說下,下面的程式碼解釋,在一行一行的看。
定義一個布林型的變數,先迴圈NumberOfFunctions
的值得到匯出函式地址表的下標,在定義變數k用於迴圈NumberOfNames
的值,現在k代表匯出函式序號表的下標,透過if判斷來判斷匯出函式地址表的下標與匯出函式序號表中的值一樣不一樣,如果一樣就拿k去匯出函式名稱中找到對應地址,得到函式名,在拿k加上Base成員的值得到匯出序號,再拿匯出函式序號表的值去匯出函式地址表中找到對應的函式地址。
BOOL flag = FALSE; for (int i = 0; i < Exp->NumberOfFunctions; i++) { int k = 0; for (; k < Exp->NumberOfNames; k++) { if (i == AddressOfOrdinals[k]) { flag = TRUE; break; } } if (flag == TRUE) { PBYTE FunName = (PBYTE)ptr + rtf(ptr, AddressOfNames[k]); printf("Function:[%s][%d][%x]\n", FunName, k + Exp->Base, AddressOfFunctions[AddressOfOrdinals[k]]); } }
for (int i = 0; i < Exp->NumberOfFunctions; i++) { }
定義一個for迴圈用於得到匯出函式地址表的下標。
int k = 0;
定義一個變數k
,作為匯出函式序號表的下標,透過下標可以得到匯出函式序號表中的值,
for (; k < Exp->NumberOfNames; k++) { if (i == AddressOfOrdinals[k]) { flag = TRUE; break; } }
這裡又定義了一個迴圈,來判斷匯出函式地址表的下標和匯出函式序號表中的值一樣不一樣,如果一樣代表這個函式存在,修改掉flag
的值為true並跳出當前迴圈。
if (flag == TRUE) { PBYTE FunName = (PBYTE)ptr + rtf(ptr, AddressOfNames[k]); printf("Function:[%s][%d][%x]\n", FunName, k + Exp->Base, AddressOfFunctions[AddressOfOrdinals[k]]); }
判斷flag的值是不是true如果是就執行下面的程式碼。
PBYTE FunName = (PBYTE)ptr + rtf(ptr, AddressOfNames[k]);
定義一個PBYTE
型別的指標來指向當前函式名,先獲取到函式名稱表中k
對應的地址,因為函式名稱表中的地址是一個rva所以要進行rva到foa的轉換,在加上基址(ptr)即可定位到函式名字的地址。
printf("Function:[%s][%d][%x]\n", FunName, k + Exp->Base, AddressOfFunctions[AddressOfOrdinals[k]]);
列印函式名字,和匯出序號,和函式地址,因為匯出函式序號表中存的不是真正的匯出函式序號加上Base成員的值才是,這裡透過匯出函式序號表中的值來找函式地址。
執行結果
0x4結語
主要是介紹了pe檔案結構匯出表和匯出表的一些成員,以及如何使用程式碼列印出它們,涉及到指標和結構體相關的知識。需要注意的是指標的型別和三張子表儲存值的寬度。
由於作者水平有限,文章如有錯誤歡迎指出。
相關文章
- PE檔案結構解析12022-05-20
- PE檔案結構解析22022-05-23
- PE 檔案結構圖2023-09-05
- PE檔案結構複習2020-11-11
- PE檔案結構(五)基址重定位2014-10-07
- PE檔案結構(四) 輸出表2014-10-06
- 再探.NET的PE檔案結構(安全篇)2019-05-11
- PE檔案結構(二) 區塊,檔案偏移與RVA轉換2014-10-02
- PE檔案格式詳細解析(一)2020-03-08
- SQLite3資料庫檔案結構解析2024-06-26SQLite資料庫
- PE檔案格式詳細解析(二)--IAT2020-03-08
- PE教程3: File Header (檔案頭)2015-11-15Header
- PE檔案格式2015-11-15
- 深入解析Class類檔案的結構2019-03-24
- PE結構分析(二)2021-04-24
- 深入剖析PE檔案2014-09-25
- 手工構造一個超微型的 PE 檔案 (轉)2007-08-17
- PE結構體中匯出表/匯入表解析——初階2018-01-27結構體
- PE教程2: 檢驗PE檔案的有效性2015-11-15
- ionic 2.x 3.x專案結構解析2017-08-16
- Java虛擬機器,類檔案結構深度解析2019-05-14Java虛擬機
- oracle體系結構梳理---redo和undo檔案解析2015-01-26Oracle
- 類檔案結構_class類檔案的的結構2018-04-11
- PE檔案格式的RVA概念2008-04-14
- PE檔案格式詳細解析(六)-- 基址重定位表(Base Relocation Table)2020-03-10
- 羽夏殼世界—— PE 結構(上)2022-04-10
- BMP檔案結構2013-12-20
- Xamarin XAML語言教程XAML檔案結構與解析XAML2017-04-13
- 初步瞭解PE檔案格式(上)2017-07-03
- win32 PE 檔案格式 (轉)2007-12-15Win32
- windows載入PE檔案的流程2024-09-08Windows
- PE結構各欄位偏移參考2013-04-24
- office檔案格式複合文件二進位制結構解析2019-12-16
- jmeter基礎之目錄結構解析及配置檔案修改2020-12-08JMeter
- Class類檔案結構2017-08-18
- wsdl檔案結構分析2011-06-16
- Linux檔案結構2011-06-19Linux
- 控制檔案的結構2010-07-10