c++ 在内存中加载 exe/dll (不使用CreateProcess、LoadLibrary 等 API)

背景

在网上搜索了很多病毒木马的分析报告,看了一段时间后,发现还是有很多病毒木马都能够模拟PE加载器,把DLL或者是EXE等PE文件,直接从内存中直接加载到自己的内存中执行,不需要通过API函数去操作,以此躲过一些杀软的检测。

程序实现原理(以exe 为例,dll 同理)

  • 首先,在EXE文件中,根据PE结构格式获取其加载映像的大小SizeOfImage,并根据SizeOfImage在自己的程序中申请一块可读、可写、可执行的内存,那么这块内存的首地址就是EXE程序的加载基址
  • 然后,根据EXE中的PE结构格式获取其映像对齐大小SectionAlignment,然后把EXE文件数据按照SectionAlignment对齐大小拷贝到上述申请的可读、可写、可执行的内存中
  • 接着,根据PE结构的重定位表,重新对重定位表进行修正
  • 接着,根据PE结构的导入表,加载所需的DLL,并获取导入表导入函数的地址并写入导入表中
  • 接着,修改EXE的加载基址ImageBase
  • 最后,根据PE结构获取EXE的入口地址AddressOfEntryPoint,然后跳转到入口地址处继续执行

这样,EXE就成功加载到程序中并运行起来了。要注意的一个问题就是,并不是所有的EXE都有重定位表,对于没有重定位表的EXE程序,那就不适用于本文介绍的方法。

编码实现(exe)

    // 模拟PE加载器加载内存EXE文件到进程中
    // lpData: 内存EXE文件数据的基址
    // dwSize: 内存EXE文件的内存大小
    // 返回值: 内存EXE加载到进程的加载基址
    LPVOID MmRunExe(LPVOID lpData, DWORD dwSize)
    {
        LPVOID lpBaseAddress = NULL;
        // 获取镜像大小
        DWORD dwSizeOfImage = GetSizeOfImage(lpData);
        // 在进程中开辟一个可读、可写、可执行的内存块
        lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (NULL == lpBaseAddress)
        {
            ShowError("VirtualAlloc");
            return NULL;
        }
        ::RtlZeroMemory(lpBaseAddress, dwSizeOfImage);
        // 将内存PE数据按SectionAlignment大小对齐映射到进程内存中
        if (FALSE == MmMapFile(lpData, lpBaseAddress))
        {
            ShowError("MmMapFile");
            return NULL;
        }
        // 修改PE文件重定位表信息
        if (FALSE == DoRelocationTable(lpBaseAddress))
        {
            ShowError("DoRelocationTable");
            return NULL;
        }
        // 填写PE文件导入表信息
        if (FALSE == DoImportTable(lpBaseAddress))
        {
            ShowError("DoImportTable");
            return NULL;
        }
        //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。
        //统一设置成一个属性PAGE_EXECUTE_READWRITE
        DWORD dwOldProtect = 0;
        if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
        {
            ShowError("VirtualProtect");
            return NULL;
        }
        // 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
        if (FALSE == SetImageBase(lpBaseAddress))
        {
            ShowError("SetImageBase");
            return NULL;
        }
        // 跳转到PE的入口点处执行, 函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint
        if (FALSE == CallExeEntry(lpBaseAddress))
        {
            ShowError("CallExeEntry");
            return NULL;
        }
        return lpBaseAddress;
    }


    int _tmain(int argc, _TCHAR* argv[])
    {
        char szFileName[] = "KuaiZip_Setup_2.8.28.8.exe";
        // 打开EXE文件并获取EXE文件大小
        HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_ARCHIVE, NULL);
        if (INVALID_HANDLE_VALUE == hFile)
        {
            ShowError("CreateFile");
            return 1;
        }
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        // 申请动态内存并读取DLL到内存中
        BYTE *pData = new BYTE[dwFileSize];
        if (NULL == pData)
        {
            ShowError("new");
            return 2;
        }
        DWORD dwRet = 0;
        ReadFile(hFile, pData, dwFileSize, &dwRet, NULL);
        CloseHandle(hFile);
        // 判断有无重定位表
        if (FALSE == IsExistRelocationTable(pData))
        {
            printf("[FALSE] IsExistRelocationTable\n");
            system("pause");
            return 0;
        }
        // 将内存DLL加载到程序中
        LPVOID lpBaseAddress = MmRunExe(pData, dwFileSize);
        if (NULL == lpBaseAddress)
        {
            ShowError("MmRunExe");
            return 3;
        }
        system("pause");
        return 0;
    }

效果图

在这里插入图片描述


编码实现(dll)

    // 模拟LoadLibrary加载内存DLL文件到进程中
    // lpData: 内存DLL文件数据的基址
    // dwSize: 内存DLL文件的内存大小
    // 返回值: 内存DLL加载到进程的加载基址
    LPVOID MmLoadLibrary(LPVOID lpData, DWORD dwSize)
    {
        LPVOID lpBaseAddress = NULL;
        // 获取镜像大小
        DWORD dwSizeOfImage = GetSizeOfImage(lpData);
        // 在进程中开辟一个可读、可写、可执行的内存块
        lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (NULL == lpBaseAddress)
        {
            ShowError("VirtualAlloc");
            return NULL;
        }
        ::RtlZeroMemory(lpBaseAddress, dwSizeOfImage);
        // 将内存DLL数据按SectionAlignment大小对齐映射到进程内存中
        if (FALSE == MmMapFile(lpData, lpBaseAddress))
        {
            ShowError("MmMapFile");
            return NULL;
        }
        // 修改PE文件重定位表信息
        if(FALSE == DoRelocationTable(lpBaseAddress))
        {
            ShowError("DoRelocationTable");
            return NULL;
        }
        // 填写PE文件导入表信息
        if (FALSE == DoImportTable(lpBaseAddress))
        {
            ShowError("DoImportTable");
            return NULL;
        }
        //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。
        //统一设置成一个属性PAGE_EXECUTE_READWRITE
        DWORD dwOldProtect = 0;
        if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
        {
            ShowError("VirtualProtect");
            return NULL;
        }
        // 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
        if (FALSE == SetImageBase(lpBaseAddress))
        {
            ShowError("SetImageBase");
            return NULL;
        }
        // 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint
        if (FALSE == CallDllMain(lpBaseAddress))
        {
            ShowError("CallDllMain");
            return NULL;
        }
        return lpBaseAddress;
    }

    // 模拟GetProcAddress获取内存DLL的导出函数
    // lpBaseAddress: 内存DLL文件加载到进程中的加载基址
    // lpszFuncName: 导出函数的名字
    // 返回值: 返回导出函数的的地址
    LPVOID MmGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName)
    {
        LPVOID lpFunc = NULL;
        // 获取导出表
        PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
        PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
        PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        // 获取导出表的数据
        PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfNames);
        PCHAR lpFuncName = NULL;
        PWORD lpAddressOfNameOrdinalsArray = (PWORD)((DWORD)pDosHeader + pExportTable->AddressOfNameOrdinals);
        WORD wHint = 0;
        PDWORD lpAddressOfFunctionsArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfFunctions);
        DWORD dwNumberOfNames = pExportTable->NumberOfNames;
        DWORD i = 0;
        // 遍历导出表的导出函数的名称, 并进行匹配
        for (i = 0; i < dwNumberOfNames; i++)
        {
            lpFuncName = (PCHAR)((DWORD)pDosHeader + lpAddressOfNamesArray[i]);
            if (0 == ::lstrcmpi(lpFuncName, lpszFuncName))
            {
                // 获取导出函数地址
                wHint = lpAddressOfNameOrdinalsArray[i];
                lpFunc = (LPVOID)((DWORD)pDosHeader + lpAddressOfFunctionsArray[wHint]);
                break;
            }
        }
        return lpFunc;
    }

    // 释放从内存加载的DLL到进程内存的空间
    // lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
    // 返回值: 成功返回TRUE,否则返回FALSE
    BOOL MmFreeLibrary(LPVOID lpBaseAddress)
    {
        BOOL bRet = FALSE;
        if (NULL == lpBaseAddress)
        {
            return bRet;
        }
        bRet = ::VirtualFree(lpBaseAddress, 0, MEM_RELEASE);
        lpBaseAddress = NULL;
        return bRet;
    }


我们编写一个用来测试的DLL程序TestDll,导出函数的代码如下所示:

    BOOL ShowMessage(char *lpszText, char *lpszCaption)
    {
        ::MessageBox(NULL, lpszText, lpszCaption, MB_OK);
        return TRUE;
    }

在 main 函数中,调用上述封装好的函数接口进行测试。main 函数为:

    int _tmain(int argc, _TCHAR* argv[])
    {
        char szFileName[MAX_PATH] = "TestDll.dll";
        // 打开DLL文件并获取DLL文件大小
        HANDLE hFile = ::CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_ARCHIVE, NULL);
        if (INVALID_HANDLE_VALUE == hFile)
        {
            ShowError("CreateFile");
            return 1;
        }
        DWORD dwFileSize = ::GetFileSize(hFile, NULL);
        // 申请动态内存并读取DLL到内存中
        BYTE *lpData = new BYTE[dwFileSize];
        if (NULL == lpData)
        {
            ShowError("new");
            return 2;
        }
        DWORD dwRet = 0;
        ::ReadFile(hFile, lpData, dwFileSize, &dwRet, NULL);
        // 将内存DLL加载到程序中
        LPVOID lpBaseAddress = MmLoadLibrary(lpData, dwFileSize);
        if (NULL == lpBaseAddress)
        {
            ShowError("MmLoadLibrary");
            return 3;
        }
        printf("DLL加载成功\n");
        // 获取DLL导出函数并调用
        typedef BOOL(*typedef_ShowMessage)(char *lpszText, char *lpszCaption);
        typedef_ShowMessage ShowMessage = (typedef_ShowMessage)MmGetProcAddress(lpBaseAddress, "ShowMessage");
        if (NULL == ShowMessage)
        {
            ShowError("MmGetProcAddress");
            return 4;
        }
        ShowMessage("CDIY - www.coderdiy.com - 专注计算机技术交流分享\n", "CDIY");
        // 释放从内存加载的DLL
        BOOL bRet = MmFreeLibrary(lpBaseAddress);
        if (FALSE == bRet)
        {
            ShowError("MmFreeLirbary");
        }
        // 释放
        delete[] lpData;
        lpData = NULL;
        ::CloseHandle(hFile);
        system("pause");
        return 0;
    }

效果图

在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值