文章目录
- MemoryModule - 应用编程细节
- 概述
- 笔记
- 实验环境
- 升级MemoryModule,在上下文中加入DLL在内存载入前的信息
- MemoryModule.h
- MemoryModule.cpp
- 实现接口MemoryGetPayload()
- 整理 - 在内存载入的DLL中,取得资源表中的信息,取得载入前的DLL内容载荷
- 备注
- END
MemoryModule - 应用编程细节
概述
MemoryModule将DLL从内存载入,试过了好使。
现在有如下需求:
- 在内存载入的DLL中,对自身对应的载入前实体做完整性的hash校验。
- 在内存载入的DLL中,取得资源信息(文件版本资源中的版本信息等,字符串资源)
需求1,官方没提供内存载入之前的实体信息,这个要自己改,在CTX上加入载入前的信息(pData, lenData)
需求2,官方演示了前半段,演示的不全。需要自己整理一下。
笔记
实验环境
vs2019 x64 unicode debug mfc dlg
升级MemoryModule,在上下文中加入DLL在内存载入前的信息
MemoryModule.h
/*** Get address of exported method. Supports loading both by name and by* ordinal value.*/
FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR);// 增加接口, 取DLL在内存中载入前的载荷(pData, lenData)
// 有了这个接口, 在DLL中就可以拿载入前的DLL数据做完整性校验(可以知道是不是被逆向工程师改了)
// 这个接口的好处,不用将内存载入前的信息(pData, lenData)通过DLL接口传进去,那样比较扎眼。
// 有了这个接口,只需要DLL提供一个接口,将上下文HMEMORYMODULE hDllWasLoad传进去。不容易被人家改
bool MemoryGetPayload(HMEMORYMODULE hDllWasLoad, uint8_t** ppData, int* pLen);
MemoryModule.cpp
将 MemoryModule.c 改名为MemoryModule.cpp
#include "pch.h" // add this for vs2019 build#include <windows.h>
#include <winnt.h>
#include <stddef.h>
#include <stdint.h> // add this for uint8_t
#include <tchar.h>
#ifdef DEBUG_OUTPUT
#include <stdio.h>
#endif
typedef struct {PIMAGE_NT_HEADERS headers;unsigned char *codeBase;HCUSTOMMODULE *modules;int numModules;BOOL initialized;BOOL isDLL;BOOL isRelocated;CustomAllocFunc alloc;CustomFreeFunc free;CustomLoadLibraryFunc loadLibrary;CustomGetProcAddressFunc getProcAddress;CustomFreeLibraryFunc freeLibrary;struct ExportNameEntry *nameExportsTable;void *userdata;ExeEntryProc exeEntry;DWORD pageSize;
#ifdef _WIN64POINTER_LIST *blockedMemory;
#endifuint8_t* pDataInByLoad; // add DLL payload info(pData, lenData) on MEMORYMODULE define's tailint lenDataInByLoad;
} MEMORYMODULE, *PMEMORYMODULE;
修正OutputLastError()的编译错误(工程为vs2019 + unicode)
static inline void
OutputLastError(const char *msg)
{
#ifndef DEBUG_OUTPUTUNREFERENCED_PARAMETER(msg);
#elseLPVOID tmp;char *tmpmsg;FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL);tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen((const char*)tmp) + 3); // bugfixsprintf(tmpmsg, "%s: %s", msg, (const char*)tmp); // bugfixOutputDebugStringA(tmpmsg); // bugfixLocalFree(tmpmsg);LocalFree(tmp);
#endif
}
升级MemoryLoadLibraryEx()
HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size,CustomAllocFunc allocMemory,CustomFreeFunc freeMemory,CustomLoadLibraryFunc loadLibrary,CustomGetProcAddressFunc getProcAddress,CustomFreeLibraryFunc freeLibrary,void *userdata)
{
// ...result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MEMORYMODULE));if (result == NULL) {freeMemory(code, 0, MEM_RELEASE, userdata);
#ifdef _WIN64FreePointerList(blockedMemory, freeMemory, userdata);
#endifSetLastError(ERROR_OUTOFMEMORY);return NULL;}result->pDataInByLoad = (uint8_t*)data; // add thisresult->lenDataInByLoad = (int)size; // add thisresult->codeBase = code;result->isDLL = (old_header->FileHeader.Characteristics & IMAGE_FILE_DLL) != 0;result->alloc = allocMemory;result->free = freeMemory;result->loadLibrary = loadLibrary;result->getProcAddress = getProcAddress;result->freeLibrary = freeLibrary;result->userdata = userdata;result->pageSize = sysInfo.dwPageSize;
#ifdef _WIN64result->blockedMemory = blockedMemory;
#endif
// ...
}
实现接口MemoryGetPayload()
bool MemoryGetPayload(HMEMORYMODULE hDllWasLoad, uint8_t** ppData, int* pLen)
{bool b_rc = false;PMEMORYMODULE hDllCtx = NULL;do {if ((NULL == hDllWasLoad) || (NULL == ppData) || (NULL == pLen)){break;}hDllCtx = (PMEMORYMODULE)hDllWasLoad;*ppData = hDllCtx->pDataInByLoad;*pLen = hDllCtx->lenDataInByLoad;b_rc = true;} while (false);return b_rc;
}
整理 - 在内存载入的DLL中,取得资源表中的信息,取得载入前的DLL内容载荷
以下是刚做完实验的草稿代码,能看出以上2个需求的实现了。
再做整理,就可以是正式的代码。
// extern "C" __declspec(dllexport) bool APIENTRY test(HMEMORYMODULE hDll)
typedef bool (APIENTRY* PFN_test)(HMEMORYMODULE hDll);
// DLL定义了一个接口 e.g. init(HMEMORYMODULE hDll), 只要调用这个接口,就可以在接口内知道DLL在内存载入前的载荷,和载入后的资源信息(STRING by resource ID, FILE VERSION(e.g. 内部exe名称))#pragma comment(lib, "Version.lib") // VerQueryValue// _T("CompanyName")
CString get_FileInfoValue(BYTE* byData, const TCHAR* pszValueName)
{CString csQuery;CString csRc;TCHAR* lpVers = NULL;unsigned int uLen = 0;std::string strA;std::wstring strW;_ASSERT(NULL != pszValueName);// _T("\\StringFileInfo\\%s\\%s") // 拼接要查询的文件版本信息的字符串csQuery.Format(_T("\\StringFileInfo\\%s\\%s"), _T("080404b0"), pszValueName);_ASSERT(NULL != byData);// \StringFileInfo\080404b0\CompanyName// 取得了文件资源区内容后,就可以直接用WIN32API VerQueryValue(), 这个API和DLL载入的基地址没关系,只和文件版本信息的具体数据有关系。VerQueryValue(byData, (LPCWSTR)csQuery, (void**)&lpVers, &uLen);if ((uLen > 0) && (NULL != lpVers)){csRc = lpVers;}return csRc;
}void CLoaderDlg::OnBnClickedButton1() {uint8_t* pBuf = NULL;int len = 0;bool b = false;HMEMORYMODULE handle = NULL;PFN_test pfn_test = NULL;HMEMORYRSRC hRc = NULL;DWORD resourceSize = 0;LPVOID resourceData = NULL;CString csTmp;int i_rc = 0;TCHAR buffer[MAX_PATH + 1];do {len = sizeof(ucAry_ary_DllForTest_x64Debug);DLOG(INFO) << "sizeof(ucAry_ary_DllForTest_x64Debug) = " << len;pBuf = new uint8_t[len + 1];assert(NULL != pBuf);pBuf[len] = '\0';memcpy(pBuf, ucAry_ary_DllForTest_x64Debug, len);DLOG(INFO) << "pBuf = " << (void*)pBuf << ", len = " << len;handle = MemoryLoadLibrary(pBuf, len);if (NULL == handle) {DLOG(INFO) << "Can't load library from memory";assert(false);break;}DLOG(INFO) << "handle = " << handle;// HMEMORYMODULE module, LPCTSTR name, LPCTSTR type, WORD languagehRc = MemoryFindResource(handle, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);if (NULL == hRc){DLOG(INFO) << hRc << " = MemoryFindResourceEx()";break;}resourceSize = MemorySizeofResource(handle, hRc);resourceData = MemoryLoadResource(handle, hRc);// 已经验证过了,好使csTmp = get_FileInfoValue((BYTE*)resourceData, _T("CompanyName"));// csTmp is _T("TODO: <公司名>")// IDS_STRING102// 已经验证过了,好使i_rc = MemoryLoadString(handle, 102, buffer, sizeof(buffer));// i_rc = 6, buffer = _T("测试字符串2")if (i_rc >= 0){buffer[i_rc] = TEXT('\0');}pfn_test = (PFN_test)MemoryGetProcAddress(handle, "test");if (NULL == pfn_test) {break;}// 已经验证过了,只要内存载入DLL成功,MemoryModule库的API在DLL外部调用和接口内部调用,都是正确的。// 因为EXE和DLL在一个进程空间内, 内存指针都是一个(只要内存指针有效,在DLL外部和DLl内部访问的handle(从内存载入DLL的上下文句柄)都是同一个)b = pfn_test(handle);DLOG(INFO) << "pfn_test() = " << b;} while (false);if (NULL != handle) {MemoryFreeLibrary(handle);handle = NULL;}if (NULL != pBuf) {delete[] pBuf;pBuf = NULL;}
}
备注
刚开始,只是简单的用MemoryModule从内存载入DLL
后来有新需求时,如果不借助MemoryModule,而是自己取东西,那得对PE结构有多熟悉才能搞定啊。
查了很多资料,都不理想。
再想想,既然MemoryModule都从内存载入了DLL, 执行DLL接口都好使。那这些PE内部信息,MemoryModule的载入操作应该都门清啊。
回头来看,果真如此。
如果不满足于使用第三方库,而是尽力再了解的深入一些(应该比普通应用再深入一点就行),就有很多好东西可以挖出来用。