0

I have to start my application with admin privileges ( very important ).

When I execute this code without admin privileges everything is perfect. There is an icon in MyComputer.

NETRESOURCE nrServer;
memset(&nrServer, 0, sizeof (NETRESOURCE));
nrServer.dwType = RESOURCETYPE_ANY;
nrServer.lpLocalName = L"S:";
nrServer.lpRemoteName = L"\\\\192.168.32.36\\folderName";
nrServer.lpProvider = L"";
auto dwError = WNetAddConnection2(&nrServer, L"user", L"pass", 0);

But when I execute this code above in application with admin privileges, there is no icon in MyComputer.

I think that can be usefull: Mapped network drives are not showing in My Computer

Is there any way to execute winapi function as not admin when my application has admin privileges?

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Drive mappings are setup on a per-user basis. When you run your code as an admin, it is likely running as a different user than the one who is looking at Explorer. – Remy Lebeau Jun 24 '20 at 19:04
  • @RemyLebeau Thank you Remy for answer. You are the best. So do you see any way to mapping disk from application which has admin privileges? – Tomasz Nowakowski Jun 24 '20 at 19:06
  • Your code running as admin will have to spawn a new process that runs as the user who is logged in to the current Windows session, and then that new process can create the mapping. See [How can I launch an unelevated process from my elevated process and vice versa?](https://devblogs.microsoft.com/oldnewthing/20131118-00/?p=2643) and [How can I launch an unelevated process from my elevated process, redux](https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443) on Raymond Chen's blog – Remy Lebeau Jun 24 '20 at 19:12
  • 1
    The alternative is not run your whole app as an admin to begin with, but instead have it elevate dynamically (via a new process) only when it needs to perform admin tasks. – Remy Lebeau Jun 24 '20 at 19:12
  • 1
    That should be the starting point, not an alternative. – Asteroids With Wings Jun 24 '20 at 19:16
  • @RemyLebeau I have to read this article, but thank you VERY MUCH! – Tomasz Nowakowski Jun 24 '20 at 19:17
  • @AsteroidsWithWings I have to run netsh cmd command in my application. I use QT, so I use qprocess->start(R"(netsh …………...)"). When I don't execute my app with admin privileges that command don't be executed properly. – Tomasz Nowakowski Jun 24 '20 at 19:20
  • @RemyLebeau Can you tell me how to run only choosen by me processes, which have to be with admin privileges and started my app without them? Something like runas administator cmdCommand? – Tomasz Nowakowski Jun 24 '20 at 19:24
  • The [1st article I pointed you to](https://devblogs.microsoft.com/oldnewthing/20131118-00/?p=2643) tells you how to run an elevated process from an un-elevated process: "*Going from an unelevated process to an elevated process is easy. You can run a process with elevation by passing the `runas` verb to `Shell­Execute` or `Shell­Execute­Ex`.*" To prevent your main app from running as an admin, simply specify `asInvoker` instead of `requireAdministrator` in its UAC manifest. But that won't stop an admin from using "Run as Administrator" on the EXE, if they really want to – Remy Lebeau Jun 24 '20 at 19:32
  • @RemyLebeau I have a small problem with InitializeProcThreadAttributeList function. I get information "use of undeclared indentifier InitializeProcThreadAttributeList ". When I go to processthreadsapi.h I see that function, but It is not active because #if _WIN32_WINNT >= 0x0600 is not true. I read that can be because of include order. – Tomasz Nowakowski Jun 24 '20 at 19:52
  • It means your project is not setup to compile for Vista+. So fix your project setup (which means your code may not run on XP and earlier anymore, if you still need that), see [Setting WINVER or _WIN32_WINNT](https://docs.microsoft.com/en-us/windows/win32/WinProg/using-the-windows-headers#setting-winver-or-_win32_winnt) and [Update WINVER and _WIN32_WINNT](https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019). Otherwise, load `InitializeProcThreadAttributeList()` dynamically at runtime using `GetProcAddress()`. `STARTUPINFOEX` was introduced in Vista. – Remy Lebeau Jun 24 '20 at 20:12
  • yes, this possible, but require not small code. need impersonate not elevated user token before call `WNetAddConnection2`. for this need first get TCB privilege. this is possible if you run as admin (more exactly if you have debug privilege) – RbMm Jun 24 '20 at 21:49
  • not need run new process of course – RbMm Jun 24 '20 at 21:50
  • @RemyLebeau -*Drive mappings are setup on a per-user basis* - this is wrong. per session locally unique identifier ( LUID) basis. admin usually run as same user like explorer, but in another logon session. with another luid. but anyway not need exec new process for task – RbMm Jun 24 '20 at 22:11
  • @RbMm "*per session locally unique identifier (LUID) basis*" - same thing. Separate user sessions = separate mappings storage. "*admin usually run as same user like explorer*" - you can't rely on that, though. Sure, if you own the PC, you are likely your own admin and can elevate yourself when needed. But in a corporate environment, that is usually not the case. So if the Windows Session user and the admin user are actually two different users, then yes, you should use another process, or impersonation like you say. – Remy Lebeau Jun 24 '20 at 22:16
  • @RemyLebeau - yes admin can be different user and can be the same user as explorer(shell). but this is not important for solution. anyway elevated admin will be run in another logon session. i try say - it not per user basis but per token luid bases. also *if `LocalSystem` calls the `WNetAddConnection2` function, then the mapped drive is visible to all user logon sessions.* and we easy can impersonate as *LocalSystem* too. not need separate process – RbMm Jun 24 '20 at 22:22

1 Answers1

0

from WNetAddConnection2W

the WNet functions create and delete network drive letters in the MS-DOS device namespace associated with a logon session because MS-DOS devices are identified by AuthenticationID (a locally unique identifier, or LUID, associated with a logon session.)

also

if a code that runs as LocalSystem calls the WNetAddConnection2 function, then the mapped drive is visible to all user logon sessions.

technically this mean if code run as LocalSystem in the \GLOBAL??\ folder will be create symbolic link to network disk. otherwise link will be created under

\Sessions\0\DosDevices\<token LogonId>\

and will be visible only for threads(processes) which have the same LogonId in token

if your code have admin privileges - it usually (almost always) have debug privileges. with this we can open process with LocalSystem token and impersonate it before call WNetAddConnection2.

possible also get TCB privilege and after this call WTSQueryUserToken, convert primary token to impersonation token, via DuplicateToken, and impersonate - SetThreadToken. and call WNetAddConnection2 finally.

ok. i try first simply impersonate to LocalSystem

let we have function

NTSTATUS ImpersonateSystemOrTcbToken(bool bTcb);

which set LocalSystem or token with Tcb privileges to current thread (as far i know all LocalSystem tokens have TCB privilege but anyway write 2 different code for get exactly token with TCB or with LocalSystem)

and

HRESULT AdjustDebugPrivilegesToThread();

which enable debug privileges in current thread token (it must exist in admin token)

in this case code can be next:

inline HRESULT BOOL_TO_HRESULT(BOOL f)
{
    return f ? NOERROR : HRESULT_FROM_WIN32(GetLastError());
}

HRESULT MapRemoteDrive(PCWSTR local, PCWSTR remote, PCWSTR username, PCWSTR password)
{
    NETRESOURCEW nr = {
        0, RESOURCETYPE_DISK, 0, 0, const_cast<PWSTR>(local), const_cast<PWSTR>(remote)
    };
    return HRESULT_FROM_WIN32(WNetAddConnection2W(&nr, password, username, 0));
}

HRESULT MapRemoteDriveEx1(PCWSTR local, PCWSTR remote, PCWSTR username, PCWSTR password)
{
    HRESULT hr = BOOL_TO_HRESULT(ImpersonateSelf(::SecurityImpersonation));

    if (SUCCEEDED(hr))
    {
        if (SUCCEEDED(hr = AdjustDebugPrivilegesToThread()) &&
            SUCCEEDED(hr = HRESULT_FROM_NT(ImpersonateSystemOrTcbToken(false))))
        {
            hr = MapRemoteDrive(local, remote, username, password);
            // WNetCancelConnection2W(local, 0, TRUE);
        }

        SetThreadToken(0, 0);
    }

    return hr;
}

code work ok and really network location created, but with next view:

enter image description here

despite this - drive is browsed correct on click. i not research why is Disconected word in description. but possible some problems with permissions here

if try create drive for concrete LUID, code will be more complex

HRESULT MapRemoteDriveEx2(PCWSTR local, PCWSTR remote, PCWSTR username, PCWSTR password)
{
    HRESULT hr = BOOL_TO_HRESULT(ImpersonateSelf(::SecurityImpersonation));

    if (SUCCEEDED(hr))
    {
        HANDLE hToken, hImpToken;
        if (SUCCEEDED(hr = AdjustDebugPrivilegesToThread()) &&
            SUCCEEDED(hr = HRESULT_FROM_NT(ImpersonateSystemOrTcbToken(true))) &&
            SUCCEEDED(hr = BOOL_TO_HRESULT(WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken))))
        {
            hr = BOOL_TO_HRESULT(DuplicateToken(hToken, ::SecurityImpersonation, &hImpToken));
            CloseHandle(hToken);
            if (SUCCEEDED(hr))
            {
                hr = BOOL_TO_HRESULT(SetThreadToken(0, hImpToken));
                CloseHandle(hImpToken);

                if (SUCCEEDED(hr))
                {
                    hr = MapRemoteDrive(local, remote, username, password);
                    // WNetCancelConnection2W(local, 0, TRUE);
                }
            }
        }

        SetThreadToken(0, 0);
    }

    return hr;
}

with this result full ok

enter image description here

now code for util functions:

HRESULT AdjustDebugPrivilegesToThread()
{
    ULONG dwError;
    HANDLE hToken;
    if (OpenThreadToken(NtCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken))
    {
        static const ::TOKEN_PRIVILEGES tp = { 1, { { { SE_DEBUG_PRIVILEGE } } } };

        AdjustTokenPrivileges(hToken, FALSE, const_cast<::PTOKEN_PRIVILEGES>(&tp), 0, 0, 0);

        dwError = GetLastError();

        CloseHandle(hToken);
    }
    else
    {
        dwError = GetLastError();
    }

    return HRESULT_FROM_WIN32(dwError);
}

and..

NTSTATUS GetSystemToken(PVOID buf)
{
    NTSTATUS status;

    union {
        PVOID pv;
        PBYTE pb;
        PSYSTEM_PROCESS_INFORMATION pspi;
    };

    pv = buf;
    ULONG NextEntryOffset = 0;

    do 
    {
        pb += NextEntryOffset;

        HANDLE hProcess, hToken, hNewToken;

        CLIENT_ID ClientId = { pspi->UniqueProcessId };

        if (ClientId.UniqueProcess)
        {
            static SECURITY_QUALITY_OF_SERVICE sqos = {
                sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
            };

            static OBJECT_ATTRIBUTES soa = { sizeof(soa), 0, 0, 0, 0, &sqos };

            if (0 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &ClientId))
            {
                status = NtOpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE, &hToken);

                NtClose(hProcess);

                if (0 <= status)
                {
                    ULONG rcb;
                    TOKEN_STATISTICS ts;
                    static const LUID SystemLuid = SYSTEM_LUID;

                    status = -1;

                    if (0 <= NtQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &rcb) &&
                        ts.AuthenticationId.LowPart == SystemLuid.LowPart &&
                        ts.AuthenticationId.HighPart == SystemLuid.HighPart)
                    {
                        status = NtDuplicateToken(hToken, TOKEN_IMPERSONATE, 
                            &soa, FALSE, TokenImpersonation, &hNewToken);
                    }

                    NtClose(hToken);

                    if (0 <= status)
                    {
                        status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hNewToken, sizeof(hNewToken));
                        
                        NtClose(hNewToken);

                        return status;
                    }
                }
            }
        }

    } while (NextEntryOffset = pspi->NextEntryOffset);

    return STATUS_UNSUCCESSFUL;
}

NTSTATUS GetTcbToken(PVOID buf)
{
    NTSTATUS status;

    union {
        PVOID pv;
        PBYTE pb;
        PSYSTEM_PROCESS_INFORMATION pspi;
    };

    pv = buf;
    ULONG NextEntryOffset = 0;

    do 
    {
        pb += NextEntryOffset;

        HANDLE hProcess, hToken, hNewToken;

        if (pspi->InheritedFromUniqueProcessId && pspi->UniqueProcessId)
        {
            static SECURITY_QUALITY_OF_SERVICE sqos = {
                sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
            };

            static OBJECT_ATTRIBUTES soa = { sizeof(soa), 0, 0, 0, 0, &sqos };

            CLIENT_ID ClientId = { pspi->UniqueProcessId };

            if (0 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &ClientId))
            {
                status = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE, &hToken);

                NtClose(hProcess);

                if (0 <= status)
                {
                    status = NtDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, 
                        &soa, FALSE, TokenImpersonation, &hNewToken);

                    NtClose(hToken);

                    if (0 <= status)
                    {
                        static const TOKEN_PRIVILEGES tp = { 1, { { { SE_DEBUG_PRIVILEGE } } } };

                        status = NtAdjustPrivilegesToken(hNewToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(&tp), 0, 0, 0);

                        if (STATUS_SUCCESS == status)   
                        {
                            status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hNewToken, sizeof(hNewToken));
                        }

                        NtClose(hNewToken);

                        if (STATUS_SUCCESS == status)
                        {
                            return STATUS_SUCCESS;
                        }
                    }
                }
            }
        }

    } while (NextEntryOffset = pspi->NextEntryOffset);

    return STATUS_UNSUCCESSFUL;
}

NTSTATUS ImpersonateSystemOrTcbToken(bool bTcb)
{
    NTSTATUS status;

    ULONG cb = 0x10000;

    do 
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        if (PBYTE buf = new BYTE[cb += 0x1000])
        {
            if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
            {
                status = (bTcb ? GetTcbToken : GetSystemToken)(buf);

                if (status == STATUS_INFO_LENGTH_MISMATCH)
                {
                    status = STATUS_UNSUCCESSFUL;
                }
            }

            delete [] buf;
        }

    } while(status == STATUS_INFO_LENGTH_MISMATCH);

    return status;
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • PERFECT. Thank you very much! – Tomasz Nowakowski Jul 22 '20 at 14:24
  • @TomaszNowakowski You could [accept](https://stackoverflow.com/help/someone-answers) this answer if it does help you. – Drake Wu Jul 24 '20 at 01:05
  • @DrakeWu-MSFT How? I don't see button "solved". I can't add point to RbMm because I don't have 15 points reputation. – Tomasz Nowakowski Jul 25 '20 at 04:39
  • How/Why is `FALSE` passed to 4th argument of `NtDuplicateToken`, which expects `SECURITY_IMPERSONATION_LEVEL`. This doesn't compile on C++. If FALSE/0 is passed, are you implying `SecurityAnonymous`? – Ajay May 14 '21 at 07:07
  • @Ajay 4th argument of NtDuplicateToken is *_In_ BOOLEAN EffectiveOnly,* (look in *ntifs.h*) my code of course compiled. the `SECURITY_IMPERSONATION_LEVEL` is passed via 3th argument `POBJECT_ATTRIBUTES` where exist pointer to `SECURITY_QUALITY_OF_SERVICE` and here exist `SECURITY_IMPERSONATION_LEVEL` - i use here `SecurityImpersonation` – RbMm May 14 '21 at 08:40
  • @Ajay i think you confuse `DuplicateTokenEx` (which really take `SECURITY_IMPERSONATION_LEVEL` in 4th argument) and `NtDuplicateToken` which have another signature and take `SECURITY_IMPERSONATION_LEVEL` from `SECURITY_QUALITY_OF_SERVICE ` inside `OBJECT_ATTRIBUTES` (pointer to this in 3th argument) – RbMm May 14 '21 at 08:47