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..df6bfe4
--- /dev/null
+++ b/Brovan/Core/Emulation/OS/Windows/Process/GuestSessionRegistry.cs
@@ -0,0 +1,649 @@
+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;
+
+ ClearMailbox(i);
+ 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)
+ {
+ }
+ }
+
+ ///
+ /// 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)
+ _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..d63146b
--- /dev/null
+++ b/Brovan/Core/Emulation/OS/Windows/Process/NtCreateUserProcess.cs
@@ -0,0 +1,132 @@
+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,
+ };
+
+ 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;
+
+ 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..df6d808 100644
--- a/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs
+++ b/Brovan/Core/Emulation/OS/Windows/Process/NtReadVirtualMemory.cs
@@ -81,23 +81,45 @@ 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.WriteMemory(BufferPtr, Instance.WinHelper.GenerateRandomData((int)NumberOfBytesToRead))) // generate random data?
+ if (!Instance.IsRegionMapped(LocalBuffer, NumberOfBytesToRead))
+ return NTSTATUS.STATUS_MEMORY_NOT_ALLOCATED;
+
+ 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)
+ {
+ 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($"[+] 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..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++)
@@ -1212,8 +1221,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 +1237,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;
@@ -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;
@@ -3560,6 +3569,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 +3607,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 +3631,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 +3725,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 +3897,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 +4291,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