Issue Capturing Global (Session 0) OutputDebugString messages via Win32 API?

2.4k Views Asked by At

I am currently working on a simple application to capture OutputDebugString messages (similar to Windows Sysinternals DbgView or DBMon.NET). Everything is working as expected when accessing OutputDebugString messages from the Local session (i.e., Local\DBWIN_BUFFER_READY, Local\DBWIN_DATA_READY and Local\DBWIN_BUFFER).

However, when I attempt to access any output from Session 0 (i.e., Global\DBWIN_BUFFER_READY, etc) I receive no output. Based on the behaviour of DbgView, I am operating on the assumption that the application must be run with some level of Administrative privileges. I am thinking I am configuring the SecurityDescriptor incorrectly, or I am missing something completely to access Global OutputDebugString messages (read as... I am now somewhat lost on the matter).

I have highlighted snippets of code below, but the full source may be found on CodePlex

Any help or insight on the matter would be greatly appreciated. Thanks in advance!

Security Descriptor Configuration

I have tried a few different configurations, but the committed code currently looks as follows.

[DllImport("advapi32.dll", SetLastError = true)]
private static extern Boolean InitializeSecurityDescriptor(ref SecurityDescriptor sd, UInt32 dwRevision);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern Boolean SetSecurityDescriptorDacl(ref SecurityDescriptor sd, Boolean daclPresent, IntPtr dacl, Boolean daclDefaulted);

public SecurityDescriptor InitializeSecurityDescriptor()
{
  const Int32 securityDescriptorRevision = 1;
  var securityDescriptor = new SecurityDescriptor();

  // Initialize the security descriptor.
  if (!InitializeSecurityDescriptor(ref securityDescriptor, securityDescriptorRevision))
    throw new Win32Exception(Marshal.GetLastWin32Error());

  // Set information in a discretionary access control list
  if (!SetSecurityDescriptorDacl(ref securityDescriptor, true, IntPtr.Zero, false))
    throw new Win32Exception(Marshal.GetLastWin32Error());

  return securityDescriptor;
}

This code is ultimately called in the setup of my DbWinMessageSource class as you would expect...

_windowsApi.Advanced.InitializeSecurityDescriptor();

SecurityAttributes and Events

The code currently committed on CodePlex is using the Local\** prefix, but the only difference should be that Local\** is replaced with Global\** as far as I understand it? However this does not seem to capture the output as expected. Again, the relevant code snippet...

  public const Int32 ErrorAlreadyExists = 183;

  [DllImport("kernel32.dll", SetLastError = true)]
  private static extern IntPtr CreateEvent(ref SecurityAttributes sa, Boolean bManualReset, Boolean bInitialState, String lpName);

  public Handle CreateLocalEvent(ref SecurityAttributes securityAttributes, String objectName)
    {
      Verify.NotWhitespace(objectName);
      return CreateEvent(ref securityAttributes, "Local", objectName);
    }

    public Handle CreateGlobalEvent(ref SecurityAttributes securityAttributes, String objectName)
    {
      Verify.NotWhitespace(objectName);
      return CreateEvent(ref securityAttributes, "Global", objectName);
    }

    private static Handle CreateEvent(ref SecurityAttributes securityAttributes, String objectNamePrefix, String objectName)
    {
      IntPtr handle = CreateEvent(ref securityAttributes, false, false, String.Format(@"{0}\{1}", objectNamePrefix, objectName));

      if(Marshal.GetLastWin32Error() == ErrorAlreadyExists)
        throw new Win32Exception(ErrorAlreadyExists);

      if (handle == IntPtr.Zero)
        throw new Win32Exception(Marshal.GetLastWin32Error());

      return new Handle(handle, CloseHandle);
    }

Again, ultimately called in the setup of the DbWinMessageSource as follows:

  _dbwinBufferReadyEvent = _windowsApi.Basic.CreateGlobalEvent(ref securityAttributes, "DBWIN_BUFFER_READY");
  _dbwinDataReadyEvent = _windowsApi.Basic.CreateGlobalEvent(ref securityAttributes, "DBWIN_DATA_READY");
  _dbwinBufferFile = _windowsApi.Basic.CreateGlobalFileMapping(ref securityAttributes, "DBWIN_BUFFER");

I am testing with a simple web application using Log4Net with an OutputDebugString appender configured. When I am running the application via Visual Studio, I capture all Local output as expected. When I move the application in to IIS and configure the code to capture anything in the Global session; I get nothing. I have confirmed that DbgView is capturing the output from IIS as I would expect (so it is definitely something I am doing wrong).

Hopefully that is enough context, but if more information or detail is required; let me know.

Note: Developing on Windows 7 Professional if that makes a difference.

EDIT

As pointed out by Tyranid (and Luke), all that should be required is Administrator priviledges, and SE_CREATE_GLOBAL_NAME. I ran a few more tests, and the code setup above is actually capturing some Global messages (for instance, during an IISRESET); however the code above is not capturing any data from the Log4Net OutputDebugString appender when an application is running inside of IIS (routed through Local\** during VS sessions). All the Win32 api calls are returning successfully, nor are any errors returned when invoking Marshal.GetLastWin32Error(). For good measure, I added some code to make sure that the current Windows token has SE_CREATE_GLOBAL_NAME. The roughed in code looked as follows:

  using (var identity = WindowsIdentity.GetCurrent())
  {
    if (identity == null)
      return;

    TokenPrivilege tp;

    tp.Count = 1;
    tp.Luid = 0;
    tp.Attr = SE_PRIVILEGE_ENABLED;
    if (!LookupPrivilegeValue(null, SE_CREATE_GLOBAL_NAME, ref tp.Luid))
      throw new Win32Exception(Marshal.GetLastWin32Error());

    if (!AdjustTokenPrivileges(identity.Token, false, ref tp, Marshal.SizeOf(tp), IntPtr.Zero, IntPtr.Zero))
      throw new Win32Exception(Marshal.GetLastWin32Error());
  }

Any further insight on this would be greatly appreciated.

1

There are 1 best solutions below

3
On BEST ANSWER

You sure you are managing to create the section/file mapping object? Yes you need to be admin to do that because creating a file mapping in the global namespace requires SeCreateGlobalPrivilege in your token. Now it is possible you might need to enable that before calling CreateFileMapping but it has been a while since I played with that and don't have my code to hand.

Oh and you really shouldn't specify a NULL DACL, it is just wrong on many levels, I know dbgview does it, but it is just lazy, plus it can break when integrity levels get involved on vista+.