1. 編寫程式列印所有的導出表資訊;
下面是相關代碼,裡面含有列印資料目錄代碼
//簡單列印可選PE頭的資料目錄和導出表名稱及函數位址
VOID FileBufferPrintDataDirectory(IN LPVOID pFileBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
DWORD FoaAddress = 0;
if (pFileBuffer == NULL)
{
printf("FileBuffer 擷取失敗!\r\n");
return;
}
//判斷是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("無效的MZ辨別\r\n");
return;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
//判斷是否是有效的PE标志
if (*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("無效的PE标記\r\n");
return;
}
//定位NT頭
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader)+4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
// pDataDirectory = PIMAGE_DATA_DIRECTORY((&pOptionHeader->NumberOfRvaAndSizes + 1));
pDataDirectory = pOptionHeader->DataDirectory;
FoaAddress = RvaToFileOffset(pFileBuffer,pDataDirectory->VirtualAddress);
printf("FoaAddress: %#X \r\n",FoaAddress);
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + FoaAddress);
printf("\t\t RVA\t\t 大小\r\n");
//列印相關資訊測試
//#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
//下面是一種粗糙的周遊寫法;
/*
for (int i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++,pDataDirectory++)
{
printf("%#08X \r\n",pDataDirectory->VirtualAddress);
printf("%#08X \r\n",pDataDirectory->Size);
}
*/
for (DWORD i = 0; i< IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
{
DirectoryString(i);
printf("%08X\t%08X\r\n",pDataDirectory[i].VirtualAddress,pDataDirectory[i].Size);
}
printf("***********************************************************\r\n");
printf("pExportDirectory->AddressOfFunctions : \t\t%#08X \r\n",pExportDirectory->AddressOfFunctions);
printf("pExportDirectory->AddressOfNames : \t\t%#08X \r\n",pExportDirectory->AddressOfNames);
printf("pExportDirectory->AddressOfNameOrdinals : \t%#08X \r\n",pExportDirectory->AddressOfNameOrdinals);
printf("pExportDirectory->Base : \t\t\t%#08X \r\n",pExportDirectory->Base);
printf("pExportDirectory->Characteristics : \t\t%#08X \r\n",pExportDirectory->Characteristics);
printf("pExportDirectory->MajorVersion : \t\t%#08X \r\n",pExportDirectory->MajorVersion);
printf("pExportDirectory->MinorVersion : \t\t%#08X \r\n",pExportDirectory->MinorVersion);
printf("pExportDirectory->Name : \t\t\t%#08X \r\n",pExportDirectory->Name);
printf("pExportDirectory->NumberOfFunctions : \t\t%#08X \r\n",pExportDirectory->NumberOfFunctions);
printf("pExportDirectory->NumberOfNames : \t\t%#08X \r\n",pExportDirectory->NumberOfNames);
printf("***********************************************************\r\n");
//定義導出表函數位址,函數序号,導出表位址序号,函數名稱對應檔案中的FOA位置;
//Name, AddressOfNames, AddressOfFunctions, AddressOfOrdinals;
DWORD dwNameFoa = RvaToFileOffset(pFileBuffer,pExportDirectory->Name);
DWORD dwAddressOfNamesFoa = RvaToFileOffset(pFileBuffer,pExportDirectory->AddressOfNames);
DWORD dwAddressOfFunctionsFoa = RvaToFileOffset(pFileBuffer,pExportDirectory->AddressOfFunctions);
DWORD dwAddressOfOrdinalsFoa = RvaToFileOffset(pFileBuffer,pExportDirectory->AddressOfNameOrdinals);
//擷取導出表名稱,函數位址表,函數序号表,函數名稱表;
PBYTE pDllOrExeName = (PBYTE)((DWORD)pDosHeader + dwNameFoa);
//上面之是以使用PBYTE是因為要列印每個字元,需要一個一個位元組讀取;
printf("導出表的名稱: %s \r\n",pDllOrExeName);//這裡列印出來的就是exe的名稱ipmsg.exe
//導出函數位址表,在檔案中的位置
PDWORD pAddressFunctionTable = (PDWORD)((DWORD)pDosHeader + dwAddressOfFunctionsFoa);
printf("函數位址表的位置: %#08X \r\n",pAddressFunctionTable);
//導出函數序号表,在檔案中的位置
PDWORD pOrdinaFunctionTable = (PDWORD)((DWORD)pDosHeader + dwAddressOfOrdinalsFoa);
printf("函數序号表的位置: %#08X \r\n",pOrdinaFunctionTable);
//導出函數名稱表,在檔案中的位置
PDWORD pNameFunctionTable = (PDWORD)((DWORD)pDosHeader + dwAddressOfNamesFoa);
printf("函數名稱表的位置: %#08X \r\n",pNameFunctionTable);
//判斷是以序号導出函數,還是以函數名字導出
BOOL indexNumIsExist = FALSE;
/*indexNumIsExist --> 簡單點了解,這個參數就是要獲得導出表函數名稱下标和序号表下标她們之間如果能找到對應
相同的下标,那麼這個參數indexNumIsExist是TRUE,否則是FAlSE;
詳細解釋:可以根據導出表函數名稱下标号,對應序号表中的下标号,她們之間進行比對,
如果在序号表和導出表函數名稱表裡面能夠找到對應相同的下表号碼,那麼就可以根據在序号表中找到的下标号
對應的值,拿着這個對應的值,就可以在導出表函數位址的這張表中去尋找下标為這個對應的值,然後就是對應
導出表函數的位址;
*/
for (DWORD n = 0; n< pExportDirectory->NumberOfFunctions; n++)
//n 是導出表函數位址表對應的下标,根據NumberOfFunctions的個數決定
{
//擷取導出表函數位址表的起始位置;
//printf("導出表函數位址表的位置: %#08X ",pAddressFunctionTable[n]);
indexNumIsExist = FALSE;//這個參數為FALSE就表示,可以直接通過序号導出函數找函數位址
//nNameIndex --> 導出表函數名稱下标,這個下面是根據NumberOfNames個數決定,下面開始判斷驗證是否可以;
for (DWORD nNameIndex = 0; nNameIndex < pExportDirectory->NumberOfNames; nNameIndex++)
{
/*根據導出表函數名稱的下标(索引),這裡了解就是直接将函數名稱下标号當作序号表的下标,然後通過序号表
的起始位置加上這個序号表的下标去定位得出序号表中對應的值,是否等于導出表函數位址表的下标,而這個
下标就是上面我們定義好的參數n,是導出表函數位址的個數,即下标号,如果相等,那麼indexNumIsExist
等于TRUE,得出結論可以通過函數名稱導出函數;
*/
if (n == pOrdinaFunctionTable[nNameIndex])
{
indexNumIsExist = TRUE;
break;
}
}
if (indexNumIsExist == TRUE)
{
//下面是取出導出表函數名稱表中對應下标的位址
DWORD dwNameFoa = pNameFunctionTable[nNameIndex];//這裡還是Rva位址
PBYTE pFunctionName = (PBYTE)((DWORD)pDosHeader + RvaToFileOffset(pFileBuffer,dwNameFoa));
//上面是将導出表函數名稱表對應下标的位址取出來對應的RVA位址,然後将其轉換為FOA位址
//再通過這個FOA的位址進行偏移,這個偏移就相當于是通過導出表函數名稱表裡面的位址,定位
//到準确的函數名稱,擷取函數名稱的字元串;
printf("導出表函數名稱-->[%s]",pFunctionName);
printf("導出表函數位址表的位置: %#08X",pAddressFunctionTable[n]);
//擷取導出表函數序列号表
printf("導出表函數序列号表: [%d] \r\n", n - pExportDirectory->Base);
//序号表裡面的值+Base的值=導出表函數位址表裡面的下标值;
//上面n是導出表函數位址表的下标值,是以n - pExportDirectory->Base就可以得到序号表的值
}
else
{
//如果上述不成立,序号表根本就不存在,判斷是否存在這個函數
if (pAddressFunctionTable[n] != 0)
{
//确認為是隐藏函數
printf("導出表函數名稱-->");
printf("導出表函數位址表下标[%d]-->", n + pExportDirectory->Base);
printf("導出表函數位址表的位置:%#08X ",pAddressFunctionTable[n]);
}
}
printf("\r\n");
}
return;
}
VOID DirectoryString(DWORD dwIndex)
{
switch(dwIndex)
{
case 0:
printf("輸出表:\t\t");
break;
case 1:
printf("輸入表:\t\t");
break;
case 2:
printf("資源:\t\t");
break;
case 3:
printf("異常:\t\t");
break;
case 4:
printf("安全:\t\t");
break;
case 5:
printf("重定位:\t\t");
break;
case 6:
printf("調試:\t\t");
break;
case 7:
printf("版權:\t\t");
break;
case 8:
printf("全局指針:\t");
break;
case 9:
printf("TLS表:\t\t");
break;
case 10:
printf("載入配置:\t");
break;
case 11:
printf("輸入範圍:\t");
break;
case 12:
printf("IAT:\t\t");
break;
case 13:
printf("延時輸入\t");
break;
case 14:
printf("COM:\t\t");
break;
case 15:
printf("保留:\t\t");
break;
}
}
執行效果:
2.GetFunctionAddrByName(FileBuffer指針,函數名指針)
//根據導出表函數名稱擷取導出表函數位址
DWORD GetFunctionAddrByName(IN PVOID pFileBuffer, IN LPSTR pFunctionOfName)
{
printf("Function Name: %s\n", pFunctionOfName);
// 初始化PE頭部結構體
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
// 判斷指針是否有效
if (!pFileBuffer)
{
printf("pFileBuffer不是有效的指針\r\n");
return 0;
}
//判斷是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("pFileBuffer不是有效的MZ檔案\r\n");
return 0;
}
//判斷是否是一個有效的PE标志
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((PWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("pFileBuffer不是一個有效的PE标志\r\n");
return 0;
}
//強制結構體類型轉換
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04); // 這裡必須強制類型轉換
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
if (!pDataDirectory->VirtualAddress)
{
printf("這個程式沒有導出表.\n");
return 0;
}
printf("導出表RVA位址: %#X\r\n", pDataDirectory->VirtualAddress);
DWORD Foa_ExportTable = RvaToFileOffset(pFileBuffer, pDataDirectory->VirtualAddress);
printf("導出表FOA位址: %#X\r\n", Foa_ExportTable);
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + Foa_ExportTable);
//上面操作完成之後,就是通過RVA轉換為FOA位址,定位到了導出表的起始位置;
if (!debug)
{
printf("Characteristics: %#X\r\n", pExportDirectory->Characteristics);
printf("TimeDateStamp: %#X\r\n", pExportDirectory->TimeDateStamp);
printf("MajorVersion: %#X\r\n", pExportDirectory->MajorVersion);
printf("MinorVersion: %#X\r\n", pExportDirectory->MinorVersion);
printf("Name: %s\r\n", (PVOID)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->Name)));
printf("Base: %#X\r\n", pExportDirectory->Base);
printf("NumberOfFunctions: %#X\r\n", pExportDirectory->NumberOfFunctions);
printf("NumberOfNames: %#X\r\n", pExportDirectory->NumberOfNames);
printf("AddressOfFunctions: %#X\r\n", pExportDirectory->AddressOfFunctions);
printf("AddressOfNames: %#X\r\n", pExportDirectory->AddressOfNames);
printf("AddressOfNameOrdinals: %#X\r\n", pExportDirectory->AddressOfNameOrdinals);
}
//同樣是将導出表函數名稱表,序号表,函數位址表對應起始位置的RVA轉換為FOA
DWORD Foa_AddressOfNames = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames);
DWORD Foa_AddressOfNameOrdinals = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals);
DWORD Foa_AddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);
if (!debug)
{
DWORD Foa_Address_Names = (DWORD)pFileBuffer + Foa_AddressOfNames;
printf("AddressOfNames Offset Value: %#X\r\n", Foa_Address_Names);//偏移看看有沒有内容
printf("Foa_AddressOfNames: %#X\r\n", Foa_AddressOfNames);//看看轉換後的位址
printf("Foa_AddressOfNameOrdinals: %#X\r\n", Foa_AddressOfNameOrdinals);
printf("Foa_AddressOfFunctions: %#X\r\n", Foa_AddressOfFunctions);
}
//1.通過循環确認從導出表函數名稱表中找與目标函數名是否相同;如相同,則傳回該名字在表中的索引
DWORD ordIndex = -1;
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
{
DWORD nameOffset = *(PDWORD)((DWORD)pFileBuffer + (DWORD)((LPDWORD)Foa_AddressOfNames + i));
/*上面得到的nameOffset的參數值的了解:
Foa_AddressOfNames的值是0X5223C,這個是FileBuffer下對應的FOA記憶體偏移位址,通過FileBuffer
直接移動到名稱位址表(AddressOfNames)的首位址位置;這裡有一個重點了解對象,就是我們看到的這些
AddressOfNames,AddressOfNameOrdinals,AddressOfFunctions全是RVA位址,然後轉換之後的FOA位址
都是對應了解為像使用Winhex以16進制形式打開ipmsg.exe檔案,然後最左邊顯示的那一對記憶體位址;
就是我們所說的RVA位址和FOA位址,了解這個,就不難了解上面代碼(LPDWORD)Foa_AddressOfNames + i);
她們自己相加實際是以使用Winhex以16進制形式打開ipmsg.exe檔案的左邊記憶體位址進行偏移依次找到對應
名稱位址表AddressOfNames的每個偏移位址,然後使用指針的方式通過* 取出裡面對應的每個值,而這每個
值也是RVA位址,是以得到的nameOffset的值需要再次轉換為FOA位址,再使用檔案FileBuffer偏移的方式
找到真正的函數名稱位址;
*/
LPSTR nameAddr = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, nameOffset));
if (!strcmp(nameAddr, pFunctionOfName))
{
ordIndex = i;
break;
}
}
if (ordIndex == -1)
{
printf("導出表中沒有這個函數名稱.\n");
return 0;
}
//2.通過獲得的索引在導出表序号表中找到對應函數的序号
WORD ord = *(PWORD)((DWORD)pFileBuffer + (DWORD)((LPWORD)Foa_AddressOfNameOrdinals + ordIndex));
if (!debug)
{
DWORD Foa_AddressNameOrdinals = (DWORD)pFileBuffer + Foa_AddressOfNameOrdinals ;
printf("AddressOfNameOrdinals Offset Value: %#X\r\n", Foa_AddressNameOrdinals);
printf("Foa_AddressOfNameOrdinals: %#X\r\n", Foa_AddressOfNameOrdinals);
printf("ordInex in AddressOfNames: %#X\r\n", ordIndex);
printf("ordInex in AddressOfNameOrdinals: %#X\r\n", ord);
}
//3.通過序号表中查出來的值,當作下标索引從導出表函數位址表中找函數位址
DWORD addr = *(PDWORD)((DWORD)pFileBuffer + (DWORD)((LPDWORD)Foa_AddressOfFunctions + ord));
//DWORD Foa_AddrFun = RvaToFileOffset(pFileBuffer, addr);
return addr;
}
3.GetFunctionAddrByOrdinals(FileBuffer指針,函數名導出序号)
//根據序号擷取導出表函數位址
DWORD GetFunctionAddrByOrdinals(PVOID pFileBuffer, DWORD pFunctionOfOrdinal)
{
printf("Function Ordinal: %#x\n", pFunctionOfOrdinal);
// 初始化PE頭部結構體
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
// 判斷指針是否有效
if (!pFileBuffer)
{
printf("pFileBuffer不是有效的指針\r\n");
return 0;
}
//判斷是否是有效的MZ标志
if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("pFileBuffer不是有效的MZ檔案\r\n");
return 0;
}
//判斷是否是一個有效的PE标志
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if (*((PWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("pFileBuffer不是一個有效的PE标志\r\n");
return 0;
}
// 強制結構體類型轉換
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04); // 這裡必須強制類型轉換
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
if (!pDataDirectory->VirtualAddress)
{
printf("這個程式沒有導出表.\r\n");
return 0;
}
printf("導出表RVA位址: %#X\r\n", pDataDirectory->VirtualAddress);
DWORD Foa_ExportTable = RvaToFileOffset(pFileBuffer, pDataDirectory->VirtualAddress);
printf("導出表FOA位址: %#X\r\n", Foa_ExportTable);
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + Foa_ExportTable);
if (!debug)
{
printf("Characteristics: %#X\r\n", pExportDirectory->Characteristics);
printf("TimeDateStamp:%#X\r\n", pExportDirectory->TimeDateStamp);
printf("MajorVersion: %#X\r\n", pExportDirectory->MajorVersion);
printf("MinorVersion: %#X\r\n", pExportDirectory->MinorVersion);
printf("Name: %s\r\n", (PVOID)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->Name)));
printf("Base: %#X\r\n", pExportDirectory->Base);
printf("NumberOfFunctions: %#X\r\n", pExportDirectory->NumberOfFunctions);
printf("NumberOfNames: %#X\r\n", pExportDirectory->NumberOfNames);
printf("AddressOfFunctions: %#X\r\n", pExportDirectory->AddressOfFunctions);
printf("AddressOfNames: %#X\r\n", pExportDirectory->AddressOfNames);
printf("AddressOfNameOrdinals: %#X\r\n", pExportDirectory->AddressOfNameOrdinals);
}
//下面就是算法來,如果是通過序号直接得出導出表函數位址表中的位址,那麼隻要序号-Base值即可
DWORD Sequence = pFunctionOfOrdinal - pExportDirectory->Base;
DWORD Foa_AddressOfFunctions = RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions);
if (!debug)
{
DWORD Foa_AddressFunctions = Foa_AddressOfFunctions + (DWORD)pFileBuffer;
printf("AddressOfFunctions裡面内容: %#X\r\n", Foa_AddressFunctions);
printf("Foa_AddressOfFunctions: %#X\r\n", Foa_AddressOfFunctions);
}
PDWORD pFoa_AddressOfFunctions = (PDWORD)((DWORD)pFileBuffer + Foa_AddressOfFunctions);
for (DWORD n = 0; n < Sequence; n++)
{
pFoa_AddressOfFunctions++;
}
//DWORD Foa_AddrFun = RvaToFileOffset(pFileBuffer, *pFoa_AddressOfFunctions);
return *pFoa_AddressOfFunctions;
}
驗證執行效果:
執行之前的要傳入的參數:
調用函數傳入參數的相關代碼
VOID PrintFunctionAddrByNameAndOrdinals()
{
LPVOID pFileBuffer = NULL;
DWORD FileBufferSize = 0;
DWORD BaseName_FunctionAddr = 0;
DWORD BaseOrdinals_FUnctionAddr = 0;
char pFunctionName[] = "png_set_rows";
//File-->FileBuffer
FileBufferSize = ReadPEFile(FilePath_In,&pFileBuffer);
if (FileBufferSize == 0 || !pFileBuffer)
{
printf("檔案-->緩沖區失敗\r\n");
return ;
}
printf("FileBufferSize: %#X \r\n",FileBufferSize);
BaseName_FunctionAddr = GetFunctionAddrByName(pFileBuffer,pFunctionName);
printf("GetFunctionAddrByName: %#X \r\n",BaseName_FunctionAddr);
BaseOrdinals_FUnctionAddr = GetFunctionAddrByOrdinals(pFileBuffer,0x000000AE);
printf("GetFunctionAddrByOrdinals: %#X \r\n",BaseOrdinals_FUnctionAddr);
free(pFileBuffer);
}
執行結果:
迷茫的人生,需要不斷努力,才能看清遠方模糊的志向!