Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a user token from SID, expand environment variables in user context

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.

Edit: 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.

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

like image 668
user3819404 Avatar asked Nov 21 '17 11:11

user3819404


2 Answers

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

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

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

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

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

for example for get %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);
    }
}

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
like image 112
RbMm Avatar answered Sep 21 '22 13:09

RbMm


If you have the SID, I believe you can retrieve the AppData value from HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders.

Not sure if it's the same for every Windows version though.

like image 43
jweyrich Avatar answered Sep 19 '22 13:09

jweyrich