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

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.

Related

How to start a new process as user "NT AUTHORITY\Network Service"?

I am trying to launch a new process as NT AUTHORITY\Network Service from a process that is running as NT AUTHORITY\System.
I have looked at other questions, such as the following, which does not provide a working example: CreateProcess running as user: "NT AUTHORITY/Network Service" without knowing the credentials?
And, I have come across some posts which talk about copying a token from a process that is already running as NT AUTHORITY\Network Service: Windows API and Impersonation Part 1 - How to get SYSTEM using Primary Tokens.
I wonder, is there a way to launch a process without having to depend on another process to copy a token from? Is there a way to hand-craft a token that can help launch a process as NT AUTHORITY\Network Service using CreateProcessAsUserW(), for example?
I came across a function LogonUser which can be used to create token for required user. The doc shows an example for creating token for NT AUTHORITY\LocalService like this:
LogonUser(L"LocalService", L"NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hToken)
I used the above in combination with CreateProcessAsUser function which starts child process as NT AUTHORITY\NetworkService where the parent is running as NT AUTHORITY\System
#include <Windows.h>
HANDLE token;
LogonUser(
L"NetworkService",
L"NT AUTHORITY",
nullptr,
LOGON32_LOGON_SERVICE,
LOGON32_PROVIDER_DEFAULT,
&token);
// Setup required variables to start the process
LPPROCESS_INFORMATION lpProcessInformation;
STARTUPINFOEX si;
PWCHAR path;
PCWSTR environmentBlockPtr = nullptr;
DWORD creationFlags;
WCHAR* commandStr;
CreateProcessAsUser(
token,
nullptr,
const_cast<WCHAR*>(commandStr),
nullptr,
nullptr,
FALSE,
creationFlags,
const_cast<WCHAR*>(environmentBlockPtr),
path,
&si.StartupInfo,
lpProcessInformation);
I suggest you do it via a scheduled task and then delete the task after it runs (or maybe there is a one-shot setting you could use). While System has the create token privilege the NtCreateToken function is not part of the documented API and using it would be an enormous pain. If not a scheduled task then as a service (again even if you are only going to run it once).
Is there a way to hand-craft a token that can help launch a process
as NT AUTHORITY\Network Service
yes. by call NtCreateToken. but for this need have SE_CREATE_TOKEN_PRIVILEGE. but services.exe and services, even running as 'NT AUTHORITY\System' have not it. so you can not just call NtCreateToken. first you need find token with this privilege and only after this.
for get token with required privileges set we can use next code:
extern const SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof (sqos), SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
};
extern const OBJECT_ATTRIBUTES oa_sqos = { sizeof(oa_sqos), 0, 0, 0, 0, const_cast<SECURITY_QUALITY_OF_SERVICE*>(&sqos) };
NTSTATUS GetToken(_In_ PVOID buf, _In_ const TOKEN_PRIVILEGES* RequiredSet, _Out_ PHANDLE phToken)
{
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)
{
if (0 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION,
const_cast<POBJECT_ATTRIBUTES>(&oa_sqos), &ClientId))
{
status = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE, &hToken);
NtClose(hProcess);
if (0 <= status)
{
status = NtDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE|TOKEN_QUERY,
const_cast<POBJECT_ATTRIBUTES>(&oa_sqos), FALSE, TokenImpersonation, &hNewToken);
NtClose(hToken);
if (0 <= status)
{
status = NtAdjustPrivilegesToken(hNewToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(RequiredSet), 0, 0, 0);
if (STATUS_SUCCESS == status)
{
*phToken = hNewToken;
return STATUS_SUCCESS;
}
NtClose(hNewToken);
}
}
}
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return STATUS_UNSUCCESSFUL;
}
NTSTATUS GetToken(_In_ const TOKEN_PRIVILEGES* RequiredSet, _Out_ PHANDLE phToken)
/*++
Routine Description:
try found process token with RequiredSet; duplicate and adjust privilege
Arguments:
RequiredSet - set of privileges which must be in token
phToken - Impersonation Token with all privileges from RequiredSet, all it is enabled (even if some is disabled in original token)
--*/
{
NTSTATUS status;
ULONG cb = 0x40000;
do
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (PBYTE buf = new BYTE[cb += PAGE_SIZE])
{
if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
{
status = GetToken(buf, RequiredSet, phToken);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
status = STATUS_UNSUCCESSFUL;
}
}
delete [] buf;
}
} while(status == STATUS_INFO_LENGTH_MISMATCH);
return status;
}
with this we can do next:
#define BEGIN_PRIVILEGES(name, n) static const union { TOKEN_PRIVILEGES name;\
struct { ULONG PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[n];} label(_) = { n, {
#define LAA(se) {{se}, SE_PRIVILEGE_ENABLED }
#define LAA_D(se) {{se} }
#define END_PRIVILEGES }};};
BEGIN_PRIVILEGES(tp_dbg, 2)
LAA(SE_DEBUG_PRIVILEGE), // need for open processes
LAA(SE_IMPERSONATE_PRIVILEGE), // need for impersonate token
END_PRIVILEGES
BEGIN_PRIVILEGES(tp_cai, 3)
LAA(SE_CREATE_TOKEN_PRIVILEGE),
LAA(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE),
LAA(SE_INCREASE_QUOTA_PRIVILEGE),
END_PRIVILEGES
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
);
NTSTATUS CreateServiceToken(HANDLE hToken, PHANDLE phToken)
{
NTSTATUS status;
PVOID stack = alloca(guz);
PVOID buf = 0;
ULONG cb = 0, rcb;
struct {
PTOKEN_GROUPS ptg;
PTOKEN_STATISTICS pts;
PTOKEN_DEFAULT_DACL ptdd;
PTOKEN_PRIVILEGES ptp;
} s;
void** ppv = (void**)&s.ptp;
static const ULONG rcbV[] = {
sizeof(TOKEN_GROUPS)+0x80,
sizeof(TOKEN_STATISTICS),
sizeof(TOKEN_DEFAULT_DACL)+0x80,
sizeof(TOKEN_PRIVILEGES)+0x80,
};
static TOKEN_INFORMATION_CLASS TokenInformationClassV[] = {
TokenGroups,
TokenStatistics,
TokenDefaultDacl,
TokenPrivileges,
};
ULONG n = _countof(TokenInformationClassV);
do
{
TOKEN_INFORMATION_CLASS TokenInformationClas = TokenInformationClassV[--n];
rcb = rcbV[n], cb = 0;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
status = NtQueryInformationToken(hToken, TokenInformationClas, buf, cb, &rcb);
} while (status == STATUS_BUFFER_TOO_SMALL);
if (0 > status)
{
return status;
}
*(ppv--) = buf, stack = buf;
} while (n);
static const SID NetworkService = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_NETWORK_SERVICE_RID } };
static const TOKEN_OWNER to = { const_cast<SID*>(&NetworkService) };
static const TOKEN_USER tu = { { const_cast<SID*>(&NetworkService) } };
static const TOKEN_SOURCE ts = { {"Advapi"}, SYSTEM_LUID};
return NtCreateToken(phToken, TOKEN_ALL_ACCESS, 0, TokenPrimary,
&s.pts->AuthenticationId, &s.pts->ExpirationTime,
const_cast<PTOKEN_USER>(&tu), s.ptg, s.ptp, const_cast<PTOKEN_OWNER>(&to),
(PTOKEN_PRIMARY_GROUP)&to, s.ptdd, const_cast<PTOKEN_SOURCE>(&ts));
}
NTSTATUS RunAsNetworkService()
{
HANDLE hMyToken, hToken;
NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hMyToken);
if (0 <= status)
{
if (0 <= (status = NtAdjustPrivilegesToken(hMyToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(&tp_dbg), 0, 0, 0)))
{
if (0 <= (status = GetToken(&tp_cai, &hToken)))
{
status = RtlSetCurrentThreadToken(hToken);
NtClose(hToken);
if (0 <= status)
{
if (0 <= (status = CreateServiceToken(hMyToken, &hToken)))
{
ULONG SessionId;
ProcessIdToSessionId(GetCurrentProcessId(), &SessionId);
if (0 <= (status = NtSetInformationToken(hToken, TokenSessionId, &SessionId, sizeof(SessionId))))
{
STARTUPINFO si = { sizeof(si) };
si.lpDesktop = const_cast<PWSTR>(L"WinSta0\\Default");
PROCESS_INFORMATION pi;
WCHAR cmdline[] = L"cmd /k whoami.exe /user";
if (CreateProcessAsUserW(hToken, 0, cmdline, 0, 0, 0, 0, 0, 0, &si, &pi))
{
NtClose(pi.hThread);
NtClose(pi.hProcess);
}
else
{
status = RtlGetLastNtStatus();
}
}
NtClose(hToken);
}
RtlSetCurrentThreadToken();
}
}
}
NtClose(hMyToken);
}
return status;
}
(code not use /RTCs )

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

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

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;

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

STATUS_ACCESS_DENIED on a call to NtQueryMutant

Disclaimer:
The only reason for the question and the code below to exist is an external component used in my application, which cannot be replaced, at least in the near future. This component's logic intercepts WinAPI calls from the application and performs various tasks based on these calls.
One of the things the component does, it creates mutex for each thread initialized inside the application. However, it doesn't close the mutexes, which results in handles leak.
Therefore, in order to prevent the leak and because I don't have access to the component's source code, I have to invent ugly workarounds and use esoteric API's.
End of disclaimer
I am trying to check state of mutexes in my application. In order to do this without changing the state of each of the objects I check, I have to use the NtQueryMutant method from ntdll.dll.
Based on examples here and here I wrote the following code to achieve this:
enum MUTANT_INFORMATION_CLASS
{
MutantBasicInformation
};
struct MUTANT_BASIC_INFORMATION {
LONG CurrentCount;
BOOLEAN OwnedByCaller;
BOOLEAN AbandonedState;
};
typedef NTSTATUS(WINAPI*QueryMutexHandler)(HANDLE, MUTANT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
//somewhere in the code:
QueryMutexHandler queryMutex = reinterpret_cast<QueryMutexHandler>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryMutant"));
MUTANT_BASIC_INFORMATION mutantInfo;
NTSTATUS status = queryMutex(objectHandleCopy, MutantBasicInformation, &mutantInfo, sizeof(MUTANT_BASIC_INFORMATION), nullptr);
if (NT_SUCCESS(status))
{
//never arriving here
}
The status I receive here is always -1073741790 (0xFFFF FFFF C000 0022) which is, except being negative number, looks exactly like STATUS_ACCESS_DENIED.
That is very strange, because previously in code I use both NtQuerySystemInformation and NtQueryObject without any problem.
Additional details: my OS is Windows 7 SP1, the mutexes I try to query belong to the process I am performing the query from.
for effective test Mutant you need it handle and it access mask. you can got it from SYSTEM_HANDLE_INFORMATION_EX structure. if we already have MUTANT_QUERY_STATE - can direct query, if no - need reopen handle with MUTANT_QUERY_STATE
NTSTATUS QueryMutant(HANDLE hMutant, ULONG GrantedAccess, MUTANT_BASIC_INFORMATION* pmbi)
{
if (GrantedAccess & MUTANT_QUERY_STATE)
{
return ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
}
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), hMutant, NtCurrentProcess(),&hMutant,
MUTANT_QUERY_STATE, 0, 0);
if (0 <= status)
{
status = ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
ZwClose(hMutant);
}
return status;
}
and you not need all time use NtQueryObject for determinate type of handle. you can use SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex . for get OBJECT_TYPE_INFORMATION by this index. for this you need only once call ZwQueryObject(0, ObjectAllTypeInformation, ) at start, but exist problem how convert SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex to array index (zero bassed). begin from win8.1 'OBJECT_TYPE_INFORMATION.TypeIndex' is valid and match to SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex, but for early version - you need once get SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex for some known object type and calc delta
static volatile UCHAR guz;
NTSTATUS getProcessIndex(USHORT& ObjectTypeIndex)
{
HANDLE hProcess;
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), NtCurrentProcess(), NtCurrentProcess(), &hProcess, 0, 0, DUPLICATE_SAME_ACCESS);
if (0 <= status)
{
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId && (HANDLE)Handles->HandleValue == hProcess)
{
ObjectTypeIndex = Handles->ObjectTypeIndex;
goto __break;
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
__break:
ZwClose(hProcess);
}
return status;
}
class ZOBJECT_ALL_TYPES_INFORMATION
{
OBJECT_TYPE_INFORMATION* _TypeInformation;
DWORD _NumberOfTypes, _TypeIndexDelta;
public:
operator DWORD()
{
return _NumberOfTypes;
}
operator OBJECT_TYPE_INFORMATION*()
{
return _TypeInformation;
}
DWORD operator[](OBJECT_TYPE_INFORMATION* TypeInformation)
{
return (DWORD)(TypeInformation - _TypeInformation) + _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](DWORD Index)
{
return Index < _NumberOfTypes ? _TypeInformation + Index : 0;
}
ULONG TypeIndexToIndex(DWORD ObjectTypeIndex)
{
return ObjectTypeIndex -= _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](PCUNICODE_STRING TypeName);
ZOBJECT_ALL_TYPES_INFORMATION();
~ZOBJECT_ALL_TYPES_INFORMATION();
};
ZOBJECT_ALL_TYPES_INFORMATION g_AOTI;
OBJECT_TYPE_INFORMATION* ZOBJECT_ALL_TYPES_INFORMATION::operator[](PCUNICODE_STRING TypeName)
{
if (DWORD NumberOfTypes = _NumberOfTypes)
{
OBJECT_TYPE_INFORMATION* TypeInformation = _TypeInformation;
do
{
if (RtlEqualUnicodeString(TypeName, &TypeInformation->TypeName, TRUE))
{
return TypeInformation;
}
} while (TypeInformation++, -- NumberOfTypes);
}
return 0;
}
ZOBJECT_ALL_TYPES_INFORMATION::ZOBJECT_ALL_TYPES_INFORMATION()
{
_TypeInformation = 0, _NumberOfTypes = 0;
USHORT ProcessTypeIndex;
if (0 > getProcessIndex(ProcessTypeIndex))
{
return ;
}
NTSTATUS status;
PVOID stack = alloca(guz);
union {
PVOID pv;
OBJECT_TYPES_INFORMATION* poati;
};
DWORD cb = 0, rcb = 0x2000;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryObject(0, ObjectAllTypeInformation, poati, cb, &rcb)))
{
if (DWORD NumberOfTypes = poati->NumberOfTypes)
{
if (OBJECT_TYPE_INFORMATION* TypeInformation = (OBJECT_TYPE_INFORMATION*)LocalAlloc(0, rcb))
{
_NumberOfTypes = NumberOfTypes;
_TypeInformation = TypeInformation;
ULONG Index = 0;
union {
ULONG_PTR uptr;
OBJECT_TYPE_INFORMATION* pti;
};
union {
PWSTR buf;
PBYTE pb;
PVOID pv;
};
pti = poati->TypeInformation;
pv = TypeInformation + NumberOfTypes;
do
{
STATIC_UNICODE_STRING_(Process);
if (RtlEqualUnicodeString(&Process, &pti->TypeName, TRUE))
{
_TypeIndexDelta = ProcessTypeIndex - Index;
}
ULONG Length = pti->TypeName.Length, MaximumLength = pti->TypeName.MaximumLength;
memcpy(buf, pti->TypeName.Buffer, Length);
*TypeInformation = *pti;
TypeInformation++->TypeName.Buffer = buf;
pb += Length;
uptr += (sizeof(OBJECT_TYPE_INFORMATION) + MaximumLength + sizeof(PVOID)-1) & ~ (sizeof(PVOID)-1);
} while (Index++, --NumberOfTypes);
}
}
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
}
ZOBJECT_ALL_TYPES_INFORMATION::~ZOBJECT_ALL_TYPES_INFORMATION()
{
if (_TypeInformation)
{
LocalFree(_TypeInformation);
}
}
and finally use next code, without NtQueryObject:
void TestMutant()
{
NTSTATUS status;
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId)
{
if (OBJECT_TYPE_INFORMATION* poti = g_AOTI[g_AOTI.TypeIndexToIndex(Handles->ObjectTypeIndex)])
{
STATIC_UNICODE_STRING_(Mutant);
if (RtlEqualUnicodeString(&Mutant, &poti->TypeName, TRUE))
{
MUTANT_BASIC_INFORMATION mbi;
QueryMutant((HANDLE)Handles->HandleValue, Handles->GrantedAccess, &mbi);
}
}
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
}
can test with
void Az()
{
HANDLE hMutant;
if (0 <= ZwCreateMutant(&hMutant, SYNCHRONIZE, 0, TRUE))
{
TestMutant();
ZwClose(hMutant);
}
}