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

时间:2017-11-21 11:57:26

标签: c++ c windows winapi

我有一个服务正在运行,并希望访问常见的用户文件夹,如startup。为此,我想为系统中的每个用户(包括已注销)扩展环境变量,如<VirtualHost *:80> serverName 37.35.66.98/just-do-it/ DocumentRoot /var/www/html/just-do-it/web <Directory /var/www/html/just-do-it/web> AllowOverride All Order Allow,Deny Allow from All </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> 。我可以获取登录用户的会话ID并从中创建令牌,然后调用%APPDATA%。但是注销用户呢。对他们来说不会是一个会话。我唯一可以得到的就是帐户名(使用ExpandEnvironmentStringsForUser()NetUserEnum())和SID&#39;来自注册表(NetQueryDisplayInformation()) 我可以从SID获取用户令牌或使用SID模拟用户,还是可以使用SID扩展环境变量。

编辑: 我需要从所有用户的启动位置删除一些文件。为此,我需要在每个用户的上下文中展开HKLM\software\Microst\Windows NT\current Version\Profile List%APPDATA%,无论是否已登录。

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

2 个答案:

答案 0 :(得分:3)

从任何给定的 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 
    );

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

此令牌将 SID 作为令牌用户sid,3权限(SE_BACKUP_PRIVILEGESE_RESTORE_PRIVILEGE - 这需要调用LoadUserProfile api和{{ 1}} for for Traverse Privilege)和一组 - Everyone(s-1-1-0)

但是对于来电SE_CHANGE_NOTIFY_PRIVILEGE,我们必须拥有NtCreateToken权限,否则我们会收到错误SE_CREATE_TOKEN_PRIVILEGE。大多数系统过程都没有。只有少数(如 lsass.exe )。说 services.exe 和所有服务 - 没有此权限。所以一开始我们必须得到它。这可以通过枚举进程来完成,看看 - 它有这个权限,从这个进程得到令牌,并冒充它:

STATUS_PRIVILEGE_NOT_HELD

获得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; } 权限后,我们可以通过这种方式获得一些已知的文件夹路径:

SE_CREATE_TOKEN_PRIVILEGE

例如get %AppData%

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

最后我们可以枚举本地用户配置文件,并为每个找到的sid获取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);
    }
}

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

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

答案 1 :(得分:1)

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

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