Susarla Nikhilesh
Susarla Nikhilesh

Reputation: 178

Run a program as user identity but with elevated privileges

Scenario:

An administrator will install the application. The application has some kernel level operations so, it has to run with privileged mode. But the user does not have administrator credentials to run the application in elevated mode.

So, what are the best possibility to solve the above scenario.

Solution one (tried): While installing the application through administrator, we would create an admin where we know his user name and password. So, when the user tries to perform any operation, we will run the application as elevated mode using the functions processstartinfo() and process.start() in c#. But, the application runs in admin name, so the mapped drives for the user are not visible as this admin is not in the ACL list. So, this method is getting ruled out.

Can there be a solution where elevating the user it self as admin till the operation is completed using c# application?

I'm stuck and I don't find any articles to read upon on this.

Edit : At an enterprise level, the windows doesn't ask for admin creds for using bitlocker. So, we want to achieve the same type of functionality.

Is it possible?

Can anyone please help?

Thank you.

Upvotes: 10

Views: 3923

Answers (4)

Simon Mourier
Simon Mourier

Reputation: 139276

What you can do is use COM+ Component Services. With .NET the easiest way is use Enterprise Services's ServicedComponent which has all sorts of wrappers and utility classes to interop with COM+ Component services.

So here are steps to do it:

1) Create a .NET Framework Class Library.

2) Add it a strong name and sign it with it

3) Add it a class like this for example (I've also put some utility method to diagnose things)

[ComVisible(true)]
public class AdminClass : ServicedComponent
{
    public int DoSomethingAsAdmin()
    {
        // test something that a normal user shouldn't see
        return Directory.GetFiles(Path.Combine(Environment.SystemDirectory, "config")).Length;
    }

    public string WindowsIdentityCurrentName => WindowsIdentity.GetCurrent().Name;
    public string CurrentProcessFilePath => Process.GetCurrentProcess().MainModule.FileName;

    // depending on how you call regsvcs, you can run as a 32 or 64 bit surrogate dllhost.exe
    public bool Is64BitProcess => Environment.Is64BitProcess;
}

4) Add the following to AssemblyInfo.cs

[assembly: ApplicationName("AdminApp")]
[assembly: SecurityRole("AdminAppUser")]
[assembly: ApplicationActivation(ActivationOption.Server)]

What this does is define a COM+ application named "AdminApp", add a role named "AdminAppUser" to it, and declare the app will run as a "server" which means "out-of-process".

5) Compile that and run this command as admin

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regsvcs.exe AdminApp.dll

or this command:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regsvcs.exe AdminApp.dll

Both commands will create the the COM + application, and host the .NET library DLL in a surrogate .exe (dllhost.exe). If you choose the first, the hosted process will run as x64, and if you run the second, the hosted process will run as x86.

You can check the result of this registration if you run Component Services (from Windows/Run):

enter image description here

6) Right-click the app and you'll see a whole bunch of cool things you can configure. Note you can even run this as a service (in the 'Activation' tab), etc. What you must do is configure the identity which will run this process, something like this:

enter image description here

Here, I've used a custom admin account. You don't want to use any of the other builtin choices.

7) Now, since default security has been enabled, basically nobody can calls this component. So we just have to add a user to the role "AdminAppUser" we created earlier. You can of course do this using the UI as shown here:

enter image description here

but here is a piece of code that does this programmatically (we use the COM+ administration objects) :

AddUserInRole("AdminApp", "AdminAppUser", @"SMO01\simon");

....

static void AddUserInRole(string appName, string roleName, string userName)
{
    dynamic catalog = Activator.CreateInstance(Type.GetTypeFromProgID("COMAdmin.COMAdminCatalog"));

    // the list of collection hierarchy : https://learn.microsoft.com/en-us/windows/desktop/cossdk/com--administration-collections
    var apps = catalog.GetCollection("Applications");
    var app = GetCollectionItem(apps, appName);
    if (app == null)
        throw new Exception("Application '" + appName + "' was not found.");

    var roles = apps.GetCollection("Roles", app.Key);
    var role = GetCollectionItem(roles, roleName);
    if (role == null)
        throw new Exception("Role '" + roleName + "' was not found.");

    // UsersInRole collection
    // https://learn.microsoft.com/en-us/windows/desktop/cossdk/usersinrole
    var users = roles.GetCollection("UsersInRole", role.Key);
    var user = GetCollectionItem(users, userName);
    if (user == null)
    {
        user = users.Add();
        user.Value["User"] = userName;
        users.SaveChanges();
    }
}

static dynamic GetCollectionItem(dynamic collection, string name)
{
    collection.Populate();
    for (int i = 0; i < collection.Count; i++)
    {
        var item = collection.Item(i);
        if (item.Name == name)
            return item;
    }
    return null;
}

The result should be like this:

enter image description here

8) Now, for the client app, using the AdminApp facilities is easy. Don't reference the .DLL as a standard .NET reference, but use it as any other external COM component. You could reference the .TLB file that was created by regsvcs, or just use the magic dynamic keyword as I demonstrate here (the drawback is you don't get autocompletion):

using System;
using System.Security.Principal;

namespace UserApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Is64BitProcess " + Environment.Is64BitProcess);
            Console.WriteLine("Running As " + WindowsIdentity.GetCurrent().Name);

            var type = Type.GetTypeFromProgID("AdminApp.AdminClass");
            dynamic trustedClass = Activator.CreateInstance(type);

            Console.WriteLine("Admin App Process Path: " + trustedClass.CurrentProcessFilePath);
            Console.WriteLine("Admin App Running As: " + trustedClass.WindowsIdentityCurrentName);
            Console.WriteLine("Admin App Is64BitProcess: " + trustedClass.Is64BitProcess);
            Console.WriteLine("Admin App DoSomethingAsAdmin: " + trustedClass.DoSomethingAsAdmin());
        }
    }
}

Now, when you run it for example as "simon", you should see something like this, it works:

Is64BitProcess False
Running As SMO01\simon
Admin App Process Path: C:\WINDOWS\system32\dllhost.exe
Admin App Running As: SMO01\myAdmin
Admin App Is64BitProcess: True
Admin App DoSomethingAsAdmin: 71

and when you run it for example as "bob" who's not configured in the role, you should see something like this with an access denied, this is expected:

Is64BitProcess False
Running As SMO01\bob

Unhandled Exception: System.UnauthorizedAccessException: Retrieving the COM class factory for component with CLSID {0DC1F11A-A187-3B6D-9888-17E635DB0974} failed due to the following error: 80070005 Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)).
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at UserApp.Program.Main(String[] args) in C:\Users\simon\source\repos\TrustedSystem\UserApp\Program.cs:line 14

Note we've created a trusted system without setting any password anywhere. And, I've only scratched the surface of what you can do with COM+ component. For example, you can export the app as an .MSI for easy deployment, etc.

Upvotes: 0

user4950026
user4950026

Reputation:

You could try creating a parallel thread that has admin privileges in order to execute the kernel operations. This way your whole program is contained in one executable.

Upvotes: 0

Svek
Svek

Reputation: 12898

I think you might be stuck because you are dealing with two functions that have permissions features, and both are required to properly run your application.

I have noted that you have these two:

  • File System Path. Only the logged-on user has access to this (the admin account does not).
  • Application Processes. Only the admin account can run these processes (the logged-on user will be prompted with user elevation).

To illustrate this...

        |  File System Path   |  Application Process
User    |        OK           |          --
Admin   |        --           |          OK

The solution needs to look like this...

        |  File System Path   |  Application Process
Service |        OK           |          OK

If possible, I would say this is the most straight-forward way, whereby you create a service account that has permissions to do both.

Upvotes: 0

Joe Sonderegger
Joe Sonderegger

Reputation: 804

The solution that I did was to separate the program into two parts. One part runs as a service that is run using admin privileges and the other part runs using normal privileges. The communication between the two programs can run via Ethernet or using shared memory.

Upvotes: 3

Related Questions