C语言中从内存映射PE文件获取导入表触发访问违例的排查
内存映射PE文件读取导入表时触发访问违例的解决方法
问题背景
网上多数PE文件导入表读取示例都是直接传递文件路径给CreateFile,但我已通过MapViewOfFile将PE文件映射到内存,且持有文件的起始和结束指针。运行代码时,在DumpImportsSection函数的if ((importDesc->TimeDateStamp == 0) && (importDesc->Name == 0))行触发access violation 0xC0000005异常,怀疑是参数传递方式有误。
可复现代码片段
int assumeMain(){ PVOID startAddress; HANDLE secHdl = NULL; startAddress= MapViewOfFile( secHdl, FILE_MAP_READ, // originally was FILE_MAP_READ 0L, 0L, 0 ); if (startAddress== NULL){ // do stuff } anPefile((unsigned char*)startAddress), size); // 原代码笔误,语法错误 } int anPefile(_In_reads_bytes_(Size) PUCHAR StartingAddress, _In_ SIZE_T Size,){ // 多了逗号,语法错误 PUCHAR start = StartingAddress; PUCHAR end = start + Size; getImports(start, end); } int getImports(PUCHAR start, PUCHAR end) { DWORD base = (DWORD)start; DWORD size = (DWORD)end - (DWORD)start; PIMAGE_DOS_HEADER fileHeader = (PIMAGE_DOS_HEADER)start; PIMAGE_NT_HEADERS fileNTHeader = (PIMAGE_NT_HEADERS)((BYTE*)fileHeader + fileHeader->e_lfanew); DWORD resourceVirtualAddress = fileNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; if (resourceVirtualAddress == 0) { printf("No resource directory found in the PE file\n"); return NULL; // int类型函数返回NULL不合适,应返回错误码 } DumpImportsSection(base, fileNTHeader); return filerank; // filerank未定义 } // This function is from pedump project. void DumpImportsSection(DWORD base, PIMAGE_NT_HEADERS pNTHeader) { PIMAGE_IMPORT_DESCRIPTOR importDesc; PIMAGE_SECTION_HEADER pSection; PIMAGE_THUNK_DATA thunk, thunkIAT = 0; PIMAGE_IMPORT_BY_NAME pOrdinalName; DWORD importsStartRVA; PSTR pszTimeDate; // Look up where the imports section is (normally in the .idata section) // but not necessarily so. Therefore, grab the RVA from the data dir. importsStartRVA = GetImgDirEntryRVA(pNTHeader, IMAGE_DIRECTORY_ENTRY_IMPORT); if (!importsStartRVA) return; printf("importsStartRVA: %d\n", importsStartRVA); // Get the IMAGE_SECTION_HEADER that contains the imports. This is // usually the .idata section, but doesn't have to be. pSection = GetEnclosingSectionHeader(importsStartRVA, pNTHeader); if (!pSection) return; importDesc = (PIMAGE_IMPORT_DESCRIPTOR) GetPtrFromRVA(importsStartRVA, pNTHeader, base); if (!importDesc) return; printf("Imports Table: \n"); while (1) { printf("Before importing\n"); printf("Name: %p\n", &importDesc); // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR if ((importDesc->TimeDateStamp == 0) && (importDesc->Name == 0)) break; printf("After importing\n"); printf(" %s\n", GetPtrFromRVA(importDesc->Name, pNTHeader, base)); printf(" OrigFirstThunk: %08X (Unbound IAT)\n", importDesc->Characteristics); pszTimeDate = ctime((PLONG)&importDesc->TimeDateStamp); printf(" TimeDateStamp: %08X", importDesc->TimeDateStamp); printf(pszTimeDate ? " -> %s" : "\n", pszTimeDate); printf(" ForwarderChain: %08X\n", importDesc->ForwarderChain); printf(" First thunk RVA: %08X\n", importDesc->FirstThunk); thunk = (PIMAGE_THUNK_DATA)importDesc->Characteristics; thunkIAT = (PIMAGE_THUNK_DATA)importDesc->FirstThunk; if (thunk == 0) // No Characteristics field? { // Yes! Gotta have a non-zero FirstThunk field then. thunk = thunkIAT; if (thunk == 0) // No FirstThunk field? Ooops!!! return; } // Adjust the pointer to point where the tables are in the // mem mapped file. thunk = (PIMAGE_THUNK_DATA)GetPtrFromRVA((DWORD)thunk, pNTHeader, base); if (!thunk) return; thunkIAT = (PIMAGE_THUNK_DATA) GetPtrFromRVA((DWORD)thunkIAT, pNTHeader, base); printf(" Ordn Name\n"); while (1) // Loop forever (or until we break out) { if (thunk->u1.AddressOfData == 0) break; if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) { printf(" %4u", IMAGE_ORDINAL(thunk->u1.Ordinal)); } else { pOrdinalName = thunk->u1.AddressOfData; pOrdinalName = (PIMAGE_IMPORT_BY_NAME) GetPtrFromRVA((DWORD)pOrdinalName, pNTHeader, base); printf(" %4u %s", pOrdinalName->Hint, pOrdinalName->Name); } // If the user explicitly asked to see the IAT entries, or // if it looks like the image has been bound, append the address if (fShowIATentries || importDesc->TimeDateStamp) printf(" (Bound to: %08X)", thunkIAT->u1.Function); printf("\n"); thunk++; // Advance to next thunk thunkIAT++; // advance to next thunk } importDesc++; // advance to next IMAGE_IMPORT_DESCRIPTOR printf("\n"); } printf("Exiting\n"); }
问题根源分析
- 64位地址截断:
getImports中用DWORD存储基地址,在64位系统下DWORD是32位类型,会截断64位内存地址,导致GetPtrFromRVA计算出的指针无效,访问时触发违例。 - PE结构字段误用:
DumpImportsSection中把importDesc->Characteristics当成OriginalFirstThunk使用,这是PE32的旧字段名,PE32+规范中该字段已改为OriginalFirstThunk,直接访问会导致结构解析错误。 - 缺失边界检查:所有PE结构指针访问前未验证是否在映射内存的
[start, end]范围内,一旦PE结构偏移错误或文件损坏,就会访问到映射区域外的内存。 - MapViewOfFile调用无效:
assumeMain中secHdl为NULL,MapViewOfFile必然失败,后续操作基于无效指针,直接导致异常。
修复方案
1. 修正基地址类型
将getImports中的基地址类型从DWORD改为UINT_PTR,同时更新DumpImportsSection的参数类型:
// getImports函数中 UINT_PTR base = (UINT_PTR)start; // DumpImportsSection函数声明 void DumpImportsSection(UINT_PTR base, PIMAGE_NT_HEADERS pNTHeader)
2. 修正PE结构字段引用
将所有importDesc->Characteristics替换为importDesc->OriginalFirstThunk,对应PE规范中的未绑定IAT字段:
printf(" OrigFirstThunk: %08X (Unbound IAT)\n", importDesc->OriginalFirstThunk); thunk = (PIMAGE_THUNK_DATA)(UINT_PTR)importDesc->OriginalFirstThunk;
3. 添加边界检查
在访问任何PE结构前,验证指针是否在映射内存范围内,比如在getImports中:
// 验证DOS头合法性 if ((PUCHAR)fileHeader + sizeof(IMAGE_DOS_HEADER) > end) { printf("Invalid DOS header\n"); return -1; } // 验证NT头指针合法性 PUCHAR ntHeaderPtr = (PUCHAR)fileHeader + fileHeader->e_lfanew; if (ntHeaderPtr + sizeof(IMAGE_NT_HEADERS) > end) { printf("Invalid NT headers\n"); return -1; } PIMAGE_NT_HEADERS fileNTHeader = (PIMAGE_NT_HEADERS)ntHeaderPtr;
4. 修复MapViewOfFile调用
确保先获取合法的文件和映射句柄:
int assumeMain(){ PVOID startAddress = NULL; HANDLE hFile = CreateFileA("your_pe_file.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("Failed to open file\n"); return -1; } HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { CloseHandle(hFile); printf("Failed to create file mapping\n"); return -1; } startAddress = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (startAddress == NULL) { CloseHandle(hMap); CloseHandle(hFile); printf("Failed to map view of file\n"); return -1; } // 获取文件大小 LARGE_INTEGER fileSize; GetFileSizeEx(hFile, &fileSize); anPefile((PUCHAR)startAddress, (SIZE_T)fileSize.QuadPart); // 清理资源 UnmapViewOfFile(startAddress); CloseHandle(hMap); CloseHandle(hFile); return 0; }
内容的提问来源于stack exchange,提问作者chelsey
相关产品推荐
相关产品推荐

