UIPI Issue - Drag and Drop (Elevated Programs)

AceInfinity

Emeritus, Contributor
Joined
Feb 21, 2012
Posts
1,728
Location
Canada
Alright, so I ran into this a few times, but at first I was not sure what was going on. I most recently had this issue with AutoBSA++, because I need this program to be invoked with administrator privileges...

The issue is with dragging and dropping.

The culprit is an aspect of UAC called UIPI (User Interface Privilege Isolation); it prevents unelevated programs from hijacking resources from an elevated one, and so interaction with dragging and dropping is also affected. Microsoft claims that it is an important security feature however, so the only thing you can do is learn about how to bypass this.

This is because UIPI blocks windows messages being sent from a process with a lower MIC level to another running at a higher MIC level. Drag and drop is implemented by Windows Messages however, so if you try to do anything involving a drag and drop from Windows Explorer for instance, which has a medium MIC (lower than a high MIC is the significant part here), then the messages are blocked, and the drag-drop action will not succeed, because it was never interpreted in the first place to take any action.

The answer to bypassing it is the ChangeWindowMessageFilter() function from user32.dll.

OLE drag and drop does not use Windows Messages however, but callbacks. If you review this link for RegisterDragDrop() it is evident, since you can only register one window at a time: RegisterDragDrop function (COM)

Here is the significant message I found: WM_DROPFILES message (Windows)

Information on this "feature" is found in this blog post by MSFT: Q: Why Doesn?t Drag-and-Drop work when my Application is Running Elevated? ? A: Mandatory Integrity Control and UIPI - Pat's Windows Development Blog - Site Home - MSDN Blogs

It's a bit ironic, as much as frustrating, that when you invoke the program with a higher level of permissions that you are given a more strict limitation as to what you can do in this regard.

Here is the end-all-be-all solution that the article gives:
The best solution is to only use drag and drop between the same MIC levels.

Now, as a developer, that is completely useless to me I have to say... And as long as UAC is enabled, Explorer will run at medium MIC. Perhaps there are people out there using my program that will not understand why this is so, and perhaps they don't want to disable UAC however?

So they basically just say that you're screwed unless you just don't run the program as administrator, which will inevitably invoke the program to run with a higher MIC than explorer.exe (assuming UAC is enabled).

I spent so much time with hit testing and control transparency for click actions on the control here: FileDropTextBox Class... Which was added to my AutoBSA++ project to figure out why it did not work, and I couldn't see why it wouldn't work. Everything between my test project and my AutoBSA++ project during the tests seemed the exact same, but in my AutoBSA++ program, the drag drop wasn't working, this sparked in my head about elevated privileges, and after some searching I found this information.

I still need to actually test this... But at least now I know what's going on. I spent the last hour learning about MIC and UIPI. I think this is something that needs to be reconsidered in future versions of Windows...
 
Alright, so here was my solution for making a textbox that will accept a filename drop when invoked with high privileges...

Code:
// Author: AceInfinity
// Copyright: Tech.Reboot.Pro 2013
// 
// This textbox allows you to drag and drop filepath's directly to the Text
// property for display in both elevated and normal MIC levels when the parent process is invoked.
//
// This is essentially a workaround/bypass, to the annoying UIFI, which blocks the required
// windows messages needed in order to perform drag and drop from a program with a lower 
// MIC level, to one with a higher MIC level by changing the message filter and invoking the proper methods when those 
// windows messages are read.

using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class FileDropTextBox : TextBox
{
	public FileDropTextBox()
	{
		DragAcceptFiles(Handle, true);

		ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
		ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD);
		ChangeWindowMessageFilter(WM_COPYGLOBALDATA, MSGFLT_ADD);
	}

	const int WM_DROPFILES = 0x233;
	const int WM_COPYDATA = 0x004A;
	const int WM_COPYGLOBALDATA = 0x0049;

	[DllImport("shell32.dll", CharSet = CharSet.Ansi)]
	internal static extern void DragAcceptFiles(IntPtr hWnd, bool fAccept);

	[DllImport("user32.dll", SetLastError = true)]
	internal static extern bool ChangeWindowMessageFilter(uint message, int dwFlag);

	[DllImport("shell32.dll", CharSet = CharSet.Auto)]
	internal static extern uint DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder lpszFile, uint cch);

	[DllImport("shell32.dll")]
	internal static extern void DragFinish(IntPtr hDrop);

	const int MSGFLT_ADD = 1;

	protected override void WndProc(ref Message m)
	{
		base.WndProc(ref m);
		switch (m.Msg)
		{
			case WM_DROPFILES:
				StringBuilder sFile = new StringBuilder(256);
				uint result = DragQueryFile(m.WParam, 0, sFile, (uint)sFile.Capacity);
				DragFinish(m.WParam);
				OnDragDrop(new DragEventArgs(new FileDropDataObject(sFile.ToString(), result != 0), 2,
											 Cursor.Position.X, Cursor.Position.Y, DragDropEffects.All,
											 DragDropEffects.All));
				break;
		}
	}

	protected override void OnDragDrop(DragEventArgs e)
	{
		base.OnDragDrop(e);
		if (e.Data.GetDataPresent(DataFormats.FileDrop))
		{
			Text = (string)e.Data.GetData(DataFormats.FileDrop);
		}
	}

	private class FileDropDataObject : IDataObject
	{
		private readonly string _dropfile;
		private readonly bool _dataPresent;

		public FileDropDataObject(string file, bool dataPresent)
		{
			_dropfile = file;
			_dataPresent = dataPresent;
		}

		public object GetData(Type format)
		{
			throw new NotImplementedException();
		}

		// IMPORTANT
		public object GetData(string format)
		{
			if (format == DataFormats.FileDrop)
			{
				return _dropfile;
			}
			return null;
		}

		public object GetData(string format, bool autoConvert)
		{
			throw new NotImplementedException();
		}

		public bool GetDataPresent(Type format)
		{
			throw new NotImplementedException();
		}

		// IMPORTANT
		public bool GetDataPresent(string format)
		{
			if (format == DataFormats.FileDrop)
			{
				return _dataPresent;
			}
			return false;
		}

		public bool GetDataPresent(string format, bool autoConvert)
		{
			throw new NotImplementedException();
		}

		public string[] GetFormats()
		{
			throw new NotImplementedException();
		}

		public string[] GetFormats(bool autoConvert)
		{
			throw new NotImplementedException();
		}

		public void SetData(object data)
		{
			throw new NotImplementedException();
		}

		public void SetData(Type format, object data)
		{
			throw new NotImplementedException();
		}

		public void SetData(string format, object data)
		{
			throw new NotImplementedException();
		}

		public void SetData(string format, bool autoConvert, object data)
		{
			throw new NotImplementedException();
		}
	}
}

There's lots more you could do with this, but since I don't need anything beyond the above functionality... I won't be going there...

This class was set up for extensibility however. If you wanted to extend this to further functionality, this is the direction you would go, otherwise I would remove the class I declared which inherits the IDataObject interface, and do everything in the overriden WndProc method.
 
Last edited:

Has Sysnative Forums helped you? Please consider donating to help us support the site!

Back
Top