You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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");
}

问题根源分析

  1. 64位地址截断:getImports中用DWORD存储基地址,在64位系统下DWORD是32位类型,会截断64位内存地址,导致GetPtrFromRVA计算出的指针无效,访问时触发违例。
  2. PE结构字段误用:DumpImportsSection中把importDesc->Characteristics当成OriginalFirstThunk使用,这是PE32的旧字段名,PE32+规范中该字段已改为OriginalFirstThunk,直接访问会导致结构解析错误。
  3. 缺失边界检查:所有PE结构指针访问前未验证是否在映射内存的[start, end]范围内,一旦PE结构偏移错误或文件损坏,就会访问到映射区域外的内存。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 21:37:02