// 01 遍历导出表.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "windows.h" //************************************ // Method: IsPeFile // FullName: IsPeFile // Access: public // Returns: bool 成功失败 // Qualifier: // Parameter: TCHAR * szPath 路径 //************************************ bool IsPeFile(TCHAR* szPath) { BOOL bSuccess = TRUE; //1 将PE文件读取到内存 HANDLE hFile = CreateFile( szPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL , NULL ); DWORD dwSize = GetFileSize(hFile, NULL); DWORD dwRubbish = 0; unsigned char * pBuf = new unsigned char[dwSize]; ReadFile(hFile, pBuf, dwSize, &dwRubbish, NULL); //2 判断是否是PE文件 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf; if (pDos->e_magic != IMAGE_DOS_SIGNATURE) { bSuccess = FALSE; goto Error; } PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pBuf + pDos->e_lfanew); if (pNt->Signature != IMAGE_NT_SIGNATURE) { bSuccess = FALSE; goto Error; } Error: if (pBuf != NULL) { delete[]pBuf; } if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } return bSuccess; } //************************************ // Method: RvaToOffect // FullName: RvaToOffect // Access: public // Returns: DWORD // Qualifier: 将RVA转换为Offect // Parameter: DWORD rva 要转换的RVA // Parameter: unsigned char * pFile 存储pe文件内容的缓冲区 //************************************ DWORD RvaToOffect(DWORD rva, unsigned char* pFile) { //1 找到NT头 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFile; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pFile + pDos->e_lfanew); //2 找到数据目录表 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt); //3 判断要转换的位置是不是PE头部 if (rva < pSection->VirtualAddress) { return rva; } //4 在数据目录表中遍历,进行计算 for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++) { if ( (rva >= pSection->VirtualAddress) && (rva <= pSection->VirtualAddress + pSection->Misc.VirtualSize) ) { return rva - pSection->VirtualAddress + pSection->PointerToRawData; } pSection++; } return -1; } int _tmain(int argc, _TCHAR* argv[]) { //1 将PE文件读取到内存 HANDLE hFile = CreateFile( L"D:\\user32.dll", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL , NULL ); DWORD dwSize = GetFileSize(hFile, NULL); DWORD dwRubbish = 0; unsigned char * pBuf = new unsigned char[dwSize]; ReadFile(hFile, pBuf, dwSize, &dwRubbish, NULL); //2 找到dos头 PIMAGE_DOS_HEADER pDos = PIMAGE_DOS_HEADER(pBuf); //3 找到nt头 PIMAGE_NT_HEADERS pNt = PIMAGE_NT_HEADERS(pBuf + pDos->e_lfanew); //4 找到扩展头 PIMAGE_OPTIONAL_HEADER pOption = &(pNt->OptionalHeader); //5 找到数据目录表 PIMAGE_DATA_DIRECTORY pDataDirectory = pOption->DataDirectory; //6 找到导出表的数据目录 PIMAGE_DATA_DIRECTORY pExportDirectory = (pDataDirectory +0); //7 解析导出表的数据目录 //7.1 得到导出表的文件偏移 DWORD dwExOffect = RvaToOffect(pExportDirectory->VirtualAddress, pBuf); //7.2 得到导出表结构体 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pBuf + dwExOffect); //7.3打印dll的名字,注意:并不能直接打印,它提供的只是一个名字的RVA偏移 char* pName = (char*)(RvaToOffect(pExport->Name, pBuf) + pBuf); printf("%s\n", pName); //8 为解析导出表做准备 //8.1 函数的个数 DWORD dwNumOfFun = pExport->NumberOfFunctions; //8.2 名称的个数 DWORD dwNumOfName = pExport->NumberOfNames; //8.3 函数地址表的位置 PDWORD pOffectOfFun = (PDWORD) (RvaToOffect(pExport->AddressOfFunctions, pBuf) + pBuf); //8.4序号表的位置 PWORD pOrder = (PWORD) (RvaToOffect(pExport->AddressOfNameOrdinals, pBuf) + pBuf); //8.5 名称表的位置 PDWORD pOffectOfName = (PDWORD) (RvaToOffect(pExport->AddressOfNames,pBuf) + pBuf); //8.6 序号基数 WORD wBase = pExport->Base; //9 开始解析导出表 for (int i = 0; i < dwNumOfFun;i++){ //9.1 假如这是一个无效地址 if (pOffectOfFun[i] == 0) continue; //9.2 不是无效地址,就去序号表中找到这个序号 int j = 0; for (; j < dwNumOfName; j++){ if (pOrder[j] == i){ //9.2.1找到了这个序号,说明这个函数有名字,属于名称导出 char* pNameOfFun = (char*)(RvaToOffect(pOffectOfName[j], pBuf) + pBuf); printf(" 函数序号为:%hx 函数地址为:%X 函数名为:%s\n", wBase+i, pOffectOfFun[i], pNameOfFun); break; } } if (j == dwNumOfName){ //9.2.2假如没有找到这个序号,说明这个函数没有名字,只有序号, //属于序号导出,这个序号叫做虚序号 printf(" 函数序号为:%hx 函数地址为:%X 函数名为:NULL\n", wBase+i, pOffectOfFun[i]); } } return 0; }