五一(2020年,遷移文章)假期沒有回家,是因為在四月份已經向公司提了離職.估計五月中旬應該就能辦理離職手續,在公司呆了6年多,合同由3(2次)年換成無固定期限勞動合同.因為家庭原因,最終還是要回去的.不知不覺間北漂了8年,在公司工作期間經曆我人生的大事(結婚和生子).
關于PE相關的内容,在前面也寫過 <<學習PE文件結構>> 和 <<在解析PE遇到的問題>>,這裡也不多的介紹了.
在看正文之前,先看看圖,對下邊具體要做的事情,有一個大概的認知.
關系圖(省略節表部分)
從PE中解析導出表,根據導出表的地址,進行獲取函數調用的地址
數據目錄表在 <<學習PE文件結構>> 這邊博文中,已經對可選PE頭進行了解析. 在IMAGE_OPTIONAL_HEADER結構體中這個字段DataDirectory,就是我們通常所說的數據目錄表.重新看看這個可選PE頭結構.
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 //數據目标表的長度
//可選PE頭
typedef 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 MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
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;
DWORD NumberOfRvaAndSizes;
//數據目錄表
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32
//數據目錄表結構
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //在内存中的相對地址
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
先将數據目錄表信息打印出來,在用PETool進行對比,看看是否是有問題.
//打印出 數據目錄表 信息
void print_data_dir(char* base)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header =
(PIMAGE_NT_HEADERS)((unsigned long)base dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header 4); //4為nt頭中Signature DWORD
PIMAGE_OPTIONAL_HEADER optional_header =
(PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header IMAGE_SIZEOF_FILE_HEADER);
for (int i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i )
{
IMAGE_DATA_DIRECTORY data_dir = optional_header->DataDirectory[i];
printf("%d data:x size:%x\n", i, data_dir.VirtualAddress, data_dir.Size);
}
}
int main(int argc, char* argv[])
{
char* filename = "DllExportTable.dll"; //以動态庫為例
int size = file_size(filename);
if (size > 0)
{
char* buf;
file_to_memory(filename, size, &buf);
print_data_dir(buf);
}
else
{
printf("file not found!\n");
}
return 0;
}
PE中的數據目錄表
導出表 | |
1 |
導入表 |
2 |
資源表 |
3 |
異常表 |
4 |
安全證書表 |
5 |
重定位表 |
6 |
調試信息表 |
7 |
版權所有表 |
8 |
全局指針表 |
9 |
TLS(線程本地存儲表) |
10 |
加載配置表 |
11 |
綁定導入表 |
12 |
IAT(導入地址表) |
13 |
延遲導入表 |
14 |
COM表 |
15 |
保留(這個暫時沒用) |
正常我們隻需要關注重點表: 導出表/導入表/重定位/IAT
導出表從數據目錄表中,知道了第一個表是導出表,并且導出表的VirtualAddress.這個時候要了解這兩個RVA和FOA知識點.
RVA(相對虛拟地址):VirtualAddress就是RVA.
FOA(文件偏移地址)
如果解析PE時候,如果不進行内存拉伸,VirtualAddress就需要用RVA轉FOA的轉換,這個轉換是拿導出表的VirtualAddress在節表中查找每個節的VirtualAddress的區域中.然後根據節中的PointerToRelocation(文件中的偏移)
//相對虛拟地址轉換為文件偏移地址
int rva_to_foa(char* base, unsigned long* va)
{
unsigned long rva = *va;
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header 4);
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header IMAGE_SIZEOF_FILE_HEADER);
//1. 獲取節表
PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_header file_header->SizeOfOptionalHeader);
DWORD alignment = optional_header->SectionAlignment; //内存對齊大小 4096
//2. 獲取節表的個數
int sections = nt_header->FileHeader.NumberOfSections;
int offset = -1;
for (int i = 0; i < sections; i )
{
PIMAGE_SECTION_HEADER psection = section_headersection_header i;
//3. 獲取節的RVA
DWORD section_start = psection->VirtualAddress;
if (rva < section_start)
{
offset = rva;
return offset;
}
int block_count = psection->SizeOfRawData / alignment;
block_count = psection->SizeOfRawData % alignment ? 1 : 0;
//4. 判斷導出表的相對虛拟地址rva 是否在這個節中
if (rva >= section_start && rva < section_start block_count * alignment)
{
//5. 獲取節的文件中偏移加上導出表的rva減去節的rva (真正在文件中的偏移地址)
offset = psection->PointerToRawData rva - section_start;
return offset;
}
}
return offset;
}
//打印出 數據目錄表
void print_data_dir(char* base)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header 4); //4為nt頭中Signature DWORD
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header IMAGE_SIZEOF_FILE_HEADER);
for (int i=0;i< IMAGE_NUMBEROF_DIRECTORY_ENTRIES;i )
{
IMAGE_DATA_DIRECTORY data_dir = optional_header->DataDirectory[i];
printf("%d data:x size:%x\n", i, data_dir.VirtualAddress, data_dir.Size);
}
IMAGE_DATA_DIRECTORY export_dir = optional_header->DataDirectory[0];
int offset = rva_to_foa(base, &export_dir.VirtualAddress);
//根據導出表的rva,進行foa轉換
//獲取導出表
PIMAGE_EXPORT_DIRECTORY export_table = (PIMAGE_EXPORT_DIRECTORY)(base offset);
}
//IMAGE_EXPORT_DIRECTORY結構體 主要的字段
//DWORD Name;
//DWORD Base;
//IMAGE_EXPORT_DIRECTORY 導出表
//DWORD NumberOfFunctions; //導出函數的個數
//DWORD NumberOfNames; //函數名稱的函數個數
//DWORD AddressOfFunctions; 導出函數的地址表 rva 個數用NumberOfFunctions
//DWORD AddressOfNames; // 導出函數名稱表 rva 個數用NumberOfNames
//DWORD AddressOfNameOrdinals; // 導出序号表 rva 個數用NumberOfNames
//函數名字 通過AddressOfNames(将地址rva到foa) 查到之後,去AddressOfNameOrdinals 獲取序号,然後在根據序号去AddressOfFunctions表找到函數的地址
//序号 通過序号 減去base 得到的下标,直接去AddressOfFunctions
上面拿到導出表,通過導出表解析,可以實現GetProcAddress功能.
動态庫頭文件:
#ifdef __cplusplus
extern "C"
{
#endif
int add(int a, int b);
int sub(int a, int b);
#ifdef __cplusplus
}
#endif
動态庫源文件:
_declspec (dllexport) int add(int a, int b)
{
return a b;
}
_declspec (dllexport) int sub(int a, int b)
{
return a - b;
}
看看如何實現:
//獲取函數的調用地址
void* my_proc_address(char* base, char* func_name)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header 4);
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_header file_header->SizeOfOptionalHeader);
IMAGE_DATA_DIRECTORY export_table = optional_header->DataDirectory[0];
PIMAGE_EXPORT_DIRECTORY export = (PIMAGE_EXPORT_DIRECTORY)(base export_table.VirtualAddress);
unsigned long names = export->NumberOfNames;
unsigned long* address_names = (unsigned long*)(base export->AddressOfNames);
unsigned short* address_name_ordinals = (unsigned short*)(base export->AddressOfNameOrdinals);
unsigned long* address_fuctions = (unsigned long*)(base export->AddressOfFunctions);
for (int i = 0; i < names; i )
{
char* name = (char*)(base address_names[i]);
if (strcmp(name, func_name) == 0)
{
int ordinal = address_name_ordinals[i];
void* func_addr = (void*)(base address_fuctions[ordinal]);
return func_addr;
}
}
return NULL;
}
int main(int argc, char* argv[])
{
typedef int(*Add)(int a, int b);
HMODULE hmodule = LoadLibraryEx("DllExportTable.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
//my_proc_address 實現GetProcAddress()功能
Add add = (Add)my_proc_address(hmodule, "add");
int sum = add(100, 100);
printf("sum=%d\n", sum);
}
通過導出表獲取函數的調用地址,實現GetProAddress功能
上面實現GetProcAddress函數的代碼,為什麼沒有進行RVA到FOA的轉換呢? 因為LoadLibraryEx函數已經dll文件加載到内存上進行了拉伸操作.所以不需要進行轉換.
通過序号查找函數的調用地址
//通過序号,查找函數在内存中地址
//1. 獲取導出表在内存中的位置
//2. 序号減去導出表中起始函數序号(Base),得到的下标就是
void* my_proc_ordinal(char* base, int ordinal)
{
if (ordinal > 0)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header 4);
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_header file_header->SizeOfOptionalHeader);
IMAGE_DATA_DIRECTORY export_table = optional_header->DataDirectory[0]; //導出表 内存中相對地址
PIMAGE_EXPORT_DIRECTORY export = (PIMAGE_EXPORT_DIRECTORY)(base export_table.VirtualAddress); //導出表 在内存的地址
//這裡獲取函數個數,要特殊處理一下
//正常情況下,用NumberOfFunctions就能獲取到
//其他情況下,如在def文件,讓個别函數沒有名稱或者指定序号
unsigned long numbers = export->Base export->NumberOfFunctions - 1; //起始序号加個數 在減一
if (ordinal > numbers)
{
return NULL;
}
unsigned long* address_fuctions = (unsigned long*)(base export->AddressOfFunctions);
unsigned long base_count = export->Base;
return (void*)(base address_fuctions[ordinal - base_count]);
}
return NULL;
}
個人能力有限,如果您發現有什麼不對,請私信我
如果您覺得對您有用的話,可以點個贊或者加個關注,歡迎大家一起進行技術交流
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!