7

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.

user3819404
  • 611
  • 6
  • 18
  • If you can get the username, can't you access directories from `c:\Users\%username%\Appdata\Roaming\Microsoft\StartMenu\Programs\Startup` ? – qwn Nov 21 '17 at 12:03
  • What if user has changed `%APPDATA%` – user3819404 Nov 21 '17 at 12:07
  • What is your ultimate aim? - There is likely a better approach such as using *\all users* startup or running a task via a logon script to perform the actions. – Alex K. Nov 21 '17 at 12:25
  • Possibly related: 1) https://stackoverflow.com/q/45458728/298054; 2) https://stackoverflow.com/q/37630726/298054; – jweyrich Nov 21 '17 at 12:26

2 Answers2

8

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
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Is this code supposed to be run from driver mode?I am having hard time getting this compiled. – user3819404 Nov 22 '17 at 07:36
  • @user3819404 - of course no. this is user mode code. that you having problem with compilation - have no any doubt, because you now include *ntifs.h* (like I do this). so you need may be direct declare some Nt api declaration yourself, or replace it to win32 analog - say `NtQueryInformationToken` to `GetTokenInformation` and so on. however undocumented and not declared in windows headers files only `NtCreateToken` – RbMm Nov 22 '17 at 07:46
  • Note `TOKEN_DEFAULT_DACL tdd;` is not initialized, and I was receiving ACCESS_DENIED (compiling in debug mode, it was set to a guard value like 0xCC) until I did `TOKEN_DEFAULT_DACL tdd = {};` – Sirotnikov Dec 20 '18 at 18:53
  • 1
    @Sirotnikov - in my code it declared as `static TOKEN_DEFAULT_DACL tdd;` - so it initialized in my code to 0. you sure miss `static` keyword – RbMm Dec 20 '18 at 18:58
  • 1
    You're right. I just wanted it mentioned in case someone makes my mistake, and spends an hour debugging :) – Sirotnikov Dec 21 '18 at 20:28
  • For those who want a more natural Windows API equivalent for calling `NtCreateToken()` (pass in a string to parse, get back a token), there's my implementation here: https://github.com/cubiclesoft/createprocess-windows – CubicleSoft May 15 '21 at 03:16
  • @CubicleSoft - you too can look for [run as pro](https://github.com/rbmm/run-as-pro) – RbMm May 15 '21 at 06:39
1

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.

jweyrich
  • 31,198
  • 5
  • 66
  • 97
  • 2
    The user isn't logged on with a loaded profile under "HKU\". You could get the `ProfileImagePath` from the profile list, enable SeRestorePrivilege, and call [`RegLoadKey`](https://msdn.microsoft.com/en-us/library/ms724889) to load the user's NTUSER.DAT hive. – Eryk Sun Nov 21 '17 at 12:45