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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With