How to get all (non-disabled) user SIDs via Windows API? - c++

I'm looking for a way to retrieve all user SIDs on a system via the Windows API.
Retrieving all user SIDs can be done with via wmic useraccount get sid. Is there a way of getting this information via the Windows API instead?
Additionally, the wmic command returns the SIDs of all accounts, including disabled accounts - wmic useraccount get disabled,sid will show which accounts are disabled. It would be a bonus if a solution could advise on how to retrieve the SIDs of accounts that are not disabled, but this is not crucial.

You could use the function:
NET_API_STATUS NET_API_FUNCTION NetUserEnum(
LPCWSTR servername,
DWORD level,
DWORD filter,
LPBYTE *bufptr,
DWORD prefmaxlen,
LPDWORD entriesread,
LPDWORD totalentries,
PDWORD resume_handle
);
with servername = NULL to enumerate local computer accounts, then use:
BOOL LookupAccountNameW(
LPCWSTR lpSystemName,
LPCWSTR lpAccountName,
PSID Sid,
LPDWORD cbSid,
LPWSTR ReferencedDomainName,
LPDWORD cchReferencedDomainName,
PSID_NAME_USE peUse
);
to retrieve SID's.
Refer to https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuserenum and https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew for details and examples.
In function NetUserEnum, setting the parameter level=1 will return detailed information about user accounts, and the bufptr parameter will point to an array of USER_INFO_1 structures.
Examining the member usri1_flags of structure USER_INFO_1 with mask UF_ACCOUNTDISABLE give the status of account.
Following RbMm comment, note that specifying in function NetUserEnum the parameter level=3, the bufptr parameter will point to an array of USER_INFO_3 structures, that contains user RID's.
The member usri3_user_id contains the relative ID (RID) of the user, and the member usri3_primary_group_id contains the RID of the Primary Global Group for the user. Using these values you don't need to call LookupAccountNameW.
The efficiency is boosted using suggestions from RbMm in the comments below.

There are several ways.
A simple one is with NetQueryDisplayInformation
Test sample (Windows 10, VS 2015) =>
NET_API_STATUS NetStatus;
DWORD dwIndex = 0;
DWORD dwEntriesRequested = 0xFFFFFFFF;
DWORD dwPreferredMaximumLength = 0xFFFFFFFF;
DWORD dwReturnedEntryCount;
PVOID pNDU = NULL;
do {
NetStatus = NetQueryDisplayInformation(NULL, 1, dwIndex, dwEntriesRequested, dwPreferredMaximumLength, &dwReturnedEntryCount, &pNDU);
if (NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA)
break;
for (int i = 0; i < dwReturnedEntryCount; i++)
{
PNET_DISPLAY_USER NetDisplayUser = (PNET_DISPLAY_USER)(((LPBYTE)pNDU) + sizeof(NET_DISPLAY_USER) * i);
PSID pSID = ConvertNameToSID(NetDisplayUser->usri1_name);
LPWSTR pszSid = NULL;
ConvertSidToStringSid(pSID, &pszSid);
BOOL bIsAccountDisabled = ((NetDisplayUser->usri1_flags & UF_ACCOUNTDISABLE) != 0) ? TRUE : FALSE;
WCHAR wsBuffer[MAX_PATH];
wsprintf(wsBuffer, L"%4.4ld %-20.20ws SID : %ws - Disabled : %ws - Comment : %ws\n",
NetDisplayUser->usri1_next_index,
NetDisplayUser->usri1_name,
pszSid,
(bIsAccountDisabled ? L"True" : L"False"),
NetDisplayUser->usri1_comment
);
LocalFree(pSID);
OutputDebugString(wsBuffer);
dwIndex = NetDisplayUser->usri1_next_index;
}
NetApiBufferFree(pNDU);
} while (NetStatus == ERROR_MORE_DATA);
PSID ConvertNameToSID(LPTSTR lpszName)
{
WCHAR wszDomainName[256];
DWORD dwSizeDomain = sizeof(wszDomainName) / sizeof(TCHAR);
DWORD dwSizeSid = 0;
SID_NAME_USE sidName;
LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, wszDomainName, &dwSizeDomain, &sidName);
PSID pSid;
pSid = (PSID)LocalAlloc(LPTR, dwSizeSid);
LookupAccountName(NULL, lpszName, pSid, &dwSizeSid, wszDomainName, &dwSizeDomain, &sidName);
return pSid;
}

for enumerate user accounts in SAM (Security Account Manager) database we can use or NetQueryDisplayInformation (more fast) or NetUserEnum (if we need more detail user information). or SAM api (fastest, include ntsam.h and link with samlib.lib )
note that if we have user (RID) we not need use LookupAccountName - this is very not efficient in this case (many heavy remote calls internal - LsaOpenPolicy, LsaLookupNames2, LsaClose . internal LsaLookupNames2 use anyway SAM api SamLookupNamesInDomain).
really all what we need - first get domain SID and than append user RID to it. get domain SID we can by LsaQueryInformationPolicy with PolicyAccountDomainInformation for SID of the account domain (computer) - always exist and with PolicyDnsDomainInformation or PolicyPrimaryDomainInformation for get SID of the primary domain (exist only if computer part of Domain)
void PrintUsersInDomain(PUNICODE_STRING ServerName, PSID DomainSid)
{
PWSTR szServerName = 0;
if (ServerName)
{
if (ULONG Length = ServerName->Length)
{
szServerName = ServerName->Buffer;
// if not null terminated
if (Length + sizeof(WCHAR) < ServerName->MaximumLength || *(PWSTR)((PBYTE)szServerName + Length))
{
szServerName = (PWSTR)alloca(Length + sizeof(WCHAR));
memcpy(szServerName, ServerName->Buffer, Length);
*(PWSTR)((PBYTE)szServerName + Length) = 0;
}
}
}
UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(DomainSid);
ULONG DestinationSidLength = GetSidLengthRequired(SubAuthorityCount + 1);
PSID UserSid = alloca(DestinationSidLength);
CopySid(DestinationSidLength, UserSid, DomainSid);
++*GetSidSubAuthorityCount(UserSid);
PULONG pRid = GetSidSubAuthority(UserSid, SubAuthorityCount);
PVOID Buffer;
ULONG Index = 0, ReturnedEntryCount;
NET_API_STATUS status;
do
{
switch (status = NetQueryDisplayInformation(szServerName, 1, Index,
64, MAX_PREFERRED_LENGTH, &ReturnedEntryCount, &Buffer))
{
case NOERROR:
case ERROR_MORE_DATA:
if (ReturnedEntryCount)
{
PNET_DISPLAY_USER pndu = (PNET_DISPLAY_USER)Buffer;
do
{
//if (!(pndu->usri1_flags & UF_ACCOUNTDISABLE))
{
*pRid = pndu->usri1_user_id;
PWSTR szSid;
if (ConvertSidToStringSidW(UserSid, &szSid))
{
DbgPrint("\t[%08x] %S %S\n", pndu->usri1_flags, pndu->usri1_name, szSid);
LocalFree(szSid);
}
}
Index = pndu->usri1_next_index;
} while (pndu++, --ReturnedEntryCount);
}
NetApiBufferFree(Buffer);
}
} while (status == ERROR_MORE_DATA);
}
void PrintUsersInDomain_fast(PUNICODE_STRING ServerName, PSID DomainSid)
{
SAM_HANDLE ServerHandle, DomainHandle = 0;
//SAM_SERVER_ENUMERATE_DOMAINS|SAM_SERVER_LOOKUP_DOMAIN
NTSTATUS status = SamConnect(ServerName, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, 0);
DbgPrint("SamConnect(%wZ) = %x\n", ServerName, status);
if (0 <= status)
{
status = SamOpenDomain(ServerHandle, DOMAIN_READ|DOMAIN_EXECUTE, DomainSid, &DomainHandle);
SamCloseHandle(ServerHandle);
}
if (0 <= status)
{
UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(DomainSid);
ULONG DestinationSidLength = GetSidLengthRequired(SubAuthorityCount + 1);
PSID UserSid = alloca(DestinationSidLength);
CopySid(DestinationSidLength, UserSid, DomainSid);
++*GetSidSubAuthorityCount(UserSid);
PULONG pRid = GetSidSubAuthority(UserSid, SubAuthorityCount);
PVOID Buffer;
ULONG Index = 0, TotalAvailable, TotalReturned, ReturnedEntryCount;
do
{
if (0 <= (status = SamQueryDisplayInformation(DomainHandle,
DomainDisplayUser,
Index,
2,
0x10000,
&TotalAvailable,
&TotalReturned,
&ReturnedEntryCount,
&Buffer)))
{
if (ReturnedEntryCount)
{
PSAM_DISPLAY_USER psdu = (PSAM_DISPLAY_USER)Buffer;
do
{
//if (!(psdu->AccountControl & USER_ACCOUNT_DISABLED))
{
*pRid = psdu->Rid;
PWSTR szSid;
if (ConvertSidToStringSidW(UserSid, &szSid))
{
DbgPrint("\t[%08x] %wZ %S\n", psdu->AccountControl, &psdu->AccountName, szSid);
LocalFree(szSid);
}
}
Index = psdu->Index;
} while (psdu++, --ReturnedEntryCount);
}
SamFreeMemory(Buffer);
}
} while (status == STATUS_MORE_ENTRIES);
SamCloseHandle(DomainHandle);
}
}
void PrintUsers()
{
LSA_HANDLE PolicyHandle;
LSA_OBJECT_ATTRIBUTES ObjectAttributes = { sizeof(ObjectAttributes) };
NTSTATUS status;
if (0 <= (status = LsaOpenPolicy(0, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle)))
{
union {
PVOID buf;
PPOLICY_DNS_DOMAIN_INFO pddi;
PPOLICY_ACCOUNT_DOMAIN_INFO padi;
};
if (0 <= LsaQueryInformationPolicy(PolicyHandle, PolicyAccountDomainInformation, &buf))
{
DbgPrint("DomainName=<%wZ>\n", &padi->DomainName);
if (padi->DomainSid)
{
PrintUsersInDomain_fast(&padi->DomainName, padi->DomainSid);
PrintUsersInDomain(&padi->DomainName, padi->DomainSid);
}
LsaFreeMemory(buf);
}
if (0 <= LsaQueryInformationPolicy(PolicyHandle, PolicyDnsDomainInformation, &buf))
{
DbgPrint("DomainName=<%wZ>\n", &pddi->Name);
if (pddi->Sid)
{
PrintUsersInDomain_fast(&pddi->Name, pddi->Sid);
PrintUsersInDomain(&pddi->Name, pddi->Sid);
}
LsaFreeMemory(buf);
}
LsaClose(PolicyHandle);
}
}
typedef struct SAM_DISPLAY_USER {
ULONG Index;
ULONG Rid;
ULONG AccountControl; /* User account control bits */
UNICODE_STRING AccountName;
UNICODE_STRING AdminComment;
UNICODE_STRING FullName;
} *PSAM_DISPLAY_USER;

Related

LsaLogonUser in a WCP context to sign in into windows is not working

When I boot my computer, I am attempting to have sign in into Windows without having to enter credentials. These credentials are available and entered from other means (which excludes the solution of using the winlogon default registry values), in a "single sign-on" kind of deal. Anyways, I am therefore attempting to go about it by creating a Windows Credential Provider (WCP). I got this to run. In this WCP I am applying the LsaLogonUser function with MSV1_0_INTERACTIVE_LOGON. Everything returns the result of success but it does not make the computer to actually log on. I might've misunderstood how it all works so I was wondering if anyone know why it doesn't work. My thoughts is that the issue potentially is that the session only lasts as long as the scope does. That is, as long as the token exists. So when the LogonUI (by Winlogon) process finishes it's execution, it is automatically signed out or something. What am I missing?
Here is a big chunk of code of how it executes:
void login(std::wstring domain, std::wstring username, std::wstring secret)
{
//Get a handle to LSA
HANDLE hLSA = nullptr;
NTSTATUS status = LsaConnectUntrusted(&hLSA);
if (status != 0)
{
int winError = LsaNtStatusToWinError(status);
LLLOG(L"Error calling LsaConnectUntrusted. Error code: " + std::to_wstring(winError) );
return;
}
if (!hLSA)
{
LLLOG(L"hLSA is NULL");
return;
}
//Build LsaLogonUser parameters
LSA_STRING originName = {};
char originNameStr[] = "WCP";
originName.Buffer = originNameStr;
originName.Length = (USHORT)strlen(originNameStr);
originName.MaximumLength = originName.Length;
ULONG authPackage = 0;
PLSA_STRING authPackageName = new LSA_STRING();
char authPackageBuf[] = MSV1_0_PACKAGE_NAME;
authPackageName->Buffer = authPackageBuf;
authPackageName->Length = (USHORT)strlen(authPackageBuf);
authPackageName->MaximumLength = (USHORT)strlen(authPackageBuf);
status = LsaLookupAuthenticationPackage(hLSA, authPackageName, &authPackage);
if (status != 0)
{
int winError = LsaNtStatusToWinError(status);
LLLOG(L"Call to LsaLookupAuthenticationPackage failed. Error code: " + std::to_wstring(winError));
return;
}
DWORD authBufferSize = 0;
PVOID authBuffer = CreateNtlmLogonStructure(domain, username, secret, &authBufferSize);
LLLOG(L"authBufferSize: " + std::to_wstring(authBufferSize));
//Get TokenSource
HANDLE hProcess = GetCurrentProcess();
HANDLE procToken = nullptr;
BOOL success = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &procToken);
if (!success)
{
DWORD errorCode = GetLastError();
LLLOG(L"Call to OpenProcessToken failed. Errorcode: " + std::to_wstring(errorCode));
return;
}
TOKEN_SOURCE tokenSource = {};
DWORD realSize = 0;
success = GetTokenInformation(procToken, TokenSource, &tokenSource, sizeof(tokenSource), &realSize);
if (!success)
{
LLLOG(L"Call to GetTokenInformation failed.");
return;
}
//
PVOID profileBuffer = NULL;
ULONG profileBufferSize = 0;
LUID loginId;
HANDLE token = NULL;
QUOTA_LIMITS quotaLimits;
NTSTATUS subStatus = 0;
status = LsaLogonUser(
hLSA,
&originName,
Interactive,
authPackage,
authBuffer,
authBufferSize,
0,
&tokenSource,
&profileBuffer,
&profileBufferSize,
&loginId,
&token,
&quotaLimits,
&subStatus);
if (status != 0)
{
NTSTATUS winError = LsaNtStatusToWinError(status);
LLLOG(L"Error calling LsaLogonUser. Error code: " + std::to_wstring(winError));
return;
}
LLLOG(L"Success!");
LsaFreeReturnBuffer(profileBuffer);
CloseHandle(token);
HeapFree(GetProcessHeap(), 0, authBuffer);
LLLOG(L"Cleanup complete.");
return;
}
//size will be set to the size of the structure created
PVOID CreateNtlmLogonStructure(std::wstring domain, std::wstring username, std::wstring password, DWORD* size)
{
size_t wcharSize = sizeof(wchar_t);
size_t totalSize = sizeof(MSV1_0_INTERACTIVE_LOGON) + ((domain.length() + username.length() + password.length()) * wcharSize);
MSV1_0_INTERACTIVE_LOGON* ntlmLogon = (PMSV1_0_INTERACTIVE_LOGON)(new BYTE[totalSize]);
size_t offset = sizeof(MSV1_0_INTERACTIVE_LOGON);
ntlmLogon->MessageType = MsV1_0InteractiveLogon;
offset += WriteUnicodeString(domain, &(ntlmLogon->LogonDomainName), ntlmLogon, offset);
offset += WriteUnicodeString(username, &(ntlmLogon->UserName), ntlmLogon, offset);
offset += WriteUnicodeString(password, &(ntlmLogon->Password), ntlmLogon, offset);
*size = (DWORD)totalSize;
return ntlmLogon;
}
size_t WriteUnicodeString(std::wstring str, UNICODE_STRING* uniStr, PVOID baseAddress, size_t offset)
{
const wchar_t* buffer = str.c_str();
size_t size = str.length() * sizeof(wchar_t);
uniStr->Length = (USHORT)size;
uniStr->MaximumLength = (USHORT)size;
uniStr->Buffer = (PWSTR)((UINT_PTR)baseAddress + offset);
memcpy((PVOID)((UINT_PTR)baseAddress + offset), str.c_str(), size);
return size;
}

Can I mapping network drive in very special way using winapi?

I have to start my application with admin privileges ( very important ).
When I execute this code without admin privileges everything is perfect. There is an icon in MyComputer.
NETRESOURCE nrServer;
memset(&nrServer, 0, sizeof (NETRESOURCE));
nrServer.dwType = RESOURCETYPE_ANY;
nrServer.lpLocalName = L"S:";
nrServer.lpRemoteName = L"\\\\192.168.32.36\\folderName";
nrServer.lpProvider = L"";
auto dwError = WNetAddConnection2(&nrServer, L"user", L"pass", 0);
But when I execute this code above in application with admin privileges, there is no icon in MyComputer.
I think that can be usefull:
Mapped network drives are not showing in My Computer
Is there any way to execute winapi function as not admin when my application has admin privileges?
from WNetAddConnection2W
the WNet functions create and delete network drive letters in the MS-DOS device namespace associated with a logon session because
MS-DOS devices are identified by AuthenticationID (a locally unique
identifier, or LUID, associated with a logon session.)
also
if a code that runs as LocalSystem calls the WNetAddConnection2 function, then the mapped drive is visible to all
user logon sessions.
technically this mean if code run as LocalSystem in the \GLOBAL??\ folder will be create symbolic link to network disk. otherwise link will be created under
\Sessions\0\DosDevices\<token LogonId>\
and will be visible only for threads(processes) which have the same LogonId in token
if your code have admin privileges - it usually (almost always) have debug privileges. with this we can open process with LocalSystem token and impersonate it before call WNetAddConnection2.
possible also get TCB privilege and after this call WTSQueryUserToken, convert primary token to impersonation token, via DuplicateToken, and impersonate - SetThreadToken. and call WNetAddConnection2 finally.
ok. i try first simply impersonate to LocalSystem
let we have function
NTSTATUS ImpersonateSystemOrTcbToken(bool bTcb);
which set LocalSystem or token with Tcb privileges to current thread (as far i know all LocalSystem tokens have TCB privilege but anyway write 2 different code for get exactly token with TCB or with LocalSystem)
and
HRESULT AdjustDebugPrivilegesToThread();
which enable debug privileges in current thread token (it must exist in admin token)
in this case code can be next:
inline HRESULT BOOL_TO_HRESULT(BOOL f)
{
return f ? NOERROR : HRESULT_FROM_WIN32(GetLastError());
}
HRESULT MapRemoteDrive(PCWSTR local, PCWSTR remote, PCWSTR username, PCWSTR password)
{
NETRESOURCEW nr = {
0, RESOURCETYPE_DISK, 0, 0, const_cast<PWSTR>(local), const_cast<PWSTR>(remote)
};
return HRESULT_FROM_WIN32(WNetAddConnection2W(&nr, password, username, 0));
}
HRESULT MapRemoteDriveEx1(PCWSTR local, PCWSTR remote, PCWSTR username, PCWSTR password)
{
HRESULT hr = BOOL_TO_HRESULT(ImpersonateSelf(::SecurityImpersonation));
if (SUCCEEDED(hr))
{
if (SUCCEEDED(hr = AdjustDebugPrivilegesToThread()) &&
SUCCEEDED(hr = HRESULT_FROM_NT(ImpersonateSystemOrTcbToken(false))))
{
hr = MapRemoteDrive(local, remote, username, password);
// WNetCancelConnection2W(local, 0, TRUE);
}
SetThreadToken(0, 0);
}
return hr;
}
code work ok and really network location created, but with next view:
despite this - drive is browsed correct on click. i not research why is Disconected word in description. but possible some problems with permissions here
if try create drive for concrete LUID, code will be more complex
HRESULT MapRemoteDriveEx2(PCWSTR local, PCWSTR remote, PCWSTR username, PCWSTR password)
{
HRESULT hr = BOOL_TO_HRESULT(ImpersonateSelf(::SecurityImpersonation));
if (SUCCEEDED(hr))
{
HANDLE hToken, hImpToken;
if (SUCCEEDED(hr = AdjustDebugPrivilegesToThread()) &&
SUCCEEDED(hr = HRESULT_FROM_NT(ImpersonateSystemOrTcbToken(true))) &&
SUCCEEDED(hr = BOOL_TO_HRESULT(WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken))))
{
hr = BOOL_TO_HRESULT(DuplicateToken(hToken, ::SecurityImpersonation, &hImpToken));
CloseHandle(hToken);
if (SUCCEEDED(hr))
{
hr = BOOL_TO_HRESULT(SetThreadToken(0, hImpToken));
CloseHandle(hImpToken);
if (SUCCEEDED(hr))
{
hr = MapRemoteDrive(local, remote, username, password);
// WNetCancelConnection2W(local, 0, TRUE);
}
}
}
SetThreadToken(0, 0);
}
return hr;
}
with this result full ok
now code for util functions:
HRESULT AdjustDebugPrivilegesToThread()
{
ULONG dwError;
HANDLE hToken;
if (OpenThreadToken(NtCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken))
{
static const ::TOKEN_PRIVILEGES tp = { 1, { { { SE_DEBUG_PRIVILEGE } } } };
AdjustTokenPrivileges(hToken, FALSE, const_cast<::PTOKEN_PRIVILEGES>(&tp), 0, 0, 0);
dwError = GetLastError();
CloseHandle(hToken);
}
else
{
dwError = GetLastError();
}
return HRESULT_FROM_WIN32(dwError);
}
and..
NTSTATUS GetSystemToken(PVOID buf)
{
NTSTATUS status;
union {
PVOID pv;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
};
pv = buf;
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
HANDLE hProcess, hToken, hNewToken;
CLIENT_ID ClientId = { pspi->UniqueProcessId };
if (ClientId.UniqueProcess)
{
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 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &ClientId))
{
status = NtOpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE, &hToken);
NtClose(hProcess);
if (0 <= status)
{
ULONG rcb;
TOKEN_STATISTICS ts;
static const LUID SystemLuid = SYSTEM_LUID;
status = -1;
if (0 <= NtQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &rcb) &&
ts.AuthenticationId.LowPart == SystemLuid.LowPart &&
ts.AuthenticationId.HighPart == SystemLuid.HighPart)
{
status = NtDuplicateToken(hToken, TOKEN_IMPERSONATE,
&soa, FALSE, TokenImpersonation, &hNewToken);
}
NtClose(hToken);
if (0 <= status)
{
status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hNewToken, sizeof(hNewToken));
NtClose(hNewToken);
return status;
}
}
}
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return STATUS_UNSUCCESSFUL;
}
NTSTATUS GetTcbToken(PVOID buf)
{
NTSTATUS status;
union {
PVOID pv;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
};
pv = buf;
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
HANDLE hProcess, hToken, hNewToken;
if (pspi->InheritedFromUniqueProcessId && pspi->UniqueProcessId)
{
static SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
};
static OBJECT_ATTRIBUTES soa = { sizeof(soa), 0, 0, 0, 0, &sqos };
CLIENT_ID ClientId = { pspi->UniqueProcessId };
if (0 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &ClientId))
{
status = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE, &hToken);
NtClose(hProcess);
if (0 <= status)
{
status = NtDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE,
&soa, FALSE, TokenImpersonation, &hNewToken);
NtClose(hToken);
if (0 <= status)
{
static const TOKEN_PRIVILEGES tp = { 1, { { { SE_DEBUG_PRIVILEGE } } } };
status = NtAdjustPrivilegesToken(hNewToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(&tp), 0, 0, 0);
if (STATUS_SUCCESS == status)
{
status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hNewToken, sizeof(hNewToken));
}
NtClose(hNewToken);
if (STATUS_SUCCESS == status)
{
return STATUS_SUCCESS;
}
}
}
}
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return STATUS_UNSUCCESSFUL;
}
NTSTATUS ImpersonateSystemOrTcbToken(bool bTcb)
{
NTSTATUS status;
ULONG cb = 0x10000;
do
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (PBYTE buf = new BYTE[cb += 0x1000])
{
if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
{
status = (bTcb ? GetTcbToken : GetSystemToken)(buf);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
status = STATUS_UNSUCCESSFUL;
}
}
delete [] buf;
}
} while(status == STATUS_INFO_LENGTH_MISMATCH);
return status;
}

cfapi: cloud operation is invalid

I'm making a Cloud Sync Engines Supports Placeholder based on CloudMirror. And got problem on CF_CALLBACK_TYPE_FETCH_DATA
When i double click file (placeholder) in window explorer, app trigger FILE_ATTRIBUTE_PINNED and Hydrating file. And then cfapi call FETCH_DATA and read asynchronous file (my app work look same with CloudMirror).
But i got HRESULT return from CfExecute is 0x8007017c the cloud operation is invalid. Debug look all value is true
Then how to resolve it, thank.
#define CHUNKSIZE 4096
#define FIELD_SIZE( type, field ) ( sizeof( ( (type*)0 )->field ) )
#define CF_SIZE_OF_OP_PARAM( field )( FIELD_OFFSET( CF_OPERATION_PARAMETERS, field ) + FIELD_SIZE( CF_OPERATION_PARAMETERS, field ) )
struct READ_COMPLETION_CONTEXT
{
OVERLAPPED Overlapped;
LARGE_INTEGER CallbackInfo_FileSize;
CF_CONNECTION_KEY CallbackInfo_ConnectionKey;
CF_TRANSFER_KEY CallbackInfo_TransferKey;
HANDLE PipeHandle{ 0 };
LARGE_INTEGER StartOffset;
LARGE_INTEGER RemainingLength;
ULONG BufferSize;
WCHAR* FullPath{ nullptr };
BYTE* Buffer{ nullptr };
~READ_COMPLETION_CONTEXT()
{
if (FullPath) delete FullPath;
if (Buffer) delete Buffer;
if (PipeHandle) CloseHandle(PipeHandle);
}
void Cancel()
{
TransferData(
CallbackInfo_ConnectionKey,
CallbackInfo_TransferKey,
NULL,
StartOffset,
RemainingLength,
STATUS_UNSUCCESSFUL);
}
};
void CALLBACK FETCH_DATA(_In_ CONST CF_CALLBACK_INFO* callbackInfo, _In_ CONST CF_CALLBACK_PARAMETERS* callbackParameters)
{
try
{
//...
if (DownloadItem(/*call to c++\cli for stream download and copy async to pipe server*/))
{
std::wstring pipename(L"\\\\.\\pipe\\");
pipename.append(ci->Id);
HANDLE hpipe = CreateFile(pipename.c_str(),
GENERIC_READ,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL); // no template file
if (hpipe != INVALID_HANDLE_VALUE)
{
if (GetLastError() != ERROR_PIPE_BUSY)
{
READ_COMPLETION_CONTEXT* readContext = new READ_COMPLETION_CONTEXT();
DWORD chunkBufferSize = (ULONG)min(callbackParameters->FetchData.RequiredLength.QuadPart, CHUNKSIZE);
std::wstring fullClientPath(callbackInfo->VolumeDosName);
fullClientPath.append(callbackInfo->NormalizedPath);
readContext->Overlapped.Offset = callbackParameters->FetchData.RequiredFileOffset.LowPart;
readContext->Overlapped.OffsetHigh = callbackParameters->FetchData.RequiredFileOffset.HighPart;
readContext->CallbackInfo_FileSize = callbackInfo->FileSize;
readContext->CallbackInfo_ConnectionKey = callbackInfo->ConnectionKey;
readContext->CallbackInfo_TransferKey = callbackInfo->TransferKey;
readContext->PipeHandle = hpipe;
readContext->StartOffset = callbackParameters->FetchData.RequiredFileOffset;
readContext->RemainingLength = callbackParameters->FetchData.RequiredLength;
readContext->BufferSize = chunkBufferSize;
readContext->FullPath = Utilities::WStringToWCHARP(fullClientPath);
readContext->Buffer = new BYTE[chunkBufferSize];
if (ReadFileEx(hpipe, readContext->Buffer, chunkBufferSize, &readContext->Overlapped, OverlappedCompletionRoutine))
if (GetLastError() == S_OK) return;
delete readContext;
}
else CloseHandle(hpipe);
}
}
}
catch (...)
{
}
TransferData(
callbackInfo->ConnectionKey,
callbackInfo->TransferKey,
NULL,
callbackParameters->FetchData.RequiredFileOffset,
callbackParameters->FetchData.RequiredLength,
STATUS_UNSUCCESSFUL);
}
void CALLBACK CANCEL_FETCH_DATA(_In_ CONST CF_CALLBACK_INFO* callbackInfo,_In_ CONST CF_CALLBACK_PARAMETERS* callbackParameters)
{
}
HRESULT TransferData(
_In_ CF_CONNECTION_KEY connectionKey,
_In_ LARGE_INTEGER transferKey,
_In_reads_bytes_opt_(length.QuadPart) LPCVOID transferData,
_In_ LARGE_INTEGER startingOffset,
_In_ LARGE_INTEGER length,
_In_ NTSTATUS completionStatus)
{
CF_OPERATION_INFO opInfo = { 0 };
CF_OPERATION_PARAMETERS opParams = { 0 };
opInfo.StructSize = sizeof(opInfo);
opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA;
opInfo.ConnectionKey = connectionKey;
opInfo.TransferKey = transferKey;
opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData);
opParams.TransferData.CompletionStatus = completionStatus;
opParams.TransferData.Buffer = transferData;
opParams.TransferData.Offset = startingOffset;
opParams.TransferData.Length = length;
HRESULT hresult = CfExecute(&opInfo, &opParams);
return hresult;
}
void WINAPI OverlappedCompletionRoutine(
_In_ DWORD errorCode,
_In_ DWORD numberOfBytesTransfered,
_Inout_ LPOVERLAPPED overlapped)
{
READ_COMPLETION_CONTEXT* readContext = (READ_COMPLETION_CONTEXT*)overlapped;
if (errorCode == 0 && !GetOverlappedResult(readContext->PipeHandle, overlapped, &numberOfBytesTransfered, TRUE)) errorCode = GetLastError();
if (errorCode != 0)
{
readContext->Cancel();
delete readContext;
return;
}
assert(numberOfBytesTransfered != 0);
LONGLONG total = readContext->CallbackInfo_FileSize.QuadPart;
LONGLONG completed = readContext->StartOffset.QuadPart + numberOfBytesTransfered;
Utilities::ApplyTransferStateToFile(readContext->FullPath,
readContext->CallbackInfo_ConnectionKey,
readContext->CallbackInfo_TransferKey,
total,
completed);
HRESULT hresult = TransferData(
readContext->CallbackInfo_ConnectionKey,
readContext->CallbackInfo_TransferKey,
errorCode == 0 ? readContext->Buffer : NULL,
readContext->StartOffset,
Utilities::LongLongToLargeInteger(numberOfBytesTransfered),
errorCode);
if (hresult != S_OK)
{
readContext->Cancel();
delete readContext;
winrt::check_hresult(hresult);
return;
}
readContext->StartOffset.QuadPart += numberOfBytesTransfered;
readContext->RemainingLength.QuadPart -= numberOfBytesTransfered;
if (readContext->RemainingLength.QuadPart > 0)
{
DWORD bytesToRead = (DWORD)(min(readContext->RemainingLength.QuadPart, readContext->BufferSize));
readContext->Overlapped.Offset = readContext->StartOffset.LowPart;
readContext->Overlapped.OffsetHigh = readContext->StartOffset.HighPart;
if (!ReadFileEx(readContext->PipeHandle, readContext->Buffer, bytesToRead, &readContext->Overlapped, OverlappedCompletionRoutine))
{
readContext->Cancel();
delete readContext;
}
}
else delete readContext;//done
}
Edit: After test, fake byteread = 4096 it running successful.
Then, how much the min limit of data transfer?
My question in another forum
The chuck size seems required a multiple of 4096. Define this size to
(4096*N) will solve the 0x8007017c error.
This is suspected to the issue of offset/length alignment during transfer data operation. This is what the API spec says about these parameters.
OpParams.TransferData.Offset and OpParams.TransferData.Length describe a range in the placeholder to which the sync provider is transferring the data. There is no requirement that the sync provider return all data as requested in one shot. It is also OK for a sync provider to return more data than requested. As an example, the sync provider can decide to over-read, for performance or other reasons. The sync provider can also perform multiple TRANSFER_DATA operations repeatedly as a response to the same FETCH_DATA callback. The only requirement is that both offset and length are 4KB aligned unless the range described ends on the logical file size (EoF), in which case, the length is not required to be 4KB aligned as long as the resulting range ends on or beyond the logical file size.

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

How to load registry hive for all users in a loop

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:
open lsass.exe and get its token, or impersonate its thread.
enable SE_CREATE_TOKEN_PRIVILEGE in the token, after impersonation
enumerate HKEY_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()
create a token with that SID
and finally call LoadUserProfile()
-------------- EDIT code example by request ----------------------------
code used ntdll export (which somebody here very not like) but as is
we need got SE_CREATE_TOKEN_PRIVILEGE to create token by yourself in
future
enum 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);
}
}