【问题标题】:Can I mapping network drive in very special way using winapi?我可以使用winapi以非常特殊的方式映射网络驱动器吗?
【发布时间】:2020-10-15 03:13:22
【问题描述】:

我必须以管理员权限启动我的应用程序(非常重要)。

当我在没有管理员权限的情况下执行此代码时,一切都很完美。 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);

但是当我在具有管理员权限的应用程序中执行上面的代码时,MyComputer 中没有图标。

我认为这很有用: Mapped network drives are not showing in My Computer

当我的应用程序具有管理员权限时,有没有办法以非管理员身份执行 winapi 功能?

【问题讨论】:

  • 驱动器映射是按用户设置的。当您以管理员身份运行代码时,它可能以不同于查看 Explorer 的用户身份运行。
  • @RemyLebeau 谢谢雷米的回答。你是最棒的。那么您是否看到任何从具有管理员权限的应用程序映射磁盘的方法?
  • 您以管理员身份运行的代码必须生成一个以登录到当前 Windows 会话的用户身份运行的新进程,然后该新进程才能创建映射。请参阅 Raymond Chen 博客上的 How can I launch an unelevated process from my elevated process and vice versa?How can I launch an unelevated process from my elevated process, redux
  • 另一种方法是不以管理员身份运行整个应用程序,而是仅在需要执行管理员任务时(通过新进程)动态提升它。
  • 这应该是起点,而不是替代方案。

标签: c++ winapi net-use


【解决方案1】:

来自WNetAddConnection2W

WNet 函数在与 登录会话 关联的 MS-DOS 设备名称空间中创建和删除网络驱动器号,因为 MS-DOS 设备由 AuthenticationID(本地唯一的 标识符或 LUID,与登录会话相关联。)

还有

如果作为 LocalSystem 运行的代码调用 WNetAddConnection2 函数,则映射的驱动器对所有人可见 用户登录会话。

从技术上讲,这意味着如果代码在\GLOBAL??\ 文件夹中以LocalSystem 的身份运行,则会创建指向网络磁盘的符号链接。否则将在

下创建链接

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

并且仅对令牌中具有相同 LogonId 的线程(进程)可见

如果您的代码具有管理员权限 - 它通常(几乎总是)具有调试权限。有了这个,我们可以使用 LocalSystem 令牌打开进程并在调用 WNetAddConnection2 之前模拟它。

还可能获得 TCB 特权,在此调用 WTSQueryUserToken 之后,通过 DuplicateToken 和模拟 - SetThreadToken 将主令牌转换为模拟令牌。最后打电话给WNetAddConnection2

好的。我首先尝试简单地模拟 LocalSystem

让我们有函数

NTSTATUS ImpersonateSystemOrTcbToken(bool bTcb);

LocalSystem 或具有 Tcb 权限的令牌设置为当前线程(据我所知,所有 LocalSystem 令牌都具有 TCB 权限,但无论如何写 2使用 TCB 或使用 LocalSystem 获取确切令牌的不同代码)

HRESULT AdjustDebugPrivilegesToThread();

在当前线程令牌中启用调试权限(它必须存在于管理员令牌中)

在这种情况下,代码可以是下一个:

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;
}

代码工作正常,确实创建了网络位置,但有下一个视图:

尽管如此 - 驱动器在点击时浏览正确。我不研究为什么描述中的 Disconected 词。但这里的权限可能存在一些问题

如果尝试为具体的 LUID 创建驱动器,代码会更复杂

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;
}

这个结果完全没问题

现在为 util 函数编写代码:

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);
}

和..

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;
}

【讨论】:

  • 完美。非常感谢!
  • @TomaszNowakowski 如果对你有帮助,你可以accept这个答案。
  • @DrakeWu-MSFT 怎么样?我没有看到“已解决”按钮。我无法为 RbMm 加分,因为我没有 15 分的声望。
  • 如何/为什么将FALSE 传递给NtDuplicateToken 的第四个参数,它需要SECURITY_IMPERSONATION_LEVEL。这不能在 C++ 上编译。如果 FALSE/0 被传递,你是在暗示SecurityAnonymous
  • @Ajay NtDuplicateToken 的第 4 个参数是 _In_ BOOLEAN EffectiveOnly,(查看 ntifs.h)我的代码当然已编译。 SECURITY_IMPERSONATION_LEVEL 通过第三个参数POBJECT_ATTRIBUTES 传递,其中存在指向SECURITY_QUALITY_OF_SERVICE 的指针,这里存在SECURITY_IMPERSONATION_LEVEL - 我在这里使用SecurityImpersonation
猜你喜欢
  • 2013-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多