diff --git a/nmf-view/Program.cs b/nmf-view/Program.cs index f710835..fb25c22 100644 --- a/nmf-view/Program.cs +++ b/nmf-view/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Forms; @@ -7,60 +6,14 @@ namespace nmf_view { static class Program { - - + [DllImport("user32.dll", SetLastError = true)] + static extern bool SetProcessDPIAware(); private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs ue) { Exception unhandledException = (Exception)ue.ExceptionObject; - ReportException(unhandledException); + Utilities.ReportException(unhandledException); } - internal static void ReportException(Exception eX) - { - string sTitle = "Sorry, you may have found an error..."; - ReportException(eX, sTitle, null); - } - public static void ReportException(Exception eX, string sTitle, string sCallerMessage) - { - Trace.WriteLine("******ReportException()******\n" + eX.Message + "\n" + eX.StackTrace + "\n" + eX.InnerException); - - if ((eX is System.Threading.ThreadAbortException)) // TODO: What about ObjectDisposedException? - { - return; - } - - string sMessage; - if (eX is OutOfMemoryException) - { - sTitle = "Insufficient Memory Address Space"; - sMessage = "An out-of-memory exception was encountered.\nGC Total Allocated: " + GC.GetTotalMemory(false).ToString("N0") + " bytes."; - } - else - { - if (String.IsNullOrEmpty(sCallerMessage)) - { - sMessage = "NMF-View encountered an unexpected problem. If you believe this is a bug, please copy this message by hitting CTRL+C, and submit a issue report on GitHub"; - } - else - { - sMessage = sCallerMessage; - } - } - - MessageBox.Show( - sMessage + "\n\n" + - eX.Message + "\n\n" + - "Type: " + eX.GetType().ToString() + "\n" + - "Source: " + eX.Source + "\n" + - eX.StackTrace + "\n\n" + - eX.InnerException + "\n" + - "NMF-View v" + Application.ProductVersion + ((8 == IntPtr.Size) ? " (x64 " : " (x86 ") + Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") + ") [.NET " + Environment.Version + " on " + Environment.OSVersion.VersionString + "] ", - sTitle); - } - - [DllImport("user32.dll", SetLastError = true)] - static extern bool SetProcessDPIAware(); - [STAThread] static void Main() { diff --git a/nmf-view/Utilities.cs b/nmf-view/Utilities.cs index bdd7e39..09820f1 100644 --- a/nmf-view/Utilities.cs +++ b/nmf-view/Utilities.cs @@ -2,6 +2,8 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; using System.Windows.Forms; namespace nmf_view @@ -12,6 +14,26 @@ class Utilities [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsUserAnAdmin(); + public static bool DenyProcessTermination() + { + try + { + var hCurrentProcess = Process.GetCurrentProcess().SafeHandle; + var processSecurity = new ProcessSecurity(hCurrentProcess); + SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + // Create a rule to deny process termination. + ProcessAccessRule rule = new ProcessAccessRule(sid, ProcessAccessRights.Terminate, false, + InheritanceFlags.None, PropagationFlags.None, AccessControlType.Deny); + processSecurity.AddAccessRule(rule); + processSecurity.SaveChanges(hCurrentProcess); + } + catch (Exception eX) + { + ReportException(eX); + return false; + } + return true; + } internal static void CopyToClipboard(string s) { DataObject data = new DataObject(); @@ -47,5 +69,102 @@ internal static void OpenExplorerTo(string manifestFilename) /* User may abort */ } } + + internal static void ReportException(Exception eX) + { + string sTitle = "Sorry, you may have found an error..."; + ReportException(eX, sTitle, null); + } + public static void ReportException(Exception eX, string sTitle, string sCallerMessage) + { + Trace.WriteLine("******ReportException()******\n" + eX.Message + "\n" + eX.StackTrace + "\n" + eX.InnerException); + + if ((eX is System.Threading.ThreadAbortException)) // TODO: What about ObjectDisposedException? + { + return; + } + + string sMessage; + if (eX is OutOfMemoryException) + { + sTitle = "Insufficient Memory Address Space"; + sMessage = "An out-of-memory exception was encountered.\nGC Total Allocated: " + GC.GetTotalMemory(false).ToString("N0") + " bytes."; + } + else + { + if (String.IsNullOrEmpty(sCallerMessage)) + { + sMessage = "NMF-View encountered an unexpected problem. If you believe this is a bug, please copy this message by hitting CTRL+C, and submit a issue report on GitHub"; + } + else + { + sMessage = sCallerMessage; + } + } + + MessageBox.Show( + sMessage + "\n\n" + + eX.Message + "\n\n" + + "Type: " + eX.GetType().ToString() + "\n" + + "Source: " + eX.Source + "\n" + + eX.StackTrace + "\n\n" + + eX.InnerException + "\n" + + "NMF-View v" + Application.ProductVersion + ((8 == IntPtr.Size) ? " (x64 " : " (x86 ") + Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") + ") [.NET " + Environment.Version + " on " + Environment.OSVersion.VersionString + "] ", + sTitle); + } + } + internal class ProcessSecurity : NativeObjectSecurity + { + public ProcessSecurity(SafeHandle processHandle) + : base(false, ResourceType.KernelObject, processHandle, AccessControlSections.Access) { } + + public void AddAccessRule(ProcessAccessRule rule) + { + base.AddAccessRule(rule); + } + + public void SaveChanges(SafeHandle processHandle) + { + Persist(processHandle, AccessControlSections.Access); + } + + public override Type AccessRightType + { + get { return typeof(ProcessAccessRights); } + } + + public override AccessRule AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) + { + return new ProcessAccessRule(identityReference, (ProcessAccessRights)accessMask, isInherited, inheritanceFlags, propagationFlags, type); + } + + public override Type AccessRuleType + { + get { return typeof(ProcessAccessRule); } + } + + public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, + PropagationFlags propagationFlags, AuditFlags flags) + { + throw new NotImplementedException(); + } + + public override Type AuditRuleType + { + get { throw new NotImplementedException(); } + } + } + + internal class ProcessAccessRule : AccessRule + { + public ProcessAccessRule(IdentityReference identityReference, ProcessAccessRights accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) + : base(identityReference, (int)accessMask, isInherited, inheritanceFlags, propagationFlags, type) { } + + public ProcessAccessRights ProcessAccessRights { get { return (ProcessAccessRights)AccessMask; } } + } + [Flags] + internal enum ProcessAccessRights + { + Terminate = 1 } } diff --git a/nmf-view/frmMain.Designer.cs b/nmf-view/frmMain.Designer.cs index 3a16890..dea1763 100644 --- a/nmf-view/frmMain.Designer.cs +++ b/nmf-view/frmMain.Designer.cs @@ -128,10 +128,10 @@ private void InitializeComponent() this.txtLog.Dock = System.Windows.Forms.DockStyle.Fill; this.txtLog.Font = new System.Drawing.Font("Consolas", 9.857143F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.txtLog.ForeColor = System.Drawing.SystemColors.Window; - this.txtLog.Location = new System.Drawing.Point(3, 178); + this.txtLog.Location = new System.Drawing.Point(3, 226); this.txtLog.Name = "txtLog"; this.txtLog.ReadOnly = true; - this.txtLog.Size = new System.Drawing.Size(1457, 630); + this.txtLog.Size = new System.Drawing.Size(1457, 582); this.txtLog.TabIndex = 3; this.txtLog.Text = ""; this.txtLog.WordWrap = false; @@ -147,7 +147,7 @@ private void InitializeComponent() this.pnlTop.Location = new System.Drawing.Point(3, 4); this.pnlTop.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.pnlTop.Name = "pnlTop"; - this.pnlTop.Size = new System.Drawing.Size(1457, 174); + this.pnlTop.Size = new System.Drawing.Size(1457, 222); this.pnlTop.TabIndex = 2; // // clbOptions @@ -161,18 +161,19 @@ private void InitializeComponent() "Tamper using Fiddler", "Propagate closures", "Log message bodies", - "Write log to Desktop"}); + "Write log to Desktop", + "Immortal"}); this.clbOptions.Location = new System.Drawing.Point(1012, 0); this.clbOptions.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.clbOptions.Name = "clbOptions"; - this.clbOptions.Size = new System.Drawing.Size(445, 174); + this.clbOptions.Size = new System.Drawing.Size(445, 222); this.clbOptions.TabIndex = 1; this.clbOptions.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.clbOptions_ItemCheck); // // pbApp // this.pbApp.Image = ((System.Drawing.Image)(resources.GetObject("pbApp.Image"))); - this.pbApp.Location = new System.Drawing.Point(696, -5); + this.pbApp.Location = new System.Drawing.Point(798, 21); this.pbApp.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.pbApp.Name = "pbApp"; this.pbApp.Size = new System.Drawing.Size(193, 172); @@ -185,7 +186,7 @@ private void InitializeComponent() // pbExt // this.pbExt.Image = global::nmf_view.Properties.Resources.ext; - this.pbExt.Location = new System.Drawing.Point(0, 0); + this.pbExt.Location = new System.Drawing.Point(14, 21); this.pbExt.Margin = new System.Windows.Forms.Padding(0); this.pbExt.Name = "pbExt"; this.pbExt.Size = new System.Drawing.Size(193, 168); @@ -200,7 +201,7 @@ private void InitializeComponent() this.lblArrow.AutoSize = true; this.lblArrow.Font = new System.Drawing.Font("Segoe Print", 39.85714F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.lblArrow.ForeColor = System.Drawing.SystemColors.ControlDarkDark; - this.lblArrow.Location = new System.Drawing.Point(168, -4); + this.lblArrow.Location = new System.Drawing.Point(182, 17); this.lblArrow.Name = "lblArrow"; this.lblArrow.Size = new System.Drawing.Size(653, 189); this.lblArrow.TabIndex = 4; @@ -296,7 +297,7 @@ private void InitializeComponent() this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(5, 32); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(1217, 32); + this.label2.Size = new System.Drawing.Size(1218, 32); this.label2.TabIndex = 0; this.label2.Text = "NOT YET IMPLEMENTED (Eventually, select which NativeMessagingHosts this applicati" + "on should intercept/proxy.)"; @@ -472,7 +473,7 @@ private void InitializeComponent() this.lblVersion.AutoSize = true; this.lblVersion.Location = new System.Drawing.Point(39, 113); this.lblVersion.Name = "lblVersion"; - this.lblVersion.Size = new System.Drawing.Size(93, 32); + this.lblVersion.Size = new System.Drawing.Size(94, 32); this.lblVersion.TabIndex = 2; this.lblVersion.Text = "v0.0.0.0"; // diff --git a/nmf-view/frmMain.cs b/nmf-view/frmMain.cs index a66d5db..6da316f 100644 --- a/nmf-view/frmMain.cs +++ b/nmf-view/frmMain.cs @@ -34,8 +34,8 @@ enum FileType : uint const int STD_OUTPUT_HANDLE = -11; const int STD_ERROR_HANDLE = -12; - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - static extern bool FreeConsole(); +// [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] +// static extern bool FreeConsole(); private const int SW_SHOW = 5; [DllImport("User32")] @@ -86,20 +86,38 @@ struct app_state private static async Task WriteToApp(string sMessage) { - byte[] arrPayload = Encoding.UTF8.GetBytes(sMessage); - byte[] arrSize = BitConverter.GetBytes((UInt32)arrPayload.Length); - await oSettings.strmToApp.WriteAsync(arrSize, 0, 4); - await oSettings.strmToApp.WriteAsync(arrPayload, 0, arrPayload.Length); - await oSettings.strmToApp.FlushAsync(); - } + try + { + byte[] arrPayload = Encoding.UTF8.GetBytes(sMessage); + byte[] arrSize = BitConverter.GetBytes((UInt32)arrPayload.Length); + await oSettings.strmToApp.WriteAsync(arrSize, 0, 4); + await oSettings.strmToApp.WriteAsync(arrPayload, 0, arrPayload.Length); + await oSettings.strmToApp.FlushAsync(); + } + catch (Exception eX) + { + MessageBox.Show("Failed to send message\n" + eX.Message, "WriteToApp Failed"); + } +} private static async Task WriteToExtension(string sMessage) { - byte[] arrPayload = Encoding.UTF8.GetBytes(sMessage); - byte[] arrSize = BitConverter.GetBytes((UInt32)arrPayload.Length); - await oSettings.strmToExt.WriteAsync(arrSize, 0, 4); - await oSettings.strmToExt.WriteAsync(arrPayload, 0, arrPayload.Length); - await oSettings.strmToExt.FlushAsync(); + try + { + if (null == oSettings.strmToExt) return; + byte[] arrPayload = Encoding.UTF8.GetBytes(sMessage); + byte[] arrSize = BitConverter.GetBytes((UInt32)arrPayload.Length); + await oSettings.strmToExt.WriteAsync(arrSize, 0, 4); + if (null == oSettings.strmToExt) return; + await oSettings.strmToExt.WriteAsync(arrPayload, 0, arrPayload.Length); + if (null == oSettings.strmToExt) return; + await oSettings.strmToExt.FlushAsync(); + if (null == oSettings.strmToExt) return; + } + catch (Exception eX) + { + MessageBox.Show("Failed to send message\n" + eX.Message, "WriteToExtension Failed"); + } } private void MaybeWriteToLogfile(string sMsg) @@ -135,10 +153,16 @@ public bool IsExtensionAttached() private void markExtensionDetached() { + Trace.WriteLine("markExtensionDetached()"); this.BeginInvoke((MethodInvoker)delegate { + Trace.WriteLine("in the delegate..."); pbExt.BackColor = Color.DarkGray; + Trace.WriteLine("color was set"); toolTip1.SetToolTip(pbExt, $"Was connected to {oSettings.sExtensionID}.\nDisconnected"); + Trace.WriteLine("tooltip was set"); + btnSendToExtension.Enabled = false; + Trace.WriteLine("extension was enabled. Done callback."); }); } @@ -148,58 +172,74 @@ private void markAppDetached() { pbApp.BackColor = Color.DarkGray; toolTip1.SetToolTip(pbApp, $"Was connected to {oSettings.sExeName}.\nDisconnected"); + btnSendToApp.Enabled = false; }); } private void detachExtension() { - if (!IsExtensionAttached()) return; - log("Canceling reads..."); - ctsExt.Cancel(); - log("Detaching Extension pipes..."); - // Unfortunately, none of this seems to work to let the other side know we're going away. - if (null != oSettings.strmToExt) { - FieldInfo fiHandle = oSettings.strmToExt.GetType().GetField("_handle", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); - SafeFileHandle sh = fiHandle.GetValue(oSettings.strmToExt) as SafeFileHandle; - - if (null != sh) + try + { + if (!IsExtensionAttached()) return; // TODO: We should have asserts that verify that the UX reflects the current state properly. + log("Canceling reads..."); + ctsExt.Cancel(); // Doesn't seem to help. + log("Detaching Extension pipes..."); + // Unfortunately, none of this seems to work to let the other side know we're going away. + if (null != oSettings.strmToExt) { - log ($"stdout handle was 0x{sh.DangerousGetHandle().ToInt64():x}"); - // typeof(SafeFileHandle).InvokeMember("ReleaseHandle", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, sh, new object[] { }); - } + FieldInfo fiHandle = oSettings.strmToExt.GetType().GetField("_handle", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); + if (null != fiHandle) + { + SafeFileHandle sh = fiHandle.GetValue(oSettings.strmToExt) as SafeFileHandle; - oSettings.strmToExt.Close(); - oSettings.strmToExt = null; - CancelIo(GetStdHandle(STD_OUTPUT_HANDLE)); - CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE)); - log("stdout closed."); - } - if (null != oSettings.strmFromExt) { - FieldInfo fiHandle = oSettings.strmFromExt.GetType().GetField("_handle", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); - SafeFileHandle sh = fiHandle.GetValue(oSettings.strmFromExt) as SafeFileHandle; + if (null != sh) + { + log($"stdout handle was 0x{sh.DangerousGetHandle().ToInt64():x}"); + // typeof(SafeFileHandle).InvokeMember("ReleaseHandle", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, sh, new object[] { }); + } + } - if (null != sh) + oSettings.strmToExt.Close(); + oSettings.strmToExt = null; + CancelIo(GetStdHandle(STD_OUTPUT_HANDLE)); + CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE)); + log("stdout closed."); + } + if (null != oSettings.strmFromExt) { - log($"stdin handle was 0x{sh.DangerousGetHandle().ToInt64():x}"); - //typeof(SafeFileHandle).InvokeMember("ReleaseHandle", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, sh, new object[] { }); + FieldInfo fiHandle = oSettings.strmFromExt.GetType().GetField("_handle", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); + if (null != fiHandle) + { + SafeFileHandle sh = fiHandle.GetValue(oSettings.strmFromExt) as SafeFileHandle; + if (null != sh) + { + log($"stdin handle was 0x{sh.DangerousGetHandle().ToInt64():x}"); + //typeof(SafeFileHandle).InvokeMember("ReleaseHandle", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, sh, new object[] { }); + } + } + CancelIo(GetStdHandle(STD_INPUT_HANDLE)); + oSettings.strmFromExt.Close(); + oSettings.strmFromExt = null; + + // CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); // TODO: This hangs because there's still the async read on that pipe. + log("stdin closed."); } + // MessageBox.Show("Closed the stdin?"); + // CancelIo(GetStdHandle(STD_ERROR_HANDLE)); + // CloseHandle(GetStdHandle(STD_ERROR_HANDLE)); + // FreeConsole(); - oSettings.strmFromExt.Close(); - oSettings.strmFromExt = null; - CancelIo(GetStdHandle(STD_INPUT_HANDLE)); - // CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); // TODO: This hangs because there's still the async read on that pipe. - log("stdin closed."); + log("Extension pipes detached."); + } + catch (Exception eX) + { + MessageBox.Show(eX.Message, "Error detaching from Extension"); } - // MessageBox.Show("Closed the stdin?"); - // CancelIo(GetStdHandle(STD_ERROR_HANDLE)); - // CloseHandle(GetStdHandle(STD_ERROR_HANDLE)); - // FreeConsole(); - - log("Extension pipes detached."); markExtensionDetached(); if (oSettings.bPropagateClosures) detachApp(); } private void detachApp() { + Trace.WriteLine("detachApp()"); if (!IsAppAttached()) return; log("Canceling reads..."); ctsApp.Cancel(); @@ -410,6 +450,7 @@ private async Task MessageShufflerForApp() private void log(string sMsg, bool bIsBody = false) { sMsg = $"{DateTime.Now:HH:mm:ss:ffff} - {sMsg}"; + Trace.WriteLine(sMsg); if (!bIsBody || oSettings.bLogMessageBodies) { this.BeginInvoke((MethodInvoker)delegate @@ -456,6 +497,7 @@ private void CloseLogfile() private void frmMain_Load(object sender, EventArgs e) { Trace.WriteLine("NMF-View was started with the command line: " + Environment.CommandLine); + Console.Error.WriteLine("****\n**** NMF-View was started with the command line: " + Environment.CommandLine + "\n****"); // Configure default options. clbOptions.SetItemChecked(2, true); // Propagate closures clbOptions.SetItemChecked(3, true); // Record bodies @@ -465,6 +507,7 @@ private void frmMain_Load(object sender, EventArgs e) if (sCurrentExe.Contains(".reflect.")) clbOptions.SetItemChecked(0, true); if (sCurrentExe.Contains(".fiddler.")) clbOptions.SetItemChecked(1, true); if (sCurrentExe.Contains(".log.")) clbOptions.SetItemChecked(4, true); + if (sCurrentExe.Contains(".immortal.")) clbOptions.SetItemChecked(5, true); string sExtraInfo = $" [{Path.GetFileName(Application.ExecutablePath)}:{Process.GetCurrentProcess().Id}{(Utilities.IsUserAnAdmin() ? " Elevated" : String.Empty)}]"; log($"I am{sExtraInfo}"); @@ -525,10 +568,13 @@ private void WaitForMessages() var hInType = GetFileType(hIn); var hOut = GetStdHandle(STD_OUTPUT_HANDLE); var hOutType = GetFileType(hOut); + var hErr = GetStdHandle(STD_ERROR_HANDLE); + var hErrType = GetFileType(hErr); oSettings.strmFromExt = Console.OpenStandardInput(); oSettings.strmToExt = Console.OpenStandardOutput(); log($"Attached stdin (0x{hIn.ToInt64():x}, {hInType}) and " + $"stdout (0x{hOut.ToInt64():x}, {hOutType}) streams."); + log($"Not using stderr (0x{hErr.ToInt64():x}, {hErrType})."); pbExt.BackColor = Color.FromArgb(159, 255, 159); Task.Run(async () => await MessageShufflerForExtension()); } @@ -557,6 +603,22 @@ private void clbOptions_ItemCheck(object sender, ItemCheckEventArgs e) } return; } + if (e.Index == 5) + { + e.NewValue = CheckState.Indeterminate; + if (e.CurrentValue != CheckState.Unchecked) { return; } + if (Utilities.DenyProcessTermination()) + { + log("Immortality enabled.\r\nTerminateProcess() calls from non-elevated applications will now be ignored with an\r\n" + + "ACCESS_DENIED result, but unfortunately this cannot protect us from our Chromium parent\r\n" + + "because it already has a handle to us with the Terminate process right granted.\r\n"); + } + else + { + log("Immortality could not be enabled."); + } + return; + } } private void pbApp_Click(object sender, EventArgs e) @@ -745,7 +807,7 @@ private void PopulateTroubleshooter() { // https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/api/messaging/native_process_launcher_win.cc;l=134;drc=09a4396a448775456084fe36bb84662f5757d988 rtbTroubleshoot.AppendText($"{sPipe}\r\n"); - // It would be nice to show the owner here, but we can't because we cannot get the handle to the pipe + // It would be nice to show the owner here, but we can't do so readily because we cannot get the handle to the pipe // which only allows one connection. // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getnamedpipeserverprocessid }