手动加载DLL(PE文件)

以前学重载内核时学到了手动加载一个PE文件,想在Ring3层也实现一遍,不过在GitHub上看到有现成的源码了就不自己写了。本篇文章就分析一下这个mmLoader,看看怎么实现手动加载PE文件

阅读前需要了解PE文件结构,还不了解的自行恶补

这个库为了实现注入ShellCode,自己实现了memset等常用库函数,还自己定义了要用到的库函数表。实际使用时,如果不用注入ShellCode使用常规的库函数就好

虽然这个库是用来加载DLL的,修改一下也可以用来加载EXE

加载PE文件的主要步骤:

  1. 将PE头和各个节映射到内存
  2. 重定位
  3. 修复IAT
  4. 调用模块入口点

映射文件到内存

这一步就是把PE文件的内容读到内存相应的位置,要注意文件中各节的对齐和内存中的对齐是不一样的

/// <summary>
/// Maps all the sections.
/// </summary>
/// <param name="pMemModule">The <see cref="MemModule" /> instance.</param>
/// <returns>True if successful.</returns>
BOOL MapMemModuleSections(PMEM_MODULE pMemModule, LPVOID lpPeModuleBuffer)
{
    // Validate
    if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable || NULL == lpPeModuleBuffer)
        return FALSE;

    // Function pointer
    // VirtualAlloc的函数指针,ShellCode用的,实际使用时直接写VirtualAlloc即可,后面类似的不再赘述
    Type_VirtualAlloc pfnVirtualAlloc = (Type_VirtualAlloc)(pMemModule->pNtFuncptrsTable->pfnVirtualAlloc);
    Type_VirtualFree pfnVirtualFree = (Type_VirtualFree)(pMemModule->pNtFuncptrsTable->pfnVirtualFree);

    // Convert to IMAGE_DOS_HEADER
    // PE文件头部指针,即DOS头
    PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)(lpPeModuleBuffer);

    // Get the pointer to IMAGE_NT_HEADERS
    // NT头,MakePointer是个宏,返回某指针+某偏移量后的新指针
    PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(
        PIMAGE_NT_HEADERS, pImageDosHeader, pImageDosHeader->e_lfanew);

    // Get the section count
    int nNumberOfSections = pImageNtHeader->FileHeader.NumberOfSections;

    // Get the section header
    // 节头
    PIMAGE_SECTION_HEADER pImageSectionHeader = MakePointer(
        PIMAGE_SECTION_HEADER, pImageNtHeader, sizeof(IMAGE_NT_HEADERS));

    // Find the last section limit
    // 计算整个模块尺寸
    DWORD dwImageSizeLimit = 0;
    for (int i = 0; i < nNumberOfSections; ++i)
    {
        if (0 != pImageSectionHeader[i].VirtualAddress)
        {
            if (dwImageSizeLimit < (pImageSectionHeader[i].VirtualAddress + pImageSectionHeader[i].SizeOfRawData))
                dwImageSizeLimit = pImageSectionHeader[i].VirtualAddress + pImageSectionHeader[i].SizeOfRawData;
        }
    }

    // Align the last image size limit to the page size
    // 按页对齐
    dwImageSizeLimit = (dwImageSizeLimit + pMemModule->dwPageSize - 1) & ~(pMemModule->dwPageSize - 1);

    // Reserve virtual memory 
    // 优先使用ImageBase作为模块基址,分配一块内存
    LPVOID lpBase = pfnVirtualAlloc(
        (LPVOID)(pImageNtHeader->OptionalHeader.ImageBase), 
        dwImageSizeLimit,
        MEM_RESERVE | MEM_COMMIT, 
        PAGE_READWRITE);

    // Failed to reserve space at ImageBase, then it's up to the system
    // 这个基址不能使用,让系统随机选另一个基址(后面需要重定位)
    if (NULL == lpBase)
    {
        // Reserver memory in arbitrary address
        lpBase = pfnVirtualAlloc(
            NULL, 
            dwImageSizeLimit,
            MEM_RESERVE | MEM_COMMIT, 
            PAGE_READWRITE);

        // Failed again, return 
        if (NULL == lpBase)
        {
            pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED;
            return FALSE;
        }
    }

    // Commit memory for PE header
    LPVOID pDest = pfnVirtualAlloc(lpBase, pImageNtHeader->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE);
    if (!pDest)
    {
        pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED;
        return FALSE;
    }

    // Copy the data of PE header to the memory allocated
    // 复制PE头
    mml_memmove(pDest, lpPeModuleBuffer, pImageNtHeader->OptionalHeader.SizeOfHeaders);

    // Store the base address of this module.
    pMemModule->lpBase = pDest;
    pMemModule->dwSizeOfImage = pImageNtHeader->OptionalHeader.SizeOfImage;
    pMemModule->bLoadOk = TRUE;

    // Get the DOS header, NT header and Section header from the new PE header buffer
    pImageDosHeader = (PIMAGE_DOS_HEADER)pDest;
    pImageNtHeader = MakePointer(PIMAGE_NT_HEADERS, pImageDosHeader, pImageDosHeader->e_lfanew);
    pImageSectionHeader = MakePointer(PIMAGE_SECTION_HEADER, pImageNtHeader, sizeof(IMAGE_NT_HEADERS));

    // Map all section data into the memory
    // 复制所有的节
    LPVOID pSectionBase = NULL;
    LPVOID pSectionDataSource = NULL;
    for (int i = 0; i < nNumberOfSections; ++i)
    {
        if (0 != pImageSectionHeader[i].VirtualAddress)
        {
            // Get the section base
            pSectionBase = MakePointer(LPVOID, lpBase, pImageSectionHeader[i].VirtualAddress);

            if (0 == pImageSectionHeader[i].SizeOfRawData)
            {
                if (pImageNtHeader->OptionalHeader.SectionAlignment > 0)
                {
                    // If the size is zero, but the section alignment is not zero then allocate memory with the aligment
                    pDest = pfnVirtualAlloc(pSectionBase, pImageNtHeader->OptionalHeader.SectionAlignment,
                        MEM_COMMIT, PAGE_READWRITE);
                    if (NULL == pDest)
                    {
                        pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED;
                        return FALSE;
                    }

                    // Always use position from file to support alignments smaller than page size.
                    mml_memset(pSectionBase, 0, pImageNtHeader->OptionalHeader.SectionAlignment);
                }
            }
            else
            {
                // Commit this section to target address
                pDest = pfnVirtualAlloc(pSectionBase, pImageSectionHeader[i].SizeOfRawData, MEM_COMMIT, PAGE_READWRITE);
                if (NULL == pDest)
                {
                    pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED;
                    return FALSE;
                }

                // Get the section data source and copy the data to the section buffer
                pSectionDataSource = MakePointer(LPVOID, lpPeModuleBuffer, pImageSectionHeader[i].PointerToRawData);
                mml_memmove(pDest, pSectionDataSource, pImageSectionHeader[i].SizeOfRawData);
            }

            // Get next section header
            pImageSectionHeader[i].Misc.PhysicalAddress = (DWORD)(ULONGLONG)pDest;
        }
    }

    return TRUE;
}

重定位

代码中那些绝对地址是以模块基址=ImageBase为前提硬编码的,如果模块加载的基址不是ImageBase指定的基址,则需要重定位

新地址=旧地址-ImageBase+实际模块基址,只要算出实际模块基址-ImageBase,重定位时加上偏移量就行了

/// <summary>
/// Relocates the module.
/// </summary>
/// <param name="pMemModule">The <see cref="MemModule" /> instance.</param>
/// <returns>True if successful.</returns>
BOOL RelocateModuleBase(PMEM_MODULE pMemModule)
{
    // Validate the parameters
    if (NULL == pMemModule  || NULL == pMemModule->pImageDosHeader)
        return FALSE;

    PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(
        PIMAGE_NT_HEADERS,
        pMemModule->pImageDosHeader, 
        pMemModule->pImageDosHeader->e_lfanew);

    // Get the delta of the real image base with the predefined
    // 计算偏移量
    LONGLONG lBaseDelta = ((PUINT8)pMemModule->iBase - (PUINT8)pImageNtHeader->OptionalHeader.ImageBase);

    // This module has been loaded to the ImageBase, no need to do relocation
    if (0 == lBaseDelta) return TRUE;

    if (0 == pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress
        || 0 == pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size)
        return TRUE;

    // 重定位表
    PIMAGE_BASE_RELOCATION pImageBaseRelocation = MakePointer(PIMAGE_BASE_RELOCATION, pMemModule->lpBase, 
        pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

    if (NULL == pImageBaseRelocation)
    {
        pMemModule->dwErrorCode = MMEC_INVALID_RELOCATION_BASE;
        return FALSE;
    }

    while (0 != (pImageBaseRelocation->VirtualAddress + pImageBaseRelocation->SizeOfBlock))
    {
        PWORD pRelocationData = MakePointer(PWORD, pImageBaseRelocation, sizeof(IMAGE_BASE_RELOCATION));

        int NumberOfRelocationData = (pImageBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

        for (int i = 0; i < NumberOfRelocationData; i++)
        {
            if (IMAGE_REL_BASED_HIGHLOW == (pRelocationData[i] >> 12))
            {
                // 需要重定位的地址
                PDWORD pAddress = (PDWORD)(pMemModule->iBase + pImageBaseRelocation->VirtualAddress + (pRelocationData[i] & 0x0FFF));
                // 重定位
                *pAddress += (DWORD)lBaseDelta;
            }

#ifdef _WIN64
            if (IMAGE_REL_BASED_DIR64 == (pRelocationData[i] >> 12))
            {
                PULONGLONG pAddress = (PULONGLONG)(pMemModule->iBase + pImageBaseRelocation->VirtualAddress + (pRelocationData[i] & 0x0FFF));
                *pAddress += lBaseDelta;
            }
#endif
        }

        pImageBaseRelocation = MakePointer(PIMAGE_BASE_RELOCATION, pImageBaseRelocation, pImageBaseRelocation->SizeOfBlock);
    }

    return TRUE;
}

修复IAT

这一步载入本模块依赖的模块,并将本模块的IAT定位到依赖模块的相应的函数上

/// <summary>
/// Resolves the import table.
/// </summary>
/// <param name="pMemModule">The <see cref="MemModule" /> instance.</param>
/// <returns>True if successful.</returns>
BOOL ResolveImportTable(PMEM_MODULE pMemModule)
{
    if (NULL == pMemModule  || NULL == pMemModule->pNtFuncptrsTable || NULL == pMemModule->pImageDosHeader)
        return FALSE;

    Type_GetModuleHandleA pfnGetModuleHandleA = (Type_GetModuleHandleA)(pMemModule->pNtFuncptrsTable->pfnGetModuleHandleA);
    Type_LoadLibraryA pfnLoadLibraryA = (Type_LoadLibraryA)(pMemModule->pNtFuncptrsTable->pfnLoadLibraryA);
    Type_GetProcAddress pfnGetProcAddress = (Type_GetProcAddress)(pMemModule->pNtFuncptrsTable->pfnGetProcAddress);

    PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(PIMAGE_NT_HEADERS, pMemModule->pImageDosHeader, pMemModule->pImageDosHeader->e_lfanew);

    if (pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0
        || pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)
        return TRUE;

    PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = MakePointer(PIMAGE_IMPORT_DESCRIPTOR, pMemModule->lpBase, 
        pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    // 遍历导入的模块
    for (; pImageImportDescriptor->Name; pImageImportDescriptor++)
    {
        // Get the dependent module name
        // 依赖的模块名
        PCHAR pDllName = MakePointer(PCHAR, pMemModule->lpBase, pImageImportDescriptor->Name);

        // Get the dependent module handle
        // 取依赖的模块句柄,其实直接用LoadLibrary就行了,这里两个函数都用了而且释放模块时也没有FreeLibrary,会造成内存泄漏
        HMODULE hMod = pfnGetModuleHandleA(pDllName);

        // Load the dependent module
        if (NULL == hMod) hMod = pfnLoadLibraryA(pDllName);

        // Failed
        if (NULL == hMod)
        {
            pMemModule->dwErrorCode = MMEC_IMPORT_MODULE_FAILED;
            return FALSE;
        }
        // Original thunk
        PIMAGE_THUNK_DATA pOriginalThunk = NULL;
        if (pImageImportDescriptor->OriginalFirstThunk)
            pOriginalThunk = MakePointer(PIMAGE_THUNK_DATA, pMemModule->lpBase, pImageImportDescriptor->OriginalFirstThunk);
        else
            pOriginalThunk = MakePointer(PIMAGE_THUNK_DATA, pMemModule->lpBase, pImageImportDescriptor->FirstThunk);

        // IAT thunk
        PIMAGE_THUNK_DATA pIATThunk = MakePointer(PIMAGE_THUNK_DATA, pMemModule->lpBase, 
            pImageImportDescriptor->FirstThunk);

        // 遍历导入的函数
        for (; pOriginalThunk->u1.AddressOfData; pOriginalThunk++, pIATThunk++)
        {
            // 取函数地址
            FARPROC lpFunction = NULL;
            if (IMAGE_SNAP_BY_ORDINAL(pOriginalThunk->u1.Ordinal))
            {
                lpFunction = pfnGetProcAddress(hMod, (LPCSTR)IMAGE_ORDINAL(pOriginalThunk->u1.Ordinal));
            }
            else
            {
                PIMAGE_IMPORT_BY_NAME pImageImportByName = MakePointer(
                    PIMAGE_IMPORT_BY_NAME, pMemModule->lpBase, pOriginalThunk->u1.AddressOfData);

                lpFunction = pfnGetProcAddress(hMod, (LPCSTR)&(pImageImportByName->Name));
            }

            // Write into IAT
            // 写到IAT
#ifdef _WIN64
            pIATThunk->u1.Function = (ULONGLONG)lpFunction;
#else
            pIATThunk->u1.Function = (DWORD)lpFunction;
#endif
        }
    }

    return TRUE;
}

设置各节内存保护

没有这一步也无所谓,为了安全和严谨就加上这一步吧

/// <summary>
/// Sets the memory protected stats of all the sections.
/// </summary>
/// <param name="pMemModule">The <see cref="MemModule" /> instance.</param>
/// <returns>True if successful.</returns>
BOOL SetMemProtectStatus(PMEM_MODULE pMemModule)
{
    if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable)
        return FALSE;

    // 索引含义:可执行、可读、可写,根据这3个属性决定内存保护
    int ProtectionMatrix[2][2][2] = {
        {
            // not executable
            { PAGE_NOACCESS, PAGE_WRITECOPY },
            { PAGE_READONLY, PAGE_READWRITE },
        },
        {
            // executable
            { PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY },
            { PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE },
        },
    };

    Type_VirtualProtect pfnVirtualProtect = (Type_VirtualProtect)(pMemModule->pNtFuncptrsTable->pfnVirtualProtect);
    Type_VirtualFree pfnVirtualFree = (Type_VirtualFree)(pMemModule->pNtFuncptrsTable->pfnVirtualFree);

    PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)(pMemModule->lpBase);

    ULONGLONG ulBaseHigh = 0;
#ifdef _WIN64
    ulBaseHigh = (pMemModule->iBase & 0xffffffff00000000);
#endif

    PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(
        PIMAGE_NT_HEADERS, pImageDosHeader, pImageDosHeader->e_lfanew);

    int nNumberOfSections = pImageNtHeader->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER pImageSectionHeader = MakePointer(
        PIMAGE_SECTION_HEADER, pImageNtHeader, sizeof(IMAGE_NT_HEADERS));

    // 遍历各节
    for (int idxSection = 0; idxSection < nNumberOfSections; idxSection++)
    {
        DWORD protectFlag = 0;
        DWORD oldProtect = 0;
        BOOL isExecutable = FALSE;
        BOOL isReadable = FALSE;
        BOOL isWritable = FALSE;

        BOOL isNotCache = FALSE;
        ULONGLONG dwSectionBase = (pImageSectionHeader[idxSection].Misc.PhysicalAddress | ulBaseHigh);
        DWORD dwSecionSize = pImageSectionHeader[idxSection].SizeOfRawData;
        if (0 == dwSecionSize) continue;

        // This section is in this page
        // 接下来根据节属性设置页面内存保护
        DWORD dwSectionCharacteristics = pImageSectionHeader[idxSection].Characteristics;

        // Discardable
        if (dwSectionCharacteristics & IMAGE_SCN_MEM_DISCARDABLE)
        {
            pfnVirtualFree((LPVOID)dwSectionBase, dwSecionSize, MEM_DECOMMIT);
            continue;
        }

        // Executable
        if (dwSectionCharacteristics & IMAGE_SCN_MEM_EXECUTE)
            isExecutable = TRUE;

        // Readable
        if (dwSectionCharacteristics & IMAGE_SCN_MEM_READ)
            isReadable = TRUE;

        // Writable
        if (dwSectionCharacteristics & IMAGE_SCN_MEM_WRITE)
            isWritable = TRUE;

        if (dwSectionCharacteristics & IMAGE_SCN_MEM_NOT_CACHED)
            isNotCache = TRUE;

        protectFlag = ProtectionMatrix[isExecutable][isReadable][isWritable];
        if (isNotCache) protectFlag |= PAGE_NOCACHE;
        // 设置内存保护
        if (!pfnVirtualProtect((LPVOID)dwSectionBase, dwSecionSize, protectFlag, &oldProtect))
        {
            pMemModule->dwErrorCode = MMEC_PROTECT_SECTION_FAILED;
            return FALSE;
        }
    }

    return TRUE;
}

调用TLS回调和模块入口点

这一步也是可选的,不过如果不做有些全局变量可能未初始化。做了的话有可能失败,因为操作系统不认我们手动加载的模块,调用某些函数有可能失败…

/// <summary>
/// Executes the TLS callback function.
/// </summary>
/// <param name="pMemModule">The <see cref="MemModule" /> instance.</param>
/// <returns>True if successful.</returns>
BOOL ExecuteTLSCallback(PMEM_MODULE pMemModule)
{
    if (NULL == pMemModule || NULL == pMemModule->pImageDosHeader)
        return FALSE;

    PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(
        PIMAGE_NT_HEADERS,
        pMemModule->pImageDosHeader,
        pMemModule->pImageDosHeader->e_lfanew);

    IMAGE_DATA_DIRECTORY imageDirectoryEntryTls = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
    if (imageDirectoryEntryTls.VirtualAddress == 0) return TRUE;

    PIMAGE_TLS_DIRECTORY tls = (PIMAGE_TLS_DIRECTORY)(pMemModule->iBase + imageDirectoryEntryTls.VirtualAddress);
    PIMAGE_TLS_CALLBACK* callback = (PIMAGE_TLS_CALLBACK *)tls->AddressOfCallBacks;
    if (callback)
    {
        while (*callback)
        {
            (*callback)((LPVOID)pMemModule->hModule, DLL_PROCESS_ATTACH, NULL);
            callback++;
        }
    }
    return TRUE;
}

/// <summary>
/// Calls the module entry.
/// </summary>
/// <param name="pMemModule">The <see cref="MemModule" /> instance.</param>
/// <param name="dwReason">The reason of the calling.</param>
/// <returns>True if successful.</returns>
BOOL CallModuleEntry(PMEM_MODULE pMemModule, DWORD dwReason)
{
    if (NULL == pMemModule || NULL == pMemModule->pImageDosHeader)
        return FALSE;

    PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(
        PIMAGE_NT_HEADERS,
        pMemModule->pImageDosHeader, 
        pMemModule->pImageDosHeader->e_lfanew);

    Type_DllMain pfnModuleEntry = NULL;

    pfnModuleEntry = MakePointer(
        Type_DllMain, 
        pMemModule->lpBase, 
        pImageNtHeader->OptionalHeader.AddressOfEntryPoint);

    if (NULL == pfnModuleEntry)
    {
        pMemModule->dwErrorCode = MMEC_INVALID_ENTRY_POINT;
        return FALSE;
    }

    return pfnModuleEntry(pMemModule->hModule, dwReason, NULL);
}

总流程

这个函数就是把之前分析过的函数调用一遍实现加载PE文件。有些不重要而且看名字就知道干什么的函数就不分析了

BOOL __stdcall LoadMemModule(PMEM_MODULE pMemModule, LPVOID lpPeModuleBuffer, BOOL bCallEntry)
{
    if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable || NULL == lpPeModuleBuffer)
        return FALSE;

    pMemModule->dwErrorCode = ERROR_SUCCESS;

    // Verify file format
    if (FALSE == IsValidPEFormat(pMemModule, lpPeModuleBuffer))
    {
        return FALSE;
    }

    // Map PE header and section table into memory
    if (FALSE == MapMemModuleSections(pMemModule, lpPeModuleBuffer))
        return FALSE;

    // Relocate the module base
    if (FALSE == RelocateModuleBase(pMemModule))
    {
        UnmapMemModule(pMemModule);
        return FALSE;
    }

    // Resolve the import table
    if (FALSE == ResolveImportTable(pMemModule))
    {
        UnmapMemModule(pMemModule);
        return FALSE;
    }

    pMemModule->dwCrc = mml_getcrc32(
        0, pMemModule->lpBase, pMemModule->dwSizeOfImage);

    // Correct the protect flag for all section pages
    if (FALSE == SetMemProtectStatus(pMemModule))
    {
        UnmapMemModule(pMemModule);
        return FALSE;
    }

    if (FALSE == ExecuteTLSCallback(pMemModule))
        return FALSE;

    if (bCallEntry)
    {
        if (FALSE == CallModuleEntry(pMemModule, DLL_PROCESS_ATTACH))
        {
            // failed to call entry point,
            // clean resource, return false
            UnmapMemModule(pMemModule);
            return FALSE;
        }
    }

    return TRUE;
}

应用

  1. 重载某模块,绕过各种hook
    重载模块的话会有其他问题,比如全局变量在内存中有两份副本。其实也很好解决,重定位时定位到原模块就行了。不过用来绕hook未免小题大做了,针对性地用汇编绕过部分hook就行了
  2. 隐藏注入模块
    你手动加载的模块,操作系统根本不知道它的存在,模块列表里没有,也不会引起任何回调
  3. 获取模块的原始内容,比如内核中的SSDT
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章