Files
hfiref0x 6daa8d4862 Update Yuubari and fix some bugs
Yuubari updated to 1.61. Aside of this multiple fixes and small enhancements has been made across the codebase:

- Fix path concatenation logic in supConcatenatePaths (Akagi & Yuubari): strip/handle leading/trailing backslashes correctly, avoid double separators, compute lengths safely and remove fragile boolean flags, and ensure proper null-termination.
- Fusion subsystem: add SxsAllocInitUnicodeString helper for safe allocation/copy/init of UNICODE_STRING; use it to populate KeyName and DllName in redirection list entries; extend structures to include KeyName; update FusionOutputCallback to report KeyName and free strings correctly.
- Console: replace GetTickCount timeout logic with elapsed-time check to avoid wraparound, using dwStart.
- Compress: fix CRC handling by restoring HeaderCrc after check and ensure exception handler sets failure flag (prevents silent return/possible leak).
- AIC: free async handle and RPC binding on exception to avoid resource leaks.
- Shared/cmdline: correct divider variable types for wide/ansi functions.
- Windefend: rename parameter for clarity (MpClientBase -> ImageBase).
- Fusutil: remove unused NI_DLL_EXT_SIZE/NI_DLL_AUX_EXT_SIZE defines.

These changes address correctness, resource management, and robustness issues observed in path handling, fusion parsing, exception paths and timeouts.
2026-02-17 23:36:03 +07:00

693 lines
17 KiB
C

/*******************************************************************************
*
* (C) COPYRIGHT AUTHORS, 2020 - 2026
*
* TITLE: FUSUTIL.C
*
* VERSION: 3.69
*
* DATE: 12 Feb 2026
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
*
*******************************************************************************/
#include "global.h"
/*
* fusUtilInitFusion
*
* Purpose:
*
* Load .NET Assembly Manager dll and remember function pointers.
*
*/
BOOLEAN fusUtilInitFusion(
_In_ DWORD dwVersion
)
{
HMODULE hFusion;
LPCWSTR lpFusionDir;
pfnCreateAssemblyCache CreateAssemblyCache;
pfnCreateAssemblyEnum CreateAssemblyEnum;
WCHAR szBuffer[MAX_PATH * 2];
if (g_ctx->FusionContext.Initialized)
return TRUE;
if (dwVersion != 2 && dwVersion != 4)
return FALSE;
//
// Build path to assembly manager dll
//
_strcpy(szBuffer, g_ctx->szSystemRoot);
_strcat(szBuffer, MSNETFRAMEWORK_DIR);
#ifdef _WIN64
_strcat(szBuffer, TEXT("64"));
#endif
if (dwVersion == 2)
lpFusionDir = NET2_DIR;
else
lpFusionDir = NET4_DIR;
supConcatenatePaths(szBuffer, lpFusionDir, ARRAYSIZE(szBuffer));
supConcatenatePaths(szBuffer, TEXT("fusion.dll"), ARRAYSIZE(szBuffer));
hFusion = LoadLibraryEx(szBuffer, NULL, 0);
if (hFusion == NULL)
return FALSE;
CreateAssemblyCache = (pfnCreateAssemblyCache)GetProcAddress(hFusion, "CreateAssemblyCache");
CreateAssemblyEnum = (pfnCreateAssemblyEnum)GetProcAddress(hFusion, "CreateAssemblyEnum");
if (CreateAssemblyCache == NULL ||
CreateAssemblyEnum == NULL)
{
FreeLibrary(hFusion);
return FALSE;
}
g_ctx->FusionContext.hFusion = hFusion;
g_ctx->FusionContext.CreateAssemblyCache = CreateAssemblyCache;
g_ctx->FusionContext.CreateAssemblyEnum = CreateAssemblyEnum;
g_ctx->FusionContext.Initialized = TRUE;
return TRUE;
}
/*
* fusUtilBinToUnicodeHex
*
* Purpose:
*
* Bin to Hex special edition.
*
*/
VOID fusUtilBinToUnicodeHex(
_In_ const BYTE* pSrc,
_In_ UINT cSrc,
_Out_cap_(2 * cSrc + 1) LPWSTR pDst)
{
UINT x;
UINT y;
#define TOHEX(a) (WCHAR)((a)>=10 ? L'a'+(a)-10 : L'0'+(a))
for (x = 0, y = 0; x < cSrc; ++x)
{
UINT v;
v = pSrc[x] >> 4;
pDst[y++] = TOHEX(v);
v = pSrc[x] & 0x0f;
pDst[y++] = TOHEX(v);
}
pDst[y] = L'\0';
}
/*
* fusUtilGetAssemblyName
*
* Purpose:
*
* Return assembly name.
*
* Note: Use supHeapFree to release lpAssemblyName allocated memory.
*
*/
HRESULT fusUtilGetAssemblyName(
_In_ IAssemblyName* pInterface,
_Inout_ LPWSTR* lpName,
_Out_opt_ PSIZE_T pcchName,
_Inout_opt_ LPWSTR* lpDisplayName,
_Out_opt_ PSIZE_T pcchDisplayName
)
{
DWORD cchName = 0;
HRESULT hr;
LPWSTR assemblyName = NULL, displayName = NULL;
do {
if (pcchName) *pcchName = 0;
if (pcchDisplayName) *pcchDisplayName = 0;
hr = pInterface->lpVtbl->GetName(pInterface, &cchName, NULL);
if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
break;
assemblyName = (LPWSTR)supHeapAlloc((cchName * sizeof(WCHAR)) + sizeof(UNICODE_NULL));
if (assemblyName)
hr = pInterface->lpVtbl->GetName(pInterface, &cchName, (LPOLESTR)assemblyName);
else
hr = E_OUTOFMEMORY;
if (pcchName) {
if (SUCCEEDED(hr))
*pcchName = cchName;
}
cchName = 0;
hr = pInterface->lpVtbl->GetDisplayName(pInterface, NULL, &cchName, 0);
if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
break;
displayName = (LPWSTR)supHeapAlloc((cchName * sizeof(WCHAR)) + sizeof(UNICODE_NULL));
if (displayName)
hr = pInterface->lpVtbl->GetDisplayName(pInterface, (LPOLESTR)displayName, &cchName, 0);
else
hr = E_OUTOFMEMORY;
if (pcchDisplayName) {
if (SUCCEEDED(hr))
*pcchDisplayName = cchName;
}
} while (FALSE);
*lpName = assemblyName;
if (lpDisplayName)
*lpDisplayName = displayName;
return hr;
}
/*
* fusUtilGetAssemblyMVIDFromZapCache
*
* Purpose:
*
* Query cache zap assembly mvid.
*
*/
BOOL fusUtilGetAssemblyMVIDFromZapCache(
_In_ LPCWSTR AssemblyName,
_Inout_ GUID* ModuleVersionId
)
{
BOOL bFound = FALSE, bResult = FALSE;
HRESULT hr;
IAssemblyEnum* asmEnum = NULL;
IAssemblyName* asmName = NULL;
LPWSTR lpAssemblyName = NULL;
DWORD dwSize;
do {
hr = g_ctx->FusionContext.CreateAssemblyEnum(&asmEnum, NULL, NULL, ASM_CACHE_ZAP, NULL);
if ((FAILED(hr)) || (asmEnum == NULL))
break;
//
// Locate assembly and remember it name/display name.
//
while ((hr = asmEnum->lpVtbl->GetNextAssembly(asmEnum, NULL, &asmName, 0)) == S_OK) {
if (SUCCEEDED(fusUtilGetAssemblyName(asmName,
&lpAssemblyName,
NULL,
NULL,
NULL)))
{
if (_strcmpi(AssemblyName, lpAssemblyName) == 0) {
bFound = TRUE;
break;
}
else {
supHeapFree(lpAssemblyName);
lpAssemblyName = NULL;
}
}
asmName->lpVtbl->Finalize(asmName);
asmName->lpVtbl->Release(asmName);
}
if (FAILED(hr) || bFound == FALSE)
break;
dwSize = 0;
hr = asmName->lpVtbl->GetProperty(asmName, ASM_NAME_MVID, NULL, &dwSize);
if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
break;
if (dwSize != sizeof(GUID))
break;
hr = asmName->lpVtbl->GetProperty(asmName, ASM_NAME_MVID, ModuleVersionId, &dwSize);
bResult = SUCCEEDED(hr);
} while (FALSE);
if (asmName) {
asmName->lpVtbl->Finalize(asmName);
asmName->lpVtbl->Release(asmName);
}
if (asmEnum)
asmEnum->lpVtbl->Release(asmEnum);
if (lpAssemblyName)
supHeapFree(lpAssemblyName);
return bResult;
}
/*
* fusUtilGetAssemblyPath
*
* Purpose:
*
* Return given assembly file path.
*
* Note: Use supHeapFree to release lpAssemblyPath allocated memory.
*
*/
HRESULT fusUtilGetAssemblyPath(
_In_ IAssemblyCache* pInterface,
_In_ LPCWSTR lpAssemblyName,
_Inout_ LPCWSTR* lpAssemblyPath
)
{
HRESULT hr = E_FAIL;
ASSEMBLY_INFO asmInfo;
LPWSTR assemblyPath;
*lpAssemblyPath = NULL;
RtlSecureZeroMemory(&asmInfo, sizeof(asmInfo));
pInterface->lpVtbl->QueryAssemblyInfo(pInterface,
QUERYASMINFO_FLAG_GETSIZE,
lpAssemblyName,
&asmInfo);
if (asmInfo.cchBuf == 0) //empty pszCurrentAssemblyPathBuf
return E_FAIL;
assemblyPath = (LPWSTR)supHeapAlloc(asmInfo.cchBuf * sizeof(WCHAR));
if (assemblyPath == NULL)
return E_FAIL;
asmInfo.pszCurrentAssemblyPathBuf = assemblyPath;
hr = pInterface->lpVtbl->QueryAssemblyInfo(pInterface,
QUERYASMINFO_FLAG_VALIDATE,
lpAssemblyName,
&asmInfo);
if (!SUCCEEDED(hr)) {
supHeapFree(asmInfo.pszCurrentAssemblyPathBuf);
}
else {
*lpAssemblyPath = assemblyPath;
}
return hr;
}
/*
* fusUtilGetAssemblyPathByName
*
* Purpose:
*
* Return given assembly file path.
*
* Note: Use supHeapFree to release lpAssemblyPath allocated memory.
*
*/
BOOLEAN fusUtilGetAssemblyPathByName(
_In_ LPWSTR lpAssemblyName,
_Inout_ LPWSTR* lpAssemblyPath
)
{
HRESULT hr;
IAssemblyCache* asmCache = NULL;
do {
hr = g_ctx->FusionContext.CreateAssemblyCache(&asmCache, 0);
if ((FAILED(hr)) || (asmCache == NULL))
break;
hr = fusUtilGetAssemblyPath(asmCache,
lpAssemblyName,
lpAssemblyPath);
asmCache->lpVtbl->Release(asmCache);
} while (FALSE);
return SUCCEEDED(hr);
}
/*
* fusUtilReferenceStreamByName
*
* Purpose:
*
* Query stream pointer by stream name.
*
*/
BOOL fusUtilReferenceStreamByName(
_In_ STORAGEHEADER* StorageHeader,
_In_ LPCSTR StreamName,
_Out_ PSTORAGESTREAM* StreamRef
)
{
WORD i;
PBYTE streamPtr;
STORAGESTREAM* pStorStream;
ULONG offset;
SIZE_T nameLen;
*StreamRef = NULL;
streamPtr = (PBYTE)RtlOffsetToPointer(StorageHeader, sizeof(STORAGEHEADER));
i = 0;
do {
pStorStream = (STORAGESTREAM*)streamPtr;
if (IsBadReadPtr(pStorStream->rcName, sizeof(CHAR)))
return FALSE;
if (_strcmpi_a(pStorStream->rcName, StreamName) == 0) {
*StreamRef = pStorStream;
return TRUE;
}
nameLen = _strlen_a(pStorStream->rcName) + 1;
if (nameLen > MAXUSHORT)
return FALSE;
offset = ALIGN_UP(FIELD_OFFSET(STORAGESTREAM, rcName) + nameLen, ULONG);
streamPtr = (PBYTE)RtlOffsetToPointer(streamPtr, offset);
i++;
} while (i < StorageHeader->iStreams);
return FALSE;
}
/*
* fusUtilGetImageMVID
*
* Purpose:
*
* Query MVID value from image metadata.
*
* Ref: https://www.ntcore.com/files/dotnetformat.htm
*
*/
BOOL fusUtilGetImageMVID(
_In_ LPCWSTR lpImageName,
_Out_ GUID* ModuleVersionId
)
{
BOOL bResult = FALSE;
HMODULE hModule;
PVOID baseAddress;
IMAGE_COR20_HEADER* cliHeader;
ULONG sz, offset, mvidIndex, i;
STORAGESIGNATURE* pStorSign;
STORAGEHEADER* pStorHeader;
STORAGESTREAM* pStreamGuid;
STORAGESTREAM* pStreamTables;
STORAGETABLESHEADER* pTablesHeader;
PBYTE tablesPtr;
LPGUID guidsPtr;
RPC_STATUS st;
st = UuidCreateNil(ModuleVersionId);
if (st != S_OK)
return FALSE;
hModule = LoadLibraryEx(lpImageName, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (hModule) {
baseAddress = (PBYTE)(((ULONG_PTR)hModule) & ~3);
cliHeader = (IMAGE_COR20_HEADER*)RtlImageDirectoryEntryToData(baseAddress, TRUE,
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, &sz);
if (cliHeader && sz >= sizeof(IMAGE_COR20_HEADER)) {
pStorSign = (STORAGESIGNATURE*)RtlOffsetToPointer(baseAddress, cliHeader->MetaData.VirtualAddress);
if (pStorSign && !IsBadReadPtr(pStorSign, sizeof(STORAGESIGNATURE)) &&
pStorSign->lSignature == STORAGE_MAGIC_SIG)
{
offset = FIELD_OFFSET(STORAGESIGNATURE, pVersion) + pStorSign->iVersionString;
pStorHeader = (STORAGEHEADER*)RtlOffsetToPointer(pStorSign, offset);
pStreamTables = NULL;
if (!fusUtilReferenceStreamByName(pStorHeader, "#~", &pStreamTables)) {
FreeLibrary(hModule);
return FALSE;
}
pStreamGuid = NULL;
if (!fusUtilReferenceStreamByName(pStorHeader, "#GUID", &pStreamGuid)) {
FreeLibrary(hModule);
return FALSE;
}
pTablesHeader = (STORAGETABLESHEADER*)RtlOffsetToPointer(pStorSign, pStreamTables->iOffset);
sz = 0;
//
// __popcnt64 or the garbage code below
//
for (i = 0; i < MAX_CLR_TABLES; i++)
if ((i < 32 && (pTablesHeader->Valid.u.LowPart >> i) & 1) ||
(i >= 32 && (pTablesHeader->Valid.u.HighPart >> i) & 1))
{
sz++;
}
offset = FIELD_OFFSET(STORAGETABLESHEADER, Rows) + (sz * sizeof(ULONG));
tablesPtr = (PBYTE)RtlOffsetToPointer(pTablesHeader, offset);
tablesPtr += sizeof(WORD);
if (pTablesHeader->HeapOffsetSizes & MD_STRINGS_BIT)
tablesPtr += sizeof(DWORD);
else
tablesPtr += sizeof(WORD);
if (pTablesHeader->HeapOffsetSizes & MD_GUIDS_BIT)
mvidIndex = *(PULONG)tablesPtr;
else
mvidIndex = *(PUSHORT)tablesPtr;
if (mvidIndex) {
guidsPtr = (LPGUID)RtlOffsetToPointer(pStorSign, pStreamGuid->iOffset);
RtlCopyMemory(ModuleVersionId, &guidsPtr[mvidIndex - 1], sizeof(GUID));
bResult = TRUE;
}
}
}
FreeLibrary(hModule);
}
return bResult;
}
/*
* fusUtilpFusionScanFiles
*
* Purpose:
*
* Scan directory for files of given type.
*
* Note:
* Return TRUE to abort further scan, FALSE otherwise.
*
*/
BOOL fusUtilpFusionScanFiles(
_In_ LPWSTR lpDirectory,
_In_ LPWSTR lpExtension,
_In_ pfnFusionScanFilesCallback pfnCallback,
_In_opt_ PVOID pvUserContext
)
{
BOOL bResult = FALSE;
HANDLE hFile;
LPWSTR lpLookupDirectory = NULL;
SIZE_T cchBuffer;
WIN32_FIND_DATA fdata;
//
// Allocate buffer for path to the file including backslash and terminating null.
//
cchBuffer = (2 + _strlen(lpDirectory) + _strlen(lpExtension));
lpLookupDirectory = (LPWSTR)supHeapAlloc(cchBuffer * sizeof(WCHAR));
if (lpLookupDirectory) {
_strcpy(lpLookupDirectory, lpDirectory);
supConcatenatePaths(lpLookupDirectory, lpExtension, cchBuffer);
hFile = FindFirstFile(lpLookupDirectory, &fdata);
if (hFile != INVALID_HANDLE_VALUE) {
do {
if (pfnCallback(lpDirectory, &fdata, pvUserContext)) {
bResult = TRUE;
break;
}
} while (FindNextFile(hFile, &fdata));
FindClose(hFile);
}
supHeapFree(lpLookupDirectory);
}
return bResult;
}
/*
* fusUtilScanDirectory
*
* Purpose:
*
* Recursively scan directories looking for files with given extension.
*
*/
BOOL fusUtilScanDirectory(
_In_ LPWSTR lpDirectory,
_In_ LPWSTR lpExtension,
_In_ pfnFusionScanFilesCallback pfnCallback,
_In_opt_ PVOID pvUserContext
)
{
BOOL bResult = FALSE;
SIZE_T cchBuffer;
HANDLE hDirectory;
LPWSTR lpFilePath;
WIN32_FIND_DATA fdata;
if (fusUtilpFusionScanFiles(lpDirectory, lpExtension, pfnCallback, pvUserContext))
return TRUE;
//
// Allocate buffer for path including backslash, search mask, terminating null and space for filename.
//
cchBuffer = 4 + MAX_PATH + _strlen(lpDirectory);
lpFilePath = (LPWSTR)supHeapAlloc(cchBuffer * sizeof(WCHAR));
if (lpFilePath == NULL)
return FALSE;
_strcpy(lpFilePath, lpDirectory);
supConcatenatePaths(lpFilePath, TEXT("*"), cchBuffer);
hDirectory = FindFirstFile(lpFilePath, &fdata);
if (hDirectory != INVALID_HANDLE_VALUE) {
do {
if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
(fdata.cFileName[0] != L'.')
)
{
_strcpy(lpFilePath, lpDirectory);
_strcat(lpFilePath, fdata.cFileName);
bResult = fusUtilScanDirectory(lpFilePath,
lpExtension,
pfnCallback,
pvUserContext);
if (bResult)
break;
}
} while (FindNextFile(hDirectory, &fdata));
FindClose(hDirectory);
}
supHeapFree(lpFilePath);
return bResult;
}
/*
* fusUtilFindFileByMVIDCallback
*
* Purpose:
*
* supFusionScanDirectory callback for MVID comparison.
*
*/
BOOL fusUtilFindFileByMVIDCallback(
_In_ LPWSTR CurrentDirectory,
_In_ WIN32_FIND_DATA* FindData,
_In_ PVOID UserContext
)
{
FUSION_SCAN_PARAM* ScanParam = (FUSION_SCAN_PARAM*)UserContext;
LPWSTR lpFileName;
SIZE_T cchBuffer;
GUID mVid;
RPC_STATUS rpcStatus;
cchBuffer = 2 + MAX_PATH + _strlen(CurrentDirectory);
lpFileName = (LPWSTR)supHeapAlloc(cchBuffer * sizeof(WCHAR));
if (lpFileName) {
_strcpy(lpFileName, CurrentDirectory);
supConcatenatePaths(lpFileName, FindData->cFileName, cchBuffer);
if (fusUtilGetImageMVID(lpFileName, &mVid)) {
if (0 == UuidCompare(ScanParam->ReferenceMVID,
&mVid,
&rpcStatus))
{
ScanParam->lpFileName = lpFileName;
return TRUE;
}
}
supHeapFree(lpFileName);
}
return FALSE;
}
#define NI_DLL_EXT L".ni.dll"
#define NI_DLL_AUX_EXT L".ni.dll.aux"
/*
* fusUtilCombineNativeImageCacheName
*
* Purpose:
*
* Build cache image name from assembly name.
*
*/
VOID fusUtilCombineNativeImageCacheName(
_In_ LPCWSTR lpAssemblyName,
_Inout_ LPWSTR lpNativeImageName,
_In_ DWORD cchNativeName,
_In_ BOOLEAN fIsAux
)
{
supConcatenatePaths(lpNativeImageName, lpAssemblyName, cchNativeName);
if (fIsAux) {
_strcat(lpNativeImageName, NI_DLL_AUX_EXT);
}
else {
_strcat(lpNativeImageName, NI_DLL_EXT);
}
}