With admin privileges, I need to enumerate all users on a Windows 7+ system (even ones that are logged off). Then I need to load the registry hive for each user and set a key.
NetUserEnum gives me the SID (I guess LsaEnumerateLogonSessions would as well). WTSEnumerateSessions followed by WTSQueryUserToken (to get a token) would be nice but it does not work for users who are not actively logged on.
So, my question, after calling NetUserEnum, how do I use the SID to load the registry for that user? Any recommended way of doing this?
Information about local user profiles is stored in this Registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
It is possible to enumerate it subkeys, where each subkey has a ProfileImagePath
that points to the folder where ntuser.dat
is located.
But, directly loading a user profile by RegLoadKey()
is very bad. First, the profile may already be loaded. Second, it is possible that after you load the profile yourself, the system may also try loading the profile. Note the RefCount
value. The system uses that value to load the profile if it is not already loaded, incrementing RefCount
. And UnloadUserProfile()
decrements RefCount
and unloads the profile only when it become 0 by calling RegUnLoadKey()
. So all profile load/unload operations must be synchronized.
There is only one correct way to load a profile - call LoadUserProfile()
. (internally it performs a RPC call to profsvc.LoadUserProfileServer
in svchost.exe -k netsvcs
, where all synchronization is done).
So how do you get the user token for LoadUserProfile()
? I guess call LogonUser()
, which you said you do not want to do (and cannot unless you have the user's password).
But, there does exist another way that works (I tested this), but it is undocumented. LoadUserProfile
used only the user Sid
from token (query for TOKEN_USER
information with TokenUser
iformation class) and then work with
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<Sid>
key
It is possible to create a token by calling ZwCreateToken()
with any given SID, but for this call you need SE_CREATE_TOKEN_PRIVILEGE
. This priviledge exists only in the lsass.exe
process. So a possible solution is:
lsass.exe
and get its token, or impersonate its thread.SE_CREATE_TOKEN_PRIVILEGE
in the token, after impersonationHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList
, and for each subkey query its Sid
value, or (if Sid
does not exist) convert the subkey name to a SID using ConvertStringSidToSid()
LoadUserProfile()
-------------- EDIT code example by request ----------------------------
code used ntdll export (which somebody here very not like) but as is
SE_CREATE_TOKEN_PRIVILEGE
to create token by yourself in
futureenum processes in the system, open token for every process, look are SE_CREATE_TOKEN_PRIVILEGE
exist in token, if yes - duplicate this token and if need enable SE_CREATE_TOKEN_PRIVILEGE
in it. finally impersonate with duplicated token
BOOL g_IsXP;// true if we on winXP, false otherwise
static volatile UCHAR guz;
static OBJECT_ATTRIBUTES zoa = { sizeof(zoa) };
NTSTATUS ImpersonateIfConformToken(HANDLE hToken)
{
ULONG cb = 0, rcb = 0x200;
PVOID stack = alloca(guz);
union {
PVOID buf;
PTOKEN_PRIVILEGES ptp;
};
NTSTATUS status;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
{
if (ULONG PrivilegeCount = ptp->PrivilegeCount)
{
PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
do
{
if (Privileges->Luid.LowPart == SE_CREATE_TOKEN_PRIVILEGE && !Privileges->Luid.HighPart)
{
static SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
};
static OBJECT_ATTRIBUTES soa = { sizeof(soa), 0, 0, 0, 0, &sqos };
if (0 <= (status = ZwDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, &soa, FALSE, TokenImpersonation, &hToken)))
{
if (Privileges->Attributes & SE_PRIVILEGE_ENABLED)
{
status = STATUS_SUCCESS;
}
else
{
static TOKEN_PRIVILEGES tp = {
1, { { { SE_CREATE_TOKEN_PRIVILEGE }, SE_PRIVILEGE_ENABLED } }
};
status = ZwAdjustPrivilegesToken(hToken, FALSE, &tp, 0, 0, 0);
}
if (status == STATUS_SUCCESS)
{
status = ZwSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hToken, sizeof(HANDLE));
}
ZwClose(hToken);
}
return status;
}
} while (Privileges++, --PrivilegeCount);
}
return STATUS_PRIVILEGE_NOT_HELD;
}
} while (status == STATUS_BUFFER_TOO_SMALL);
return status;
}
NTSTATUS GetCreateTokenPrivilege()
{
BOOLEAN b;
RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &b);
ULONG cb = 0, rcb = 0x10000;
PVOID stack = alloca(guz);
union {
PVOID buf;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
};
NTSTATUS status;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQuerySystemInformation(SystemProcessInformation, buf, cb, &rcb)))
{
status = STATUS_UNSUCCESSFUL;
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
if (pspi->InheritedFromUniqueProcessId && pspi->UniqueProcessId)
{
CLIENT_ID cid = { pspi->UniqueProcessId };
NTSTATUS s = STATUS_UNSUCCESSFUL;
HANDLE hProcess, hToken;
if (0 <= ZwOpenProcess(&hProcess, g_IsXP ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &cid))
{
if (0 <= ZwOpenProcessToken(hProcess, TOKEN_DUPLICATE|TOKEN_QUERY, &hToken))
{
s = ImpersonateIfConformToken(hToken);
NtClose(hToken);
}
NtClose(hProcess);
}
if (s == STATUS_SUCCESS)
{
return STATUS_SUCCESS;
}
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return status;
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
return STATUS_UNSUCCESSFUL;
}
if we have SE_CREATE_TOKEN_PRIVILEGE
- we can create token !
NTSTATUS CreateUserToken(PHANDLE phToken, PSID Sid)
{
HANDLE hToken;
TOKEN_STATISTICS ts;
NTSTATUS status = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken);
if (0 <= status)
{
if (0 <= (status = ZwQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &ts.DynamicCharged)))
{
ULONG cb = 0, rcb = 0x200;
PVOID stack = alloca(guz);
union {
PVOID buf;
PTOKEN_PRIVILEGES ptp;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
{
TOKEN_USER User = { { Sid } };
static TOKEN_SOURCE Source = { {' ','U','s','e','r','3','2', ' '} };
static TOKEN_DEFAULT_DACL tdd;// 0 default DACL
static TOKEN_GROUPS Groups;// no groups
static SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING
};
static OBJECT_ATTRIBUTES oa = {
sizeof oa, 0, 0, 0, 0, &sqos
};
status = ZwCreateToken(phToken, TOKEN_ALL_ACCESS, &oa, TokenPrimary,
&ts.AuthenticationId, &ts.ExpirationTime, &User, &Groups, ptp, (PTOKEN_OWNER)&Sid,
(PTOKEN_PRIMARY_GROUP)&Sid, &tdd, &Source);
break;
}
} while (status == STATUS_BUFFER_TOO_SMALL);
}
ZwClose(hToken);
}
return status;
}
and finally enumerate and load/unload user profiles
void EnumProf()
{
PROFILEINFO pi = { sizeof(pi), PI_NOUI };
pi.lpUserName = L"*";
STATIC_OBJECT_ATTRIBUTES(soa, "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList");
HANDLE hKey;
if (0 <= ZwOpenKey(&hKey, KEY_READ, &soa))
{
PVOID stack = alloca(sizeof(WCHAR));
union
{
PVOID buf;
PKEY_BASIC_INFORMATION pkbi;
PKEY_VALUE_PARTIAL_INFORMATION pkvpi;
} u = {};
DWORD cb = 0, rcb = 64;
NTSTATUS status;
ULONG Index = 0;
do
{
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(u.buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwEnumerateKey(hKey, Index, KeyBasicInformation, u.buf, cb, &rcb)))
{
*(PWSTR)RtlOffsetToPointer(u.pkbi->Name, u.pkbi->NameLength) = 0;
PSID Sid;
if (ConvertStringSidToSidW(u.pkbi->Name, &Sid))
{
HANDLE hToken;
if (0 <= CreateUserToken(&hToken, Sid))
{
if (LoadUserProfile(hToken, &pi))
{
UnloadUserProfile(hToken, pi.hProfile);
}
NtClose(hToken);
}
LocalFree(Sid);
}
}
} while (status == STATUS_BUFFER_OVERFLOW);
Index++;
} while (0 <= status);
ZwClose(hKey);
}
}