本文介绍了从 SID 创建用户令牌,在用户上下文中展开环境变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a service running, and want to access common user folders like startup.For this i want to expand environment variables like %APPDATA% for each user on the system(including logged off). I can get the session id's of logged on users and create a token out of it and then call ExpandEnvironmentStringsForUser(). But what about the logged off users.There will not be a session for them.The only thing i can get for them is account name (using NetUserEnum() or NetQueryDisplayInformation()) and SID's from registry (HKLM\software\Microst\Windows NT\current Version\Profile List)Can i get a user token from SID or impersonate a user using SID, or is there some way to expand environment variables using SID.

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

I need to delete some files from startup location of all users.For this i need to expand %APPDATA% and %USERPROFILE% in context of each user, whether logged in or not.

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

EDIT 2:The problem boils down to expanding environment variables like %APPDATA% for different users without having a token to that user.

推荐答案

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

create token from any given SID is possible, but not simply. exist undocumented system api for create token:

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 错误.例如,我们可以从当前进程令牌中获取此值.所有其他参数,通常可以是任何有效的感测数据.所以可以通过下一种方式创建令牌:

here AuthenticationId must be some valid logon session id, otherwise we got STATUS_NO_SUCH_LOGON_SESSION error. we can get this value from current process token for example. all another parameters, in general can be any valid by sense data. so can create token in next way:

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_PRIVILEGE, SE_RESTORE_PRIVILEGE - 这需要调用 LoadUserProfile api 和 SE_CHANGE_NOTIFY_PRIVILEGE 用于具有遍历权限)和一组 - Everyone (s-1-1-0).

this token will be have given SID as token user sid, 3 privilege (SE_BACKUP_PRIVILEGE, SE_RESTORE_PRIVILEGE - this need for call LoadUserProfile api and SE_CHANGE_NOTIFY_PRIVILEGE for have Traverse Privilege) and one group - Everyone (s-1-1-0).

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

but for call NtCreateToken we must have SE_CREATE_TOKEN_PRIVILEGE privilege otherwise we got error STATUS_PRIVILEGE_NOT_HELD. most system process have not it. only few (like lsass.exe). say services.exe and all services - have not this privilege. so at begin we must got it. this can be done by enumerate processes, look - which have this privilege, got token from this process, and impersonate with it:

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

after we got SE_CREATE_TOKEN_PRIVILEGE privilege we can get some known folder path in this way:

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

例如获取%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);
    }
}

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

finally we can enumerate local user profiles and for every found sid get it appdata 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);
    }
}

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

for example i got next result:

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

这篇关于从 SID 创建用户令牌,在用户上下文中展开环境变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-23 12:06