x64 process detected as ARM64 process in Windows 11 for ARM running in Parallels

soleon

Bit poster
I have run into what looks like an emulation bug in the Windows 11 ARM64 guest: the Win32 API IsWow64Process2 cannot distinguish an emulated x64 process from a native ARM64 one. It reports every process as "running natively." This breaks any tool that uses this API to detect a target process's architecture.

Environment
  • Host: MacBook Air M5 with Parallels App Store Edition 1.26.3 (23929)
  • Parallels Tools: 1.26.3 (23929), VM type "Parallels ARM Virtual Machine"
  • Guest: Windows 11 Pro 25H2, ARM64, build 26200.8737
What I expected
On physical Windows-on-ARM hardware, calling IsWow64Process2(hProcess, &processMachine, &nativeMachine) on an emulated x64 process returns:
  • processMachine = IMAGE_FILE_MACHINE_AMD64 (0x8664)
  • nativeMachine = IMAGE_FILE_MACHINE_ARM64 (0xAA64)
For a native ARM64 process it returns processMachine = IMAGE_FILE_MACHINE_UNKNOWN (0). That difference is exactly how you tell an emulated x64 process from a native ARM64 one.

What actually happens in the Parallels guest
For a confirmed x64 process, the call returns:
  • processMachine = IMAGE_FILE_MACHINE_UNKNOWN (0)
  • nativeMachine = IMAGE_FILE_MACHINE_ARM64 (0xAA64)
which is identical to what a native ARM64 process returns. So, there is no way to tell the two apart. I verified the target really was x64 two independent ways: its on-disk PE header machine field is 0x8664, and it had loaded the x64 .NET runtime from C:\Program Files\dotnet\x64\...\coreclr.dll. I also confirmed the calling process was genuinely native ARM64 (not itself emulated), and I got the same wrong result whether the caller was native ARM64 or emulated x64.

Minimal repro (C#)
Compile as a console app and pass a PID. Run it against a known x64 process (any x64 app running under emulation) and against a native ARM64 process and compare.
using System;
using System.Runtime.InteropServices;

class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool IsWow64Process2(IntPtr hProcess, out ushort processMachine, out ushort nativeMachine);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
[DllImport("kernel32.dll")]
static extern bool CloseHandle(IntPtr h);

static string Name(ushort m) => m switch
{
0x0 => "UNKNOWN(0)",
0x14c => "I386/x86",
0x8664 => "AMD64/x64",
0xAA64 => "ARM64",
_ => "0x" + m.ToString("X")
};

static void Main(string[] args)
{
int pid = int.Parse(args[0]);
IntPtr h = OpenProcess(0x1000 /* QueryLimitedInformation */, false, pid);
if (h == IntPtr.Zero) { Console.WriteLine("OpenProcess failed: " + Marshal.GetLastWin32Error()); return; }
bool ok = IsWow64Process2(h, out ushort pm, out ushort nm);
CloseHandle(h);
Console.WriteLine($"ok={ok} processMachine={Name(pm)} nativeMachine={Name(nm)}");
}
}


Observed output
  • Against a native ARM64 process: processMachine=UNKNOWN(0) nativeMachine=ARM64 (correct)
  • Against an emulated x64 process: processMachine=UNKNOWN(0) nativeMachine=ARM64 (wrong; should be processMachine=AMD64/x64)
Impact
Any software that relies on IsWow64Process2 to determine another process's architecture misbehaves. In my case a WPF debugging tool picks an injector/DLL by target architecture; because every process looks like native ARM64, it loads an ARM64 DLL into an x64 process, and Windows correctly refuses to load it. Debuggers, profilers, and DLL-injection based tools would all be affected. The same API on real Windows-on-ARM hardware works correctly, so this appears specific to the Parallels x64 emulation layer rather than to Windows itself.

Questions
  1. Is this a known limitation of the current x64 emulation, and is a fix planned?
  2. Is there a supported way to correctly detect an emulated x64 (or x86) guest process's architecture from another process?
Happy to provide more diagnostics or test a build. Thanks.
 
Back
Top