// ImageResource.h: 不经加载器,直接从内存中的模块映像读取资源 // // 加壳部署(EXE + SC_AES/sRDI)下,载荷映像以裸 shellcode 运行、未注册进加载器, // FindResource/LoadIcon/CreateDialogParamA 这类按 hInstance 找资源的 API 全部失败 // (实测 Win11 返回 err=1812 / 59)。但映像的节(含 .rsrc)是按 PE 布局正常映射 // 的,资源目录里的偏移又都是 RVA,因此可以直接遍历 IMAGE_RESOURCE_DIRECTORY // 取到资源数据。本文件只读调用方给出的映像,不做任何映射。 // // 适用:正常 EXE/DLL、MemoryModule 手工映射 DLL、sRDI 包装的 shellcode 载荷。 // 注意:调用方传入的必须是已映射映像的基址(如 &__ImageBase),不是磁盘文件。 // // 稳定性:所有偏移都按 SizeOfImage / 资源目录大小做越界检查,映像布局异常 // (被第三方加壳改坏、资源被裁剪等)时一律返回 NULL,绝不越界读取。 #ifndef YAMA_IMAGE_RESOURCE_H #define YAMA_IMAGE_RESOURCE_H #include #ifdef __cplusplus extern "C" const IMAGE_DOS_HEADER __ImageBase; #endif // RT_ICON / RT_GROUP_ICON 的数字 ID(与同名 RT_ 宏对应) #define IMAGE_RES_TYPE_ICON 3 #define IMAGE_RES_TYPE_GROUP_ICON 14 #pragma pack(push, 1) typedef struct GRPICONDIRENTRY { BYTE bWidth; BYTE bHeight; BYTE bColorCount; BYTE bReserved; WORD wPlanes; WORD wBitCount; DWORD dwBytesInRes; WORD nID; } GRPICONDIRENTRY; #pragma pack(pop) #define IMAGE_RES_DIR_IN_BOUNDS(resOffset, resSize) \ ((resOffset) <= (resSize) - sizeof(IMAGE_RESOURCE_DIRECTORY)) #define IMAGE_RES_ENTRY_ARRAY_OK(resOffset, resCount, resSize) \ ((resCount) <= ((resSize) - (resOffset) - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)) // 在已映射映像中查找 type/id(取第一个语言项)的资源数据,返回映像内指针 static const BYTE* FindImageResource(const void* pvImage, WORD nType, WORD nId, DWORD* pdwSize) { if (pdwSize) *pdwSize = 0; if (!pvImage) return NULL; const BYTE* pBase = (const BYTE*)pvImage; const DWORD dwLfanew = *(const DWORD*)(pBase + 0x3C); // 正常 PE 头远小于一页;sRDI 的 CLEARHEADER 只保留 PE 头(e_lfanew 之后), // 所以这里不能用 MZ 判断,只能靠 e_lfanew 与 PE 签名 if (dwLfanew == 0 || dwLfanew > 0x1000) return NULL; const PIMAGE_NT_HEADERS pNt = (const PIMAGE_NT_HEADERS)(pBase + dwLfanew); if (pNt->Signature != IMAGE_NT_SIGNATURE) return NULL; const DWORD dwImageSize = pNt->OptionalHeader.SizeOfImage; const DWORD dwResRva = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; const DWORD dwResSize = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size; if (dwResRva == 0 || dwResSize < sizeof(IMAGE_RESOURCE_DIRECTORY) || dwResRva > dwImageSize || dwResSize > dwImageSize - dwResRva) { return NULL; } const BYTE* pRes = pBase + dwResRva; // 已映射映像:资源目录与数据项偏移都是相对资源节起点的偏移 / RVA // 目录三层:类型 -> 名称/ID -> 语言 const WORD keys[2] = { nType, nId }; DWORD dwDirOff = 0; for (int i = 0; i < 2; i++) { if (!IMAGE_RES_DIR_IN_BOUNDS(dwDirOff, dwResSize)) return NULL; const IMAGE_RESOURCE_DIRECTORY* pRd = (const IMAGE_RESOURCE_DIRECTORY*)(pRes + dwDirOff); const DWORD dwCount = (DWORD)pRd->NumberOfNamedEntries + pRd->NumberOfIdEntries; if (!IMAGE_RES_ENTRY_ARRAY_OK(dwDirOff, dwCount, dwResSize)) return NULL; const IMAGE_RESOURCE_DIRECTORY_ENTRY* pEntry = (const IMAGE_RESOURCE_DIRECTORY_ENTRY*)(pRd + 1); const IMAGE_RESOURCE_DIRECTORY_ENTRY* pFound = NULL; for (DWORD j = 0; j < dwCount; j++) { // 只按数字 ID 匹配;命名项(NameIsString)跳过 if (!(pEntry[j].Name & IMAGE_RESOURCE_NAME_IS_STRING) && pEntry[j].Id == keys[i]) { pFound = &pEntry[j]; break; } } if (!pFound || !pFound->DataIsDirectory) return NULL; dwDirOff = pFound->OffsetToDirectory; } if (!IMAGE_RES_DIR_IN_BOUNDS(dwDirOff, dwResSize)) return NULL; const IMAGE_RESOURCE_DIRECTORY* pLangDir = (const IMAGE_RESOURCE_DIRECTORY*)(pRes + dwDirOff); const DWORD dwLangCount = (DWORD)pLangDir->NumberOfNamedEntries + pLangDir->NumberOfIdEntries; if (dwLangCount == 0 || !IMAGE_RES_ENTRY_ARRAY_OK(dwDirOff, dwLangCount, dwResSize)) return NULL; const IMAGE_RESOURCE_DIRECTORY_ENTRY* pLang = (const IMAGE_RESOURCE_DIRECTORY_ENTRY*)(pLangDir + 1); if (pLang->DataIsDirectory) return NULL; const DWORD dwDataOff = pLang->OffsetToData; if (dwDataOff > dwResSize - sizeof(IMAGE_RESOURCE_DATA_ENTRY)) return NULL; const IMAGE_RESOURCE_DATA_ENTRY* pData = (const IMAGE_RESOURCE_DATA_ENTRY*)(pRes + dwDataOff); const DWORD dwRva = pData->OffsetToData; const DWORD dwBytes = pData->Size; if (dwRva > dwImageSize || dwBytes > dwImageSize - dwRva) return NULL; if (pdwSize) *pdwSize = dwBytes; return pBase + dwRva; // OffsetToData 是 RVA } // 按图标组(RT_GROUP_ICON)ID 建 HICON:选尺寸最接近 nWidth x nHeight 的一帧, // 用 CreateIconFromResourceEx 创建。返回的 HICON 由调用方负责 DestroyIcon。 static HICON CreateImageIcon(const void* pvImage, WORD nGroupId, int nWidth, int nHeight) { DWORD dwGroupSize = 0; const BYTE* pGroup = FindImageResource(pvImage, IMAGE_RES_TYPE_GROUP_ICON, nGroupId, &dwGroupSize); if (!pGroup || dwGroupSize < 6 + sizeof(GRPICONDIRENTRY)) return NULL; // GRPICONDIR: WORD idReserved, idType, idCount const WORD nCount = *(const WORD*)(pGroup + 4); if (*(const WORD*)(pGroup + 2) != 1 || nCount == 0) return NULL; if (dwGroupSize < 6 + (DWORD)nCount * sizeof(GRPICONDIRENTRY)) return NULL; const GRPICONDIRENTRY* pBest = NULL; int nBestDiff = 0x7FFFFFFF; for (WORD i = 0; i < nCount; i++) { const GRPICONDIRENTRY* pEntry = (const GRPICONDIRENTRY*)(pGroup + 6 + i * sizeof(GRPICONDIRENTRY)); const int w = pEntry->bWidth ? pEntry->bWidth : 256; // 0 表示 256 const int h = pEntry->bHeight ? pEntry->bHeight : 256; const int dx = w > nWidth ? w - nWidth : nWidth - w; const int dy = h > nHeight ? h - nHeight : nHeight - h; if (dx + dy < nBestDiff) { nBestDiff = dx + dy; pBest = pEntry; } } if (!pBest) return NULL; DWORD dwIconSize = 0; const BYTE* pIcon = FindImageResource(pvImage, IMAGE_RES_TYPE_ICON, pBest->nID, &dwIconSize); if (!pIcon || dwIconSize == 0) return NULL; return (HICON)CreateIconFromResourceEx((PBYTE)pIcon, dwIconSize, TRUE, 0x00030000, nWidth, nHeight, LR_DEFAULTCOLOR); } #endif // YAMA_IMAGE_RESOURCE_H