从SID创建用户令牌,在用户上下文中展开环境变量

use*_*404 7 c c++ windows winapi

我有一个服务正在运行,并希望访问常见的用户文件夹,如启动.为此我想扩展环境变量,如%APPDATA%系统上的每个用户(包括注销).我可以获取登录用户的会话ID并从中创建令牌然后调用ExpandEnvironmentStringsForUser().但是注销用户呢.对他们来说不会是会话.我唯一可以得到的就是帐户名(使用NetUserEnum()NetQueryDisplayInformation())和来自注册表的SID(HKLM\software\Microst\Windows NT\current Version\Profile List)我可以从SID获取用户令牌还是冒充用户使用SID,或者是否有某种方法可以使用SID扩展环境变量.

编辑:我需要从所有用户的启动位置删除一些文件.为此我需要扩展%APPDATA%%USERPROFILE%在每个用户的上下文中,无论是否登录.

编辑2:问题归结为扩展环境变量,例如%APPDATA%针对不同用户,而没有给该用户的令牌.

RbM*_*bMm 6

从任何给定的SID创建令牌是可能的,但不是简单的。存在用于创建令牌的未记录系统 api:

extern "C" NTSYSCALLAPI NTSTATUS NTAPI NtCreateToken(
    _Out_ PHANDLE   TokenHandle,
    _In_ ACCESS_MASK    DesiredAccess,
    _In_opt_ POBJECT_ATTRIBUTES     ObjectAttributes,
    _In_ TOKEN_TYPE     TokenType,
    _In_ PLUID      AuthenticationId,
    _In_ PLARGE_INTEGER     ExpirationTime,
    _In_ PTOKEN_USER    User,
    _In_ PTOKEN_GROUPS      Groups,
    _In_ PTOKEN_PRIVILEGES      Privileges,
    _In_opt_ PTOKEN_OWNER   Owner,
    _In_ PTOKEN_PRIMARY_GROUP   PrimaryGroup,
    _In_opt_ PTOKEN_DEFAULT_DACL    DefaultDacl,
    _In_ PTOKEN_SOURCE      TokenSource 
    );
Run Code Online (Sandbox Code Playgroud)

这里的AuthenticationId必须是一些有效的登录会话 ID,否则我们会STATUS_NO_SUCH_LOGON_SESSION出错。例如,我们可以从当前进程令牌中获取此值。所有其他参数,通常可以是任何有效的感测数据。所以可以用下一种方式创建令牌:

NTSTATUS CreateUserToken(PHANDLE phToken, PSID Sid)
{
    HANDLE hToken;
    NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken);

    if (0 <= status)
    {
        TOKEN_STATISTICS ts;

        status = NtQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &ts.DynamicCharged);

        NtClose(hToken);

        if (0 <= status)
        {
            TOKEN_PRIMARY_GROUP tpg = { Sid };
            TOKEN_USER User = { { Sid } }; 

            static TOKEN_SOURCE Source = { { "User32 "} };

            static TOKEN_DEFAULT_DACL tdd;
            static _SID EveryOne = { SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, { SECURITY_WORLD_RID } };
            static TOKEN_GROUPS Groups = { 1, { { &EveryOne,  SE_GROUP_ENABLED|SE_GROUP_MANDATORY } } };

            struct TOKEN_PRIVILEGES_3 {
                ULONG PrivilegeCount;
                LUID_AND_ATTRIBUTES Privileges[3];
            } Privileges = {
                3, {
                    { { SE_BACKUP_PRIVILEGE }, SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT },
                    { { SE_RESTORE_PRIVILEGE }, SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT },
                    { { SE_CHANGE_NOTIFY_PRIVILEGE }, SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT }
                }
            };

            static SECURITY_QUALITY_OF_SERVICE sqos = {
                sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING
            };

            static OBJECT_ATTRIBUTES oa = { 
                sizeof oa, 0, 0, 0, 0, &sqos
            };

            status = NtCreateToken(phToken, TOKEN_ALL_ACCESS, &oa, TokenImpersonation, 
                &ts.AuthenticationId, &ts.ExpirationTime, &User, &Groups, (PTOKEN_PRIVILEGES)&Privileges, 0,
                &tpg, &tdd, &Source);
        }
    }

    return status;
}
Run Code Online (Sandbox Code Playgroud)

此令牌将被赋予SID作为令牌用户 sid、3 权限 ( SE_BACKUP_PRIVILEGE, SE_RESTORE_PRIVILEGE- 这需要调用LoadUserProfileapi 和SE_CHANGE_NOTIFY_PRIVILEGE具有遍历特权)和一个组 -每个人 (s-1-1-0)

但是对于通话,NtCreateToken我们必须具有SE_CREATE_TOKEN_PRIVILEGE特权,否则我们会出错STATUS_PRIVILEGE_NOT_HELD。大多数系统进程都没有它。只有少数(如lsass.exe)。说services.exe和所有服务 - 没有这个特权。所以一开始我们必须得到它。这可以通过枚举进程来完成,看看 - 拥有这个特权,从这个进程中获得令牌,并模拟它:

BOOL g_IsXP;// true if we on winXP, false otherwise
static volatile UCHAR guz;
OBJECT_ATTRIBUTES zoa = { sizeof zoa };

NTSTATUS ImpersonateIfConformToken(HANDLE hToken)
{
    ULONG cb = 0, rcb = 0x200;
    PVOID stack = alloca(guz);zoa;

    union {
        PVOID buf;
        PTOKEN_PRIVILEGES ptp;
    };

    NTSTATUS status;
    do 
    {
        if (cb < rcb)
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        if (0 <= (status = NtQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
        {
            if (ULONG PrivilegeCount = ptp->PrivilegeCount)
            {
                ULONG n = 1;
                BOOL bNeedAdjust = FALSE;

                PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
                do 
                {
                    if (!Privileges->Luid.HighPart)
                    {
                        switch (Privileges->Luid.LowPart)
                        {
                        case SE_CREATE_TOKEN_PRIVILEGE:
                            if (!(Privileges->Attributes & SE_PRIVILEGE_ENABLED))
                            {
                                Privileges->Attributes |= SE_PRIVILEGE_ENABLED;
                                bNeedAdjust = TRUE;
                            }

                            if (!--n)
                            {
                                static SECURITY_QUALITY_OF_SERVICE sqos = {
                                    sizeof sqos, SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE
                                };

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

                                if (0 <= (status = NtDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, &soa, FALSE, TokenImpersonation, &hToken)))
                                {
                                    if (bNeedAdjust)
                                    {
                                        status = NtAdjustPrivilegesToken(hToken, FALSE, ptp, 0, 0, 0);
                                    }

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

                                    NtClose(hToken);
                                }

                                return status;
                            }
                            break;
                        }
                    }
                } while (Privileges++, --PrivilegeCount);
            }

            return STATUS_PRIVILEGE_NOT_HELD;
        }

    } while (status == STATUS_BUFFER_TOO_SMALL);

    return status;
}

NTSTATUS GetCreateTokenPrivilege()
{
    BOOLEAN b;
    NTSTATUS status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &b);

    ULONG cb = 0x10000;

    do 
    {
        status = STATUS_INSUFF_SERVER_RESOURCES;

        if (PVOID buf = LocalAlloc(0, cb))
        {
            if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
            {
                status = STATUS_UNSUCCESSFUL;

                ULONG NextEntryOffset = 0;

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

                pv = buf;

                do 
                {
                    pb += NextEntryOffset;

                    HANDLE hProcess, hToken;

                    if (pspi->UniqueProcessId && pspi->NumberOfThreads)
                    {               
                        NTSTATUS s = NtOpenProcess(&hProcess, 
                            g_xp ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION, 
                            &zoa, &pspi->TH->ClientId);

                        if (0 <= s)
                        {
                            s = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE|TOKEN_QUERY, &hToken);

                            NtClose(hProcess);

                            if (0 <= s)
                            {
                                s = ImpersonateIfConformToken(hToken);

                                NtClose(hToken);

                                if (0 <= s)
                                {
                                    status = STATUS_SUCCESS;

                                    break;
                                }
                            }
                        }
                    }

                } while (NextEntryOffset = pspi->NextEntryOffset);
            }
            LocalFree(buf);
        }

    } while (status == STATUS_INFO_LENGTH_MISMATCH);

    return status;
}
Run Code Online (Sandbox Code Playgroud)

获得SE_CREATE_TOKEN_PRIVILEGE特权后,我们可以通过这种方式获得一些已知的文件夹路径:

HRESULT GetGetKnownFolderPathBySid(REFKNOWNFOLDERID rfid, PSID Sid, PWSTR *ppszPath)
{
    PROFILEINFO pi = { sizeof(pi), PI_NOUI };
    pi.lpUserName = L"*";

    HANDLE hToken;

    NTSTATUS status = CreateUserToken(&hToken, Sid);

    if (0 <= status)
    {
        if (LoadUserProfile(hToken, &pi))
        {
            status = SHGetKnownFolderPath(rfid, 0, hToken, ppszPath);

            UnloadUserProfile(hToken, pi.hProfile);
        }
        else
        {
            status = HRESULT_FROM_WIN32(GetLastError());
        }

        CloseHandle(hToken);
    }
    else
    {
        status = HRESULT_FROM_NT(status);
    }

    return status;
}
Run Code Online (Sandbox Code Playgroud)

例如获取%AppData%

void PrintAppDataBySid(PSID Sid)
{
    PWSTR path, szSid;

    if (S_OK == GetGetKnownFolderPathBySid(FOLDERID_RoamingAppData, Sid, &path))
    {
        if (ConvertSidToStringSidW(Sid, &szSid))
        {
            DbgPrint("%S %S\n", szSid, path);
            LocalFree(szSid);
        }
        CoTaskMemFree(path);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我们可以枚举本地用户配置文件,并为每个找到的 sid 获取它的 appdata 路径:

void EnumProf()
{
    STATIC_OBJECT_ATTRIBUTES(soa, "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList");

    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };

    if (0 <= ZwOpenKey(&oa.RootDirectory, KEY_READ, &soa))
    {
        PVOID stack = alloca(sizeof(WCHAR));

        union
        {
            PVOID buf;
            PKEY_BASIC_INFORMATION pkbi;
            PKEY_VALUE_PARTIAL_INFORMATION pkvpi;
        };

        DWORD cb = 0, rcb = 16;
        NTSTATUS status;
        ULONG Index = 0;

        do 
        {
            do 
            {
                if (cb < rcb)
                {
                    cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
                }

                if (0 <= (status = ZwEnumerateKey(oa.RootDirectory, Index, KeyBasicInformation, buf, cb, &rcb)))
                {
                    *(PWSTR)RtlOffsetToPointer(pkbi->Name, pkbi->NameLength) = 0;

                    PSID _Sid, Sid = 0;

                    BOOL fOk = ConvertStringSidToSidW(pkbi->Name, &_Sid);

                    if (fOk)
                    {
                        Sid = _Sid;
                    }

                    ObjectName.Buffer = pkbi->Name;
                    ObjectName.Length = (USHORT)pkbi->NameLength;
                    HANDLE hKey;

                    if (0 <= ZwOpenKey(&hKey, KEY_READ, &oa))
                    {
                        rcb = 64;

                        NTSTATUS s;
                        do 
                        {
                            if (cb < rcb)
                            {
                                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
                            }

                            STATIC_UNICODE_STRING(usSid, "Sid");

                            if (0 <= (s = ZwQueryValueKey(hKey, &usSid, KeyValuePartialInformation, buf, cb, &rcb)))
                            {
                                if (pkvpi->DataLength >= sizeof(_SID) &&
                                    IsValidSid(pkvpi->Data) && 
                                    GetLengthSid(pkvpi->Data) == pkvpi->DataLength)
                                {
                                    Sid = pkvpi->Data;
                                }
                            }

                        } while (s == STATUS_BUFFER_OVERFLOW);

                        NtClose(hKey);
                    }

                    if (Sid)
                    {
                        PrintAppDataBySid(Sid);
                    }

                    if (fOk)
                    {
                        LocalFree(_Sid);
                    }
                }

            } while (status == STATUS_BUFFER_OVERFLOW);

            Index++;

        } while (0 <= status);

        NtClose(oa.RootDirectory);
    }
}
Run Code Online (Sandbox Code Playgroud)

例如我得到了下一个结果:

S-1-5-18 C:\Windows\system32\config\systemprofile\AppData\Roaming
S-1-5-19 C:\Windows\ServiceProfiles\LocalService\AppData\Roaming
S-1-5-20 C:\Windows\ServiceProfiles\NetworkService\AppData\Roaming
S-1-5-21-*-1000 C:\Users\defaultuser0\AppData\Roaming
S-1-5-21-*-1001 C:\Users\<user>\AppData\Roaming
Run Code Online (Sandbox Code Playgroud)


jwe*_*ich 1

如果您有 SID,我相信您可以AppData从 中检索该值 HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders

但不确定每个 Windows 版本是否都相同。

  • 用户未使用“HKU\&lt;SID&gt;”下加载的配置文件登录。您可以从配置文件列表中获取“ProfileImagePath”,启用 SeRestorePrivilege,然后调用 [`RegLoadKey`](https://msdn.microsoft.com/en-us/library/ms724889) 加载用户的 NTUSER.DAT 配置单元。 (2认同)