RunProcessAsUser causing problems - windows

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.

Related

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;

Refreshing a folder that doesn't exist in the file system

In my shell extension I have folders that don't actually exist in the file system, but only appear so to the user.
When the content of those folders is changed, I want to refresh them, and currently I do it in the same method I do for regular folders:
Win32.SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST | SHCNF_FLUSH, PIDL, IntPtr.Zero);
Whereas PIDL is a list of shell folders IDs, as required by SHCNF_IDLIST.
The problem is that explorer doesn't handle my non existing folders. Instead of refreshing them, it sends me back to the root folder.
I know that I construct the PIDL correctly since this mechanism works for existing folders, as previously mentioned.
How can I override the handler to SHChangeNotify? Or is there a better way for calling refresh?
Edit:
How my PIDL is generated:
IntPtr GetPIDL(IFolderItem target)
{
Stack stack = new Stack(5);
IntPtr data = IntPtr.Zero;
byte[] rootPIDL = null;
IFolderItem curr = target;
while (curr != null)
{
if (curr.rootPIDL != null)
{
rootPIDL = curr.rootPIDL;
}
else
{
data = curr.SerializeInt();
stack.Push(data);
}
curr = curr.ParentFolder;
}
if (rootPIDL == null && stack.Count == 0)
return IntPtr.Zero;
object[] x = stack.ToArray();
IntPtr[] pidls = null;
int count = stack.Count;
if (count > 0)
{
pidls = new IntPtr[stack.Count];
for (int i = 0; i < count; i++)
{
pidls[i] = (IntPtr)stack.Pop();
}
}
return CreatePIDL(rootPIDL, pidls);
}
My CreatePIDL implementation:
internal unsafe static IntPtr CreatePIDL(byte[] rootPIDL,IntPtr[] pidls)
{
int headerSize = Marshal.SizeOf(typeof(ushort));
int totalSize = headerSize;
if (rootPIDL != null)
totalSize += rootPIDL.Length - headerSize;
if (pidls!=null && pidls.Length > 0)
{
foreach (IntPtr data in pidls)
{
totalSize += PIDLSize(data);
}
}
IntPtr ret = PIDLAlloc(totalSize);
IntPtr currPos = ret;
if(rootPIDL!=null)
{
Marshal.Copy(rootPIDL, 0, currPos, rootLPIFQ.Length - headerSize);
currPos = Win32.AdvancePtr(currPos, rootLPIFQ.Length - headerSize);
}
if (pidls != null && pidls.Length>0)
{
foreach (IntPtr data in pidls)
{
int dataLength = PIDLSize(data);
Win32.CopyMemory(currPos, data, dataLength);
currPos = Win32.AdvancePtr(currPos, dataLength);
}
}
Marshal.WriteInt16(currPos, (short)0);
return ret;
}
internal static unsafe int PIDLSize(IntPtr ptr)
{
return (int) (*((ushort*)ptr));
}
internal unsafe static IntPtr PIDLAlloc(int size)
{
IntPtr ret = Marshal.AllocCoTaskMem(size);
if (ret == IntPtr.Zero)
throw new OutOfMemoryException();
return ret;
}
I found a workaround. It is not pretty nor optimal, yet it works well.
Instead of calling the notify with SHCNE_UPDATEDIR, I'm executing all three of the following notifiers in sequence:
Win32.SHChangeNotify(SHCNE_MKDIR, SHCNF_IDLIST | SHCNF_FLUSH, PIDL, IntPtr.Zero);
Win32.SHChangeNotify(SHCNE_CREATE, SHCNF_IDLIST | SHCNF_FLUSH, PIDL, IntPtr.Zero);
Win32.SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSH, PIDL, IntPtr.Zero);

Can DevCon notify when a driver is finished installing after a rescan?

I am trying to install a driver during a windows-setup project.
The first step I do is to copy the INF file and preinstall the driver.
SetupCopyOEMInf(infFile, null, 1, 0, null, 0, 0, null);
This correctly preinstalls the driver, but the device is not ready to use until a hardware rescan is done in Device Manager. I want to automate this as well. I have tried using the setupapi.dll to invoke the hardware rescan, but it was not always successful for me. Using devcon.exe rescan always forces the hardware rescan, but it is a synchronous command, and it returns before the device is finished installing. Is there any way to get a return result after the hardware scan completes and the driver is successfully installed?
Thanks,
Misha
Edit
Here is my working code:
public const UInt32 CR_SUCCESS = 0;
public const UInt64 CM_REENUMERATE_SYNCHRONOUS = 1;
public const UInt64 CM_LOCATE_DEVNODE_NORMAL = 0;
[DllImport("setupapi.dll")]
public static extern bool SetupCopyOEMInf(
string SourceInfFileName,
string OEMSourceMediaLocation,
int OEMSourceMediaType,
int CopyStyle,
string DestinationInfFileName,
int DestinationInfFileNameSize,
int RequiredSize,
string DestinationInfFileNameComponent
);
[DllImport("cfgmgr32.dll")]
public static extern int CM_Locate_DevNode_Ex(ref IntPtr deviceHandle, int deviceId, uint flags, IntPtr machineHandle);
[DllImport("cfgmgr32.dll")]
public static extern int CM_Reenumerate_DevNode_Ex(IntPtr devInst, UInt64 flags);
[DllImport("cfgmgr32.dll")]
public static extern int CMP_WaitNoPendingInstallEvents(UInt32 timeOut);
static void Main() {
bool success = SetupCopyOEMInf(infFile, null, 1, 0, null, 0, 0, null);
if(!success) {
throw new Exception("Error installing driver");
}
success = RescanAllDevices();
if (!success) {
throw new Exception("Error installing driver");
}
}
public static bool RescanAllDevices() {
int ResultCode = 0;
IntPtr LocalMachineInstance = IntPtr.Zero;
IntPtr DeviceInstance = IntPtr.Zero;
UInt32 PendingTime = 30000;
ResultCode = CM_Locate_DevNode_Ex(ref DeviceInstance, 0, 0, LocalMachineInstance);
if (CR_SUCCESS == ResultCode) {
ResultCode = CM_Reenumerate_DevNode_Ex(DeviceInstance, CM_REENUMERATE_SYNCHRONOUS);
ResultCode = CMP_WaitNoPendingInstallEvents(PendingTime);
}
return ResultCode == CR_SUCCESS;
}
The source for devcon is available in the WDK. It's in the src\setup\devcon directory. The logic for the rescan command is in the cmdRescan function in cmds.cpp. It would be a simple matter to copy that logic into your own code and make sure it doesn't return immediately.

Running a process at the Windows 7 Welcome Screen

So here's the scoop:
I wrote a tiny C# app a while back that displays the hostname, ip address, imaged date, thaw status (we use DeepFreeze), current domain, and the current date/time, to display on the welcome screen of our Windows 7 lab machines. This was to replace our previous information block, which was set statically at startup and actually embedded text into the background, with something a little more dynamic and functional. The app uses a Timer to update the ip address, deepfreeze status, and clock every second, and it checks to see if a user has logged in and kills itself when it detects such a condition.
If we just run it, via our startup script (set via group policy), it holds the script open and the machine never makes it to the login prompt. If we use something like the start or cmd commands to start it off under a separate shell/process, it runs until the startup script finishes, at which point Windows seems to clean up any and all child processes of the script. We're currently able to bypass that using psexec -s -d -i -x to fire it off, which lets it persist after the startup script is completed, but can be incredibly slow, adding anywhere between 5 seconds and over a minute to our startup time.
We have experimented with using another C# app to start the process, via the Process class, using WMI Calls (Win32_Process and Win32_ProcessStartup) with various startup flags, etc, but all end with the same result of the script finishing and the info block process getting killed. I tinkered with rewriting the app as a service, but services were never designed to interact with the desktop, let alone the login window, and getting things operating in the right context never really seemed to work out.
So for the question: Does anybody have a good way to accomplish this? Launch a task so that it would be independent of the startup script and run on top of the welcome screen?
This can be done through a lot of Win32 API calls. I have managed to get a program with a GUI onto the Winlogon desktop (before anyone asks, it's not an interactive GUI). Basically you need to run a loader process as SYSTEM, which will then spawn the new process. Since you most likely want this process to run on start up, you can either use the task scheduler to run the loader as SYSTEM or you can use a service to do the same thing. I'm currently using a service, but I tried using the task scheduler and it did work just fine.
Short summary:
Grab the Winlogon.exe process (as a Process)
Grab the token of winlogon using OpenProcessToken using the .handle of the Process
Create a new token and duplicate the winlogon token to it
Elevate the privileges of the token
Create the process using CreateProcessAsUser, making sure to set lpDesktop to "Winsta0\Winlogon" and using the token you created.
Code example:
// grab the winlogon process
Process winLogon = null;
foreach (Process p in Process.GetProcesses()) {
if (p.ProcessName.Contains("winlogon")) {
winLogon = p;
break;
}
}
// grab the winlogon's token
IntPtr userToken = IntPtr.Zero;
if (!OpenProcessToken(winLogon.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, out userToken)) {
log("ERROR: OpenProcessToken returned false - " + Marshal.GetLastWin32Error());
}
// create a new token
IntPtr newToken = IntPtr.Zero;
SECURITY_ATTRIBUTES tokenAttributes = new SECURITY_ATTRIBUTES();
tokenAttributes.nLength = Marshal.SizeOf(tokenAttributes);
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
threadAttributes.nLength = Marshal.SizeOf(threadAttributes);
// duplicate the winlogon token to the new token
if (!DuplicateTokenEx(userToken, 0x10000000, ref tokenAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenImpersonation, out newToken)) {
log("ERROR: DuplicateTokenEx returned false - " + Marshal.GetLastWin32Error());
}
TOKEN_PRIVILEGES tokPrivs = new TOKEN_PRIVILEGES();
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = new LUID();
if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out seDebugNameValue)) {
log("ERROR: LookupPrivilegeValue returned false - " + Marshal.GetLastWin32Error());
}
tokPrivs.Privileges = new LUID_AND_ATTRIBUTES[1];
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// escalate the new token's privileges
if (!AdjustTokenPrivileges(newToken, false, ref tokPrivs, 0, IntPtr.Zero, IntPtr.Zero)) {
log("ERROR: AdjustTokenPrivileges returned false - " + Marshal.GetLastWin32Error());
}
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "Winsta0\\Winlogon";
// start the process using the new token
if (!CreateProcessAsUser(newToken, process, process, ref tokenAttributes, ref threadAttributes,
true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero,
logInfoDir, ref si, out pi)) {
log("ERROR: CreateProcessAsUser returned false - " + Marshal.GetLastWin32Error());
}
Process _p = Process.GetProcessById(pi.dwProcessId);
if (_p != null) {
log("Process " + _p.Id + " Name " + _p.ProcessName);
} else {
log("Process not found");
}
This is one of those "You really need a good reason to do this" questions. Microsoft tries very hard to block applications running at the startup screen - every bit of code in Windows which interacts with the logon screen is very carefully code reviewed because the security consequences of a bug in code running at the logon screen are dire - if you screw up even slightly, you'll allow malware to get onto the computer.
Why do you want to run your program at the logon screen? Maybe there's a documented way of doing it that's not as risky.
I translated the code above in C++, if someone else needs it... Notice there are references to parts of my code, but it may help anyway:
static bool StartProcess(LPCTSTR lpApplicationPath)
{
CAutoGeneralHandle hWinlogonProcess = FindWinlogonProcess();
if (hWinlogonProcess == INVALID_HANDLE_VALUE)
{
DU_OutputDebugStringff(L"ERROR: Can't find the 'winlogon' process");
return false;
}
CAutoGeneralHandle hUserToken;
if (!OpenProcessToken(hWinlogonProcess, TOKEN_QUERY|TOKEN_IMPERSONATE|TOKEN_DUPLICATE, &hUserToken))
{
DU_OutputDebugStringff(L"ERROR: OpenProcessToken returned false (error %u)", GetLastError());
return false;
}
// Create a new token
SECURITY_ATTRIBUTES tokenAttributes = {0};
tokenAttributes.nLength = sizeof tokenAttributes;
SECURITY_ATTRIBUTES threadAttributes = {0};
threadAttributes.nLength = sizeof threadAttributes;
// Duplicate the winlogon token to the new token
CAutoGeneralHandle hNewToken;
if (!DuplicateTokenEx(hUserToken, 0x10000000, &tokenAttributes,
SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation,
TOKEN_TYPE::TokenImpersonation, &hNewToken))
{
DU_OutputDebugStringff(L"ERROR: DuplicateTokenEx returned false (error %u)", GetLastError());
return false;
}
TOKEN_PRIVILEGES tokPrivs = {0};
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = {0};
if (!LookupPrivilegeValue(nullptr, SE_DEBUG_NAME, &seDebugNameValue))
{
DU_OutputDebugStringff(L"ERROR: LookupPrivilegeValue returned false (error %u)", GetLastError());
return false;
}
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Escalate the new token's privileges
if (!AdjustTokenPrivileges(hNewToken, false, &tokPrivs, 0, nullptr, nullptr))
{
DU_OutputDebugStringff(L"ERROR: AdjustTokenPrivileges returned false (error %u)", GetLastError());
return false;
}
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
si.cb = sizeof si;
si.lpDesktop = L"Winsta0\\Winlogon";
// Start the process using the new token
if (!CreateProcessAsUser(hNewToken, lpApplicationPath, nullptr, &tokenAttributes, &threadAttributes,
true, CREATE_NEW_CONSOLE|INHERIT_CALLER_PRIORITY, nullptr, nullptr, &si, &pi))
{
DU_OutputDebugStringff(L"ERROR: CreateProcessAsUser returned false (error %u)", GetLastError());
return false;
}
return true;
}
I think you can do it, but it's pretty involved. Interactive apps aren't normally allowed to run on the welcome screen. At a high level, you'll need to:
Create a windows service that starts automatically
Use the windows service to create another process on the current session and desktop (using the Win32 methods WTSGetActiveConsoleSessionId and OpenInputDesktop)
I wrote an app that can interact somewhat with the login screen, but it doesn't show any UI. It probably can be done, but it may be even more involved.
Note: I found that I was unable to get results from OpenInputDesktop from my Windows service. I had to instead make the call in the other process and notify the service to restart the process on the correct desktop.
I hope that can at least get you started. Good luck!
Ignoring pre-Vista OS's, assuming you have TCB privs on your token (are running as System, basically), you can use CreateProcessAsUser to do this.
Example to be run as System (e.g.: an NT Service or with psexec -s) which will start notepad in the console session winlogon desktop:
#define WIN32_LEAN_AND_MEAN
#pragma comment(lib, "Userenv.lib")
#include <Windows.h>
#include <UserEnv.h>
#include <iostream>
#include <string>
HANDLE GetTokenForStart();
LPVOID GetEnvBlockForUser(HANDLE hToken);
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment);
int main(int argc, wchar_t* argv[])
{
//while (!IsDebuggerPresent()) Sleep(500);
try
{
HANDLE hUserToken = GetTokenForStart();
LPVOID env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
catch (std::wstring err)
{
auto gle = GetLastError();
std::wcerr << L"Error: " << err << L" GLE: " << gle << L"\r\n";
return -1;
}
}
HANDLE GetTokenForStart()
{
HANDLE hToken = 0;
{
HANDLE processToken = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE, &processToken))
{
throw std::wstring(L"Could not open current process token");
}
if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hToken))
{
throw std::wstring(L"Could not duplicate process token");
}
}
DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, TokenSessionId, &consoleSessionId, sizeof(consoleSessionId)))
{
throw std::wstring(L"Could not set session ID");
}
return hToken;
}
LPVOID GetEnvBlockForUser(HANDLE hToken)
{
LPVOID pEnvironment = NULL;
if (!CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
{
throw std::wstring(L"Could not create env block");
}
return pEnvironment;
}
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment)
{
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
si.lpDesktop = (LPWSTR)L"winsta0\\winlogon";
wchar_t path[MAX_PATH] = L"notepad.exe";
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessAsUser(hToken, NULL, path, NULL, NULL, FALSE,
CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
{
throw std::wstring(L"Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw std::wstring(L"Could not close thread handle");
}
}
Or, if you prefer C#:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace StartWinlogonManaged
{
class Program
{
static void Main(string[] args)
{
var hUserToken = GetTokenForStart();
var env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
const string
Advapi32 = "advapi32.dll",
Userenv = "userenv.dll",
Kernel32 = "kernel32.dll";
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr ProcessToken, int DesiredAccess, out IntPtr TokenHandle);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr ExistingToken, int DesiredAccess,
IntPtr TokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr NewToken);
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern int WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetTokenInformation(IntPtr hToken,
int tokenInfoClass, ref int pTokenInfo, int tokenInfoLength);
static IntPtr GetTokenForStart()
{
IntPtr hToken = IntPtr.Zero;
{
IntPtr processToken = IntPtr.Zero;
if (!OpenProcessToken(GetCurrentProcess(), 0x2001f /* TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE */, out processToken))
{
throw new Win32Exception("Could not open current process token");
}
if (!DuplicateTokenEx(processToken, 0x02000000 /* MAXIMUM_ALLOWED */, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken))
{
throw new Win32Exception("Could not duplicate process token");
}
}
int consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, 12 /* TokenSessionId */, ref consoleSessionId, 4 /* sizeof(int) */))
{
throw new Win32Exception("Could not set session ID");
}
return hToken;
}
[DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
static IntPtr GetEnvBlockForUser(IntPtr hToken)
{
IntPtr pEnvironment = IntPtr.Zero;
if (!CreateEnvironmentBlock(out pEnvironment, hToken, true))
{
throw new Win32Exception("Could not create env block");
}
return pEnvironment;
}
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessAsUser(IntPtr hToken,
StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION startupInformation);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpDesktop;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
static void StartTheProcess(IntPtr hToken, IntPtr pEnvironment)
{
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf<STARTUPINFO>();
si.dwFlags = 1 /* STARTF_USESHOWWINDOW */;
si.wShowWindow = 5 /* SW_SHOW */;
si.lpDesktop = "winsta0\\winlogon";
var path = new StringBuilder("notepad.exe", 260);
PROCESS_INFORMATION pi;
if (!CreateProcessAsUser(hToken, null, path, IntPtr.Zero, IntPtr.Zero, false,
0x410 /* CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT */, pEnvironment, null, ref si, out pi))
{
throw new Win32Exception("Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw new Win32Exception("Could not close thread handle");
}
}
}
}
Note that this does require several privileges (TCB, AssignPrimaryToken, IncreaseQuota) enabled in your token. This code also leaks handles, does not formulate a full command line, use name constants, etc..., and is only intended as an expository reference - not as a ready solution.

CreateProcessAsUser not working correctly in my experiments

I am trying to do the following:
1. I am logged in as Administrator account in my XP with SP2 machine running VS.NET 2005
2. This machine also has another account user1 which is a guest account
3. I am running a program as Administrator, from this program i want to launch a notepad.exe process which will be running under the user1 security context
4. I specifically want to use CreateProcessasUser to do this..
This is the code snipper which will explain what i have been trying..
const string GRANTED_ALL = "10000000";
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_LOGON_SERVICE = 5;
const int LOGON32_LOGON_UNLOCK = 7;
const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
const int LOGON32_PROVIDER_DEFAULT = 0;
static IntPtr hToken = IntPtr.Zero;
static IntPtr hTokenDuplicate = IntPtr.Zero;
static void Main(string[] args)
{
int last_error = 0;
if(LogonUser("user1",null,"#welcome123",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, out hToken))
{
last_error = Marshal.GetLastWin32Error();
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
last_error = 0;
last_error = Marshal.GetLastWin32Error();
if(DuplicateTokenEx(hToken,UInt32.Parse(GRANTED_ALL,System.Globalization.NumberStyles.HexNumber),
ref sa,SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,out hTokenDuplicate))
{
last_error = 0;
last_error = Marshal.GetLastWin32Error();
CreateProcessAsUser(hTokenDuplicate, "d:\\san\\notepad.exe", null,
ref sa, ref sa, false, 0, IntPtr.Zero, "d:\\san", ref si, out pi);
last_error = 0;
last_error = Marshal.GetLastWin32Error();
}
}
last_error = 0;
last_error = Marshal.GetLastWin32Error();
if (hToken != IntPtr.Zero) CloseHandle(hToken);
if (hTokenDuplicate != IntPtr.Zero) CloseHandle(hTokenDuplicate);
}
For some reason this is not working..
The DuplicateTokenEx function is returning as error code of 1305 and i cant seem to figure out why..
Instead of DuplicateTokenEx i also used the DuplicateToken, now the CreateProcessAsUser is returning an error code of 1308.
Could someone please throw light on this issue.. This appears to be an apparently very simple thing, but just cant get it right..
[Please note that I specifically want to LogonUser and then DuplicateToken and then CreateProcessAsUSer]
See CreateProcessAsUser() windowstations and desktops.
But I suggest to do it in managed way:
...
using System.Diagnostics;
using System.Security;
...
...
string progPath = #"c:\WINNT\notepad.exe";
ProcessStartInfo startInfo = new ProcessStartInfo(progPath);
startInfo.WindowStyle = ProcessWindowStyle.Normal;
startInfo.UseShellExecute = false;
startInfo.UserName = "SomeUser";
SecureString password = new SecureString();
#region setting password
password.AppendChar('p');
password.AppendChar('a');
...
#endregion
startInfo.Password = password;
Process.Start(startInfo);
...
...

Resources