CAS and How to properly elevate with Vista UAC

The question comes up: “In Vista, if I have UAC enabled, how should that affect my code if I need to run some things with privilege?”. I’ve combed the answer from many places, but I believe I’m covering all the bases here. Many times the UAC prompt is mixed up with Code Access Security (CAS) – so I’ll also explain how they are different. Below is what I’ve found on the various ways to handle this:

.NET permissions “demands” (and CAS):
When you try to do various things in your application, like write to the registry, write to a file, etc – many times there are permission demands that are made before the code executes. In other words, the run-time demands that it get permission (with your current credentials), to do this secure task. If the process can’t successfully demand privilege, it will throw a SecurityException and will not continue. For example, I can assert these same demands for my code:

Declarative:

   
private void button3_Click(object sender, EventArgs e)

    {

        try

        {

            WriteToCDrive();

        }

        catch (SecurityException exception)

        {

            Debug.WriteLine(exception.ToString());

            MessageBox.Show("You do not have privilege to do that.", "Access Denied", MessageBoxButtons.OK, MessageBoxIcon.Error);

        }

    }

 

    [FileIOPermission(SecurityAction.Demand, AllLocalFiles=FileIOPermissionAccess.Write)]

    private void WriteToCDrive()

    {

        // Write to the C drive..

    }

Imperative:

    private void button3_Click(object sender, EventArgs e)

    {

        try

        {

            WriteToCDrive();

        }

        catch (SecurityException exception)

        {

            Debug.WriteLine(exception.ToString());

            MessageBox.Show("You do not have privilege to do that.", "Access Denied", MessageBoxButtons.OK, MessageBoxIcon.Error);

        }

    }

 

    private void WriteToCDrive()

    {

        new FileIOPermission(FileIOPermissionAccess.AllAccess, @"C:").Demand();

       

        // Write to the C drive..

    }

This is different that how the UAC system will work. The code above shouldn’t cause a prompt – instead, it will just let you know it didn’t work (via the exception). If you want a UAC prompt, because you know you will need access to something, then there seems to be really two ways to do this:

Create a manifest:
You can easily create a manifest, put in it how you want to start your application – and if it requires elevation, it will do it once at the beginning of the program. There are several pieces that are needed for this:

STEP 1: Create a manifest:
Create a file called <executable name>.exe.manifest, and add it to the root of your project. In that file, paste this and modify the obvious places:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

  <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="WindowsFormsApplication1" type="win32"/>

  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">

    <security>

      <requestedPrivileges>

        <!– 

          uiAccess should only be true in Accessibility applications

          –>

 

        <!–<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>–>

        <!–<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>–>

        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>

       

      </requestedPrivileges>

    </security>

  </trustInfo>

</assembly>

STEP 2: Make it an embedded resource:
Within Visual Studio, click on the manifest file and hit F4 to bring up properties. Set the “Build Action” to “Embedded Resource”.

STEP 3: Merge in the manifest into the .exe:
As a post-build step, merge this manifest into the .exe, so that you don’t have to ship it as a seperate file. Add a post-build even that does something like this:

"mt.exe" -manifest "$(ProjectDir)$(TargetName).exe.manifest"  –outputresource:"$(TargetDir)$(TargetFileName)";#1

That’s about it. So now, you can try uncommenting the “requireAdministrator” and you’ll see that you’ll get a UAC prompt when you try to start the file (from the file system, not from within Visual Studio).

 

Have an elevation “button”:
I stole from several sources here and just decided to make a button that does this for me automatically. First, there is a Win32 API that takes care of adding a shield on the button, if it needs it. So first, here is the before and after, then the code:

image

image

This is a technique that is used a lot in Windows itself, for example:

image image

So, how is this done? Well, basically – you can tell if you are fully privileged by doing something like this:

    private bool IsAdmin

    {

        get

        {

            return new WindowsPrincipal(

                WindowsIdentity.GetCurrent()).IsInRole(

                    WindowsBuiltInRole.Administrator);

        }

    }

As for the rest, I inherited from the Button class, and did the rest internally. How this is done, it is restarts the app with privilege via RunAs. So decorating the button with the shield, greying out if you’re already an admin, etc, is all done in the control. So you can easily add this control to any screen that needs elevation. Here’s the code:

    public sealed class UacElevationButton : Button

    {

        public UacElevationButton()

        {

            this.Text = "Elevate Privilege";

            this.Click +=new EventHandler(UacElevationButton_Click);

 

            // The shield will only attach to the button if the

            // flat style is set to "System"

            this.FlatStyle = FlatStyle.System;

            AddShieldToButton(this);

 

            // If we’re already admin, then grey out the button

            this.Enabled = IsAdmin;

        }

 

        [DllImport("user32")]

        public static extern UInt32 SendMessage

            (IntPtr hWnd, UInt32 msg, UInt32 wParam, UInt32 lParam);

 

        private const int BCM_FIRST = 0x1600; //Normal button

        private const int BCM_SETSHIELD = (BCM_FIRST + 0x000C); //Elevated button

 

        private void UacElevationButton_Click(object sender, EventArgs e)

        {

            RestartElevated();

        }

 

        private void AddShieldToButton(Button button)

        {

            button.FlatStyle = FlatStyle.System;

            SendMessage(button.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF);

        }

 

        private  void RemoveShieldFromButton(Button button)

        {

            SendMessage(button.Handle, BCM_FIRST, 0, 0xFFFFFFFF);

        }

        private static void RestartElevated()

        {

            ProcessStartInfo startInfo = new ProcessStartInfo();

            startInfo.UseShellExecute = true;

            startInfo.WorkingDirectory = Environment.CurrentDirectory;

            startInfo.FileName = Application.ExecutablePath;

            startInfo.Verb = "RunAs";

            try

            {

                Process.Start(startInfo);

            }

            catch (System.ComponentModel.Win32Exception exception)

            {

                Debug.WriteLine("Error restarting application: " + exception.ToString());

                return;

            }

 

            Application.Exit();

        }

 

        private void InitializeComponent()

        {

            this.SuspendLayout();

            this.ResumeLayout(false);

        }

 

        private bool IsAdmin

        {

            get

            {

                return new WindowsPrincipal(

                    WindowsIdentity.GetCurrent()).IsInRole(

                        WindowsBuiltInRole.Administrator);

            }

        }

    }

Lastly, on the consumer side, I dragged this button onto the screen. Then, I enable/disable a groupbox, depending on if I’m an admin:

    private void Form1_Load(object sender, EventArgs e)

    {

        this.label1.Text = "Is admin: " + IsAdmin.ToString();

        this.groupBox1.Enabled = IsAdmin;

    }

And that’s pretty much it! Keeping in mind that all apps and all users should be running with least privilege, it would ideal to not need elevation. However, sometimes you do need it – and these are a couple of ways to effectively work with the operating system to get you access.

Posted in Security, Uncategorized

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 5 other followers

%d bloggers like this: