Skip to content

Instantly share code, notes, and snippets.

@hfiref0x
Created September 18, 2024 03:12
Show Gist options
  • Select an option

  • Save hfiref0x/62295f9c05f4a48dd1f782a2a8108b3c to your computer and use it in GitHub Desktop.

Select an option

Save hfiref0x/62295f9c05f4a48dd1f782a2a8108b3c to your computer and use it in GitHub Desktop.
GetSystemWow64Directory
__int64 __fastcall GetSystemWow64Directory2W(LPWSTR lpBuffer, ULONG Size, WORD ImageFileMachineType)
{
const UNICODE_STRING *usSystemDirectory; // rbx
UINT sysDirLength; // ecx
__int64 result; // rax
unsigned int maxLength; // edx
_UNICODE_STRING usQueryBuffer; // [rsp+20h] [rbp-18h] BYREF
usQueryBuffer = 0i64;
if ( Size > 0xFFFF )
LOWORD(Size) = 0xFFFF;
usQueryBuffer.Buffer = lpBuffer;
usQueryBuffer.MaximumLength = 2 * Size;
switch ( ImageFileMachineType )
{
case 1u:
usSystemDirectory = &hostDir; // "\\system32"
break;
case 0x14Cu:
usSystemDirectory = &x86Dir; // "\\SysWOW64"
break;
case 0x1C4u:
usSystemDirectory = &armDir; // "\\SysArm32"
break;
case 0x8664u:
usSystemDirectory = &amd64Dir; // "\\SysX8664"
break;
case 0xAA64u:
usSystemDirectory = &arm64Dir; // "\\SysArm64"
break;
default:
RtlSetLastWin32Error(0xA0u);
return 0i64;
}
sysDirLength = GetSystemWindowsDirectoryW(0i64, 0);
if ( !sysDirLength )
return 0i64;
result = sysDirLength + (usSystemDirectory->Length >> 1);
maxLength = usQueryBuffer.MaximumLength >> 1;
if ( maxLength < result )
return result;
usQueryBuffer.Length = 2 * GetSystemWindowsDirectoryW(usQueryBuffer.Buffer, maxLength);
if ( !usQueryBuffer.Length || RtlAppendUnicodeStringToString(&usQueryBuffer, usSystemDirectory) < 0 )
return 0i64;
usQueryBuffer.Buffer[usQueryBuffer.Length >> 1] = 0;
return usQueryBuffer.Length >> 1;
}
@hfiref0x
Copy link
Copy Markdown
Author

hfiref0x commented Mar 26, 2026

/**
 * GetSystemWow64Directory2W
 *
 * Retrieves the path of the WOW64 system directory for a given machine architecture.
 * Similar to GetSystemWow64DirectoryW but accepts an explicit machine type, allowing
 * callers to query architecture-specific system directories (e.g., SysWOW64, SysArm32).
 *
 * @param lpBuffer          Output buffer to receive the directory path (wide string).
 * @param Size              Size of the output buffer in WCHARs.
 * @param ImageFileMachineType  PE machine type constant (IMAGE_FILE_MACHINE_*).
 *
 * @return  On success: number of WCHARs written (excluding null terminator).
 *          On failure: 0, or the required buffer size if lpBuffer is too small.
 */
UINT GetSystemWow64Directory2W(LPWSTR lpBuffer, ULONG Size, WORD ImageFileMachineType)
{
    const UNICODE_STRING *usSystemDirectory;
    UINT                  sysDirLength;
    UINT                  requiredLength;
    UINT                  maxLengthChars;
    UNICODE_STRING        usQueryBuffer;

    /* Zero-initialise the query buffer descriptor */
    RtlZeroMemory(&usQueryBuffer, sizeof(usQueryBuffer));

    /*
     * UNICODE_STRING.MaximumLength is a USHORT (max 0xFFFF bytes).
     * Cap the caller-supplied size to avoid overflow when converting
     * from WCHARs to bytes below.
     */
    if (Size > 0xFFFF)
        Size = 0xFFFF;

    /* Point the UNICODE_STRING at the caller's output buffer */
    usQueryBuffer.Buffer        = lpBuffer;
    usQueryBuffer.MaximumLength = (USHORT)(Size * sizeof(WCHAR)); /* bytes */

    /*
     * Select the architecture-specific subdirectory name based on the
     * IMAGE_FILE_MACHINE_* value supplied by the caller.
     *
     * Machine type          Directory suffix   Notes
     * -------------------   ----------------   --------------------------------
     * 0x0001  (native/any)  \system32          Host (native) system directory
     * 0x014C  (i386)        \SysWOW64          32-bit x86 binaries on x64 host
     * 0x01C4  (ARM Thumb-2) \SysArm32          32-bit ARM binaries
     * 0x8664  (AMD64)       \SysX8664          64-bit x86-64 binaries
     * 0xAA64  (ARM64)       \SysArm64          64-bit ARM64 binaries
     */
    switch (ImageFileMachineType)
    {
        case IMAGE_FILE_MACHINE_TARGET_HOST: /* 0x0001 */
            usSystemDirectory = &hostDir;   /* L"\\system32"  */
            break;

        case IMAGE_FILE_MACHINE_I386:        /* 0x014C */
            usSystemDirectory = &x86Dir;    /* L"\\SysWOW64"  */
            break;

        case IMAGE_FILE_MACHINE_ARMNT:       /* 0x01C4 */
            usSystemDirectory = &armDir;    /* L"\\SysArm32"  */
            break;

        case IMAGE_FILE_MACHINE_AMD64:       /* 0x8664 */
            usSystemDirectory = &amd64Dir;  /* L"\\SysX8664"  */
            break;

        case IMAGE_FILE_MACHINE_ARM64:       /* 0xAA64 */
            usSystemDirectory = &arm64Dir;  /* L"\\SysArm64"  */
            break;

        default:
            /*
             * Unknown / unsupported machine type.
             * ERROR_NO_TOKEN (0xA0) — used here as "invalid parameter" sentinel
             * by this internal implementation.
             */
            RtlSetLastWin32Error(ERROR_NO_TOKEN);
            return 0;
    }

    /*
     * First call: query the length of the Windows directory path only.
     * Passing NULL/0 causes GetSystemWindowsDirectoryW to return the
     * required buffer size in WCHARs (including the null terminator).
     */
    sysDirLength = GetSystemWindowsDirectoryW(NULL, 0);
    if (sysDirLength == 0)
        return 0; /* Unexpected failure */

    /*
     * Calculate the total number of WCHARs needed:
     *   Windows-dir length (includes NUL from the probe call)
     *   + suffix length in WCHARs  (UNICODE_STRING.Length is in bytes)
     *
     * Note: the probe length already accounts for the NUL terminator, so
     * "requiredLength" is the character count the caller needs including NUL.
     */
    requiredLength = sysDirLength + (usSystemDirectory->Length / sizeof(WCHAR));

    /*
     * Convert MaximumLength (bytes) back to WCHARs for the size comparison.
     */
    maxLengthChars = usQueryBuffer.MaximumLength / sizeof(WCHAR);

    /*
     * If the caller's buffer is too small, return the required size so the
     * caller can retry with an appropriately sized buffer (like many Windows
     * path APIs).
     */
    if (maxLengthChars < requiredLength)
        return requiredLength;

    /*
     * Second call: actually fill lpBuffer with the Windows directory path.
     * The return value is the number of WCHARs copied (excluding NUL).
     * Multiply by sizeof(WCHAR) to get the byte count for UNICODE_STRING.Length.
     */
    usQueryBuffer.Length =
        (USHORT)(GetSystemWindowsDirectoryW(usQueryBuffer.Buffer, maxLengthChars)
                 * sizeof(WCHAR));

    if (usQueryBuffer.Length == 0)
        return 0; /* Second call failed unexpectedly */

    /*
     * Append the architecture-specific suffix (e.g. L"\\SysWOW64") to the
     * Windows-directory path already in the buffer.
     * RtlAppendUnicodeStringToString returns an NTSTATUS; negative == failure.
     */
    if (RtlAppendUnicodeStringToString(&usQueryBuffer, usSystemDirectory) < 0)
        return 0;

    /*
     * Null-terminate the result.
     * usQueryBuffer.Length is the byte offset of the first free WCHAR,
     * so dividing by sizeof(WCHAR) gives the correct character index.
     */
    usQueryBuffer.Buffer[usQueryBuffer.Length / sizeof(WCHAR)] = L'\0';

    /* Return the number of WCHARs written, excluding the null terminator */
    return usQueryBuffer.Length / sizeof(WCHAR);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment