内存直接加载运行DLL文件

前言:

  将DLL文件作为资源插入到自己程序中的方法,前面已经说过了。附上链接:MFC —— 资源文件释放(为了程序更简洁) 程序需要动态调用DLL文件,内存加载运行技术可以把这些DLL作为资源插入到自己的程序中。此时直接在内存中加载运行即可,不需要再将DLL释放到本地。

实现原理:

  将资源加载到内存,然后把DLL文件按照映像对齐大小映射到内存中,切不可直接将DLL文件数据存储到内存中。因为根据PE结构的基础知识可知,PE文件有两个对齐字段,一个是映像对齐大小SectionAlignment,另一个是文件对齐大小FileAlignment。其中,映像对齐大小是PE文件加载到内存中所用的对齐大小,而文件对齐大小是PE文件存储在本地磁盘所用的对齐大小。一般文件对齐大小会比映像对齐大小要小,这样文件会变小,以此节省磁盘空间。然而,成功映射内存数据之后,在DLL程序中会存在硬编码数据,硬编码都是以默认的加载基址作为基址来计算的。由于DLL可以任意加载到其他进程空间中,所以DLL的加载基址并非固定不变。当改变加载基址的时候,硬编码也要随之改变,这样DLL程序才会计算正确。但是,如何才能知道需要修改哪些硬编码呢?换句话说,如何知道硬编码的位置?答案就藏在PE结构的重定位表中,重定位表记录的就是程序中所有需要修改的硬编码的相对偏移位置。根据重定位表修改硬编码数据后,这只是完成了一半的工作。DLL作为一个程序,自然也会调用其他库函数,例如MessageBox。那么DLL如何知道MessageBox函数的地址呢?它只有获取正确的调用函数地址后,方可正确调用函数。PE结构使用导入表来记录PE程序中所有引用的函数及其函数地址。在DLL映射到内存之后,需要根据导入表中的导入模块和函数名称来获取调用函数的地址。若想从导入模块中获取导出函数的地址,最简单的方式是通过GetProcAddress函数来获取(此次采用的方法)。但是为了避免调用敏感的WIN32 API函数而被杀软拦截检测,采用直接遍历PE结构导出表的方式来获取导出函数地址。

实现流程:

  (1).将资源形式的dll文件加载到内存

  (2).根据PE结构获取其文件映像大小

  (3).根据文件映像大小再申请一块可读、可写、可执行的内存

  (4).将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中

  (5).根据PE结构的重定位表,对需要重定位的数据进行修正

  (6).根据PE结构的导入表,加载所需的DLL,获取函数地址并写入导入地址表

  (7).修改DLL的加载基址为第(3)步申请的空间的首地址

  (8).获取Dll的入口地址并构造DllMain函数,然后调用DllMain函数

实现代码:

    //************************************
    // 函数名:  CStartDlg::LoadMyResource
    // 返回类型:   LPVOID
    // 功能: 加载资源到内存
    // 参数1: UINT uiResourceName    资源名
    // 参数1: char * lpszResourceType    资源类型
    //************************************
LPVOID CStartDlg::LoadMyResource(UINT uiResourceName, char* lpszResourceType)
{
    //获取指定模块里的资源
    HRSRC hRsrc = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(uiResourceName), (LPCWSTR)lpszResourceType);
    if (NULL == hRsrc)
    {
        MessageBox(L"获取资源失败!");
        return FALSE;
    }
    //获取资源大小
    DWORD dwSize = SizeofResource(NULL, hRsrc);
    if (dwSize <= 0)
    {
        MessageBox(L"获取资源大小失败!");
        return FALSE;
    }
    //将资源加载到内存里
    HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
    if (NULL == hGlobal)
    {
        MessageBox(L"资源加载到内存失败!");
        return FALSE;
    }
    //锁定资源
    LPVOID lpVoid = LockResource(hGlobal);
    if (NULL == lpVoid)
    {
        MessageBox(L"锁定资源失败!");
        return FALSE;
    }
    return lpVoid;
}


    //************************************
    // 函数名:  CStartDlg::MmLoadLibrary
    // 返回类型:   LPVOID
    // 功能: 模拟LoadLibrary加载内存文件到进程中
    // 参数1: LPVOID lpData    文件基址
    // 参数2: BOOL IsExe    文件属性标志,TRUE表示exe文件,FALSE表示dll文件
    //************************************
LPVOID CStartDlg::MmLoadLibrary(LPVOID lpData,BOOL IsExe)
{
    LPVOID lpBaseAddress = NULL;
    // 获取映像大小
    DWORD dwSizeOfImage = GetSizeOfImage(lpData);
    // 在进程中申请一个可读、可写、可执行的内存块
    lpBaseAddress = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (NULL == lpBaseAddress)
    {
        MessageBox(L"申请空间失败!");
        return NULL;
    }
    // 将申请的空间的数据全部置0
    RtlZeroMemory(lpBaseAddress, dwSizeOfImage);

    // 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中
    if (FALSE == MmMapFile(lpData, lpBaseAddress))
    {
        MessageBox(L"区段映射到内存失败!");
        return NULL;
    }

    // 修改PE文件的重定位表信息
    if (FALSE == DoRelocationTable(lpBaseAddress))
    {
        MessageBox(L"修复重定位失败!");
        return NULL;
    }

    // 填写PE文件的导入表信息
    if (FALSE == DoImportTable(lpBaseAddress))
    {
        MessageBox(L"导入表填写失败!");
        return NULL;
    }
    // 修改PE文件的加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
    if (FALSE == SetImageBase(lpBaseAddress))
    {
        MessageBox(L"修改加载机制失败!");
        return NULL;
    }
    // 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点AddressOfEntryPoint
    if (FALSE == CallDllMain(lpBaseAddress,IsExe))
    {
        MessageBox(L"调用入口函数失败!");
        return NULL;
    }
    return lpBaseAddress;
}



    //************************************
    // 函数名:  CStartDlg::GetSizeOfImage
    // 返回类型:   DWORD
    // 功能: 获取文件映像大小
    // 参数1: LPVOID lpData    文件基址
    //************************************
DWORD CStartDlg::GetSizeOfImage(LPVOID lpData)
{
    //获取Dos头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;
    //判断是否是有效的PE文件        0x5A4D
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        MessageBox(L"这不是一个PE文件!");
        return 0;
    }

    //获取NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader+pDosHeader->e_lfanew);
    //判断是否是有效的PE文件        0x00004550
    if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
    {
        MessageBox(L"这不是一个PE文件!");
        return 0;
    }

    //获取文件映像大小
    return pNtHeader->OptionalHeader.SizeOfImage;
}



    //************************************
    // 函数名:  CStartDlg::MmMapFile
    // 返回类型:   BOOL
    // 功能: 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中
    // 参数1: LPVOID lpData    文件基址
    // 参数2: LPVOID lpBaseAddress    申请的内存的首地址
    //************************************
BOOL CStartDlg::MmMapFile(LPVOID lpData, LPVOID lpBaseAddress)
{
    //获取Dos头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;
    //获取NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    //获取所有头部+区段表的大小
    DWORD dwSizeOfHeaders = pNtHeader->OptionalHeader.SizeOfHeaders;
    //获取区段数量
    WORD wNumberOfSections = pNtHeader->FileHeader.NumberOfSections;
    //获取区段表数组的首元素
    PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
    //将头部(包括区段表)拷贝到内存
    RtlCopyMemory(lpBaseAddress, lpData, dwSizeOfHeaders);
    LPVOID lpSrcMem = NULL;
    LPVOID lpDestMem = NULL;
    DWORD dwSizeOfRawData = 0;
    //循环加载所有区段
    for (WORD i = 0; i < wNumberOfSections; i++)
    {
        //过滤掉无效区段
        if (0 == pSectionHeader->VirtualAddress || 0 == pSectionHeader->SizeOfRawData)
        {
            pSectionHeader++;
            continue;
        }
        //获取区段在文件中的位置
        lpSrcMem = (LPVOID)((DWORD)lpData + pSectionHeader->PointerToRawData);
        //获取区段映射到内存中的位置
        lpDestMem = (LPVOID)((DWORD)lpBaseAddress + pSectionHeader->VirtualAddress);
        //获取区段在文件中的大小
        dwSizeOfRawData = pSectionHeader->SizeOfRawData;
        //将区段数据拷贝到内存中
        RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData);
        //获取下一个区段头(属性)
        pSectionHeader++;
    }
    return TRUE;
}


    //************************************
    // 函数名:  CStartDlg::DoRelocationTable
    // 返回类型:   BOOL
    // 功能: 修改PE文件的重定位表信息
    // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
    //************************************
BOOL CStartDlg::DoRelocationTable(LPVOID lpBaseAddress)
{
    //获取Dos头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
    //获取NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    //获取重定位表的地址
    PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pDosHeader + 
        pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

    //注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址

    //判断是否有重定位表
    if ((PVOID)pReloc == (PVOID)pDosHeader)
    {
        //没有重定位表
        return TRUE;
    }

    int nNumberOfReloc = 0;
    WORD* pRelocData = NULL;
    DWORD* pAddress = NULL;
    //开始修复重定位
    while (pReloc->VirtualAddress != 0 && pReloc->SizeOfBlock != 0)
    {
        //计算本区域(每一个描述了4KB大小的区域的重定位信息)需要修正的重定位项的数量
        nNumberOfReloc = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

        for (int i = 0; i < nNumberOfReloc; i++)
        {
            //获取IMAGE_BASE_RELOCATION结构后面的数据的地址
            pRelocData = (WORD*)((DWORD)pReloc + sizeof(IMAGE_BASE_RELOCATION));

            //每个WORD由两部分组成,高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
            //大部分重定位属性值都是0x3
            //低12位是相对于IMAGE_BASE_RELOCATION中第一个元素VirtualAddress描述位置的偏移
            //找出需要修正的地址
            if ((WORD)(pRelocData[i] & 0xF000) == 0x3000)
            {
                //获取需要修正数据的地址,    按位与计算优先级比加减乘除低
                pAddress = (DWORD*)((DWORD)pDosHeader + pReloc->VirtualAddress + (pRelocData[i] & 0x0FFF));
                //进行修改
                *pAddress += (DWORD)pDosHeader - pNtHeader->OptionalHeader.ImageBase;
            }
        }

        //下一个重定位块
        pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);
    }
    return TRUE;
}



    //************************************
    // 函数名:  CStartDlg::DoImportTable
    // 返回类型:   BOOL
    // 功能: 填写PE文件的导入表信息
    // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
    //************************************
BOOL CStartDlg::DoImportTable(LPVOID lpBaseAddress)
{
    //获取Dos头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
    //获取NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    //获取导入表地址
    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + 
        pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    char* pDllName = nullptr;
    HMODULE hDll = NULL;
    PIMAGE_THUNK_DATA pIat = NULL;
    FARPROC pFuncAddress = NULL;
    PIMAGE_IMPORT_BY_NAME pImportByName = NULL;

    //循环遍历导入表
    while (pImport->Name)
    {
        //获取导入表中的Dll名称
        pDllName = (char*)((DWORD)pDosHeader + pImport->Name);
        //检索Dll模块获取模块句柄
        hDll = GetModuleHandleA(pDllName);
        //获取失败
        if (NULL == hDll)
        {
            //加载Dll模块获取模块句柄
            hDll = LoadLibraryA(pDllName);
            //加载失败
            if (NULL == hDll)
            {
                pImport++;
                continue;
            }
        }
        
        //获取IAT
        pIat = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImport->FirstThunk);

        //遍历IAT中函数
        while (pIat->u1.Ordinal)
        {
            //判断导入的函数是名称导入还是序号导入
            //判断最高位是否为1,如果是1那么是序号导入
            if (pIat->u1.Ordinal & 0x80000000)
            {
                //获取函数地址
                pFuncAddress = GetProcAddress(hDll, (LPCSTR)(pIat->u1.Ordinal & 0x7FFFFFFF));

            }
                //名称导入
            else
            {
                //获取IMAGE_IMPORT_BY_NAME结构
                pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + pIat->u1.AddressOfData);
                //获取函数地址
                pFuncAddress = GetProcAddress(hDll, pImportByName->Name);
            }
            //将函数地址填入到IAT中
            pIat->u1.Function = (DWORD)pFuncAddress;
            pIat++;
        }
        pImport++;
    }

    return TRUE;
}




    //************************************
    // 函数名:  CStartDlg::SetImageBase
    // 返回类型:   BOOL
    // 功能: 修改PE文件的加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
    // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
    //************************************
BOOL CStartDlg::SetImageBase(LPVOID lpBaseAddress)
{
    //获取Dos头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
    //获取NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
    //修改默认加载基址
    pNtHeader->OptionalHeader.ImageBase = (DWORD)lpBaseAddress;
    return TRUE;
}



    //************************************
    // 函数名:  CStartDlg::CallDllMain
    // 返回类型:   BOOL
    // 功能: 调用PE文件的入口函数
    // 参数1: LPVOID lpBaseAddress    映像对齐后的文件基址
    // 参数2: BOOL IsExe    文件属性标志,TRUE表示exe文件,FALSE表示dll文件
    //************************************
BOOL CStartDlg::CallDllMain(LPVOID lpBaseAddress,BOOL IsExe)
{
    //定义函数指针变量
    typedef_DllMain DllMain = NULL;
    typedef_wWinMain MyWinMain = NULL;

    //获取Dos头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
    //获取NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

    BOOL bRet = TRUE;
    //如果是exe文件
    if (IsExe)
    {
        MessageBox(_T("有问题,待解决"));
        //MyWinMain = (typedef_wWinMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint+0xF8D);
        //bRet = MyWinMain((HINSTANCE)lpBaseAddress, NULL, NULL, SW_SHOWNORMAL);
    }
    //dll 文件
    else {
        DllMain = (typedef_DllMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint);
        //调用入口函数,附加进程DLL_PROCESS_ATTACH
        bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);
    }

    return bRet;
}

 

  

 

posted @ 2020-05-15 20:13  自己的小白  阅读(6844)  评论(4编辑  收藏  举报