Clients packaged as SC_AES/sRDI shellcode run the payload image as raw shellcode inside the loader stub, so the chat manager was handed the stub's module handle. The stub's resource section only carries a manifest, and Windows resource APIs reject manually mapped images outright, so creating IDD_CHAT failed and the chat thread exited before the window ever existed. The chat window now falls back to an embedded compiled dialog template that is byte-identical to the IDD_CHAT resource, and the title-bar icon is rebuilt directly from the module's own resource directory instead of LoadIcon, bypassing the loader on both paths. The PE walking code checks every offset against SizeOfImage and the resource directory size, so a malformed or truncated image returns NULL instead of faulting. Deployments where the resource lookup already works keep the original code path unchanged. The dialog is now created and driven with the wide-character API. The client is still an MBCS build, but an ANSI dialog renders text with the system code page, so GBK chat text turned into mojibake on German and other non-936 systems. Incoming text is converted CP936 to UTF-16 and outgoing text strictly back to CP936; characters GBK cannot represent (emoji, accented letters) are refused with a visible notice instead of silently becoming '?'. Static labels follow the system UI language, and IDD_CHAT now uses the scalable MS Shell Dlg face so font linking can supply CJK glyphs. The window is now positioned explicitly as well. DS_CENTER in the template put it at the work area origin on the three-monitor host: the centred position came out negative there and Windows clamps out-of-range creation coordinates to that origin. It is centred in the work area of the monitor under the cursor instead, falling back to SPI_GETWORKAREA. Teardown no longer leaves a hidden window behind either. The dialog thread blocked in GetMessageW and never saw m_bRun cleared, while the destructor waited for that thread before sending the message that would have destroyed the window, so a close command that never arrived (the sub-connection already gone, or WaitSendIdle timing out before CancelIO) leaked the window, the dialog thread and the manager loop. The destructor now destroys the window first, the loop checks m_bRun before blocking, and it destroys the window itself if it exits first. Co-Authored-By: deepseek-flash
148 lines
6.8 KiB
C
148 lines
6.8 KiB
C
// 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 <windows.h>
|
||
|
||
#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
|