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

Posted

技术标签:

【中文标题】从 SID 创建用户令牌,在用户上下文中展开环境变量【英文标题】:Create a user token from SID, expand environment variables in user context 【发布时间】:2017-11-21 11:57:26 【问题描述】:

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

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

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

【问题讨论】:

如果你能得到用户名,你不能访问来自c:\Users\%username%\Appdata\Roaming\Microsoft\StartMenu\Programs\Startup的目录吗? 如果用户已更改%APPDATA% 你的最终目标是什么? - 可能有更好的方法,例如使用 \所有用户 启动或通过登录脚本运行任务来执行操作。 可能相关:1) ***.com/q/45458728/298054; 2)***.com/q/37630726/298054; 【参考方案1】:

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

获得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;

例如获取 %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 路径:

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

例如我得到下一个结果:

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

【讨论】:

这段代码是否应该从驱动程序模式运行?我很难编译它。 @user3819404 - 当然不是。这是用户模式代码。您在编译时遇到问题 - 毫无疑问,因为您现在包含 ntifs.h (就像我这样做)。所以您可能需要自己直接声明一些 Nt api 声明,或者将其替换为 win32 模拟 - 比如说 NtQueryInformationTokenGetTokenInformation 等等。但是未记录且仅在 Windows 头文件中未声明 NtCreateToken 注意TOKEN_DEFAULT_DACL tdd; 没有初始化,我收到了 ACCESS_DENIED(在调试模式下编译,它被设置为像 0xCC 这样的保护值),直到我做了TOKEN_DEFAULT_DACL tdd = ; @Sirotnikov - 在我的代码中它声明为 static TOKEN_DEFAULT_DACL tdd; - 所以它在我的代码中初始化为 0。你肯定错过了 static 关键字 你是对的。我只是想提到它,以防有人犯了我的错误,并花一个小时调试:)【参考方案2】:

如果您有 SID,我相信您可以从以下位置检索 AppDataHKEY_USERS\&lt;SID&gt;\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders.

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

【讨论】:

用户未使用“HKU\”下加载的配置文件登录。您可以从配置文件列表中获取ProfileImagePath,启用SeRestorePrivilege,然后调用RegLoadKey 来加载用户的NTUSER.DAT 配置单元。

以上是关于从 SID 创建用户令牌,在用户上下文中展开环境变量的主要内容,如果未能解决你的问题,请参考以下文章

子账户 SID 和令牌检索

如何将受限用户令牌转换为非受限用户令牌?

快速注销后令牌不会变空

查看用户和登录的属性

windows—令牌窃取

windows—令牌窃取