LsaEnumerateAccountRights returns file not found - winapi

BYTE sidbuf[SECURITY_MAX_SID_SIZE];
PSID sid = (PSID)sidbuf;
if (GetAccountSidFromName(L"Sasha", sid, sizeof(sidbuf)))
{
PLSA_UNICODE_STRING userRights;
ULONG cnt;
LSA_HANDLE policy = GetPolicyHandle();
NTSTATUS status=LsaEnumerateAccountRights(policy, sid, &userRights, &cnt);
long ff=LsaNtStatusToWinError(status);
}
BOOL GetAccountSidFromName(LPCTSTR Account, PSID Sid, const DWORD SidSize)
{
SID_NAME_USE snu;
DWORD cbSid = SidSize, cchRD = 0;
LPTSTR rd = NULL;
BOOL succ = LookupAccountName(NULL, Account, Sid, &cbSid, rd, &cchRD, &snu);
if (!succ)
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return FALSE;
rd = (LPTSTR)LocalAlloc(LPTR, cchRD * sizeof(*rd));
if (!rd)
{
SetLastError(ERROR_OUTOFMEMORY);
return FALSE;
}
cbSid = SidSize;
succ = LookupAccountName(NULL, Account, Sid, &cbSid, rd, &cchRD, &snu);
LocalFree(rd);
}
return succ;
}
LSA_HANDLE GetPolicyHandle()
{
LSA_OBJECT_ATTRIBUTES ObjectAttributes;
USHORT SystemNameLength;
NTSTATUS ntsResult;
LSA_HANDLE lsahPolicyHandle;
// Object attributes are reserved, so initialize to zeros.
ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));
// Get a handle to the Policy object.
ntsResult = LsaOpenPolicy(
NULL, //Name of the target system.
&ObjectAttributes, //Object attributes.
POLICY_ALL_ACCESS, //Desired access permissions.
&lsahPolicyHandle //Receives the policy handle.
);
return lsahPolicyHandle;
}
In my case sid is valid. GetPolicyHandle() is from MSDN.User name is Sasha as specified. Run on Visual Studio 2019 with admin rights.
I try find some solution at the Internet but found nothing. I have spended already 5 hours on it.
I have no idea how to fix it.Plaese help.

Related

How to check if a TRUSTEE is me

I'm using the GetSecurityInfo funtion to get my own process's Discretional Access Control List (DACL):
PACL oldAcl;
Pointer se;
GetSecurityInfo(GetCurrentProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
null, null, ref oldAcl, nil, ref se);
And then i can use the GetExplicitEntriesFromAcl to crack open the ACL to get at it's list of Access Control Entries (ACE) inside:
UInt32 nCount;
EXPLICIT_ACCESS[] list;
GetExplicitEntriesFromAcl(oldAcl, ref nCount, ref list);
I can go through the list of three entries on my process:
STACKOVERFLOW\ian (S-1-5-21-6198258843-697258998-2146844275-1109) [SidTypeUser]
Grant Access
0x00010000 DELETE
0x00020000 READ_CONTROL
0x00040000 WRITE_DAC
0x00080000 WRITE_OWNER
0x00100000 SYNCHRONIZE
...
0x00000008 PROCESS_VM_OPERATION
0x00000010 PROCESS_VM_READ
0x00000020 PROCESS_VM_WRITE
NT AUTHORITY\SYSTEM (S-1-5-18) [SidTypeWellKnownGroup]
Grant Access
0x00010000 DELETE
0x00020000 READ_CONTROL
0x00040000 WRITE_DAC
0x00080000 WRITE_OWNER
0x00100000 SYNCHRONIZE
...
0x00000008 PROCESS_VM_OPERATION
0x00000010 PROCESS_VM_READ
0x00000020 PROCESS_VM_WRITE
NT AUTHORITY\LogonSessionId_0_20117843 (S-1-5-5-0-20117843) [SidType_11]
Grant Access
0x00020000 READ_CONTROL
0x00100000 SYNCHRONIZE
0x00000010 PROCESS_VM_READ
0x00000400 PROCESS_QUERY_INFORMATION
0x00001000 PROCESS_QUERY_LIMITED_INFORMATION
I now want to go through and update the DACL for the process (which of course i'm allowed to do since i have WRITE_DACL - and because i'm the Owner, which means i implicitly have WRITE_DACL).
But i only want to re-write access control entries that apply to "me".
In this case there happen to be three Trustees:
S-1-5-21-6198258843-697258998-2146844275-1109 (user me)
S-1-5-18 (user LocalSystem - not me)
S-1-5-5-0-20117843 (group LoginSession - me)
Trustees are presented to us as a TRUSTEE object (note, not all of which have an SID). I know from experience that i am two of those trustees; but not the third.
Is there a function that i can use to compare "me" against a TRUSTEE?
Boolean DoIMatchThisTrustee(TRUSTEE trustee)
{
}
Why am i doing this?
No reason. I'm removing PROCESS_VM_READ, PROCESS_VM_WRITE, and PROCESS_VM_OPERATION from myself on my own process.
you actually want check Discretional Access Control List (DACL) for Sids which is "me" - so enabled members of your process token.
To determine whether a SID is enabled in a token we can use CheckTokenMembership function.
use GetSecurityInfo not the best choice i think, much better use GetKernelObjectSecurity here. however you can check and trustees if want ("note, not all of which have an SID" - this is in general case, but in case DACL you will got only TRUSTEE_IS_SID trustees). code can be next:
void Test()
{
HANDLE hToken, hImpToken;
if (OpenProcessToken(NtCurrentProcess(), TOKEN_QUERY|TOKEN_DUPLICATE, &hToken))
{
BOOL fOk = DuplicateToken(hToken, ::SecurityIdentification, &hImpToken);
CloseHandle(hToken);
if (fOk)
{
ULONG cb = 0, rcb = 256;
static volatile UCHAR guz = 0;
PVOID stack = alloca(guz);
union {
PVOID buf;
PSECURITY_DESCRIPTOR pSD;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (GetKernelObjectSecurity(NtCurrentProcess(), DACL_SECURITY_INFORMATION, pSD, cb, &rcb))
{
BOOL bPresent, bDefault;
union {
PACL Acl;
PBYTE pb;
PACE_HEADER pah;
PACCESS_ALLOWED_ACE paaa;
};
if (GetSecurityDescriptorDacl(pSD, &bPresent, &Acl, &bDefault) && bPresent && Acl)
{
CheckSidsInAcl(Acl);
if (USHORT AceCount = Acl->AceCount)
{
Acl++;
do
{
if (pah->AceType == ACCESS_ALLOWED_ACE_TYPE)
{
BOOL IsMember;
if (CheckTokenMembership(hImpToken, &paaa->SidStart, &IsMember))
{
PWSTR sz;
if (ConvertSidToStringSid(&paaa->SidStart, &sz))
{
DbgPrint("%x %S\n", IsMember, sz);
LocalFree(sz);
}
}
else
{
GetLastError();
}
}
} while (pb += pah->AceSize, --AceCount);
}
}
break;
}
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
CloseHandle(hImpToken);
}
}
}
I don't think there are any functions to compare nor normalize a TRUSTEE.
The ACEs in a ACL always store a SID (as far as I know) so it is probably safe to assume that the TRUSTEE will be a TRUSTEE_IS_*SID form when you get it from a ACL.
PSID GetSID(const TRUSTEE&t)
{
if (TRUSTEE_IS_SID == t.TrusteeForm) return (PSID) t.ptstrName;
if (TRUSTEE_IS_OBJECTS_AND_SID == t.TrusteeForm) return ((OBJECTS_AND_SID*)t.ptstrName)->pSid;
return NULL;
}
bool DoIMatchThisTrustee(TRUSTEE&t)
{
PSID tsid = GetSID(t);
PSID mysid = GetMySid(); // From process/thread token or somewhere else
return tsid && EqualSid(tsid, mysid);
}
If you don't want to assume then you can use LookupAccountName on the string forms to get a SID.
If for whatever reason you don't want to lookup any strings you can do things NT4 style and work with the ACL directly. Call GetAce to enumerate the ACL and use something like this:
PSID GetAllowedSID(const ACE_HEADER&ah)
{
switch(ah.AceType)
{
case ACCESS_ALLOWED_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_ACE*)&ah)->SidStart;
case ACCESS_ALLOWED_CALLBACK_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_CALLBACK_ACE*)&ah)->SidStart;
case ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_CALLBACK_OBJECT_ACE*)&ah)->SidStart;
case ACCESS_ALLOWED_OBJECT_ACE_TYPE: return (PSID) &((ACCESS_ALLOWED_OBJECT_ACE*)&ah)->SidStart;
default: return NULL;
}
}
If you are doing this to increase security you might want to do things the other way around and only allow "NT AUTHORITY\SYSTEM" and "BUILTIN\Administrators" these rights.
Whether or not the logon session is "you" is debatable but you cannot compare the full SID to find out, just the SECURITY_NT_AUTHORITY+SECURITY_LOGON_IDS_RID parts.
RbMm's comment reminded me that i already knew the answer somewhere deep in my brain: CheckTokenMembeship:
//Get the ACL on the process
PACL oldAcl;
Pointer se;
GetSecurityInfo(GetCurrentProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
null, null, ref oldAcl, nil, ref se); //allocates memory into se, which we must LocalFree later
//oldAcl is a pointer to inside the se blob
//Crack open the ACL, to feast on the entries inside
UInt32 nCount;
EXPLICIT_ACCESS[] list;
GetExplicitEntriesFromAcl(oldAcl, ref nCount, ref list); //allocates memory into list, which we must localFree later
//the flags i want to remove me from having
DWORD removeFlags = PROCESS_VM_READ || PROCESS_VM_WRITE || PROCESS_VM_OPERATION;
//Go through the list, looking for entries that are "me"
for (EXPLICIT_ACCESS ea in list)
{
if (ea.Trustee.TrusteeForm != TRUSTEE_IS_SID)
continue;
BOOL isMember;
CheckTokenMembership(0, PSID(ea.Trustee.ptstrName), out isMember);
if (!isMember)
continue;
//Remove the permissions
ea.grfAccessPermissions = ea.grfAccessPermissions && (!removeFlags);
aclUpdateNeeded = true;
}
//write the new DACL
SetEntriesInAcl(nCount, list, null, out newAcl); //allocates a new acl
SetSecurityInfo(GetCurrentProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
null, null, newAcl, null); //apply the new acl
LocalFree(list); //free the memory allocated by GetExplicitEntriesFromAcl
LocalFree(se); //free the memory allocated by GetSecurityInfo

Structure of the certificate pointed to by NCRYPT_KEY_HANDLE

I've written a credential provider and a key storage provider to logon to windows via certificate. As the documentation in this points is quite vague I used different samples from Microsoft to get things working.
I think I'm nearly there, but the logon behaves unpredictably. Sometimes I get through to the kerberos server (which complains about the certificate), sometimes the process fails with 0x80090029 without any information and sometimes windows crashes. As these crashes all have to do with access violations or null pointers and happen to occur in various places (kerberos.dll, Windows.UI.Logon.dll, ...) I think it has something to do with my key structure that i point the given NCRYT_KEY_HANDLE to in my OpenKey-implementation.
The KeyStorageProviderSample in the CNG-Kit has an example, but relies on a RSA-key stored in %AppData%. I don't have the private key available as it is stored in secure hardware, I just have the public part (i.e. the public certificate), that I read from another device and import via the following code:
SECURITY_STATUS WINAPI KeyHandler::ReadPemCert(__inout KSP_KEY *keyHandle)
{
LOG_FUNCTION;
CERT_CONTEXT certContext = {};
DWORD readLength = 0;
LOG("Fetch certificate");
const int maxSizeInBytes = 4096;
char pemCertificateAsBytes[maxSizeInBytes];
BluetoothClient bluetoothClient = BluetoothClient();
bluetoothClient.getCertificate((PBYTE)pemCertificateAsBytes, readLength);
DWORD certAsDerLen = readLength;
BYTE* certAsDer = new BYTE[certAsDerLen];
LOG("convert PEM to DER");
if (!CryptStringToBinaryA(pemCertificateAsBytes, 0, CRYPT_STRING_BASE64, certAsDer, &certAsDerLen, NULL, NULL))
{
LOG_LAST_ERROR("CryptStringToBinary failed. Err:");
}
LOG_BYTES_AS_HEX("DER-Zertifikat", certAsDer, certAsDerLen);
PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, certAsDer, certAsDerLen);
certContext->pCertInfo = pcCertContext->pCertInfo;
certContext->cbCertEncoded = pcCertContext->cbCertEncoded;
certContext->pbCertEncoded = pcCertContext->pbCertEncoded;
certContext->dwCertEncodingType = pcCertContext->dwCertEncodingType;
CERT_INFO *certInfo;
certInfo = certContext.pCertInfo;
CERT_PUBLIC_KEY_INFO pubKeyInfo = certInfo->SubjectPublicKeyInfo;
LOG("Aquire cryptocontext");
HCRYPTPROV hProv = 0;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
{
LOG_LAST_ERROR("CryptAcquireContext failed. Err:");
return -1;
}
}
LOG("Importing public key");
NCRYPT_KEY_HANDLE publicKeyHandle = NULL;
if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, &pubKeyInfo, &publicKeyHandle))
{
LOG_LAST_ERROR("CryptImportPublicKeyInfo failed. Err:");
return -1;
}
keyHandle->fFinished = TRUE;
keyHandle->hPublicKey = (BCRYPT_KEY_HANDLE)publicKeyHandle;
keyHandle->pszKeyBlobType = BCRYPT_RSAPUBLIC_BLOB;
LocalFree(certInfo);
return ERROR_SUCCESS;
}
The key structure is initialized this way:
SECURITY_STATUS
WINAPI
KeyHandler::CreateNewKeyObject(
__in_opt LPCWSTR pszKeyName,
__deref_out KSP_KEY **ppKey)
{
LOG_FUNCTION;
KSP_KEY *pKey = NULL;
DWORD cbKeyName = 0;
SECURITY_STATUS Status = NTE_INTERNAL_ERROR;
NTSTATUS ntStatus = STATUS_INTERNAL_ERROR;
pKey = (KSP_KEY *)HeapAlloc(GetProcessHeap(), 0, sizeof(KSP_KEY));
if (pKey == NULL)
{
return NTE_NO_MEMORY;
}
pKey->cbLength = sizeof(KSP_KEY);
pKey->dwMagic = KSP_KEY_MAGIC;
pKey->dwAlgID = KSP_RSA_ALGID;
pKey->pszKeyFilePath = NULL;
pKey->pszKeyBlobType = NULL;
pKey->dwKeyBitLength = 0;
pKey->fFinished = FALSE;
//Copy the keyname into the key struct.
if (pszKeyName != NULL)
{
cbKeyName = (DWORD)(wcslen(pszKeyName) + 1) * sizeof(WCHAR);
pKey->pszKeyName = (LPWSTR)HeapAlloc(
GetProcessHeap(),
0,
cbKeyName + sizeof(WCHAR));
if (pKey->pszKeyName == NULL)
{
return NTE_NO_MEMORY;
}
CopyMemory(pKey->pszKeyName, pszKeyName, cbKeyName);
pKey->pszKeyName[cbKeyName / sizeof(WCHAR)] = L'\0';
}
else
{
pKey->pszKeyName = NULL;
}
if (globalRSAProviderHandle == NULL)
{
ntStatus = BCryptOpenAlgorithmProvider(
&globalRSAProviderHandle,
BCRYPT_RSA_ALGORITHM,
NULL,
0);
if (!NT_SUCCESS(ntStatus))
{
return NormalizeNteStatus(ntStatus);
}
}
pKey->hProvider = globalRSAProviderHandle;
pKey->pbKeyFile = NULL;
pKey->cbKeyFile = 0;
pKey->pbPrivateKey = NULL;
pKey->cbPrivateKey = 0;
pKey->hPublicKey = NULL;
pKey->hPrivateKey = NULL;
pKey->dwExportPolicy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
pKey->dwKeyUsagePolicy = NCRYPT_ALLOW_DECRYPT_FLAG | NCRYPT_ALLOW_SIGNING_FLAG;
pKey->pbSecurityDescr = NULL;
pKey->cbSecurityDescr = 0;
InitializeListHead(&pKey->PropertyList);
*ppKey = pKey;
pKey = NULL;
return ERROR_SUCCESS;
}
Somewhere in there must be the mistake leading to the various memory errors. But as I'm quite new to windows programming and c/c++ I just can't spot the point and can't find any documentation about the datastructure that windows expects for the NCRYTP_KEY_HANDLE.
Does anybody know more about this structure?
NCRYPT_KEY_HANDLE is just a pointer to a structure that you defined.
Windows itself doesn't care about this structure and expect that your provider knows how to work with it.
In KeyHandler::ReadPemCert you mixed legacy CryptoAPI and CNG API. Since you are implementing KSP you should use only CNG API (CryptImportPublicKeyInfoEx2).
DWORD error = NTE_FAIL;
BCRYPT_KEY_HANDLE hKey = NULL;
...
PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, certAsDer, certAsDerLen);
if(!pcCertContext)
{
goto Exit;
}
if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, &pcCertContext->pCertInfo->SubjectPublicKeyInfo, 0, nullptr, &hKey))
{
goto Exit;
}
/* Also you can export key and print out the result to make sure everything works
DWORD temp = 0;
status = BCryptExportKey(hKey, 0, BCRYPT_RSAPUBLIC_BLOB, nullptr, 0, &temp, 0);
if (status != ERROR_SUCCESS)
{
goto Exit;
}
std::vector<BYTE> key(temp);
status = BCryptExportKey(hKey, 0, BCRYPT_RSAPUBLIC_BLOB, key.data(), key.size(), &temp, 0);
if (status != ERROR_SUCCESS)
{
goto Exit;
}
for (auto const& i : key)
{
std::cout << std::hex << (int)i;
}
}
*/
keyHandle->fFinished = TRUE;
keyHandle->hPublicKey = hKey;
keyHandle->pszKeyBlobType = BCRYPT_RSAPUBLIC_BLOB;
erro = ERROR_SUCCESS;
Exit:
if(pcCertContext)
{
CertFreeCertificateContext(pcCertContext);
}
return error;

RunProcessAsUser causing problems

I have an application that is being started by a Service automatically when the user logs on to his account. The application in question is a Winforms application and the problem is that sometimes when the application is started from the service it inexplicably hangs. Killing it and running it once more without a service works fine.
I am not sure why this is happening and why it only happens occasionally (not all the time). I always check the new process information and it is always running under the correct session of the logged in user.
I am not sure if I am doing something wrong when impersonating the user but in any case I am listing the code just so that maybe someone can find a flaw in it:
public int RunWithTokenInSession(IntPtr token, int nSessionID)
{
IntPtr primaryToken = GetUserTokenOfSessoin((uint)nSessionID);
if (primaryToken == IntPtr.Zero)
{
return -1;
}
STARTUPINFO StartupInfo = new STARTUPINFO();
processInfo_ = new PROCESS_INFORMATION();
StartupInfo.cb = Marshal.SizeOf(StartupInfo);
//StartupInfo.wShowWindow = (short)ShowCommands.SW_HIDE;
StartupInfo.lpDesktop = "Winsta0\\Default";
SECURITY_ATTRIBUTES Security1 = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES Security2 = new SECURITY_ATTRIBUTES();
string command = "\"" + processPath_ + "\"";
if ((arguments_ != null) && (arguments_.Length != 0))
{
command += " " + arguments_;
}
IntPtr lpEnvironment = IntPtr.Zero;
bool resultEnv = CreateEnvironmentBlock(out lpEnvironment, primaryToken, false);
if (resultEnv != true)
{
int nError = GetLastError();
}
bool b = CreateProcessAsUser(primaryToken, null, command, ref Security1, ref Security2, false, /*CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS |*/ CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, lpEnvironment, null, ref StartupInfo, out processInfo_);
int error = 0;
if (!b)
{
error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
DestroyEnvironmentBlock(lpEnvironment);
CloseHandle(primaryToken);
return error;
}
And this is the code for GetUserTokenOfSessoin:
public static IntPtr GetUserTokenOfSessoin(uint dwSessionId)
{
IntPtr currentToken = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
IntPtr hUserToken = IntPtr.Zero;
IntPtr hTokenDup = IntPtr.Zero;
bool bRet = WTSQueryUserToken(dwSessionId, out currentToken);
if (bRet == false)
{
int n = GetLastError();
return IntPtr.Zero;
}
bool fInAdminGroup = false;
//IntPtr hToken = IntPtr.Zero;
IntPtr hTokenToCheck = IntPtr.Zero;
IntPtr pElevationType = IntPtr.Zero;
IntPtr pLinkedToken = IntPtr.Zero;
int cbSize = 0;
if (Environment.OSVersion.Version.Major >= 6)
{
// Running Windows Vista or later (major version >= 6).
// Determine token type: limited, elevated, or default.
// Allocate a buffer for the elevation type information.
cbSize = sizeof(TOKEN_ELEVATION_TYPE);
pElevationType = Marshal.AllocHGlobal(cbSize);
if (pElevationType == IntPtr.Zero)
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
// Retrieve token elevation type information.
if (!GetTokenInformation(currentToken,
TOKEN_INFORMATION_CLASS.TokenElevationType, pElevationType, cbSize, out cbSize))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
// Marshal the TOKEN_ELEVATION_TYPE enum from native to .NET.
TOKEN_ELEVATION_TYPE elevType = (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(pElevationType);
// If limited, get the linked elevated token for further check.
if (elevType == TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
{
// Allocate a buffer for the linked token.
cbSize = IntPtr.Size;
pLinkedToken = Marshal.AllocHGlobal(cbSize);
if (pLinkedToken == IntPtr.Zero)
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
// Get the linked token.
if (!GetTokenInformation(currentToken, TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken, cbSize, out cbSize))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
// Marshal the linked token value from native to .NET.
hTokenToCheck = Marshal.ReadIntPtr(pLinkedToken);
bRet = DuplicateTokenEx(hTokenToCheck, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
}
else
{
bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
}
}
else
{
// XP
bRet = DuplicateTokenEx(currentToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
}
if (bRet == false)
{
int lastError = Marshal.GetLastWin32Error();
return IntPtr.Zero;
}
return primaryToken;
}
Now the issue seems to exclusively happen on Windows Server 2008 R2 as I haven't been able to reproduce it on any other version of Windows so I was wondering if there is anything special about Server 2008 and do I need to take extra security measures or obtain an extra privilege to run the process as the user.

Getting the Windows Control Panel's virtual folder

Why would GetPath always return E_FAIL when querying FOLDERID_ControlPanelFolder? Other FOLDERIDs actually do work:
HRESULT hr = S_OK;
*path = '\0';
LPWSTR pwcPath = NULL;
CoInitialize(NULL);
IKnownFolderManager *pFolderManager = NULL;
if ((hr = CoCreateInstance(__uuidof(KnownFolderManager), NULL, CLSCTX_INPROC_SERVER, __uuidof(IKnownFolderManager), (LPVOID *)&pFolderManager)) == S_OK)
{
IKnownFolder *pControlPanelFolder = NULL;
if ((hr = pFolderManager->GetFolder(FOLDERID_ControlPanelFolder, &pControlPanelFolder)) == S_OK)
{
hr = pControlPanelFolder->GetPath(0, &pwcPath);
if (hr == S_OK && pwcPath)
{
int nSize = wcslen(pwcPath);
WideCharToMultiByte(CP_ACP, 0, pwcPath, nSize, path, nSize+2, NULL, NULL);
path[nSize] = '\0';
CoTaskMemFree(pwcPath);
}
pControlPanelFolder->Release();
pControlPanelFolder = NULL;
}
pFolderManager->Release();
pFolderManager = NULL;
}
CoUninitialize();
(Yes, I stumbled upon this question but I don't have need for all that enumeration stuff.)
The Control Panel has no directory path because it does not exist on the disc. You can get its PIDL, and even a Desktop Absolute Parsing "display name" (via GetShellItem and GetDisplayName), but not a directory path.
Reason why I needed the path was that I wanted to open the controp panel with a ShellExecute "open". I now execute the control panel program directly, with the benefit of being able to select the desired applet right away (in this case "Sound"). I hope it's not too pretentious that I post this as answer:
char controlpanelpath[2000];
UINT controlpanelpathbuffersize = sizeof(controlpanelpath);
int actualcontrolpanelpathsize;
if (actualcontrolpanelpathsize = GetSystemDirectory(controlpanelpath, controlpanelpathbuffersize))
{
char *parameters = "\\control.exe mmsys.cpl,,0";
if (actualcontrolpanelpathsize + strlen(parameters) < controlpanelpathbuffersize)
{
strcat(controlpanelpath, parameters);
WinExec(controlpanelpath, SW_NORMAL);
}
}

How can I get the current user token for the physical session?

I have some code, with which I'm trying to get the current session user token:
#include <Wtsapi32.h>
DWORD activeSessionId = WTSGetActiveConsoleSessionId();
HANDLE currentToken;
BOOL queryRet = WTSQueryUserToken(activeSessionId, &currentToken);
if (!queryRet) {
DWORD err = GetLastError();
return 0;
}
Value of err is 1314.
Update 1
No luck so far, tried to grant the current process SE_TCB_NAME - but still get same error from WTSQueryUserToken (1314).
HANDLE process = GetCurrentProcess();
HANDLE processToken;
BOOL openTokenRet = OpenProcessToken(
process, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken);
if (!openTokenRet)
{
DWORD err = GetLastError();
return 0;
}
TOKEN_PRIVILEGES tokenPrivs;
BOOL lookupRet = LookupPrivilegeValue(
NULL, SE_TCB_NAME, &tokenPrivs.Privileges[0].Luid);
if (!lookupRet)
{
DWORD err = GetLastError();
return 0;
}
tokenPrivs.PrivilegeCount = 1;
tokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL adjustRet = AdjustTokenPrivileges(
processToken, FALSE, &tokenPrivs, 0, (PTOKEN_PRIVILEGES)NULL, 0);
if (!adjustRet)
{
DWORD err = GetLastError();
return 0;
}
// get the user in the active session
HANDLE currentToken;
BOOL queryRet = WTSQueryUserToken(activeSessionId, &currentToken);
if (!queryRet) {
DWORD err = GetLastError();
return 0;
}
Update 2:
Added some more debug info, but prevState.PrivilegeCount is 0...
TOKEN_PRIVILEGES prevState;
DWORD prevStateLen = 0;
BOOL adjustRet = AdjustTokenPrivileges(
processToken, FALSE, &tokenPrivs,
sizeof(TOKEN_PRIVILEGES), &prevState, &prevStateLen);
DWORD adjustErr = GetLastError();
if (!adjustRet)
{
return 0;
}
Solution:
Looks like WTSQueryUserToken can only be used when running as LocalSystem, which means I'll have to run as a service and debug from there... Doh!
Error 1314 is ERROR_PRIVILEGE_NOT_HELD. You need SE_TCB_NAME privilege to call WTSQueryUserToken.
This privilege is typically held only by code running as Local System. If this privilege is present in your token, but disabled, you can use AdjustTokenPrivileges to enable it. Since SE_TCB_NAME is a potentially very dangerous privilege to have, you should disable it again immediately after you use it. An easy way to see if you have this privilege is with Process Explorer in the Security table of the process properties window.
Per Update 1 - is AdjustTokenPrivileges return success, but GetLastError() is set to ERROR_NOT_ALL_ASSIGNED? MSDN indicates it could return this if privileges weren't enabled. Can you verify that your process does have SE_TCB_NAME privilege, but it is disabled? What account is your process running as?

Resources