From 2dd672f4bd060be19a6263ec1ac85ba859afa7ce Mon Sep 17 00:00:00 2001 From: AdvDebug <90452585+AdvDebug@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:25:06 +0300 Subject: [PATCH 1/2] Add DPI awareness and guest process launch # DPI awareness The emulator gave every program a screen of 96 DPI. It also made the host window with no DPI awareness. A program that asks for a different DPI got the wrong screen size and a window that is not sharp. The emulator now reads the DPI awareness of the program. It reads it from the application manifest in the image, from an external manifest file, or from the NtUserSetProcessDpiAwarenessContext system call. user32 does not ask the kernel for most DPI values. It calculates them from a packed value in the CLIENTINFO block of the thread and in the window structure. The emulator writes this value into the two locations. If it does not, the program reads one DPI from user32 and a different DPI from the system calls. The emulator gives the same awareness to the host window. An aware program gets a window in true pixels. An unaware program keeps the window that the desktop manager makes larger for it. The emulator also sets the composited flag in the desktop data. Without this flag, user32 uses a different code path. That path gives 96 DPI to a program that is aware. # Guest process launch A guest process could not start a different program. The emulator did not have the NtCreateUserProcess system call. DELTARUNE stopped at its menu for this reason. The game starts a new process when you select a chapter. The emulator now starts a second Brovan for each new guest process. One guest process is one host process. The window manager, the Vulkan device, the scheduler and the caches are global to a process. Two guests in one emulator need a process identity in all of them. The host operating system gives this isolation at no cost. The parent sends the image path, the command line and the directory to the child. It encodes these values with base64. Without base64, the host command line divides the values and joins them again. A value with a quotation mark or a space does not stay correct. # Session registry The Brovan instances of one session share a table in a memory-mapped file. Each instance writes one row. An instance that must stop a different guest process writes a request into the row of that process. The owner of the row reads the request and stops itself. This is the only correct method. The memory of that process is in a different host process. The emulator counts the rows before it starts a child. It permits a maximum of six guest processes in one session. # New options --cwd sets the directory in which the program starts. --guest-cmdline sets the command line of the program. The two options also accept a value in the form "base64:". --- Brovan/Core/Emulation/BinaryEmulator.cs | 8 + Brovan/Core/Emulation/Guests/WindowsGuest.cs | 44 +- .../WindowManager/LinuxWinManager.cs | 3 + .../WindowManager/WindowManager.cs | 219 ++++++ .../WindowManager/WindowsWinManager.cs | 92 ++- .../Windows/BinaryEmulator.WindowsBridge.cs | 6 + .../Windows/Process/GuestProcessLauncher.cs | 313 +++++++++ .../Windows/Process/GuestSessionRegistry.cs | 631 ++++++++++++++++++ .../OS/Windows/Process/NtCreateThreadEx.cs | 35 +- .../OS/Windows/Process/NtCreateUserProcess.cs | 130 ++++ .../Process/NtQueryInformationProcess.cs | 9 +- .../OS/Windows/Process/NtReadVirtualMemory.cs | 38 +- .../OS/Windows/Process/NtTerminateProcess.cs | 18 + .../Windows/Process/NtWriteVirtualMemory.cs | 77 +++ .../Windows/Process/RemoteProcessRequests.cs | 88 +++ .../OS/Windows/Win32k/NtGdiGetDeviceCaps.cs | 36 +- .../Win32k/NtUserEnableNonClientDpiScaling.cs | 22 + .../Win32k/NtUserGetDpiForCurrentProcess.cs | 6 +- .../Windows/Win32k/NtUserGetDpiForMonitor.cs | 17 +- .../NtUserGetProcessDpiAwarenessContext.cs | 13 + .../Win32k/NtUserGetSystemDpiForProcess.cs | 13 + .../NtUserSetProcessDpiAwarenessContext.cs | 15 + .../Emulation/OS/Windows/Win32k/Win32kDpi.cs | 427 ++++++++++++ .../OS/Windows/Win32k/Win32kHelper.cs | 4 +- .../OS/Windows/WinHelperConstants.cs | 31 +- .../Emulation/OS/Windows/WinSyscallsHelper.cs | 66 +- Brovan/EmulationMenu/EmulationMenu.cs | 3 +- Brovan/GeneralHelper.cs | 9 + Brovan/Program.cs | 118 +++- 29 files changed, 2424 insertions(+), 67 deletions(-) create mode 100644 Brovan/Core/Emulation/OS/Windows/Process/GuestProcessLauncher.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Process/NtWriteVirtualMemory.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Process/RemoteProcessRequests.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableNonClientDpiScaling.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetProcessDpiAwarenessContext.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetSystemDpiForProcess.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetProcessDpiAwarenessContext.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/Win32kDpi.cs diff --git a/Brovan/Core/Emulation/BinaryEmulator.cs b/Brovan/Core/Emulation/BinaryEmulator.cs index 574605c..029de1c 100644 --- a/Brovan/Core/Emulation/BinaryEmulator.cs +++ b/Brovan/Core/Emulation/BinaryEmulator.cs @@ -257,6 +257,8 @@ public struct BinaryEmulatorSettings /// public string RawProgramArguments; + public string WorkingDirectory; + /// /// Parsed arguments passed to the emulated process, excluding argv[0]. /// @@ -370,6 +372,7 @@ public int Compare(MemoryRegion x, MemoryRegion y) public bool Debug { get; set; } public string RawProgramArguments { get; } + public string WorkingDirectory { get; } public string[] ProgramArguments { get; } public int IPRegister { get; private set; } @@ -528,6 +531,7 @@ public BinaryEmulator(BinaryFile Binary, BinaryEmulatorSettings Settings) this.Settings = Settings; Debug = Settings.Debug; RawProgramArguments = Settings.RawProgramArguments ?? string.Empty; + WorkingDirectory = Settings.WorkingDirectory; ProgramArguments = Settings.ProgramArguments?.ToArray() ?? Array.Empty(); if (_binary.Architecture == BinaryArchitecture.x64) @@ -567,6 +571,7 @@ public BinaryEmulator(IGuestEnvironment Guest, BinaryEmulatorSettings Settings, this.Settings = Settings; Debug = Settings.Debug; RawProgramArguments = Settings.RawProgramArguments ?? string.Empty; + WorkingDirectory = Settings.WorkingDirectory; ProgramArguments = Settings.ProgramArguments?.ToArray() ?? Array.Empty(); if (Guest is GenericGuest Generic) @@ -2396,6 +2401,9 @@ public bool RunMlfqScheduler(uint BaseQuantumInstructions = 200000, int Levels = { SchedulerTick++; + if (WinHelper != null) + OS.Windows.RemoteProcessRequests.Drain(this); + bool ThreadOrderChanged = ThreadOrder.Count != KnownThreadOrderCount; bool AgingDue = AgingThresholdSlices > 0 && SchedulerTick % AgingThresholdSlices == 0; if (AgingDue) diff --git a/Brovan/Core/Emulation/Guests/WindowsGuest.cs b/Brovan/Core/Emulation/Guests/WindowsGuest.cs index e5d50f1..ea90ba2 100644 --- a/Brovan/Core/Emulation/Guests/WindowsGuest.cs +++ b/Brovan/Core/Emulation/Guests/WindowsGuest.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Text; using Brovan.Core.Emulation.OS.Windows; +using Brovan.Core.Emulation.OS.Windows.Win32k; using Brovan.Core.Helpers; using static Brovan.Core.Emulation.OS.Windows.WinSysHelper; using static Brovan.Core.Helpers.BinaryHelpers; @@ -829,6 +830,7 @@ public ulong AllocateAndInitializeTEB(BinaryEmulator Instance, EmulatedThread Th Instance._emulator.WriteMemory(Teb + Wow64InfoOffset + 0x0C, 0u); Instance._emulator.WriteMemory(Teb + Wow64TlsWow64InfoOffset, (uint)(Teb + Wow64InfoOffset)); + Win32kDpi.ApplyThreadContext(Instance, Teb); return Teb; } @@ -859,6 +861,8 @@ public ulong AllocateAndInitializeTEB(BinaryEmulator Instance, EmulatedThread Th SameTebFlags |= TEB_SAME_TEB_FLAG_SKIP_LOADER_INIT; Instance._emulator.WriteMemory(Teb + TebSameTebFlagsOffset64, SameTebFlags, 2); Instance._emulator.WriteMemory(Teb + 0x180C, (uint)0u); + + Win32kDpi.ApplyThreadContext(Instance, Teb); return Teb; } @@ -1157,6 +1161,36 @@ private void EnsureDirectBlobStandardHandles() } } + private static void JoinGuestSession(BinaryEmulator Instance, WinModule MainModule) + { + string ImageName = MainModule?.Name; + if (string.IsNullOrEmpty(ImageName)) + ImageName = Path.GetFileName(Instance._binary?.Location ?? string.Empty); + + GuestSessionRegistry.Join( + Instance.WinHelper.PID, + (uint)Instance._binary.Architecture, + ImageName, + ExitCode => + { + Instance.TriggerEventMessage($"[!] Another process in the session asked this one to stop with exit code 0x{ExitCode:X}.", LogFlags.Important); + Instance.WinHelper.HideDesktopWindow(); + Instance.StopEmulation(); + }); + } + + private static string ResolveStartupDirectory(BinaryEmulator Instance, string ImagePath) + { + string Directory = Instance.WorkingDirectory; + if (string.IsNullOrWhiteSpace(Directory)) + Directory = Path.GetDirectoryName(ImagePath); + + if (string.IsNullOrWhiteSpace(Directory)) + Directory = "C:\\"; + + return Directory.EndsWith("\\", StringComparison.Ordinal) ? Directory : Directory + "\\"; + } + public void PrepareWinEnvironment(BinaryEmulator Instance, WinModule MainModule) { Instance.EnsureInstructionHook(); @@ -1171,6 +1205,8 @@ public void PrepareWinEnvironment(BinaryEmulator Instance, WinModule MainModule) EnsureDirectBlobStandardHandles(); WinSyscallTable = HelperFunctions.BuildWinSyscallTable(Instance._binary.Architecture); + Win32kDpi.SeedFromImage(Instance, MainModule); + JoinGuestSession(Instance, MainModule); ulong PageSize = 0x2000; PEB = Instance.MapUniqueAddress(PageSize, MemoryProtection.ReadWrite); @@ -1215,9 +1251,7 @@ public void PrepareWinEnvironment(BinaryEmulator Instance, WinModule MainModule) string ImagePath = IsPeImage ? Instance._binary.Location : (!string.IsNullOrWhiteSpace(Instance._binary.Location) ? Instance._binary.Location : (!string.IsNullOrWhiteSpace(MainModule.Path) ? MainModule.Path : (!string.IsNullOrWhiteSpace(MainModule.Name) ? MainModule.Name : "blob.bin"))); - string CurrentDir = Path.GetDirectoryName(ImagePath) ?? "C:\\"; - if (string.IsNullOrWhiteSpace(CurrentDir)) CurrentDir = "C:\\"; - if (!CurrentDir.EndsWith("\\")) CurrentDir += "\\"; + string CurrentDir = ResolveStartupDirectory(Instance, ImagePath); string DesktopInfo = "Winsta0\\Default"; string WindowTitle = ImagePath; string CommandLine = GeneralHelper.QuoteCommandLineArg(ImagePath); @@ -1335,9 +1369,7 @@ private ulong BuildProcessParameters32(BinaryEmulator Instance, WinModule MainMo string ImagePath = Instance._binary.Location; if (string.IsNullOrWhiteSpace(ImagePath)) ImagePath = !string.IsNullOrWhiteSpace(MainModule.Path) ? MainModule.Path : (!string.IsNullOrWhiteSpace(MainModule.Name) ? MainModule.Name : "app.exe"); - string CurrentDir = Path.GetDirectoryName(ImagePath) ?? "C:\\"; - if (string.IsNullOrWhiteSpace(CurrentDir)) CurrentDir = "C:\\"; - if (!CurrentDir.EndsWith("\\")) CurrentDir += "\\"; + string CurrentDir = ResolveStartupDirectory(Instance, ImagePath); string DesktopInfo = "Winsta0\\Default"; string WindowTitle = ImagePath; string CommandLine = GeneralHelper.QuoteCommandLineArg(ImagePath); diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs index e8a5ecd..6c94cd4 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs @@ -32,6 +32,9 @@ public static partial class X11 [LibraryImport("libX11.so.6")] public static partial int XDisplayHeight(IntPtr display, int screen); + [LibraryImport("libX11.so.6")] + public static partial IntPtr XResourceManagerString(IntPtr display); + [LibraryImport("libX11.so.6")] public static partial nuint XWhitePixel(IntPtr display, int screen); diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs index 743b4e5..758778f 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Threading; namespace Brovan.Core.Emulation.OS.SharedHelpers @@ -23,6 +24,7 @@ private struct HostEvent private static int _pendingRepaint; private static int _closeRequested; + private static int _pendingDpi; public static void RequestClose() { @@ -36,6 +38,7 @@ public static void Reset() { Interlocked.Exchange(ref _closeRequested, 0); Interlocked.Exchange(ref _pendingRepaint, 0); + Interlocked.Exchange(ref _pendingDpi, 0); lock (InputSync) { @@ -44,6 +47,16 @@ public static void Reset() } } + public static void MarkDpiChanged(uint dpi) + { + Interlocked.Exchange(ref _pendingDpi, (int)dpi); + } + + public static uint ConsumeDpiChange() + { + return (uint)Interlocked.Exchange(ref _pendingDpi, 0); + } + public static void MarkRepaint() { Interlocked.Exchange(ref _pendingRepaint, 1); @@ -130,6 +143,211 @@ public enum WindowState Fullscreen } + public enum DpiAwareness + { + Unaware, + System, + PerMonitor + } + + internal static class HostDisplayMetrics + { + internal const uint DefaultDpi = 96; + + private const int MonitorDefaultToPrimary = 0x0001; + private const int MdtEffectiveDpi = 0; + private const int MdtRawDpi = 2; + private const int SmCxScreen = 0; + private const int SmCyScreen = 1; + private const int FallbackScreenWidth = 1920; + private const int FallbackScreenHeight = 1080; + private const uint MinimumDpi = 48; + private const uint MaximumDpi = 480; + + private static readonly object Sync = new(); + private static readonly IntPtr PerMonitorAwareV2 = new(-4); + + private static uint _systemDpi; + private static uint _rawDpi; + private static int _screenWidth; + private static int _screenHeight; + + public static bool VirtualizesUnawareWindows => OperatingSystem.IsWindows(); + + public static uint SystemDpi + { + get + { + Ensure(); + return _systemDpi; + } + } + + public static uint RawDpi + { + get + { + Ensure(); + return _rawDpi; + } + } + + public static int ScreenWidth + { + get + { + Ensure(); + return _screenWidth; + } + } + + public static int ScreenHeight + { + get + { + Ensure(); + return _screenHeight; + } + } + + public static void Invalidate() + { + lock (Sync) + _systemDpi = 0; + } + + private static void Ensure() + { + lock (Sync) + { + if (_systemDpi != 0) + return; + + _systemDpi = DefaultDpi; + _rawDpi = DefaultDpi; + _screenWidth = FallbackScreenWidth; + _screenHeight = FallbackScreenHeight; + + if (OperatingSystem.IsLinux()) + { + EnsureFromX11(); + return; + } + + if (!OperatingSystem.IsWindows()) + return; + + IntPtr previous = IntPtr.Zero; + try + { + previous = NativeWinImports.SetThreadDpiAwarenessContext(PerMonitorAwareV2); + + IntPtr monitor = NativeWinImports.MonitorFromWindow(IntPtr.Zero, MonitorDefaultToPrimary); + if (monitor != IntPtr.Zero) + { + if (NativeWinImports.GetDpiForMonitor(monitor, MdtEffectiveDpi, out uint effectiveDpi, out _) == 0 && effectiveDpi != 0) + _systemDpi = effectiveDpi; + + if (NativeWinImports.GetDpiForMonitor(monitor, MdtRawDpi, out uint rawDpi, out _) == 0 && rawDpi != 0) + _rawDpi = rawDpi; + else + _rawDpi = _systemDpi; + } + + int width = NativeWinImports.GetSystemMetrics(SmCxScreen); + int height = NativeWinImports.GetSystemMetrics(SmCyScreen); + if (width > 0 && height > 0) + { + _screenWidth = width; + _screenHeight = height; + } + } + catch + { + } + finally + { + if (previous != IntPtr.Zero) + { + try + { + NativeWinImports.SetThreadDpiAwarenessContext(previous); + } + catch + { + } + } + } + } + } + + private static void EnsureFromX11() + { + IntPtr display = IntPtr.Zero; + try + { + display = X11.XOpenDisplay(IntPtr.Zero); + if (display == IntPtr.Zero) + return; + + int screen = X11.XDefaultScreen(display); + int width = X11.XDisplayWidth(display, screen); + int height = X11.XDisplayHeight(display, screen); + if (width > 0 && height > 0) + { + _screenWidth = width; + _screenHeight = height; + } + + if (TryReadXftDpi(X11.XResourceManagerString(display), out uint dpi)) + { + _systemDpi = dpi; + _rawDpi = dpi; + } + } + catch (DllNotFoundException) + { + } + catch (EntryPointNotFoundException) + { + } + finally + { + if (display != IntPtr.Zero) + X11.XCloseDisplay(display); + } + } + + private static bool TryReadXftDpi(IntPtr resourceString, out uint dpi) + { + dpi = 0; + if (resourceString == IntPtr.Zero) + return false; + + string resources = Marshal.PtrToStringUTF8(resourceString); + if (string.IsNullOrEmpty(resources)) + return false; + + foreach (string line in resources.Split('\n')) + { + int separator = line.IndexOf(':'); + if (separator < 0 || !line.AsSpan(0, separator).TrimEnd().SequenceEqual("Xft.dpi")) + continue; + + if (!uint.TryParse(line.AsSpan(separator + 1).Trim(), out uint parsed)) + return false; + + if (parsed < MinimumDpi || parsed > MaximumDpi) + return false; + + dpi = parsed; + return true; + } + + return false; + } + } + public sealed record WindowOptions { public string Title { get; init; } = string.Empty; @@ -143,6 +361,7 @@ public sealed record WindowOptions public bool Decorated { get; init; } = true; public bool Center { get; init; } = false; public WindowState State { get; init; } = WindowState.Normal; + public DpiAwareness DpiAwareness { get; init; } = DpiAwareness.Unaware; } public struct WindowData diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs index 27d9799..40e190e 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs @@ -150,6 +150,15 @@ private static void DisposeCachedPensAndBrushes() private const uint SWP_NOACTIVATE = 0x0010; private const uint SWP_FRAMECHANGED = 0x0020; + private const uint WM_DPICHANGED = 0x02E0; + + private static readonly IntPtr DpiContextUnaware = new(-1); + private static readonly IntPtr DpiContextSystemAware = new(-2); + private static readonly IntPtr DpiContextPerMonitorAwareV2 = new(-4); + + private DpiAwareness _dpiAwareness = DpiAwareness.Unaware; + private bool _dpiAwarenessApplied; + public WindowsWinManager() { if (!GeneralHelper.IsWindows) @@ -170,6 +179,7 @@ public IWindow CreateWindow(WindowOptions options) throw new ObjectDisposedException(nameof(WindowsWinManager)); options = Normalize(options ?? new WindowOptions()); + ApplyDpiAwareness(options.DpiAwareness); uint style = options.Decorated ? WS_OVERLAPPEDWINDOW : WS_POPUP; if (options.Visible) @@ -180,7 +190,7 @@ public IWindow CreateWindow(WindowOptions options) int requestedClientWidth = Math.Max(options.Width, 1); int requestedClientHeight = Math.Max(options.Height, 1); - ResolveOuterFromClient(style, 0, requestedClientWidth, requestedClientHeight, out int outerWidth, out int outerHeight); + ResolveOuterFromClient(style, 0, requestedClientWidth, requestedClientHeight, FrameDpi(IntPtr.Zero), out int outerWidth, out int outerHeight); IntPtr hwnd = CreateWindowExW( 0, @@ -371,7 +381,7 @@ internal void UpdateWindowSize(IntPtr hwnd, uint style, int width, int height) if (!GetWindowRect(hwnd, out rect)) return; - ResolveOuterFromClient(style, 0, Math.Max(width, 1), Math.Max(height, 1), out int outerWidth, out int outerHeight); + ResolveOuterFromClient(style, 0, Math.Max(width, 1), Math.Max(height, 1), FrameDpi(hwnd), out int outerWidth, out int outerHeight); if (rect.Right - rect.Left == outerWidth && rect.Bottom - rect.Top == outerHeight) return; @@ -379,10 +389,51 @@ internal void UpdateWindowSize(IntPtr hwnd, uint style, int width, int height) SetWindowPos(hwnd, IntPtr.Zero, rect.Left, rect.Top, outerWidth, outerHeight, SWP_NOZORDER | SWP_NOACTIVATE); } - private static void ResolveOuterFromClient(uint style, uint exStyle, int clientWidth, int clientHeight, out int outerWidth, out int outerHeight) + private void ApplyDpiAwareness(DpiAwareness awareness) + { + if (_dpiAwarenessApplied && _dpiAwareness == awareness) + return; + + IntPtr context = awareness switch + { + DpiAwareness.PerMonitor => DpiContextPerMonitorAwareV2, + DpiAwareness.System => DpiContextSystemAware, + _ => DpiContextUnaware, + }; + + try + { + if (SetThreadDpiAwarenessContext(context) == IntPtr.Zero) + return; + } + catch (EntryPointNotFoundException) + { + return; + } + + _dpiAwareness = awareness; + _dpiAwarenessApplied = true; + } + + private uint FrameDpi(IntPtr hwnd) + { + if (_dpiAwareness == DpiAwareness.Unaware) + return 0; + + if (hwnd != IntPtr.Zero) + { + uint windowDpi = GetDpiForWindow(hwnd); + if (windowDpi != 0) + return windowDpi; + } + + return HostDisplayMetrics.SystemDpi; + } + + private static void ResolveOuterFromClient(uint style, uint exStyle, int clientWidth, int clientHeight, uint dpi, out int outerWidth, out int outerHeight) { RECT rect = new RECT { Left = 0, Top = 0, Right = clientWidth, Bottom = clientHeight }; - if (AdjustWindowRectEx(ref rect, style, false, exStyle)) + if (AdjustFrameRect(ref rect, style, exStyle, dpi)) { outerWidth = Math.Max(rect.Right - rect.Left, 1); outerHeight = Math.Max(rect.Bottom - rect.Top, 1); @@ -393,6 +444,23 @@ private static void ResolveOuterFromClient(uint style, uint exStyle, int clientW outerHeight = Math.Max(clientHeight, 1); } + private static bool AdjustFrameRect(ref RECT rect, uint style, uint exStyle, uint dpi) + { + if (dpi != 0) + { + try + { + if (AdjustWindowRectExForDpi(ref rect, style, false, exStyle, dpi)) + return true; + } + catch (EntryPointNotFoundException) + { + } + } + + return AdjustWindowRectEx(ref rect, style, false, exStyle); + } + internal void UpdateWindowVisibility(IntPtr hwnd, bool visible) { ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE); @@ -849,6 +917,12 @@ internal IntPtr HandleMessage(uint msg, IntPtr wParam, IntPtr lParam) HostEventQueue.MarkRepaint(); break; + case WM_DPICHANGED: + HostDisplayMetrics.Invalidate(); + HostEventQueue.MarkDpiChanged(unchecked((uint)(long)wParam) & 0xFFFF); + HostEventQueue.MarkRepaint(); + return IntPtr.Zero; + case WM_CLOSE: HostEventQueue.RequestClose(); return IntPtr.Zero; @@ -957,6 +1031,16 @@ private static extern IntPtr CreateWindowExW( [return: MarshalAs(UnmanagedType.Bool)] private static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle, [MarshalAs(UnmanagedType.Bool)] bool bMenu, uint dwExStyle); + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool AdjustWindowRectExForDpi(ref RECT lpRect, uint dwStyle, [MarshalAs(UnmanagedType.Bool)] bool bMenu, uint dwExStyle, uint dpi); + + [DllImport("user32.dll")] + private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext); + + [DllImport("user32.dll")] + private static extern uint GetDpiForWindow(IntPtr hWnd); + [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr DefWindowProcW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); diff --git a/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs b/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs index 8dbc3e3..c22721e 100644 --- a/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs +++ b/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs @@ -139,6 +139,12 @@ internal bool CanSatisfyWaitHandle(ulong Handle, EmulatedThread AcquiringThread) if (Obj is EmulatedThread Thread) return Thread.State == EmulatedThreadState.Terminated; + if (Obj is WinProcess Process) + return Process.SpawnedHasExited; + + if (Obj is WinSpawnedThread SpawnedThread) + return SpawnedThread.Process == null || SpawnedThread.Process.SpawnedHasExited; + if (Obj is WinTimer Timer) { RefreshTimerState(Timer); diff --git a/Brovan/Core/Emulation/OS/Windows/Process/GuestProcessLauncher.cs b/Brovan/Core/Emulation/OS/Windows/Process/GuestProcessLauncher.cs new file mode 100644 index 0000000..6c0741e --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Process/GuestProcessLauncher.cs @@ -0,0 +1,313 @@ +using System.Diagnostics; +using System.Text; +using Brovan.Core.Helpers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal static class GuestProcessLauncher + { + private const string SpawnDepthVariable = "BROVAN_GUEST_SPAWN_DEPTH"; + private const int MaxSpawnDepth = 8; + + private const int MaxSessionProcesses = 6; + + private const int MaxCommandLineChars = 8000; + + private const ulong ParamsCurrentDirectory64 = 0x38; + private const ulong ParamsImagePathName64 = 0x60; + private const ulong ParamsCommandLine64 = 0x70; + + private const ulong ParamsCurrentDirectory32 = 0x24; + private const ulong ParamsImagePathName32 = 0x38; + private const ulong ParamsCommandLine32 = 0x40; + + private const int MaxStringBytes = 0x8000; + + internal static bool TryLaunch(BinaryEmulator Instance, ulong ProcessParameters, string ImageNameHint, out WinProcess Process, out NTSTATUS Status) + { + Process = null; + + bool Is64 = Instance._binary.Architecture == BinaryArchitecture.x64; + string ImagePath = ReadUnicodeString(Instance, ProcessParameters + (Is64 ? ParamsImagePathName64 : ParamsImagePathName32), Is64); + string CommandLine = ReadUnicodeString(Instance, ProcessParameters + (Is64 ? ParamsCommandLine64 : ParamsCommandLine32), Is64); + string CurrentDirectory = ReadUnicodeString(Instance, ProcessParameters + (Is64 ? ParamsCurrentDirectory64 : ParamsCurrentDirectory32), Is64); + + if (string.IsNullOrWhiteSpace(ImagePath)) + ImagePath = ImageNameHint; + + if (string.IsNullOrWhiteSpace(ImagePath)) + { + Status = NTSTATUS.STATUS_INVALID_PARAMETER; + return false; + } + + if (!IsAcceptableImagePath(ImagePath)) + { + Utils.LogError($"[GuestProcessLauncher] Refusing to launch {ImagePath}: unsupported path form."); + Status = NTSTATUS.STATUS_OBJECT_PATH_SYNTAX_BAD; + return false; + } + + string HostImage = GeneralHelper.IO.ResolveHostPath(StripNtPrefix(ImagePath), BinaryFormat.PE); + if (string.IsNullOrEmpty(HostImage) || !File.Exists(HostImage)) + { + Status = NTSTATUS.STATUS_OBJECT_NAME_NOT_FOUND; + return false; + } + + if (!IsPortableExecutable(HostImage)) + { + Utils.LogError($"[GuestProcessLauncher] Refusing to launch {HostImage}: not a PE image."); + Status = NTSTATUS.STATUS_INVALID_IMAGE_FORMAT; + return false; + } + + if ((CommandLine?.Length ?? 0) > MaxCommandLineChars || (CurrentDirectory?.Length ?? 0) > MaxCommandLineChars) + { + Status = NTSTATUS.STATUS_INVALID_PARAMETER; + return false; + } + + if (!TryReserveLaunchSlot(out Status)) + return false; + + string HostExecutable = Environment.ProcessPath; + if (string.IsNullOrEmpty(HostExecutable)) + { + Status = NTSTATUS.STATUS_NOT_SUPPORTED; + return false; + } + + int Depth = GetSpawnDepth(); + if (Depth >= MaxSpawnDepth) + { + Utils.LogError($"[GuestProcessLauncher] Refusing to launch {ImagePath}: spawn depth {Depth} reached."); + Status = NTSTATUS.STATUS_INSUFFICIENT_RESOURCES; + return false; + } + + ProcessStartInfo StartInfo = new ProcessStartInfo + { + FileName = HostExecutable, + UseShellExecute = false, + WorkingDirectory = ResolveWorkingDirectory(CurrentDirectory, HostImage), + }; + + AppendEmulatorOptions(Instance, StartInfo.ArgumentList); + + StartInfo.CreateNoWindow = Utils.SilentMode; + + string GuestArguments = StripArgv0(CommandLine); + if (!string.IsNullOrEmpty(GuestArguments)) + { + StartInfo.ArgumentList.Add("--guest-cmdline"); + StartInfo.ArgumentList.Add(Encode(GuestArguments)); + } + + if (!string.IsNullOrWhiteSpace(CurrentDirectory)) + { + StartInfo.ArgumentList.Add("--cwd"); + StartInfo.ArgumentList.Add(Encode(StripNtPrefix(CurrentDirectory))); + } + + StartInfo.ArgumentList.Add(HostImage); + StartInfo.Environment[SpawnDepthVariable] = (Depth + 1).ToString(); + StartInfo.Environment["BROVAN_SESSION_ID"] = GuestSessionRegistry.SessionId; + + Process HostProcess; + try + { + HostProcess = System.Diagnostics.Process.Start(StartInfo); + } + catch (Exception Ex) + { + Utils.LogError($"[GuestProcessLauncher] Failed to launch {HostImage}: {Ex.Message}"); + Status = NTSTATUS.STATUS_NOT_SUPPORTED; + return false; + } + + if (HostProcess == null) + { + Status = NTSTATUS.STATUS_NOT_SUPPORTED; + return false; + } + + Process = new WinProcess + { + PID = unchecked((uint)HostProcess.Id), + PPID = Instance.WinHelper.PID, + Name = Path.GetFileName(HostImage), + Path = ImagePath, + Arch = Instance._binary.Architecture, + CreationTime = DateTime.UtcNow.ToFileTimeUtc(), + SpawnedHost = HostProcess, + }; + + Instance.TriggerEventMessage($"[GuestProcessLauncher] Launched {Process.Name} as host process {Process.PID} (depth {Depth + 1}).", LogFlags.Syscall); + + Status = NTSTATUS.STATUS_SUCCESS; + return true; + } + + private static string ResolveWorkingDirectory(string RequestedDirectory, string HostImage) + { + if (!string.IsNullOrWhiteSpace(RequestedDirectory)) + { + string Resolved = GeneralHelper.IO.ResolveHostPath(StripNtPrefix(RequestedDirectory), BinaryFormat.PE); + if (!string.IsNullOrEmpty(Resolved) && Directory.Exists(Resolved)) + return Resolved; + } + + return Path.GetDirectoryName(HostImage) ?? Environment.CurrentDirectory; + } + + private static void AppendEmulatorOptions(BinaryEmulator Instance, System.Collections.ObjectModel.Collection Arguments) + { + string Backend = Instance.Settings.BackendKind switch + { + EmulationBackendKind.Whp => "whp", + EmulationBackendKind.Kvm => "kvm", + _ => "unicorn", + }; + + Arguments.Add($"--backend={Backend}"); + + if (Utils.SilentMode) + Arguments.Add("--silent"); + + if (Instance.Settings.NoHooks) + Arguments.Add("--no-hooks"); + + ForwardHostOptions(Arguments); + + Arguments.Add("-c"); + Arguments.Add("start;exit"); + } + + private static void ForwardHostOptions(System.Collections.ObjectModel.Collection Arguments) + { + string[] HostArguments = Environment.GetCommandLineArgs(); + + for (int i = 1; i < HostArguments.Length; i++) + { + string Argument = HostArguments[i]; + + if (Argument.StartsWith("--net=", StringComparison.OrdinalIgnoreCase) || + Argument.StartsWith("--net-allow=", StringComparison.OrdinalIgnoreCase) || + Argument.Equals("-q", StringComparison.OrdinalIgnoreCase) || + Argument.Equals("--quick", StringComparison.OrdinalIgnoreCase)) + { + Arguments.Add(Argument); + continue; + } + + if ((Argument.Equals("--net", StringComparison.OrdinalIgnoreCase) || + Argument.Equals("--net-allow", StringComparison.OrdinalIgnoreCase)) && i + 1 < HostArguments.Length) + { + Arguments.Add(Argument); + Arguments.Add(HostArguments[++i]); + } + } + } + + private static int GetSpawnDepth() + { + return int.TryParse(Environment.GetEnvironmentVariable(SpawnDepthVariable), out int Depth) && Depth > 0 ? Depth : 0; + } + + private static bool TryReserveLaunchSlot(out NTSTATUS Status) + { + int SessionProcesses = GuestSessionRegistry.CountLive(); + if (SessionProcesses >= MaxSessionProcesses) + { + Utils.LogError($"[GuestProcessLauncher] Refusing to launch: the session already has {SessionProcesses} guest processes."); + Status = NTSTATUS.STATUS_INSUFFICIENT_RESOURCES; + return false; + } + + Status = NTSTATUS.STATUS_SUCCESS; + return true; + } + + private static bool IsAcceptableImagePath(string ImagePath) + { + string Path = StripNtPrefix(ImagePath).Replace('/', '\\'); + + if (Path.StartsWith("\\\\", StringComparison.Ordinal)) + return false; + + if (Path.StartsWith("\\Device\\", StringComparison.OrdinalIgnoreCase)) + return false; + + return Path.Length >= 2 && Path[1] == ':' && char.IsAsciiLetter(Path[0]); + } + + private static bool IsPortableExecutable(string HostImage) + { + try + { + using FileStream Stream = File.OpenRead(HostImage); + Span Header = stackalloc byte[2]; + return Stream.Read(Header) == 2 && Header[0] == (byte)'M' && Header[1] == (byte)'Z'; + } + catch (IOException) + { + return false; + } + catch (UnauthorizedAccessException) + { + return false; + } + } + + private static string StripNtPrefix(string Path) + { + if (Path.StartsWith("\\??\\", StringComparison.Ordinal)) + return Path.Substring(4); + + if (Path.StartsWith("\\\\?\\", StringComparison.Ordinal)) + return Path.Substring(4); + + return Path; + } + + private static string ReadUnicodeString(BinaryEmulator Instance, ulong Address, bool Is64) + { + if (Address == 0 || !Instance.IsRegionMapped(Address, Is64 ? 16UL : 8UL)) + return null; + + ushort Length = Instance._emulator.ReadMemoryUShort(Address); + ulong Buffer = Is64 ? Instance.ReadMemoryULong(Address + 8) : Instance.ReadMemoryUInt(Address + 4); + if (Length == 0 || Length > MaxStringBytes || Buffer == 0 || !Instance.IsRegionMapped(Buffer, Length)) + return null; + + return Instance._emulator.ReadMemoryString(Buffer, Length, Encoding.Unicode)?.TrimEnd('\0'); + } + + private static string Encode(string Value) + { + return "base64:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(Value)); + } + + private static string StripArgv0(string CommandLine) + { + if (string.IsNullOrWhiteSpace(CommandLine)) + return null; + + int Index = 0; + if (CommandLine[0] == '"') + { + Index = CommandLine.IndexOf('"', 1); + Index = Index < 0 ? CommandLine.Length : Index + 1; + } + else + { + while (Index < CommandLine.Length && CommandLine[Index] != ' ' && CommandLine[Index] != '\t') + Index++; + } + + return Index >= CommandLine.Length ? null : CommandLine.Substring(Index).TrimStart(' ', '\t'); + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs b/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs new file mode 100644 index 0000000..3616ff3 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs @@ -0,0 +1,631 @@ +using System.Buffers.Binary; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Threading; +using Brovan.Core.Helpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal static class GuestSessionRegistry + { + internal const uint ControlNone = 0; + internal const uint ControlTerminate = 1; + + private const string SessionVariable = "BROVAN_SESSION_ID"; + private const uint Magic = 0x5652424E; + private const uint Version = 1; + private const int SlotCount = 64; + private const int SlotSize = 512; + private const int HeaderSize = 64; + private const int MapSize = HeaderSize + (SlotCount * SlotSize); + private const int MaxImageBytes = SlotSize - SlotImageOffset - 2; + private const int WatcherIntervalMilliseconds = 150; + + internal const uint OpcodeReadMemory = 1; + internal const uint OpcodeWriteMemory = 2; + internal const uint OpcodeQueryPeb = 3; + internal const uint OpcodeCreateThread = 4; + + private const int MailboxSize = 0x10000; + private const int MailboxPayloadOffset = 0x40; + internal const int MaxPayloadBytes = MailboxSize - MailboxPayloadOffset; + private const int RequestTimeoutMilliseconds = 5000; + + private const int MailboxRequestSeqOffset = 0x00; + private const int MailboxResponseSeqOffset = 0x04; + private const int MailboxOpcodeOffset = 0x08; + private const int MailboxStatusOffset = 0x0C; + private const int MailboxAddressOffset = 0x10; + private const int MailboxLengthOffset = 0x18; + private const int MailboxResultLengthOffset = 0x1C; + private const int MailboxExtraOffset = 0x20; + private const int MailboxResultOffset = 0x28; + + private const int SlotStateOffset = 0x00; + private const int SlotHostPidOffset = 0x04; + private const int SlotGuestPidOffset = 0x08; + private const int SlotArchOffset = 0x0C; + private const int SlotStartTimeOffset = 0x10; + private const int SlotControlOffset = 0x18; + private const int SlotControlExitOffset = 0x1C; + private const int SlotImageLengthOffset = 0x20; + private const int SlotImageOffset = 0x28; + + private const uint SlotFree = 0; + private const uint SlotLive = 1; + + private static readonly object Sync = new(); + + private static FileStream _stream; + private static MemoryMappedFile _map; + private static MemoryMappedViewAccessor _view; + private static FileStream _mailboxStream; + private static MemoryMappedFile _mailboxMap; + private static MemoryMappedViewAccessor _mailboxView; + private static uint _lastHandledSequence; + private static Mutex _mutex; + private static Thread _watcher; + private static int _ownSlot = -1; + private static bool _initialiseFailed; + private static Action _terminateCallback; + + internal static string SessionId + { + get + { + string Existing = Environment.GetEnvironmentVariable(SessionVariable); + if (!string.IsNullOrWhiteSpace(Existing)) + return Sanitize(Existing); + + string Created = Environment.ProcessId.ToString("X") + "-" + DateTime.UtcNow.Ticks.ToString("X"); + Environment.SetEnvironmentVariable(SessionVariable, Created); + return Created; + } + } + + internal static void Join(uint GuestProcessId, uint Architecture, string ImageName, Action OnTerminateRequested) + { + lock (Sync) + { + if (_ownSlot >= 0 || _initialiseFailed) + return; + + if (!TryOpen()) + { + _initialiseFailed = true; + return; + } + + _terminateCallback = OnTerminateRequested; + + if (!TryAcquire()) + return; + + try + { + for (int i = 0; i < SlotCount; i++) + { + int Offset = HeaderSize + (i * SlotSize); + uint State = _view.ReadUInt32(Offset + SlotStateOffset); + if (State == SlotLive && IsHostProcessAlive(_view.ReadUInt32(Offset + SlotHostPidOffset))) + continue; + + WriteSlot(Offset, GuestProcessId, Architecture, ImageName); + _ownSlot = i; + break; + } + } + finally + { + Release(); + } + + if (_ownSlot < 0) + return; + + AppDomain.CurrentDomain.ProcessExit += static (_, _) => Leave(); + StartWatcher(); + } + } + + internal static void Leave() + { + lock (Sync) + { + if (_ownSlot < 0 || _view == null) + return; + + if (TryAcquire()) + { + try + { + _view.Write(HeaderSize + (_ownSlot * SlotSize) + SlotStateOffset, SlotFree); + _view.Flush(); + } + finally + { + Release(); + } + } + + _ownSlot = -1; + } + } + + internal static int CountLive() + { + int Count = 0; + + lock (Sync) + { + if (!TryOpen() || !TryAcquire()) + return 0; + + try + { + for (int i = 0; i < SlotCount; i++) + { + int Offset = HeaderSize + (i * SlotSize); + if (_view.ReadUInt32(Offset + SlotStateOffset) != SlotLive) + continue; + + if (IsHostProcessAlive(_view.ReadUInt32(Offset + SlotHostPidOffset))) + Count++; + else + _view.Write(Offset + SlotStateOffset, SlotFree); + } + } + finally + { + Release(); + } + } + + return Count; + } + + internal static bool RequestTerminate(uint GuestProcessId, uint ExitCode) + { + lock (Sync) + { + if (!TryOpen() || !TryAcquire()) + return false; + + try + { + for (int i = 0; i < SlotCount; i++) + { + int Offset = HeaderSize + (i * SlotSize); + if (_view.ReadUInt32(Offset + SlotStateOffset) != SlotLive) + continue; + + if (_view.ReadUInt32(Offset + SlotGuestPidOffset) != GuestProcessId) + continue; + + if (!IsHostProcessAlive(_view.ReadUInt32(Offset + SlotHostPidOffset))) + { + _view.Write(Offset + SlotStateOffset, SlotFree); + return false; + } + + _view.Write(Offset + SlotControlExitOffset, ExitCode); + _view.Write(Offset + SlotControlOffset, ControlTerminate); + _view.Flush(); + return true; + } + } + finally + { + Release(); + } + } + + return false; + } + + internal static NTSTATUS SendRequest( + uint GuestProcessId, + uint Opcode, + ulong Address, + ulong Extra, + ReadOnlySpan Input, + Span Output, + out int OutputLength, + out ulong Result) + { + OutputLength = 0; + Result = 0; + + if (Input.Length > MaxPayloadBytes || Output.Length > MaxPayloadBytes) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + int Slot; + uint Sequence; + + lock (Sync) + { + if (!TryOpen() || _mailboxView == null || !TryAcquire()) + return NTSTATUS.STATUS_NOT_SUPPORTED; + + try + { + Slot = FindLiveSlot(GuestProcessId); + if (Slot < 0) + return NTSTATUS.STATUS_INVALID_CID; + + long Base = (long)Slot * MailboxSize; + uint Pending = _mailboxView.ReadUInt32(Base + MailboxRequestSeqOffset); + if (Pending != _mailboxView.ReadUInt32(Base + MailboxResponseSeqOffset)) + return NTSTATUS.STATUS_INSUFFICIENT_RESOURCES; + + _mailboxView.Write(Base + MailboxOpcodeOffset, Opcode); + _mailboxView.Write(Base + MailboxAddressOffset, Address); + _mailboxView.Write(Base + MailboxExtraOffset, Extra); + _mailboxView.Write(Base + MailboxLengthOffset, (uint)(Opcode == OpcodeReadMemory ? Output.Length : Input.Length)); + _mailboxView.Write(Base + MailboxResultLengthOffset, 0u); + _mailboxView.Write(Base + MailboxStatusOffset, 0u); + + if (Input.Length > 0) + { + byte[] Buffer = Input.ToArray(); + _mailboxView.WriteArray(Base + MailboxPayloadOffset, Buffer, 0, Buffer.Length); + } + + Sequence = Pending + 1; + _mailboxView.Write(Base + MailboxRequestSeqOffset, Sequence); + _mailboxView.Flush(); + } + finally + { + Release(); + } + } + + long Deadline = Environment.TickCount64 + RequestTimeoutMilliseconds; + while (Environment.TickCount64 < Deadline) + { + Thread.Sleep(1); + + lock (Sync) + { + if (_mailboxView == null || !TryAcquire()) + continue; + + try + { + long Base = (long)Slot * MailboxSize; + if (_mailboxView.ReadUInt32(Base + MailboxResponseSeqOffset) != Sequence) + continue; + + NTSTATUS Status = (NTSTATUS)_mailboxView.ReadUInt32(Base + MailboxStatusOffset); + Result = _mailboxView.ReadUInt64(Base + MailboxResultOffset); + int Length = (int)Math.Min(_mailboxView.ReadUInt32(Base + MailboxResultLengthOffset), (uint)Output.Length); + + if (Length > 0) + { + byte[] Buffer = new byte[Length]; + _mailboxView.ReadArray(Base + MailboxPayloadOffset, Buffer, 0, Length); + Buffer.AsSpan().CopyTo(Output); + } + + OutputLength = Length; + return Status; + } + finally + { + Release(); + } + } + } + + return NTSTATUS.STATUS_TIMEOUT; + } + + internal static bool TryTakeRequest(out uint Opcode, out ulong Address, out ulong Extra, out int Length, out byte[] Input) + { + Opcode = 0; + Address = 0; + Extra = 0; + Length = 0; + Input = null; + + lock (Sync) + { + if (_ownSlot < 0 || _mailboxView == null) + return false; + + long Base = (long)_ownSlot * MailboxSize; + if (_mailboxView.ReadUInt32(Base + MailboxRequestSeqOffset) == _lastHandledSequence) + return false; + + if (!TryAcquire()) + return false; + + try + { + uint Sequence = _mailboxView.ReadUInt32(Base + MailboxRequestSeqOffset); + if (Sequence == _lastHandledSequence) + return false; + + Opcode = _mailboxView.ReadUInt32(Base + MailboxOpcodeOffset); + Address = _mailboxView.ReadUInt64(Base + MailboxAddressOffset); + Extra = _mailboxView.ReadUInt64(Base + MailboxExtraOffset); + Length = (int)Math.Min(_mailboxView.ReadUInt32(Base + MailboxLengthOffset), (uint)MaxPayloadBytes); + + if (Opcode == OpcodeWriteMemory && Length > 0) + { + Input = new byte[Length]; + _mailboxView.ReadArray(Base + MailboxPayloadOffset, Input, 0, Length); + } + + _lastHandledSequence = Sequence; + return true; + } + finally + { + Release(); + } + } + } + + internal static void CompleteRequest(uint Status, ulong Result, ReadOnlySpan Output) + { + lock (Sync) + { + if (_ownSlot < 0 || _mailboxView == null || !TryAcquire()) + return; + + try + { + long Base = (long)_ownSlot * MailboxSize; + int Length = Math.Min(Output.Length, MaxPayloadBytes); + + if (Length > 0) + { + byte[] Buffer = Output.Slice(0, Length).ToArray(); + _mailboxView.WriteArray(Base + MailboxPayloadOffset, Buffer, 0, Length); + } + + _mailboxView.Write(Base + MailboxResultLengthOffset, (uint)Length); + _mailboxView.Write(Base + MailboxResultOffset, Result); + _mailboxView.Write(Base + MailboxStatusOffset, Status); + _mailboxView.Write(Base + MailboxResponseSeqOffset, _lastHandledSequence); + _mailboxView.Flush(); + } + finally + { + Release(); + } + } + } + + private static int FindLiveSlot(uint GuestProcessId) + { + for (int i = 0; i < SlotCount; i++) + { + int Offset = HeaderSize + (i * SlotSize); + if (_view.ReadUInt32(Offset + SlotStateOffset) != SlotLive) + continue; + + if (_view.ReadUInt32(Offset + SlotGuestPidOffset) != GuestProcessId) + continue; + + return IsHostProcessAlive(_view.ReadUInt32(Offset + SlotHostPidOffset)) ? i : -1; + } + + return -1; + } + + private static void StartWatcher() + { + _watcher = new Thread(WatchForRequests) + { + IsBackground = true, + Name = "BrovanSessionWatcher", + }; + + _watcher.Start(); + } + + private static void WatchForRequests() + { + while (true) + { + Thread.Sleep(WatcherIntervalMilliseconds); + + uint Request; + uint ExitCode; + + lock (Sync) + { + if (_ownSlot < 0 || _view == null) + return; + + if (!TryAcquire()) + continue; + + try + { + int Offset = HeaderSize + (_ownSlot * SlotSize); + Request = _view.ReadUInt32(Offset + SlotControlOffset); + ExitCode = _view.ReadUInt32(Offset + SlotControlExitOffset); + if (Request != ControlNone) + _view.Write(Offset + SlotControlOffset, ControlNone); + } + finally + { + Release(); + } + } + + if (Request == ControlTerminate) + { + try + { + _terminateCallback?.Invoke(ExitCode); + } + catch (Exception Ex) + { + Utils.LogError($"[GuestSessionRegistry] Terminate request failed: {Ex.Message}"); + } + + return; + } + } + } + + private static bool TryOpen() + { + if (_view != null) + return true; + + if (_initialiseFailed) + return false; + + try + { + string Directory = Path.Combine(Path.GetTempPath(), "brovan-session-" + SessionId); + System.IO.Directory.CreateDirectory(Directory); + + _mutex = new Mutex(false, "BrovanSession_" + SessionId); + + _stream = new FileStream( + Path.Combine(Directory, "processes.bin"), + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.ReadWrite | FileShare.Delete); + + if (_stream.Length < MapSize) + _stream.SetLength(MapSize); + + _map = MemoryMappedFile.CreateFromFile(_stream, null, MapSize, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true); + _view = _map.CreateViewAccessor(0, MapSize); + + long MailboxTotal = (long)SlotCount * MailboxSize; + _mailboxStream = new FileStream( + Path.Combine(Directory, "mailboxes.bin"), + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.ReadWrite | FileShare.Delete); + + if (_mailboxStream.Length < MailboxTotal) + _mailboxStream.SetLength(MailboxTotal); + + _mailboxMap = MemoryMappedFile.CreateFromFile(_mailboxStream, null, MailboxTotal, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true); + _mailboxView = _mailboxMap.CreateViewAccessor(0, MailboxTotal); + + if (TryAcquire()) + { + try + { + if (_view.ReadUInt32(0) != Magic) + { + _view.Write(0, Magic); + _view.Write(4, Version); + _view.Write(8, (uint)SlotCount); + } + } + finally + { + Release(); + } + } + + return true; + } + catch (Exception Ex) + { + Utils.LogError($"[GuestSessionRegistry] Unavailable: {Ex.Message}"); + _initialiseFailed = true; + _view = null; + return false; + } + } + + private static bool TryAcquire() + { + if (_mutex == null) + return false; + + try + { + return _mutex.WaitOne(2000); + } + catch (AbandonedMutexException) + { + return true; + } + catch (Exception) + { + return false; + } + } + + private static void Release() + { + try + { + _mutex?.ReleaseMutex(); + } + catch (Exception) + { + } + } + + private static void WriteSlot(int Offset, uint GuestProcessId, uint Architecture, string ImageName) + { + for (int i = 0; i < SlotSize; i += 8) + _view.Write(Offset + i, 0UL); + + byte[] Name = Encoding.Unicode.GetBytes(ImageName ?? string.Empty); + int NameLength = Math.Min(Name.Length, MaxImageBytes); + + _view.Write(Offset + SlotHostPidOffset, (uint)Environment.ProcessId); + _view.Write(Offset + SlotGuestPidOffset, GuestProcessId); + _view.Write(Offset + SlotArchOffset, Architecture); + _view.Write(Offset + SlotStartTimeOffset, DateTime.UtcNow.Ticks); + _view.Write(Offset + SlotImageLengthOffset, (uint)NameLength); + + if (NameLength > 0) + _view.WriteArray(Offset + SlotImageOffset, Name, 0, NameLength); + + _view.Write(Offset + SlotStateOffset, SlotLive); + _view.Flush(); + } + + private static bool IsHostProcessAlive(uint HostProcessId) + { + if (HostProcessId == 0) + return false; + + if (HostProcessId == (uint)Environment.ProcessId) + return true; + + try + { + using System.Diagnostics.Process Existing = System.Diagnostics.Process.GetProcessById((int)HostProcessId); + return !Existing.HasExited; + } + catch (ArgumentException) + { + return false; + } + catch (InvalidOperationException) + { + return false; + } + } + + private static string Sanitize(string Value) + { + StringBuilder Builder = new StringBuilder(Value.Length); + foreach (char Character in Value) + { + if (char.IsAsciiLetterOrDigit(Character) || Character == '-' || Character == '_') + Builder.Append(Character); + } + + return Builder.Length == 0 ? "default" : Builder.ToString(); + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtCreateThreadEx.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateThreadEx.cs index 88123c6..86652b4 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtCreateThreadEx.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateThreadEx.cs @@ -72,15 +72,44 @@ public NTSTATUS Handle(BinaryEmulator Instance) if (StartRoutine == 0) return NTSTATUS.STATUS_INVALID_PARAMETER; - // Only current-process thread creation is modeled. if (!HandleManager.IsCurrentProcessPseudoHandle(ProcessHandle)) { if (!Instance.WinHelper.ValidProcessHandle(ProcessHandle)) return NTSTATUS.STATUS_INVALID_HANDLE; WinProcess Target = Instance.WinHelper.GetProcessByHandle(ProcessHandle, AccessMask.ProcessCreateThread); - if (Target == null || Target.PID != Instance.WinHelper.PID) - return NTSTATUS.STATUS_NOT_SUPPORTED; + if (Target == null) + return NTSTATUS.STATUS_ACCESS_DENIED; + + if (Target.PID != Instance.WinHelper.PID) + { + NTSTATUS RemoteStatus = GuestSessionRegistry.SendRequest( + Target.PID, + GuestSessionRegistry.OpcodeCreateThread, + StartRoutine, + Argument, + ReadOnlySpan.Empty, + Span.Empty, + out _, + out ulong RemoteThreadId); + + if (RemoteStatus != NTSTATUS.STATUS_SUCCESS) + return RemoteStatus; + + WinSpawnedThread Remote = new WinSpawnedThread + { + Process = Target, + ThreadId = (uint)RemoteThreadId, + }; + + WinHandle RemoteHandle = Instance.WinHelper.HandleManager.AddHandle(Remote, (AccessMask)(uint)DesiredAccess); + Instance.WinHelper.AddWinHandle(RemoteHandle); + + if (!Instance.WinHelper.WritePointer(ThreadHandlePtr, RemoteHandle.Handle)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + return NTSTATUS.STATUS_SUCCESS; + } } ulong? StackOverride = null; diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs new file mode 100644 index 0000000..646e616 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs @@ -0,0 +1,130 @@ +using System.Text; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal class NtCreateUserProcess : IWinSyscall + { + private const uint PsCreateSuccess = 4; + + private const ulong PsAttributeClientId = 3 | 0x10000; + private const ulong PsAttributeImageName = 5 | 0x20000; + + private const int CreateInfoStateOffset = 0x08; + private const int CreateInfoSuccessFileHandleOffset = 0x18; + private const int CreateInfoSuccessSectionHandleOffset = 0x20; + private const int MaxAttributes = 32; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong ProcessHandlePtr = Instance.WinHelper.GetArg(0); + ulong ThreadHandlePtr = Instance.WinHelper.GetArg(1); + ulong ProcessParameters = Instance.WinHelper.GetArg(8); + ulong CreateInfo = Instance.WinHelper.GetArg(9); + ulong AttributeList = Instance.WinHelper.GetArg(10); + + bool Is64 = Instance._binary.Architecture == BinaryArchitecture.x64; + int PointerSize = Is64 ? 8 : 4; + + if (ProcessParameters == 0 || !Instance.IsRegionMapped(ProcessParameters, 0x40)) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + string ImageNameHint = ReadImageNameAttribute(Instance, AttributeList, Is64); + + if (!GuestProcessLauncher.TryLaunch(Instance, ProcessParameters, ImageNameHint, out WinProcess Process, out NTSTATUS Status)) + return Status; + + WinSpawnedThread Thread = new WinSpawnedThread + { + Process = Process, + ThreadId = Process.PID, + }; + + ulong ProcessHandle = Instance.WinHelper.HandleManager.AddHandle(Process, AccessMask.GenericAll).Handle; + ulong ThreadHandle = Instance.WinHelper.HandleManager.AddHandle(Thread, AccessMask.GenericAll).Handle; + + if (ProcessHandlePtr != 0 && Instance.IsRegionMapped(ProcessHandlePtr, (ulong)PointerSize)) + Instance._emulator.WriteMemory(ProcessHandlePtr, ProcessHandle, (uint)PointerSize); + + if (ThreadHandlePtr != 0 && Instance.IsRegionMapped(ThreadHandlePtr, (ulong)PointerSize)) + Instance._emulator.WriteMemory(ThreadHandlePtr, ThreadHandle, (uint)PointerSize); + + WriteCreateInfoSuccess(Instance, CreateInfo, Is64); + WriteClientIdAttribute(Instance, AttributeList, Is64, Process.PID, Thread.ThreadId); + + return NTSTATUS.STATUS_SUCCESS; + } + + private static void WriteCreateInfoSuccess(BinaryEmulator Instance, ulong CreateInfo, bool Is64) + { + if (CreateInfo == 0 || !Instance.IsRegionMapped(CreateInfo, 0x58)) + return; + + Instance._emulator.WriteMemory(CreateInfo + CreateInfoStateOffset, PsCreateSuccess, 4); + + if (!Is64) + return; + + Instance._emulator.WriteMemory(CreateInfo + CreateInfoSuccessFileHandleOffset, 0UL, 8); + Instance._emulator.WriteMemory(CreateInfo + CreateInfoSuccessSectionHandleOffset, 0UL, 8); + } + + private static void WriteClientIdAttribute(BinaryEmulator Instance, ulong AttributeList, bool Is64, uint ProcessId, uint ThreadId) + { + if (!TryFindAttribute(Instance, AttributeList, Is64, PsAttributeClientId, out ulong ValuePointer, out ulong Size)) + return; + + int PointerSize = Is64 ? 8 : 4; + if (ValuePointer == 0 || Size < (ulong)(PointerSize * 2) || !Instance.IsRegionMapped(ValuePointer, Size)) + return; + + Instance._emulator.WriteMemory(ValuePointer, ProcessId, (uint)PointerSize); + Instance._emulator.WriteMemory(ValuePointer + (ulong)PointerSize, ThreadId, (uint)PointerSize); + } + + private static string ReadImageNameAttribute(BinaryEmulator Instance, ulong AttributeList, bool Is64) + { + if (!TryFindAttribute(Instance, AttributeList, Is64, PsAttributeImageName, out ulong ValuePointer, out ulong Size)) + return null; + + if (ValuePointer == 0 || Size == 0 || Size > 0x8000 || !Instance.IsRegionMapped(ValuePointer, Size)) + return null; + + return Instance._emulator.ReadMemoryString(ValuePointer, (int)Size, Encoding.Unicode)?.TrimEnd('\0'); + } + + private static bool TryFindAttribute(BinaryEmulator Instance, ulong AttributeList, bool Is64, ulong Attribute, out ulong ValuePointer, out ulong Size) + { + ValuePointer = 0; + Size = 0; + + int PointerSize = Is64 ? 8 : 4; + int EntrySize = PointerSize * 4; + + if (AttributeList == 0 || !Instance.IsRegionMapped(AttributeList, (ulong)PointerSize)) + return false; + + ulong TotalLength = Is64 ? Instance.ReadMemoryULong(AttributeList) : Instance.ReadMemoryUInt(AttributeList); + if (TotalLength <= (uint)PointerSize) + return false; + + ulong Count = (TotalLength - (ulong)PointerSize) / (ulong)EntrySize; + if (Count == 0 || Count > MaxAttributes || !Instance.IsRegionMapped(AttributeList, TotalLength)) + return false; + + for (ulong i = 0; i < Count; i++) + { + ulong Entry = AttributeList + (ulong)PointerSize + i * (ulong)EntrySize; + ulong EntryAttribute = Is64 ? Instance.ReadMemoryULong(Entry) : Instance.ReadMemoryUInt(Entry); + if (EntryAttribute != Attribute) + continue; + + Size = Is64 ? Instance.ReadMemoryULong(Entry + (ulong)PointerSize) : Instance.ReadMemoryUInt(Entry + (ulong)PointerSize); + ValuePointer = Is64 ? Instance.ReadMemoryULong(Entry + (ulong)(PointerSize * 2)) : Instance.ReadMemoryUInt(Entry + (ulong)(PointerSize * 2)); + return true; + } + + return false; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs index 1243574..43cc76e 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs @@ -57,7 +57,14 @@ void SetReturnLength(uint Len) return NTSTATUS.STATUS_ACCESS_DENIED; } - if (!WriteProcessBasicInformation(Instance, OutBufferPtr, Instance.PEB, 0x8UL, Process.PID, Process.PPID)) + ulong Peb = Instance.PEB; + if (Process.PID != Instance.WinHelper.PID && + GuestSessionRegistry.SendRequest(Process.PID, GuestSessionRegistry.OpcodeQueryPeb, 0, 0, ReadOnlySpan.Empty, Span.Empty, out _, out ulong RemotePeb) == NTSTATUS.STATUS_SUCCESS) + { + Peb = RemotePeb; + } + + if (!WriteProcessBasicInformation(Instance, OutBufferPtr, Peb, 0x8UL, Process.PID, Process.PPID)) return NTSTATUS.STATUS_ACCESS_VIOLATION; SetReturnLength(PbiSize); if ((Instance.Settings.Flags & LogFlags.Syscall) != 0) diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs index 59fabf7..68b7b2a 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs @@ -81,23 +81,41 @@ public NTSTATUS Handle(BinaryEmulator Instance) goto current_process; // jump to the current process handling } - if (BaseAddressPtr == 0) - return NTSTATUS.STATUS_INVALID_PARAMETER; - - if (!Instance.IsRegionMapped(BaseAddressPtr, sizeof(ulong))) - return NTSTATUS.STATUS_MEMORY_NOT_ALLOCATED; + ulong RemoteAddress = BaseAddressPtr; + ulong LocalBuffer = BufferPtr; + ulong BytesReadPtr = Instance.WinHelper.GetArg(4); - if (BufferPtr == 0) + if (RemoteAddress == 0 || LocalBuffer == 0 || NumberOfBytesToRead == 0) return NTSTATUS.STATUS_INVALID_PARAMETER; - if(NumberOfBytesToRead == 0) - return NTSTATUS.STATUS_INVALID_PARAMETER; + if (NumberOfBytesToRead > GuestSessionRegistry.MaxPayloadBytes) + NumberOfBytesToRead = GuestSessionRegistry.MaxPayloadBytes; + + if (!Instance.IsRegionMapped(LocalBuffer, NumberOfBytesToRead)) + return NTSTATUS.STATUS_MEMORY_NOT_ALLOCATED; - if (!Instance.WriteMemory(BufferPtr, Instance.WinHelper.GenerateRandomData((int)NumberOfBytesToRead))) // generate random data? + byte[] Remote = new byte[NumberOfBytesToRead]; + NTSTATUS RemoteStatus = GuestSessionRegistry.SendRequest( + Process.PID, + GuestSessionRegistry.OpcodeReadMemory, + RemoteAddress, + 0, + ReadOnlySpan.Empty, + Remote, + out int RemoteLength, + out _); + + if (RemoteStatus != NTSTATUS.STATUS_SUCCESS) + return RemoteStatus; + + if (!Instance._emulator.WriteMemory(LocalBuffer, Remote, 0, RemoteLength)) return NTSTATUS.STATUS_ACCESS_VIOLATION; + if (BytesReadPtr != 0 && Instance.IsRegionMapped(BytesReadPtr, sizeof(ulong))) + Instance._emulator.WriteMemory(BytesReadPtr, (ulong)RemoteLength, 8); + if ((Instance.Settings.Flags & LogFlags.Syscall) != 0) - Instance.TriggerEventMessage($"[+] The emulated process tried to read the memory of process \"{Process.Name}\", random data was generated for it.", LogFlags.Syscall); + Instance.TriggerEventMessage($"[+] Read 0x{RemoteLength:X} bytes from process \"{Process.Name}\" at 0x{RemoteAddress:X}.", LogFlags.Syscall); return NTSTATUS.STATUS_SUCCESS; } } diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtTerminateProcess.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtTerminateProcess.cs index 0ae196f..5691272 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtTerminateProcess.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtTerminateProcess.cs @@ -1,3 +1,4 @@ +using Brovan.Core.Helpers; using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows @@ -68,6 +69,23 @@ public NTSTATUS Handle(BinaryEmulator Instance) Instance.StopEmulation(); return NTSTATUS.STATUS_SUCCESS; } + + if (GuestSessionRegistry.RequestTerminate(Process.PID, (uint)ExitCode)) + return NTSTATUS.STATUS_SUCCESS; + + if (Process.SpawnedHost != null && !Process.SpawnedHasExited) + { + try + { + Process.SpawnedHost.Kill(); + return NTSTATUS.STATUS_SUCCESS; + } + catch (Exception Ex) + { + Utils.LogError($"[NtTerminateProcess] Failed to stop host process {Process.PID}: {Ex.Message}"); + return NTSTATUS.STATUS_ACCESS_DENIED; + } + } } } return Instance.WinUnimplemented; diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtWriteVirtualMemory.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtWriteVirtualMemory.cs new file mode 100644 index 0000000..8393780 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtWriteVirtualMemory.cs @@ -0,0 +1,77 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal class NtWriteVirtualMemory : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong ProcessHandle = Instance.WinHelper.GetArg(0); + ulong BaseAddress = Instance.WinHelper.GetArg(1); + ulong Buffer = Instance.WinHelper.GetArg(2); + ulong NumberOfBytesToWrite = Instance.WinHelper.GetArg(3); + ulong BytesWrittenPtr = Instance.WinHelper.GetArg(4); + + if (BaseAddress == 0 || Buffer == 0 || NumberOfBytesToWrite == 0) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + if (NumberOfBytesToWrite > GuestSessionRegistry.MaxPayloadBytes) + NumberOfBytesToWrite = GuestSessionRegistry.MaxPayloadBytes; + + if (!Instance.IsRegionMapped(Buffer, NumberOfBytesToWrite)) + return NTSTATUS.STATUS_MEMORY_NOT_ALLOCATED; + + byte[] Payload = Instance.ReadMemory(Buffer, (uint)NumberOfBytesToWrite); + if (Payload.Length == 0) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + if (!HandleManager.IsCurrentProcessPseudoHandle(ProcessHandle)) + { + if (!Instance.WinHelper.HandleExists(ProcessHandle)) + return NTSTATUS.STATUS_INVALID_HANDLE; + + WinProcess Process = Instance.WinHelper.GetProcessByHandle(ProcessHandle, AccessMask.ProcessVMOperation | AccessMask.ProcessVMWrite); + if (Process == null) + return NTSTATUS.STATUS_ACCESS_DENIED; + + if (Process.PID != Instance.WinHelper.PID) + { + NTSTATUS RemoteStatus = GuestSessionRegistry.SendRequest( + Process.PID, + GuestSessionRegistry.OpcodeWriteMemory, + BaseAddress, + 0, + Payload, + Span.Empty, + out _, + out ulong Written); + + if (RemoteStatus != NTSTATUS.STATUS_SUCCESS) + return RemoteStatus; + + WriteCount(Instance, BytesWrittenPtr, Written); + return NTSTATUS.STATUS_SUCCESS; + } + } + + if (!Instance.IsRegionMapped(BaseAddress, NumberOfBytesToWrite)) + return NTSTATUS.STATUS_MEMORY_NOT_ALLOCATED; + + if (!Instance._emulator.WriteMemory(BaseAddress, Payload)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + WriteCount(Instance, BytesWrittenPtr, (ulong)Payload.Length); + return NTSTATUS.STATUS_SUCCESS; + } + + private static void WriteCount(BinaryEmulator Instance, ulong BytesWrittenPtr, ulong Count) + { + if (BytesWrittenPtr == 0) + return; + + int PointerSize = Instance._binary.Architecture == BinaryArchitecture.x64 ? 8 : 4; + if (Instance.IsRegionMapped(BytesWrittenPtr, (ulong)PointerSize)) + Instance._emulator.WriteMemory(BytesWrittenPtr, Count, (uint)PointerSize); + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Process/RemoteProcessRequests.cs b/Brovan/Core/Emulation/OS/Windows/Process/RemoteProcessRequests.cs new file mode 100644 index 0000000..72c94b6 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Process/RemoteProcessRequests.cs @@ -0,0 +1,88 @@ +using Brovan.Core.Emulation.Guests; +using Brovan.Core.Helpers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal static class RemoteProcessRequests + { + internal static void Drain(BinaryEmulator Instance) + { + while (GuestSessionRegistry.TryTakeRequest(out uint Opcode, out ulong Address, out ulong Extra, out int Length, out byte[] Input)) + { + uint Status; + ulong Result = 0; + byte[] Output = null; + + try + { + Status = Execute(Instance, Opcode, Address, Extra, Length, Input, out Result, out Output); + } + catch (Exception Ex) + { + Utils.LogError($"[RemoteProcessRequests] Opcode {Opcode} failed: {Ex.Message}"); + Status = (uint)NTSTATUS.STATUS_UNSUCCESSFUL; + } + + GuestSessionRegistry.CompleteRequest(Status, Result, Output ?? Array.Empty()); + } + } + + private static uint Execute(BinaryEmulator Instance, uint Opcode, ulong Address, ulong Extra, int Length, byte[] Input, out ulong Result, out byte[] Output) + { + Result = 0; + Output = null; + + switch (Opcode) + { + case GuestSessionRegistry.OpcodeQueryPeb: + Result = Instance.PEB; + return (uint)NTSTATUS.STATUS_SUCCESS; + + case GuestSessionRegistry.OpcodeReadMemory: + { + if (Length <= 0 || !Instance.IsRegionMapped(Address, (ulong)Length)) + return (uint)NTSTATUS.STATUS_ACCESS_VIOLATION; + + byte[] Buffer = new byte[Length]; + if (!Instance.ReadMemory(Address, Buffer, (uint)Length)) + return (uint)NTSTATUS.STATUS_ACCESS_VIOLATION; + + Output = Buffer; + Result = (ulong)Length; + return (uint)NTSTATUS.STATUS_SUCCESS; + } + + case GuestSessionRegistry.OpcodeWriteMemory: + { + if (Input == null || Input.Length == 0 || !Instance.IsRegionMapped(Address, (ulong)Input.Length)) + return (uint)NTSTATUS.STATUS_ACCESS_VIOLATION; + + if (!Instance._emulator.WriteMemory(Address, Input)) + return (uint)NTSTATUS.STATUS_ACCESS_VIOLATION; + + Result = (ulong)Input.Length; + return (uint)NTSTATUS.STATUS_SUCCESS; + } + + case GuestSessionRegistry.OpcodeCreateThread: + { + if (Address == 0 || !Instance.IsRegionMapped(Address, 1)) + return (uint)NTSTATUS.STATUS_INVALID_PARAMETER; + + EmulatedThread Thread = Instance.Guest is WindowsGuest Guest + ? Guest.CreateEmulatedThread(Instance, Address, null, Extra, null, 8, 0, false) + : Instance.CreateEmulatedThread(Address, null, Extra, null); + + if (Thread == null) + return (uint)NTSTATUS.STATUS_NO_MEMORY; + + Result = Thread.ThreadId; + return (uint)NTSTATUS.STATUS_SUCCESS; + } + } + + return (uint)NTSTATUS.STATUS_NOT_SUPPORTED; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs index adaa2b2..b50f86d 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs @@ -2,27 +2,47 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k { internal class NtGdiGetDeviceCaps : IWinSyscall { + private const int HORZSIZE = 4; + private const int VERTSIZE = 6; + private const int HORZRES = 8; + private const int VERTRES = 10; + private const int LOGPIXELSX = 88; + private const int LOGPIXELSY = 90; + + private const int TenthsOfMillimetrePerInch = 254; + public NTSTATUS Handle(BinaryEmulator Instance) { int Index = unchecked((int)Instance.WinHelper.GetArg(1)); - Instance.SetRawSyscallReturn(unchecked((ulong)(uint)GetDeviceCapability(Index))); + Instance.SetRawSyscallReturn(unchecked((ulong)(uint)GetDeviceCapability(Instance, Index))); return NTSTATUS.STATUS_SUCCESS; } - internal static int GetDeviceCapability(int Index) + internal static int GetDeviceCapability(BinaryEmulator Instance, int Index) { + int Dpi = (int)Win32kDpi.GetEffectiveDpi(Instance); + + switch (Index) + { + case HORZSIZE: + return Win32kDpi.GetScreenWidth(Instance) * TenthsOfMillimetrePerInch / (Dpi * 10); + case VERTSIZE: + return Win32kDpi.GetScreenHeight(Instance) * TenthsOfMillimetrePerInch / (Dpi * 10); + case HORZRES: + return Win32kDpi.GetScreenWidth(Instance); + case VERTRES: + return Win32kDpi.GetScreenHeight(Instance); + case LOGPIXELSX: + case LOGPIXELSY: + return Dpi; + } + return Index switch { 2 => 1, // TECHNOLOGY: DT_RASDISPLAY - 4 => 320, // HORZSIZE - 6 => 180, // VERTSIZE - 8 => 1920, // HORZRES - 10 => 1080, // VERTRES 12 => 32, // BITSPIXEL 14 => 1, // PLANES 24 => -1, // NUMCOLORS - 88 => 96, // LOGPIXELSX - 90 => 96, // LOGPIXELSY 116 => 60, // VREFRESH 121 => 0x00000003, // COLORMGMTCAPS: CM_DEVICE_ICM | CM_GAMMA_RAMP _ => 0, diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableNonClientDpiScaling.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableNonClientDpiScaling.cs new file mode 100644 index 0000000..2e3d8db --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableNonClientDpiScaling.cs @@ -0,0 +1,22 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserEnableNonClientDpiScaling : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + + if (Hwnd == 0 || Instance.WinHelper.GetWindow(Hwnd) == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetRawSyscallReturn(1); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForCurrentProcess.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForCurrentProcess.cs index fc32d84..a5fdc50 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForCurrentProcess.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForCurrentProcess.cs @@ -1,3 +1,4 @@ +using Brovan.Core.Emulation.OS.SharedHelpers; using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k @@ -6,9 +7,8 @@ internal class NtUserGetDpiForCurrentProcess : IWinSyscall { public NTSTATUS Handle(BinaryEmulator Instance) { - - Instance.SetRawSyscallReturn(Win32kHelper.DEFAULT_SCREEN_DPI); + Instance.SetRawSyscallReturn(HostDisplayMetrics.SystemDpi); return NTSTATUS.STATUS_SUCCESS; } } -} \ No newline at end of file +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForMonitor.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForMonitor.cs index d01f64a..97a65b1 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForMonitor.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDpiForMonitor.cs @@ -6,16 +6,27 @@ internal class NtUserGetDpiForMonitor : IWinSyscall { public NTSTATUS Handle(BinaryEmulator Instance) { + ulong Monitor = Instance.WinHelper.GetArg(0); + uint DpiType = (uint)Instance.WinHelper.GetArg(1); ulong DpiXPtr = Instance.WinHelper.GetArg(2); ulong DpiYPtr = Instance.WinHelper.GetArg(3); + if (Monitor == 0) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + uint Dpi = Win32kDpi.GetMonitorDpi(Instance, DpiType); + if (DpiXPtr != 0 && Instance.IsRegionMapped(DpiXPtr, 4)) - Instance._emulator.WriteMemory(DpiXPtr, Win32kHelper.DEFAULT_SCREEN_DPI, 4); + Instance._emulator.WriteMemory(DpiXPtr, Dpi, 4); if (DpiYPtr != 0 && Instance.IsRegionMapped(DpiYPtr, 4)) - Instance._emulator.WriteMemory(DpiYPtr, Win32kHelper.DEFAULT_SCREEN_DPI, 4); + Instance._emulator.WriteMemory(DpiYPtr, Dpi, 4); - Instance.SetRawSyscallReturn(0); + Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; } } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetProcessDpiAwarenessContext.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetProcessDpiAwarenessContext.cs new file mode 100644 index 0000000..c6bd5c8 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetProcessDpiAwarenessContext.cs @@ -0,0 +1,13 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetProcessDpiAwarenessContext : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetRawSyscallReturn(Win32kDpi.GetProcessContext(Instance)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetSystemDpiForProcess.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetSystemDpiForProcess.cs new file mode 100644 index 0000000..5bf03be --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetSystemDpiForProcess.cs @@ -0,0 +1,13 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetSystemDpiForProcess : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetRawSyscallReturn(Win32kDpi.GetEffectiveDpi(Instance)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetProcessDpiAwarenessContext.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetProcessDpiAwarenessContext.cs new file mode 100644 index 0000000..15511bc --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetProcessDpiAwarenessContext.cs @@ -0,0 +1,15 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetProcessDpiAwarenessContext : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + uint Context = (uint)Instance.WinHelper.GetArg(0); + + Instance.SetBooleanSyscallReturn(Win32kDpi.TrySetProcessContext(Instance, Context)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kDpi.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kDpi.cs new file mode 100644 index 0000000..02933eb --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kDpi.cs @@ -0,0 +1,427 @@ +using System.Runtime.CompilerServices; +using System.Xml; +using Brovan.Core.Emulation.OS.SharedHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal static class Win32kDpi + { + internal const uint ContextUnaware = 0x00006010; + internal const uint ContextUnawareGdiScaled = 0x40006010; + internal const uint ContextPerMonitorV2 = 0x00000022; + + internal const uint AwarenessUnaware = 0; + internal const uint AwarenessSystem = 1; + internal const uint AwarenessPerMonitor = 2; + + internal const uint MonitorDpiTypeEffective = 0; + + private const uint SystemAwareFlags = 0x11; + private const uint AwarenessMask = 0xF; + private const uint DpiFieldMask = 0x1FF; + private const int DpiFieldShift = 8; + + private const ulong TebContextOffset = 0x8E8; + + private const ulong WindowContextOffset = 0x120; + private const ulong WindowDpiOffset = 0x11C; + private const ulong WindowDpiAlternateOffset = 0x11E; + + private const int ResourceDataDirectoryIndex = 2; + private const uint ResourceTypeManifest = 24; + private const uint ResourceIdCreateProcessManifest = 1; + private const int ResourceDirectoryHeaderSize = 0x10; + private const int ResourceDirectoryEntrySize = 8; + private const int ResourceDataEntrySize = 0x10; + private const int MaxResourceDirectoryEntries = 0x1000; + private const int MaxManifestBytes = 0x100000; + + private static readonly ConditionalWeakTable States = new(); + + private sealed class DpiState + { + public uint ProcessContext = ContextUnaware; + public bool Locked; + } + + private static DpiState GetState(BinaryEmulator Instance) + { + return States.GetValue(Instance, static _ => new DpiState()); + } + + internal static uint GetProcessContext(BinaryEmulator Instance) + { + return GetState(Instance).ProcessContext; + } + + internal static uint GetAwareness(BinaryEmulator Instance) + { + return GetState(Instance).ProcessContext & AwarenessMask; + } + + internal static DpiAwareness GetHostAwareness(BinaryEmulator Instance) + { + return GetAwareness(Instance) switch + { + AwarenessPerMonitor => DpiAwareness.PerMonitor, + AwarenessSystem => DpiAwareness.System, + _ => DpiAwareness.Unaware, + }; + } + + internal static uint GetEffectiveDpi(BinaryEmulator Instance) + { + return GetAwareness(Instance) == AwarenessUnaware ? HostDisplayMetrics.DefaultDpi : HostDisplayMetrics.SystemDpi; + } + + internal static uint GetMonitorDpi(BinaryEmulator Instance, uint DpiType) + { + return DpiType == MonitorDpiTypeEffective ? GetEffectiveDpi(Instance) : HostDisplayMetrics.RawDpi; + } + + internal static int GetScreenWidth(BinaryEmulator Instance) + { + return ScaleToProcess(Instance, HostDisplayMetrics.ScreenWidth); + } + + internal static int GetScreenHeight(BinaryEmulator Instance) + { + return ScaleToProcess(Instance, HostDisplayMetrics.ScreenHeight); + } + + private static int ScaleToProcess(BinaryEmulator Instance, int PhysicalPixels) + { + if (!HostDisplayMetrics.VirtualizesUnawareWindows) + return PhysicalPixels; + + uint Dpi = GetEffectiveDpi(Instance); + uint SystemDpi = HostDisplayMetrics.SystemDpi; + if (Dpi == SystemDpi || SystemDpi == 0) + return PhysicalPixels; + + return Math.Max((int)((long)PhysicalPixels * Dpi / SystemDpi), 1); + } + + internal static uint BuildContext(uint Awareness, bool GdiScaled = false) + { + return Awareness switch + { + AwarenessSystem => ((HostDisplayMetrics.SystemDpi & DpiFieldMask) << DpiFieldShift) | SystemAwareFlags, + AwarenessPerMonitor => ContextPerMonitorV2, + _ => GdiScaled ? ContextUnawareGdiScaled : ContextUnaware, + }; + } + + internal static bool IsValidContext(uint Context) + { + return (Context & AwarenessMask) <= AwarenessPerMonitor; + } + + internal static bool TrySetProcessContext(BinaryEmulator Instance, uint Context) + { + if (!IsValidContext(Context)) + return false; + + DpiState State = GetState(Instance); + if (State.Locked) + return false; + + State.ProcessContext = (Context & AwarenessMask) == AwarenessSystem + ? BuildContext(AwarenessSystem) + : Context; + + State.Locked = true; + PublishContext(Instance); + return true; + } + + private static void PublishContext(BinaryEmulator Instance) + { + foreach (EmulatedThread Thread in Instance.GetThreadsSnapshot()) + { + WindowsThreadState ThreadState = WinEmulatedThread.TryGetState(Thread); + if (ThreadState != null) + ApplyThreadContext(Instance, ThreadState.Teb); + } + + Instance.WinHelper?.RefreshDisplayDependentState(); + } + + internal static void ApplyThreadContext(BinaryEmulator Instance, ulong Teb) + { + if (Teb == 0) + return; + + Instance._emulator.WriteMemory(Teb + TebContextOffset, GetProcessContext(Instance), 4); + } + + internal static void ApplyWindowContext(BinaryEmulator Instance, ulong ClientWindowAddress) + { + if (ClientWindowAddress == 0) + return; + + uint Context = GetProcessContext(Instance); + Instance._emulator.WriteMemory(ClientWindowAddress + WindowContextOffset, Context, 4); + Instance._emulator.WriteMemory(ClientWindowAddress + WindowDpiOffset, (ushort)GetEffectiveDpi(Instance), 2); + Instance._emulator.WriteMemory(ClientWindowAddress + WindowDpiAlternateOffset, (ushort)0, 2); + } + + internal static void DrainHostDpiChange(BinaryEmulator Instance) + { + uint NewDpi = HostEventQueue.ConsumeDpiChange(); + if (NewDpi == 0) + return; + + Instance.WinHelper?.RefreshDisplayDependentState(); + + if (GetAwareness(Instance) != AwarenessPerMonitor) + return; + + ulong Hwnd = Instance.WinHelper?.GetForegroundWindow() ?? 0; + WinWindow Window = Hwnd == 0 ? null : Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + return; + + ulong Rect = Instance.WinHelper.EnsureDpiChangeRect(); + if (Rect == 0) + return; + + int Left = Window.X == unchecked((int)0x80000000) ? 0 : Window.X; + int Top = Window.Y == unchecked((int)0x80000000) ? 0 : Window.Y; + Instance._emulator.WriteMemory(Rect + 0x00, (uint)Left, 4); + Instance._emulator.WriteMemory(Rect + 0x04, (uint)Top, 4); + Instance._emulator.WriteMemory(Rect + 0x08, (uint)(Left + (int)Window.Width), 4); + Instance._emulator.WriteMemory(Rect + 0x0C, (uint)(Top + (int)Window.Height), 4); + + Win32kHelper.PostMessage(Instance, Hwnd, Win32kHelper.WM_DPICHANGED, (NewDpi << 16) | NewDpi, Rect); + } + + internal static void SeedFromImage(BinaryEmulator Instance, WinModule Module) + { + if (Module == null || Instance._binary?.FileFormat != BinaryFormat.PE) + return; + + byte[] Manifest = ReadExternalManifest(Module) ?? ReadEmbeddedManifest(Instance, Module); + if (Manifest == null) + return; + + try + { + if (TryParseManifestAwareness(Manifest, out uint Awareness, out bool GdiScaled)) + { + DpiState State = GetState(Instance); + State.ProcessContext = BuildContext(Awareness, GdiScaled); + State.Locked = true; + } + } + catch (XmlException) + { + } + catch (InvalidOperationException) + { + } + } + + private static byte[] ReadExternalManifest(WinModule Module) + { + if (string.IsNullOrEmpty(Module.Path)) + return null; + + try + { + string Path = Module.Path + ".manifest"; + if (!File.Exists(Path)) + return null; + + using FileStream Stream = File.OpenRead(Path); + if (Stream.Length == 0 || Stream.Length > MaxManifestBytes) + return null; + + byte[] Buffer = new byte[Stream.Length]; + return Stream.ReadAtLeast(Buffer, Buffer.Length, false) == Buffer.Length ? Buffer : null; + } + catch (IOException) + { + return null; + } + catch (UnauthorizedAccessException) + { + return null; + } + } + + private static byte[] ReadEmbeddedManifest(BinaryEmulator Instance, WinModule Module) + { + if (!TryFindManifestResource(Instance, Module, out ulong Address, out uint Size)) + return null; + + byte[] Buffer = new byte[Size]; + return Instance.ReadMemory(Address, Buffer, Size) ? Buffer : null; + } + + private static bool TryFindManifestResource(BinaryEmulator Instance, WinModule Module, out ulong Address, out uint Size) + { + Address = 0; + Size = 0; + + uint DirectoryRva = Instance._binary.Architecture == BinaryArchitecture.x64 + ? Instance._binary.PE.OptionalHeader64.DataDirectory[ResourceDataDirectoryIndex].VirtualAddress + : Instance._binary.PE.OptionalHeader32.DataDirectory[ResourceDataDirectoryIndex].VirtualAddress; + + if (DirectoryRva == 0) + return false; + + ulong Root = Module.MappedBase + DirectoryRva; + if (!TryFindDirectoryEntry(Instance, Root, Root, ResourceTypeManifest, out ulong TypeDirectory, out bool IsDirectory) || !IsDirectory) + return false; + + if (!TryFindDirectoryEntry(Instance, Root, TypeDirectory, ResourceIdCreateProcessManifest, out ulong NameDirectory, out IsDirectory) || !IsDirectory) + return false; + + if (!TryFindDirectoryEntry(Instance, Root, NameDirectory, uint.MaxValue, out ulong DataEntry, out IsDirectory) || IsDirectory) + return false; + + if (!Instance.IsRegionMapped(DataEntry, ResourceDataEntrySize)) + return false; + + uint DataRva = Instance.ReadMemoryUInt(DataEntry + 0x00); + uint DataSize = Instance.ReadMemoryUInt(DataEntry + 0x04); + if (DataRva == 0 || DataSize == 0 || DataSize > MaxManifestBytes) + return false; + + Address = Module.MappedBase + DataRva; + Size = DataSize; + return Instance.IsRegionMapped(Address, DataSize); + } + + private static bool TryFindDirectoryEntry(BinaryEmulator Instance, ulong Root, ulong Directory, uint Id, out ulong Target, out bool IsDirectory) + { + Target = 0; + IsDirectory = false; + + if (!Instance.IsRegionMapped(Directory, ResourceDirectoryHeaderSize)) + return false; + + int NamedEntries = Instance._emulator.ReadMemoryUShort(Directory + 0x0C); + int IdEntries = Instance._emulator.ReadMemoryUShort(Directory + 0x0E); + int TotalEntries = NamedEntries + IdEntries; + if (TotalEntries <= 0 || TotalEntries > MaxResourceDirectoryEntries) + return false; + + ulong EntryBase = Directory + ResourceDirectoryHeaderSize; + if (!Instance.IsRegionMapped(EntryBase, (ulong)(TotalEntries * ResourceDirectoryEntrySize))) + return false; + + for (int i = NamedEntries; i < TotalEntries; i++) + { + ulong Entry = EntryBase + (ulong)(i * ResourceDirectoryEntrySize); + uint EntryId = Instance.ReadMemoryUInt(Entry + 0x00); + if (Id != uint.MaxValue && EntryId != Id) + continue; + + uint OffsetToData = Instance.ReadMemoryUInt(Entry + 0x04); + IsDirectory = (OffsetToData & 0x80000000u) != 0; + Target = Root + (OffsetToData & 0x7FFFFFFFu); + return true; + } + + return false; + } + + private static bool TryParseManifestAwareness(byte[] Manifest, out uint Awareness, out bool GdiScaled) + { + Awareness = AwarenessUnaware; + GdiScaled = false; + + string DpiAwarenessValue = null; + string DpiAwareValue = null; + string GdiScalingValue = null; + + XmlReaderSettings Settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, + IgnoreComments = true, + IgnoreWhitespace = true, + IgnoreProcessingInstructions = true, + XmlResolver = null, + CloseInput = true, + }; + + using MemoryStream Stream = new MemoryStream(Manifest, 0, Manifest.Length, false); + using XmlReader Reader = XmlReader.Create(Stream, Settings); + + while (!Reader.EOF) + { + if (Reader.NodeType != XmlNodeType.Element) + { + Reader.Read(); + continue; + } + + string Name = Reader.LocalName; + if (Name != "dpiAwareness" && Name != "dpiAware" && Name != "gdiScaling") + { + Reader.Read(); + continue; + } + + string Value = Reader.ReadElementContentAsString(); + switch (Name) + { + case "dpiAwareness": + DpiAwarenessValue ??= Value; + break; + case "dpiAware": + DpiAwareValue ??= Value; + break; + default: + GdiScalingValue ??= Value; + break; + } + } + + GdiScaled = string.Equals(GdiScalingValue?.Trim(), "true", StringComparison.OrdinalIgnoreCase); + + if (DpiAwarenessValue != null) + { + foreach (string Token in DpiAwarenessValue.Split(',')) + { + switch (Token.Trim().ToLowerInvariant()) + { + case "permonitorv2": + case "permonitor": + Awareness = AwarenessPerMonitor; + return true; + case "system": + Awareness = AwarenessSystem; + return true; + case "unaware": + Awareness = AwarenessUnaware; + return true; + } + } + } + + if (DpiAwareValue == null) + return GdiScaled; + + switch (DpiAwareValue.Trim().ToLowerInvariant()) + { + case "true/pm": + case "per monitor": + case "permonitor": + Awareness = AwarenessPerMonitor; + return true; + case "true": + Awareness = AwarenessSystem; + return true; + case "false": + Awareness = AwarenessUnaware; + return true; + default: + return GdiScaled; + } + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs index c70d72e..e500c3c 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs @@ -40,7 +40,6 @@ internal static class Win32kHelper internal const uint ERROR_INVALID_PARAMETER = 87; internal const uint ERROR_CALL_NOT_IMPLEMENTED = 120; internal const uint ERROR_INVALID_WINDOW_HANDLE = 1400; - internal const uint DEFAULT_SCREEN_DPI = 96; internal const byte PenHandleType = 0x30; internal const byte BrushHandleType = 0x10; @@ -63,6 +62,7 @@ internal static class Win32kHelper internal const uint WM_SYSKEYDOWN = 0x0104; internal const uint WM_SYSKEYUP = 0x0105; internal const uint WM_SYSCHAR = 0x0106; + internal const uint WM_DPICHANGED = 0x02E0; internal const uint WM_MOUSEMOVE = 0x0200; internal const uint WM_LBUTTONDOWN = 0x0201; internal const uint WM_LBUTTONUP = 0x0202; @@ -454,6 +454,8 @@ private static void DrainHostEvents(BinaryEmulator Instance) if (Foreground == 0) return; + Win32kDpi.DrainHostDpiChange(Instance); + if (HostEventQueue.ConsumeRepaint()) InvalidateWindow(Instance, Foreground); diff --git a/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs b/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs index b504c83..3a83836 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -26,6 +26,9 @@ public struct UserSharedDisplayInfo [FieldOffset(0x08)] public ulong PrimaryMonitor; + + [FieldOffset(0x40)] + public uint CompositionFlags; } [StructLayout(LayoutKind.Explicit, Pack = 1)] @@ -1091,10 +1094,36 @@ public class WinProcess : IHandleObject public ulong InstrumentationCallback; public ulong JobObjectHandle; + public System.Diagnostics.Process SpawnedHost; + + public bool SpawnedHasExited + { + get + { + try + { + return SpawnedHost != null && SpawnedHost.HasExited; + } + catch (InvalidOperationException) + { + return true; + } + } + } + public string ObjectId => PID.ToString(); public HandleType ObjectType => HandleType.ProcessHandle; } + public sealed class WinSpawnedThread : IHandleObject + { + public WinProcess Process; + public uint ThreadId; + + public string ObjectId => $"SPAWNEDTHREAD_{ThreadId}"; + public HandleType ObjectType => HandleType.ThreadHandle; + } + public sealed class WinDirectoryEntry { public string Name; diff --git a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs index 9d224a1..10ef0ec 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs @@ -1212,8 +1212,6 @@ internal void RemoveWinHandle(ulong Handle) private readonly Stack FreeGdiHandleIndices = new(); private readonly byte[] GdiHandleUniq = new byte[GdiHandleEntryCount]; private readonly ulong[] GdiHandleAttributes = new ulong[GdiHandleEntryCount]; - private const int SmCxScreen = 0; - private const int SmCyScreen = 1; private const int EnumCurrentSettings = -1; private const ushort DevModeSize = 0xDC; private const int DevModeOffsetSize = 0x44; @@ -1230,6 +1228,8 @@ internal void RemoveWinHandle(ulong Handle) private ulong UserDesktopInfoAddress; private ulong UserDesktopOwnerAddress; private ulong UserPrimaryMonitorAddress; + private const uint DpiChangeRectSize = 0x10; + private ulong DpiChangeRectAddress; private ulong UserSharedInfoMirrorAddress; private ulong UserSharedDelta; public ulong ActiveWindow; @@ -3560,6 +3560,8 @@ private void WriteWin32ClientInfoSlot(ulong Teb, int Slot, ulong Value) } Emulator._emulator.WriteMemory(Teb + Win32ClientInfoX86Base + ((ulong)Slot * 4UL), (uint)Value, 4); + + Emulator._emulator.WriteMemory(Teb + Win32ClientInfoX64Base + ((ulong)Slot * 8UL), Value, 8); } private ulong ComputeUserSharedDelta(ulong ServerInfo, ulong HandleTable, ulong DisplayInfo) @@ -3596,7 +3598,18 @@ private ulong EnsureUserPrimaryMonitor() if (!WriteZeroMemory(Monitor, (uint)UserPrimaryMonitorSize)) return 0; - (int Width, int Height) = GetHostPrimaryMonitorSize(); + if (!WriteUserPrimaryMonitorInfo(Monitor)) + return 0; + + UserPrimaryMonitorAddress = Monitor; + return UserPrimaryMonitorAddress; + } + + private bool WriteUserPrimaryMonitorInfo(ulong Monitor) + { + int Width = Win32kDpi.GetScreenWidth(Emulator); + int Height = Win32kDpi.GetScreenHeight(Emulator); + ushort Dpi = (ushort)Win32kDpi.GetEffectiveDpi(Emulator); UserPrimaryMonitorInfo MonitorInfo = new UserPrimaryMonitorInfo { @@ -3609,36 +3622,37 @@ private ulong EnsureUserPrimaryMonitor() WorkTop = 0, WorkRight = Width, WorkBottom = Height, - DpiX = 96, - DpiY = 96 + DpiX = Dpi, + DpiY = Dpi }; Span Buffer = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref MonitorInfo, 1)); - if (!Emulator._emulator.WriteMemory(Monitor, Buffer)) - return 0; - - UserPrimaryMonitorAddress = Monitor; - return UserPrimaryMonitorAddress; + return Emulator._emulator.WriteMemory(Monitor, Buffer); } - private static (int Width, int Height) GetHostPrimaryMonitorSize() + public void RefreshDisplayDependentState() { - try - { - if (OperatingSystem.IsWindows()) - { - int Width = NativeWinImports.GetSystemMetrics(SmCxScreen); - int Height = NativeWinImports.GetSystemMetrics(SmCyScreen); + if (UserPrimaryMonitorAddress != 0 && Emulator.IsRegionMapped(UserPrimaryMonitorAddress, UserPrimaryMonitorSize)) + WriteUserPrimaryMonitorInfo(UserPrimaryMonitorAddress); - if (Width > 0 && Height > 0) - return (Width, Height); - } - } - catch + foreach (WinWindow Window in WinWindows.Values) { + if (Window.ClientWindowAddress != 0 && Emulator.IsRegionMapped(Window.ClientWindowAddress, UserWindowObjectSize)) + Win32kDpi.ApplyWindowContext(Emulator, Window.ClientWindowAddress); } + } + + public ulong EnsureDpiChangeRect() + { + if (DpiChangeRectAddress != 0 && Emulator.IsRegionMapped(DpiChangeRectAddress, DpiChangeRectSize)) + return DpiChangeRectAddress; - return (1920, 1080); + ulong Address = Emulator.MapUniqueAddress(DpiChangeRectSize, MemoryProtection.ReadWrite); + if (Address == 0 || !WriteZeroMemory(Address, DpiChangeRectSize)) + return 0; + + DpiChangeRectAddress = Address; + return DpiChangeRectAddress; } public unsafe uint GetPrimaryDisplayFrequency() @@ -3702,7 +3716,8 @@ public ulong EnsureUserDesktopInfo() UserSharedDisplayInfo DisplayInfo = new UserSharedDisplayInfo { Type = 1u, - PrimaryMonitor = PrimaryMonitor + PrimaryMonitor = PrimaryMonitor, + CompositionFlags = 1u }; Span Buffer = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref DisplayInfo, 1)); @@ -3873,6 +3888,8 @@ private void RefreshUserWindowObject(WinWindow Window) Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xB8, Window.ClientTextBytes, 4); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xC0, TextObject, 8); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xE0, 0UL, 8); + + Win32kDpi.ApplyWindowContext(Emulator, Window.ClientWindowAddress); } private ulong EnsureUserClassObject(WinWindow Window) @@ -4265,6 +4282,7 @@ private void EnsureDesktopWindow() Decorated = true, Center = true, State = WindowState.Normal, + DpiAwareness = Win32kDpi.GetHostAwareness(Emulator), }); } diff --git a/Brovan/EmulationMenu/EmulationMenu.cs b/Brovan/EmulationMenu/EmulationMenu.cs index 97e759b..7f910b8 100644 --- a/Brovan/EmulationMenu/EmulationMenu.cs +++ b/Brovan/EmulationMenu/EmulationMenu.cs @@ -2341,7 +2341,7 @@ private static void ShowData(bool Quick) } } - public static void RunEmulator(string FilePath, bool Quick, bool Silent, [AllowNull] string Command, [AllowNull] string RawProgramArguments, string[] ProgramArguments, NetworkAccessPolicy NetworkPolicyValue, bool NoHooks, EmulationBackendKind BackendKind) + public static void RunEmulator(string FilePath, bool Quick, bool Silent, [AllowNull] string Command, [AllowNull] string RawProgramArguments, string[] ProgramArguments, NetworkAccessPolicy NetworkPolicyValue, bool NoHooks, EmulationBackendKind BackendKind, [AllowNull] string WorkingDirectory = null) { SilentMode = Silent; @@ -2406,6 +2406,7 @@ public static void RunEmulator(string FilePath, bool Quick, bool Silent, [AllowN EmulateNetworking = NetworkPolicyValue.HasAnyAccess(), NetworkPolicy = NetworkPolicyValue, RawProgramArguments = RawProgramArguments, + WorkingDirectory = WorkingDirectory, ProgramArguments = ProgramArguments ?? Array.Empty(), NoHooks = NoHooks, BackendKind = BackendKind diff --git a/Brovan/GeneralHelper.cs b/Brovan/GeneralHelper.cs index edf1a50..f32f4d7 100644 --- a/Brovan/GeneralHelper.cs +++ b/Brovan/GeneralHelper.cs @@ -143,6 +143,15 @@ internal class NativeWinImports [DllImport("user32.dll")] public static extern int GetSystemMetricsForDpi(int nIndex, uint dpi); + + [DllImport("user32.dll")] + public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext); + + [DllImport("user32.dll")] + public static extern IntPtr MonitorFromWindow(IntPtr hWnd, int dwFlags); + + [DllImport("shcore.dll")] + public static extern int GetDpiForMonitor(IntPtr hMonitor, int dpiType, out uint dpiX, out uint dpiY); } internal class NativeUnixImports diff --git a/Brovan/Program.cs b/Brovan/Program.cs index 63de98f..5e6244b 100644 --- a/Brovan/Program.cs +++ b/Brovan/Program.cs @@ -128,6 +128,11 @@ static void ShowHelp() Console.WriteLine(" --net-allow= Allow a specific IPv4 or IPv6 address in addition to the selected policy."); Console.WriteLine(" --no-hooks Run the emulator with no hooks. useful when you want maximum performance and want to see some program output."); Console.WriteLine(" --backend= Choose the emulation backend: unicorn (default), kvm (Linux), or whp (Windows Hypervisor Platform)."); + Console.WriteLine(" --cwd Directory the emulated program starts in. Defaults to the directory of the binary."); + Console.WriteLine(" --guest-cmdline "); + Console.WriteLine(" Command line for the emulated program, excluding argv[0], used instead of"); + Console.WriteLine(" trailing arguments. Both options also accept \"base64:\" so that"); + Console.WriteLine(" quotes and spaces survive intact."); Console.WriteLine(); Console.WriteLine("Notes:"); Console.WriteLine(" All Brovan flags must be passed before the program path."); @@ -185,6 +190,97 @@ private static bool TryAddAllowedNetworkAddress(NetworkAccessPolicy Policy, stri return true; } + private const string Base64ArgumentPrefix = "base64:"; + + private static string DecodeArgumentValue(string Value) + { + if (string.IsNullOrEmpty(Value) || !Value.StartsWith(Base64ArgumentPrefix, StringComparison.Ordinal)) + return Value; + + try + { + return Encoding.UTF8.GetString(Convert.FromBase64String(Value.Substring(Base64ArgumentPrefix.Length))); + } + catch (FormatException) + { + PrintHighlight("[-] Invalid base64 argument value.", true); + return null; + } + } + + private static string[] SplitCommandLine(string CommandLine) + { + List Arguments = new List(); + if (string.IsNullOrWhiteSpace(CommandLine)) + return Arguments.ToArray(); + + StringBuilder Current = new StringBuilder(); + bool Quoted = false; + bool HasArgument = false; + int Backslashes = 0; + + void Flush(bool BeforeQuote, out bool EscapedQuote) + { + EscapedQuote = false; + if (BeforeQuote) + { + Current.Append('\\', Backslashes / 2); + EscapedQuote = (Backslashes % 2) != 0; + } + else + { + Current.Append('\\', Backslashes); + } + + Backslashes = 0; + } + + foreach (char Character in CommandLine) + { + if (Character == '\\') + { + Backslashes++; + HasArgument = true; + continue; + } + + if (Character == '"') + { + Flush(true, out bool EscapedQuote); + if (EscapedQuote) + Current.Append('"'); + else + Quoted = !Quoted; + + HasArgument = true; + continue; + } + + Flush(false, out _); + + if (!Quoted && (Character == ' ' || Character == '\t')) + { + if (HasArgument) + { + Arguments.Add(Current.ToString()); + Current.Clear(); + HasArgument = false; + } + + continue; + } + + Current.Append(Character); + HasArgument = true; + } + + Flush(false, out _); + if (HasArgument) + Arguments.Add(Current.ToString()); + + return Arguments.ToArray(); + } + private static bool TryParseBackendKind(string Value, out EmulationBackendKind Kind) { switch ((Value ?? string.Empty).Trim().ToLowerInvariant()) @@ -330,6 +426,8 @@ static void Main(string[] args) bool Silent = false; string Command = null; string FilePath = null; + string WorkingDirectory = null; + string GuestCommandLine = null; EmulationBackendKind BackendKind = EmulationBackendKind.Unicorn; NetworkAccessPolicy NetworkPolicy = new NetworkAccessPolicy(NetworkAccessMode.Loopback); List ProgramArgumentsList = new List(); @@ -390,6 +488,20 @@ static void Main(string[] args) case "--no-hooks": NoHooks = true; continue; + case "--cwd": + if (i + 1 >= args.Length) + continue; + + WorkingDirectory = DecodeArgumentValue(args[i + 1]); + i++; + continue; + case "--guest-cmdline": + if (i + 1 >= args.Length) + continue; + + GuestCommandLine = DecodeArgumentValue(args[i + 1]); + i++; + continue; case "--backend": if (i + 1 >= args.Length || !TryParseBackendKind(args[i + 1], out EmulationBackendKind ArgumentBackendKind)) { @@ -479,11 +591,13 @@ static void Main(string[] args) } string[] ProgramArguments = ProgramArgumentsList.ToArray(); - string RawProgramArguments = BuildRawProgramArguments(ProgramArguments); + string RawProgramArguments = GuestCommandLine ?? BuildRawProgramArguments(ProgramArguments); + if (GuestCommandLine != null) + ProgramArguments = SplitCommandLine(GuestCommandLine); // Set the dll import resolver based on the platform NativeLibraryResolver.Register(); - EmulationMenu.EmulationMenu.RunEmulator(FilePath, Quick, Silent, Command, RawProgramArguments, ProgramArguments, NetworkPolicy, NoHooks, BackendKind); + EmulationMenu.EmulationMenu.RunEmulator(FilePath, Quick, Silent, Command, RawProgramArguments, ProgramArguments, NetworkPolicy, NoHooks, BackendKind, WorkingDirectory); } } } \ No newline at end of file From 4cdd407f4abd4518998278d206f6c51d8707937c Mon Sep 17 00:00:00 2001 From: AdvDebug <90452585+AdvDebug@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:56:19 +0300 Subject: [PATCH 2/2] Fix process identity and mailbox reuse bugs This change fixes several process-management edge cases in Windows emulation. Reused guest session slots now clear mailbox state before reassignment to prevent stale sequence numbers from being treated as new requests. Newly created user processes are added to `WinProcesses` so they are tracked consistently. `NtReadVirtualMemory` now writes `BytesRead` using the active pointer size instead of always 8 bytes, improving x86/x64 correctness. The main process PID is now initialized by adopting the host process ID when available, falling back to random generation only when needed. --- .../OS/Windows/Process/GuestSessionRegistry.cs | 18 ++++++++++++++++++ .../OS/Windows/Process/NtCreateUserProcess.cs | 2 ++ .../OS/Windows/Process/NtReadVirtualMemory.cs | 8 ++++++-- .../Emulation/OS/Windows/WinSyscallsHelper.cs | 11 ++++++++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs b/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs index 3616ff3..df6bfe4 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs @@ -110,6 +110,7 @@ internal static void Join(uint GuestProcessId, uint Architecture, string ImageNa if (State == SlotLive && IsHostProcessAlive(_view.ReadUInt32(Offset + SlotHostPidOffset))) continue; + ClearMailbox(i); WriteSlot(Offset, GuestProcessId, Architecture, ImageName); _ownSlot = i; break; @@ -572,6 +573,23 @@ private static void Release() } } + /// + /// A reused slot still holds the sequence numbers of its previous owner. This process starts counting + /// from zero, so without this it would read that stale request as a new one and run it. + /// + private static void ClearMailbox(int Slot) + { + if (_mailboxView == null) + return; + + long Base = (long)Slot * MailboxSize; + for (int i = 0; i < MailboxPayloadOffset; i += 8) + _mailboxView.Write(Base + i, 0UL); + + _mailboxView.Flush(); + _lastHandledSequence = 0; + } + private static void WriteSlot(int Offset, uint GuestProcessId, uint Architecture, string ImageName) { for (int i = 0; i < SlotSize; i += 8) diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs index 646e616..d63146b 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs @@ -40,6 +40,8 @@ public NTSTATUS Handle(BinaryEmulator Instance) ThreadId = Process.PID, }; + Instance.WinHelper.WinProcesses.Add(Process); + ulong ProcessHandle = Instance.WinHelper.HandleManager.AddHandle(Process, AccessMask.GenericAll).Handle; ulong ThreadHandle = Instance.WinHelper.HandleManager.AddHandle(Thread, AccessMask.GenericAll).Handle; diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs index 68b7b2a..df6d808 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs @@ -111,8 +111,12 @@ public NTSTATUS Handle(BinaryEmulator Instance) if (!Instance._emulator.WriteMemory(LocalBuffer, Remote, 0, RemoteLength)) return NTSTATUS.STATUS_ACCESS_VIOLATION; - if (BytesReadPtr != 0 && Instance.IsRegionMapped(BytesReadPtr, sizeof(ulong))) - Instance._emulator.WriteMemory(BytesReadPtr, (ulong)RemoteLength, 8); + if (BytesReadPtr != 0) + { + uint PointerSize = (uint)Instance.WinHelper.PointerSize; + if (Instance.IsRegionMapped(BytesReadPtr, PointerSize)) + Instance._emulator.WriteMemory(BytesReadPtr, (ulong)RemoteLength, PointerSize); + } if ((Instance.Settings.Flags & LogFlags.Syscall) != 0) Instance.TriggerEventMessage($"[+] Read 0x{RemoteLength:X} bytes from process \"{Process.Name}\" at 0x{RemoteAddress:X}.", LogFlags.Syscall); diff --git a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs index 10ef0ec..86bc990 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs @@ -957,6 +957,15 @@ public ulong GetArg(int Index) private uint SequentialIdCursor = 30000; private uint AnonymousObjectCursor; + private uint AdoptHostProcessId() + { + uint HostId = (uint)Environment.ProcessId; + if (HostId != 0 && PIDs.Add(HostId)) + return HostId; + + return GenerateRandomPID(); + } + public uint GenerateRandomPID() { for (int Attempt = 0; Attempt < 64; Attempt++) @@ -1248,7 +1257,7 @@ public WinSysHelper(BinaryEmulator Emulator) SyntheticVolumeWin32GuidPath = $"\\\\?\\Volume{{{SyntheticVolumeGuid}}}\\"; SyntheticMountDevUniqueId = Guid.Parse(SyntheticVolumeGuid).ToByteArray(); KuserSharedData = new KuserSharedDataManager(Emulator); - PID = GenerateRandomPID(); + PID = AdoptHostProcessId(); PPID = GenerateRandomPID(); string FileName = null; BinaryFile Binary = Emulator._binary;