//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1993. // // File: logon32.c // // Contents: // // Classes: // // Functions: // // History: 9-30-94 RichardW Created // //---------------------------------------------------------------------------- #include "advapi.h" #include #include #include #include #include #include #include #define SECURITY_WIN32 #include #include #include #include #include #include // // We dynamically load mpr.dll (no big surprise there), in order to call // WNetLogonNotify, as defined in private\inc\mpr.h. This prototype matches // it -- consult the header file for all the parameters. // typedef (* LOGONNOTIFYFN)(LPCWSTR, PLUID, LPCWSTR, LPVOID, LPCWSTR, LPVOID, LPWSTR, LPVOID, LPWSTR *); // // The QuotaLimits are global, because the defaults // are always used for accounts, based on server/wksta, and no one ever // calls lsasetaccountquota // HANDLE Logon32LsaHandle = NULL; ULONG Logon32MsvHandle = 0xFFFFFFFF; ULONG Logon32NegoHandle = 0xFFFFFFFF; WCHAR Logon32DomainName[DNLEN+1] = L""; QUOTA_LIMITS Logon32QuotaLimits; HINSTANCE Logon32MprHandle = NULL; LOGONNOTIFYFN Logon32LogonNotify = NULL; RTL_CRITICAL_SECTION Logon32Lock; #define LockLogon() RtlEnterCriticalSection( &Logon32Lock ) #define UnlockLogon() RtlLeaveCriticalSection( &Logon32Lock ) SID_IDENTIFIER_AUTHORITY L32SystemSidAuthority = SECURITY_NT_AUTHORITY; SID_IDENTIFIER_AUTHORITY L32LocalSidAuthority = SECURITY_LOCAL_SID_AUTHORITY; #define COMMON_CREATE_SUSPENDED 0x00000001 // Suspended, do not Resume() #define COMMON_CREATE_PROCESSSD 0x00000002 // Whack the process SD #define COMMON_CREATE_THREADSD 0x00000004 // Whack the thread SD BOOL WINAPI LogonUserCommonA( LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, BOOL fExVersion, HANDLE * phToken, PSID * ppLogonSid, PVOID * ppProfileBuffer, DWORD * pdwProfileLength, PQUOTA_LIMITS pQuotaLimits ); BOOL WINAPI LogonUserCommonW( PCWSTR lpszUsername, PCWSTR lpszDomain, PCWSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, BOOL fExVersion, HANDLE * phToken, PSID * ppLogonSid, PVOID * ppProfileBuffer, DWORD * pdwProfileLength, PQUOTA_LIMITS pQuotaLimits ); //+--------------------------------------------------------------------------- // // Function: Logon32Initialize // // Synopsis: Initializes the critical section // // Arguments: [hMod] -- // [Reason] -- // [Context] -- // //---------------------------------------------------------------------------- BOOL Logon32Initialize( IN PVOID hMod, IN ULONG Reason, IN PCONTEXT Context) { NTSTATUS Status; if (Reason == DLL_PROCESS_ATTACH) { Status = RtlInitializeCriticalSection( &Logon32Lock ); return( Status == STATUS_SUCCESS ); } return( TRUE ); } /***************************************************************************\ * FindLogonSid * * Finds logon sid for a new logon from the access token. * \***************************************************************************/ PSID L32FindLogonSid( IN HANDLE hToken ) { PTOKEN_GROUPS pGroups = NULL; DWORD cbGroups; PVOID FastBuffer[ 512 / sizeof(PVOID) ]; PTOKEN_GROUPS pSlowBuffer = NULL; UINT i; PSID Sid = NULL; pGroups = (PTOKEN_GROUPS)FastBuffer; cbGroups = sizeof(FastBuffer); if(!GetTokenInformation( hToken, TokenGroups, pGroups, cbGroups, &cbGroups )) { if( GetLastError() != ERROR_INSUFFICIENT_BUFFER ) { return NULL; } pSlowBuffer = (PTOKEN_GROUPS)LocalAlloc(LMEM_FIXED, cbGroups); if( pSlowBuffer == NULL ) { return NULL; } pGroups = pSlowBuffer; if(!GetTokenInformation( hToken, TokenGroups, pGroups, cbGroups, &cbGroups )) { goto Cleanup; } } // // Get the logon Sid by looping through the Sids in the token // for(i = 0 ; i < pGroups->GroupCount ; i++) { if(pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) { DWORD dwSidLength; // // insure we are dealing with a valid Sid // if(!IsValidSid(pGroups->Groups[i].Sid)) { goto Cleanup; } // // get required allocation size to copy the Sid // dwSidLength = GetLengthSid(pGroups->Groups[i].Sid); Sid = (PSID)LocalAlloc( LMEM_FIXED, dwSidLength ); if( Sid == NULL ) { goto Cleanup; } CopySid(dwSidLength, Sid, pGroups->Groups[i].Sid); break; } } Cleanup: if( pSlowBuffer ) { LocalFree( pSlowBuffer ); } return Sid; } /******************************************************************* NAME: GetDefaultDomainName SYNOPSIS: Fills in the given array with the name of the default domain to use for logon validation. ENTRY: pszDomainName - Pointer to a buffer that will receive the default domain name. cchDomainName - The size (in charactesr) of the domain name buffer. RETURNS: TRUE if successful, FALSE if not. HISTORY: KeithMo 05-Dec-1994 Created. RichardW 10-Jan-95 Liberated from sockets and stuck in base ********************************************************************/ BOOL L32GetDefaultDomainName( PUNICODE_STRING pDomainName ) { OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS NtStatus; INT Result; DWORD err = 0; LSA_HANDLE LsaPolicyHandle = NULL; PPOLICY_ACCOUNT_DOMAIN_INFO DomainInfo = NULL; PUNICODE_STRING pDomain; if (Logon32DomainName[0] != L'\0') { RtlInitUnicodeString(pDomainName, Logon32DomainName); return(TRUE); } // // Open a handle to the local machine's LSA policy object. // InitializeObjectAttributes( &ObjectAttributes, // object attributes NULL, // name 0L, // attributes NULL, // root directory NULL ); // security descriptor NtStatus = LsaOpenPolicy( NULL, // system name &ObjectAttributes, // object attributes POLICY_EXECUTE, // access mask &LsaPolicyHandle ); // policy handle if( !NT_SUCCESS( NtStatus ) ) { BaseSetLastNTError(NtStatus); return(FALSE); } // // Query the domain information from the policy object. // NtStatus = LsaQueryInformationPolicy( LsaPolicyHandle, PolicyAccountDomainInformation, (PVOID *) &DomainInfo ); if (!NT_SUCCESS(NtStatus)) { BaseSetLastNTError(NtStatus); LsaClose(LsaPolicyHandle); return(FALSE); } (void) LsaClose(LsaPolicyHandle); // // Copy the domain name into our cache, and // CopyMemory( Logon32DomainName, DomainInfo->DomainName.Buffer, DomainInfo->DomainName.Length ); // // Null terminate it appropriately // Logon32DomainName[DomainInfo->DomainName.Length / sizeof(WCHAR)] = L'\0'; // // Clean up // LsaFreeMemory( (PVOID)DomainInfo ); // // And init the string // RtlInitUnicodeString(pDomainName, Logon32DomainName); return TRUE; } // GetDefaultDomainName //+--------------------------------------------------------------------------- // // Function: L32pInitLsa // // Synopsis: Initialize connection with LSA // // Arguments: (none) // // History: 4-21-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL L32pInitLsa(void) { STRING PackageName; ULONG MsvHandle; ULONG NegoHandle; NTSTATUS Status; // // Hookup to the LSA and locate our authentication package. // Status = LsaConnectUntrusted( &Logon32LsaHandle ); if (!NT_SUCCESS(Status)) { Logon32LsaHandle = NULL; goto Cleanup; } // // Connect with the MSV1_0 authentication package // RtlInitString(&PackageName, "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"); Status = LsaLookupAuthenticationPackage ( Logon32LsaHandle, &PackageName, &MsvHandle ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Connect with the Negotiate authentication package // RtlInitString(&PackageName, NEGOSSP_NAME_A); Status = LsaLookupAuthenticationPackage ( Logon32LsaHandle, &PackageName, &NegoHandle ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Wait until successful to update the 2 globals. // Logon32NegoHandle = NegoHandle; Logon32MsvHandle = MsvHandle; Cleanup: if( !NT_SUCCESS(Status) ) { if( Logon32LsaHandle ) { (VOID) LsaDeregisterLogonProcess( Logon32LsaHandle ); Logon32LsaHandle = NULL; } BaseSetLastNTError( Status ); return FALSE; } return TRUE; } //+--------------------------------------------------------------------------- // // Function: L32pNotifyMpr // // Synopsis: Loads the MPR DLL and notifies the network providers (like // csnw) so they know about this logon session and the credentials // // Arguments: [NewLogon] -- New logon information // [LogonId] -- Logon ID // // History: 4-24-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL L32pNotifyMpr( PMSV1_0_INTERACTIVE_LOGON NewLogon, PLUID LogonId ) { MSV1_0_INTERACTIVE_LOGON OldLogon; LPWSTR LogonScripts; DWORD status; LUID LocalServiceLuid = LOCALSERVICE_LUID; LUID NetworkServiceLuid = NETWORKSERVICE_LUID; if (RtlEqualLuid(LogonId, &LocalServiceLuid) || RtlEqualLuid(LogonId, &NetworkServiceLuid)) { // // Don't notify providers for LocalService/NetworkService logons // return( TRUE ); } if ( Logon32MprHandle == NULL ) { LockLogon(); if ( Logon32MprHandle == NULL) { Logon32MprHandle = LoadLibrary("mpr.dll"); if (Logon32MprHandle != NULL) { Logon32LogonNotify = (LOGONNOTIFYFN) GetProcAddress( Logon32MprHandle, "WNetLogonNotify"); } } UnlockLogon(); } if ( Logon32LogonNotify != NULL ) { CopyMemory(&OldLogon, NewLogon, sizeof(OldLogon)); status = Logon32LogonNotify( L"Windows NT Network Provider", LogonId, L"MSV1_0:Interactive", (LPVOID)NewLogon, L"MSV1_0:Interactive", (LPVOID)&OldLogon, L"SvcCtl", // StationName NULL, // StationHandle &LogonScripts); // LogonScripts if (status == NO_ERROR) { if (LogonScripts != NULL ) { (void) LocalFree(LogonScripts); } } return( TRUE ); } return( FALSE ); } //+--------------------------------------------------------------------------- // // Function: L32pLogonUser // // Synopsis: Wraps up the call to LsaLogonUser // // Arguments: [LsaHandle] -- // [AuthenticationPackage] -- // [LogonType] -- // [UserName] -- // [Domain] -- // [Password] -- // [LogonId] -- // [LogonToken] -- // [Quotas] -- // [pProfileBuffer] -- // [pProfileBufferLength] -- // [pSubStatus] -- // // History: 4-24-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- NTSTATUS L32pLogonUser( IN HANDLE LsaHandle, IN ULONG AuthenticationPackage, IN SECURITY_LOGON_TYPE LogonType, IN PUNICODE_STRING UserName, IN PUNICODE_STRING Domain, IN PUNICODE_STRING Password, OUT PLUID LogonId, OUT PHANDLE LogonToken, OUT PQUOTA_LIMITS Quotas, OUT PVOID *pProfileBuffer, OUT PULONG pProfileBufferLength, OUT PNTSTATUS pSubStatus ) { NTSTATUS Status; STRING OriginName; TOKEN_SOURCE SourceContext; PMSV1_0_INTERACTIVE_LOGON MsvAuthInfo; PMSV1_0_LM20_LOGON MsvNetAuthInfo; PVOID AuthInfoBuf; ULONG AuthInfoSize; WCHAR ComputerName[ MAX_COMPUTERNAME_LENGTH + 1 ]; DWORD ComputerNameLength; // // Initialize source context structure // strncpy(SourceContext.SourceName, "Advapi ", sizeof(SourceContext.SourceName)); // LATER from res file Status = NtAllocateLocallyUniqueId(&SourceContext.SourceIdentifier); if (!NT_SUCCESS(Status)) { return(Status); } // // Set logon origin // RtlInitString(&OriginName, "LogonUser API"); // // For network logons, do the magic. // if ( ( LogonType == Network ) ) { ComputerNameLength = MAX_COMPUTERNAME_LENGTH + 1; if (!GetComputerNameW( ComputerName, &ComputerNameLength ) ) { return(STATUS_INVALID_PARAMETER); } AuthInfoSize = sizeof( MSV1_0_LM20_LOGON ) + UserName->Length + Domain->Length + sizeof(WCHAR) * (ComputerNameLength + 1) + Password->Length + // NT password (LM20_PWLEN+1) ; // LM passsword (worst case) MsvNetAuthInfo = AuthInfoBuf = RtlAllocateHeap( RtlProcessHeap(), HEAP_ZERO_MEMORY, AuthInfoSize ); if ( !MsvNetAuthInfo ) { return( STATUS_NO_MEMORY ); } // // Start packing in the string // MsvNetAuthInfo->MessageType = MsV1_0NetworkLogon; // // Copy the user name into the authentication buffer // MsvNetAuthInfo->UserName.Length = UserName->Length; MsvNetAuthInfo->UserName.MaximumLength = MsvNetAuthInfo->UserName.Length; MsvNetAuthInfo->UserName.Buffer = (PWSTR)(MsvNetAuthInfo+1); RtlCopyMemory( MsvNetAuthInfo->UserName.Buffer, UserName->Buffer, UserName->Length ); // // Copy the domain name into the authentication buffer // MsvNetAuthInfo->LogonDomainName.Length = Domain->Length; MsvNetAuthInfo->LogonDomainName.MaximumLength = Domain->Length ; MsvNetAuthInfo->LogonDomainName.Buffer = (PWSTR) ((PBYTE)(MsvNetAuthInfo->UserName.Buffer) + MsvNetAuthInfo->UserName.MaximumLength); RtlCopyMemory( MsvNetAuthInfo->LogonDomainName.Buffer, Domain->Buffer, Domain->Length); // // Copy the workstation name into the buffer // MsvNetAuthInfo->Workstation.Length = (USHORT) (sizeof(WCHAR) * ComputerNameLength); MsvNetAuthInfo->Workstation.MaximumLength = MsvNetAuthInfo->Workstation.Length + sizeof(WCHAR); MsvNetAuthInfo->Workstation.Buffer = (PWSTR) ((PBYTE) (MsvNetAuthInfo->LogonDomainName.Buffer) + MsvNetAuthInfo->LogonDomainName.MaximumLength ); wcscpy( MsvNetAuthInfo->Workstation.Buffer, ComputerName ); // // Set up space for Password (Unicode) // MsvNetAuthInfo->CaseSensitiveChallengeResponse.Buffer = (PUCHAR) ((PBYTE) (MsvNetAuthInfo->Workstation.Buffer) + MsvNetAuthInfo->Workstation.MaximumLength ); MsvNetAuthInfo->CaseSensitiveChallengeResponse.Length = MsvNetAuthInfo->CaseSensitiveChallengeResponse.MaximumLength = Password->Length; RtlCopyMemory( MsvNetAuthInfo->CaseSensitiveChallengeResponse.Buffer, Password->Buffer, Password->Length); // // handle passing in the case-insensitive version. // if( (Password->Length/sizeof(WCHAR)) > LM20_PWLEN ) { Status = STATUS_INVALID_PARAMETER; } else { MsvNetAuthInfo->CaseInsensitiveChallengeResponse.Buffer = (PUCHAR) ((PBYTE) (MsvNetAuthInfo->CaseSensitiveChallengeResponse.Buffer) + MsvNetAuthInfo->CaseSensitiveChallengeResponse.MaximumLength ); MsvNetAuthInfo->CaseInsensitiveChallengeResponse.Length = LM20_PWLEN; MsvNetAuthInfo->CaseInsensitiveChallengeResponse.MaximumLength = LM20_PWLEN+1; Status = RtlUpcaseUnicodeStringToOemString( &MsvNetAuthInfo->CaseInsensitiveChallengeResponse, Password, FALSE ); } if ( !NT_SUCCESS(Status) ) { Status = STATUS_SUCCESS; // // If we're here, the NT (supplied) password is longer than the // limit allowed for LM passwords. NULL out the field, so that // MSV knows not to worry about it. // RtlZeroMemory( &MsvNetAuthInfo->CaseInsensitiveChallengeResponse, sizeof(MsvNetAuthInfo->CaseInsensitiveChallengeResponse) ); } // // to be consistent with Negotiate/Kerberos for _WINNT50 cases, // allow machine accounts to be logged on. // MsvNetAuthInfo->ParameterControl = MSV1_0_CLEARTEXT_PASSWORD_ALLOWED | MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED | MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT; } else { // // Build logon structure for non-network logons - service, // batch, interactive, unlock, new credentials, networkcleartext // AuthInfoSize = sizeof(MSV1_0_INTERACTIVE_LOGON) + UserName->Length + Domain->Length + Password->Length; MsvAuthInfo = AuthInfoBuf = RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, AuthInfoSize); if (MsvAuthInfo == NULL) { return(STATUS_NO_MEMORY); } // // This authentication buffer will be used for a logon attempt // MsvAuthInfo->MessageType = MsV1_0InteractiveLogon; // // Copy the user name into the authentication buffer // MsvAuthInfo->UserName.Length = UserName->Length; MsvAuthInfo->UserName.MaximumLength = MsvAuthInfo->UserName.Length; MsvAuthInfo->UserName.Buffer = (PWSTR)(MsvAuthInfo+1); RtlCopyMemory( MsvAuthInfo->UserName.Buffer, UserName->Buffer, UserName->Length ); // // Copy the domain name into the authentication buffer // MsvAuthInfo->LogonDomainName.Length = Domain->Length; MsvAuthInfo->LogonDomainName.MaximumLength = MsvAuthInfo->LogonDomainName.Length; MsvAuthInfo->LogonDomainName.Buffer = (PWSTR) ((PBYTE)(MsvAuthInfo->UserName.Buffer) + MsvAuthInfo->UserName.MaximumLength); RtlCopyMemory( MsvAuthInfo->LogonDomainName.Buffer, Domain->Buffer, Domain->Length ); // // Copy the password into the authentication buffer // Hide it once we have copied it. Use the same seed value // that we used for the original password in pGlobals. // MsvAuthInfo->Password.Length = Password->Length; MsvAuthInfo->Password.MaximumLength = MsvAuthInfo->Password.Length; MsvAuthInfo->Password.Buffer = (PWSTR) ((PBYTE)(MsvAuthInfo->LogonDomainName.Buffer) + MsvAuthInfo->LogonDomainName.MaximumLength); RtlCopyMemory( MsvAuthInfo->Password.Buffer, Password->Buffer, Password->Length ); } // // Now try to log this sucker on // Status = LsaLogonUser ( LsaHandle, &OriginName, LogonType, AuthenticationPackage, AuthInfoBuf, AuthInfoSize, NULL, &SourceContext, pProfileBuffer, pProfileBufferLength, LogonId, LogonToken, Quotas, pSubStatus ); // // Notify all the network providers, if this is a NON network logon. Also // skip service logons since the LSA will call WNetLogonNotify for those. // if ( NT_SUCCESS( Status ) && (LogonType != Network) && (LogonType != Service) ) { L32pNotifyMpr(AuthInfoBuf, LogonId); } // // Discard authentication buffer // RtlZeroMemory( AuthInfoBuf, AuthInfoSize ); RtlFreeHeap(RtlProcessHeap(), 0, AuthInfoBuf); return(Status); } //+--------------------------------------------------------------------------- // // Function: LogonUserCommonA // // Synopsis: ANSI wrapper for LogonUserCommonW. See description below // // Arguments: [lpszUsername] -- // [lpszDomain] -- // [lpszPassword] -- // [dwLogonType] -- // [dwLogonProvider] -- // [fExVersion] -- // [phToken] -- // [ppLogonSid] -- // [ppProfileBuffer] -- // [pdwProfileLength] -- // [pQuotaLimits] -- // // History: 2-15-2000 JSchwart Created from RichardW's LogonUserA // // Notes: // //---------------------------------------------------------------------------- BOOL WINAPI LogonUserCommonA( LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, BOOL fExVersion, HANDLE * phToken, PSID * ppLogonSid, PVOID * ppProfileBuffer, DWORD * pdwProfileLength, PQUOTA_LIMITS pQuotaLimits ) { UNICODE_STRING Username; UNICODE_STRING Domain; UNICODE_STRING Password; ANSI_STRING Temp ; NTSTATUS Status; BOOL bRet; Username.Buffer = NULL; Domain.Buffer = NULL; Password.Buffer = NULL; RtlInitAnsiString( &Temp, lpszUsername ); Status = RtlAnsiStringToUnicodeString( &Username, &Temp, TRUE ); if (!NT_SUCCESS( Status ) ) { BaseSetLastNTError(Status); bRet = FALSE; goto Cleanup; } RtlInitAnsiString( &Temp, lpszDomain ); Status = RtlAnsiStringToUnicodeString(&Domain, &Temp, TRUE ); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); bRet = FALSE; goto Cleanup; } RtlInitAnsiString( &Temp, lpszPassword ); Status = RtlAnsiStringToUnicodeString( &Password, &Temp, TRUE ); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); bRet = FALSE; goto Cleanup; } bRet = LogonUserCommonW( Username.Buffer, Domain.Buffer, Password.Buffer, dwLogonType, dwLogonProvider, fExVersion, phToken, ppLogonSid, ppProfileBuffer, pdwProfileLength, pQuotaLimits ); Cleanup: if (Username.Buffer) { RtlFreeUnicodeString(&Username); } if (Domain.Buffer) { RtlFreeUnicodeString(&Domain); } if (Password.Buffer) { RtlZeroMemory(Password.Buffer, Password.Length); RtlFreeUnicodeString(&Password); } return(bRet); } //+--------------------------------------------------------------------------- // // Function: LogonUserA // // Synopsis: ANSI wrapper for LogonUserW. See description below // // Arguments: [lpszUsername] -- // [lpszDomain] -- // [lpszPassword] -- // [dwLogonType] -- // [dwLogonProvider] -- // [phToken] -- // // History: 4-25-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL WINAPI LogonUserA( LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, HANDLE * phToken ) { return LogonUserCommonA(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, FALSE, // LogonUserA phToken, NULL, // ppLogonSid NULL, // ppProfileBuffer NULL, // pdwProfileLength NULL); // pQuotaLimits } //+--------------------------------------------------------------------------- // // Function: LogonUserExA // // Synopsis: ANSI wrapper for LogonUserExW. See description below // // Arguments: [lpszUsername] -- // [lpszDomain] -- // [lpszPassword] -- // [dwLogonType] -- // [dwLogonProvider] -- // [phToken] -- // [ppLogonSid] -- // [ppProfileBuffer] -- // [pdwProfileLength] -- // [pQuotaLimits] -- // // History: 2-15-2000 JSchwart Created from RichardW's LogonUserW // // Notes: // //---------------------------------------------------------------------------- BOOL WINAPI LogonUserExA( LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, HANDLE * phToken, PSID * ppLogonSid, PVOID * ppProfileBuffer, DWORD * pdwProfileLength, PQUOTA_LIMITS pQuotaLimits ) { return LogonUserCommonA(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, TRUE, // LogonUserExA phToken, ppLogonSid, ppProfileBuffer, pdwProfileLength, pQuotaLimits); } //+--------------------------------------------------------------------------- // // Function: LogonUserCommonW // // Synopsis: Common code for LogonUserW and LogonUserExW. Logs a user on // via plaintext password, username and domain name via the LSA. // // Arguments: [lpszUsername] -- User name // [lpszDomain] -- Domain name // [lpszPassword] -- Password // [dwLogonType] -- Logon type // [dwLogonProvider] -- Provider // [fExVersion] -- LogonUserExW or LogonUserW // [phToken] -- Returned handle to primary token // [ppLogonSid] -- Returned logon sid // [ppProfileBuffer] -- Returned user profile buffer // [pdwProfileLength] -- Returned profile length // // History: 2-15-2000 JSchwart Created from RichardW's LogonUserW // // Notes: Requires SeTcbPrivilege, and will enable it if not already // present. // //---------------------------------------------------------------------------- BOOL WINAPI LogonUserCommonW( PCWSTR lpszUsername, PCWSTR lpszDomain, PCWSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, BOOL fExVersion, HANDLE * phToken, PSID * ppLogonSid, PVOID * ppProfileBuffer, DWORD * pdwProfileLength, PQUOTA_LIMITS pQuotaLimits ) { NTSTATUS Status; ULONG PackageId; UNICODE_STRING Username; UNICODE_STRING Domain; UNICODE_STRING Password; HANDLE hTempToken; HANDLE * phTempToken; LUID LogonId; PVOID Profile; ULONG ProfileLength; NTSTATUS SubStatus = STATUS_SUCCESS; SECURITY_LOGON_TYPE LogonType; // // Validate the provider // if (dwLogonProvider == LOGON32_PROVIDER_DEFAULT) { dwLogonProvider = LOGON32_PROVIDER_WINNT50; // // if domain was not supplied, and username is not a UPN, use // _WINNT40 to be compatible. // if((lpszUsername != NULL) && (lpszDomain == NULL || lpszDomain[ 0 ] == L'\0')) { if( wcschr( lpszUsername, L'@' ) == NULL ) { dwLogonProvider = LOGON32_PROVIDER_WINNT40; } } } if (dwLogonProvider > LOGON32_PROVIDER_WINNT50) { BaseSetLastNTError(STATUS_INVALID_PARAMETER); return(FALSE); } switch (dwLogonType) { case LOGON32_LOGON_INTERACTIVE: LogonType = Interactive; break; case LOGON32_LOGON_BATCH: LogonType = Batch; break; case LOGON32_LOGON_SERVICE: LogonType = Service; break; case LOGON32_LOGON_NETWORK: LogonType = Network; break; case LOGON32_LOGON_UNLOCK: LogonType = Unlock ; break; case LOGON32_LOGON_NETWORK_CLEARTEXT: LogonType = NetworkCleartext ; break; case LOGON32_LOGON_NEW_CREDENTIALS: LogonType = NewCredentials; break; default: BaseSetLastNTError(STATUS_INVALID_PARAMETER); return(FALSE); break; } // // If the MSV handle is -1, grab the lock, and try again: // if (Logon32MsvHandle == 0xFFFFFFFF || Logon32NegoHandle == 0xFFFFFFFF) { LockLogon(); // // If the MSV handle is still -1, init our connection to lsa. We // have the lock, so no other threads can't be trying this right now. // if (Logon32MsvHandle == 0xFFFFFFFF || Logon32NegoHandle == 0xFFFFFFFF) { if (!L32pInitLsa()) { UnlockLogon(); return( FALSE ); } } UnlockLogon(); } // // Validate the parameters. NULL or empty domain or NULL or empty // user name is invalid. // RtlInitUnicodeString(&Username, lpszUsername); if (Username.Length == 0) { BaseSetLastNTError(STATUS_INVALID_PARAMETER); return(FALSE); } // // Initialize/check parameters based on which API we're servicing. // if (!fExVersion) { // // LogonUserW -- phToken is required. Initialize the token handle, // if the pointer is invalid, then catch the exception now. // *phToken = NULL; phTempToken = phToken; } else { // // LogonUserExW -- phToken, ppLogonSid, ppProfileBuffer, and // pdwProfileLength are optional. Initialize as appropriate. // if (ARGUMENT_PRESENT(phToken)) { *phToken = NULL; phTempToken = phToken; } else { // // Dummy token handle to use in the LsaLogonUser call // phTempToken = &hTempToken; } if (ARGUMENT_PRESENT(ppLogonSid)) { *ppLogonSid = NULL; } if (!!ppProfileBuffer ^ !!pdwProfileLength) { // // Can't have one without the other... // BaseSetLastNTError(STATUS_INVALID_PARAMETER); return(FALSE); } if (ARGUMENT_PRESENT(ppProfileBuffer)) { *ppProfileBuffer = NULL; *pdwProfileLength = 0; } if (ARGUMENT_PRESENT(pQuotaLimits)) { RtlZeroMemory(pQuotaLimits, sizeof(QUOTA_LIMITS)); } } // // Parse that domain. Note, if the special token . is passed in for // domain, we will use the right value from the LSA, meaning AccountDomain. // If the domain is null, the lsa will talk to the local domain, the // primary domain, and then on from there... // if (lpszDomain && *lpszDomain) { if ((lpszDomain[0] == L'.') && (lpszDomain[1] == L'\0') ) { if (!L32GetDefaultDomainName(&Domain)) { return(FALSE); } } else { RtlInitUnicodeString(&Domain, lpszDomain); } } else { RtlInitUnicodeString(&Domain, lpszDomain); } // // Finally, init the password // RtlInitUnicodeString(&Password, lpszPassword); // // Attempt the logon // Status = L32pLogonUser( Logon32LsaHandle, (dwLogonProvider == LOGON32_PROVIDER_WINNT50) ? Logon32NegoHandle : Logon32MsvHandle, LogonType, &Username, &Domain, &Password, &LogonId, phTempToken, pQuotaLimits ? pQuotaLimits : &Logon32QuotaLimits, &Profile, &ProfileLength, &SubStatus); // // Set output parameters based on which API we're servicing // // TODO: review cleanup code if something fails mid-stream. // if (!fExVersion) { if (!NT_SUCCESS(Status)) { if (Status == STATUS_ACCOUNT_RESTRICTION) { BaseSetLastNTError(SubStatus); } else { BaseSetLastNTError(Status); } return(FALSE); } if (Profile != NULL) { LsaFreeReturnBuffer(Profile); } } else { // // We may need the allocated buffers if all went well, so // check the return status first. // if (!NT_SUCCESS(Status)) { if (Status == STATUS_ACCOUNT_RESTRICTION) { BaseSetLastNTError(SubStatus); } else { BaseSetLastNTError(Status); } return(FALSE); } // // The logon succeeded -- fill in the requested output parameters. // if (ARGUMENT_PRESENT(ppLogonSid)) { *ppLogonSid = L32FindLogonSid( *phTempToken ); if (*ppLogonSid == NULL) { if (Profile != NULL) { LsaFreeReturnBuffer(Profile); } CloseHandle(*phTempToken); *phTempToken = NULL; BaseSetLastNTError(STATUS_NO_MEMORY); return(FALSE); } } if (ARGUMENT_PRESENT(ppProfileBuffer)) { if (Profile != NULL) { ASSERT(ProfileLength != 0); *ppProfileBuffer = Profile; *pdwProfileLength = ProfileLength; } } else { if (Profile != NULL) { LsaFreeReturnBuffer(Profile); } } if (!ARGUMENT_PRESENT(phToken)) { // // Close the dummy token handle // CloseHandle(*phTempToken); } } return(TRUE); } //+--------------------------------------------------------------------------- // // Function: LogonUserW // // Synopsis: Logs a user on via plaintext password, username and domain // name via the LSA. // // Arguments: [lpszUsername] -- User name // [lpszDomain] -- Domain name // [lpszPassword] -- Password // [dwLogonType] -- Logon type // [dwLogonProvider] -- Provider // [phToken] -- Returned handle to primary token // // History: 4-25-95 RichardW Created // // Notes: Requires SeTcbPrivilege, and will enable it if not already // present. // //---------------------------------------------------------------------------- BOOL WINAPI LogonUserW( PCWSTR lpszUsername, PCWSTR lpszDomain, PCWSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, HANDLE * phToken ) { return LogonUserCommonW(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, FALSE, // LogonUserW phToken, NULL, // ppLogonSid NULL, // ppProfileBuffer NULL, // pdwProfileLength NULL); // pQuotaLimits } //+--------------------------------------------------------------------------- // // Function: LogonUserExW // // Synopsis: Logs a user on via plaintext password, username and domain // name via the LSA. // // Arguments: [lpszUsername] -- User name // [lpszDomain] -- Domain name // [lpszPassword] -- Password // [dwLogonType] -- Logon type // [dwLogonProvider] -- Provider // [phToken] -- Returned handle to primary token // [ppLogonSid] -- Returned logon sid // [ppProfileBuffer] -- Returned user profile buffer // [pdwProfileLength] -- Returned profile length // [pQuotaLimits] -- Returned quota limits // // History: 2-15-2000 JSchwart Created from RichardW's LogonUserW // // Notes: Requires SeTcbPrivilege, and will enable it if not already // present. // //---------------------------------------------------------------------------- BOOL WINAPI LogonUserExW( PCWSTR lpszUsername, PCWSTR lpszDomain, PCWSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, HANDLE * phToken, PSID * ppLogonSid, PVOID * ppProfileBuffer, DWORD * pdwProfileLength, PQUOTA_LIMITS pQuotaLimits ) { return LogonUserCommonW(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, TRUE, // LogonUserExW phToken, ppLogonSid, ppProfileBuffer, pdwProfileLength, pQuotaLimits); } //+--------------------------------------------------------------------------- // // Function: ImpersonateLoggedOnUser // // Synopsis: Duplicates the token passed in if it is primary, and assigns // it to the thread that called. // // Arguments: [hToken] -- // // History: 1-10-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL WINAPI ImpersonateLoggedOnUser( HANDLE hToken ) { TOKEN_TYPE Type; ULONG cbType; HANDLE hImpToken; NTSTATUS Status; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjectAttributes; BOOL fCloseImp; Status = NtQueryInformationToken( hToken, TokenType, &Type, sizeof(TOKEN_TYPE), &cbType); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return(FALSE); } if (Type == TokenPrimary) { InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL); SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken( hToken, TOKEN_IMPERSONATE | TOKEN_QUERY, &ObjectAttributes, FALSE, TokenImpersonation, &hImpToken ); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return(FALSE); } fCloseImp = TRUE; } else { hImpToken = hToken; fCloseImp = FALSE; } Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID) &hImpToken, sizeof(hImpToken) ); if (fCloseImp) { (void) NtClose(hImpToken); } if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return(FALSE); } return(TRUE); } //+--------------------------------------------------------------------------- // // Function: L32SetProcessToken // // Synopsis: Sets the primary token for the new process. // // Arguments: [psd] -- // [hProcess] -- // [hThread] -- // [hToken] -- // // History: 4-25-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL L32SetProcessToken( HANDLE hProcess, HANDLE hThread, HANDLE hTokenToAssign, BOOL AlreadyImpersonating ) { NTSTATUS Status, AdjustStatus; PROCESS_ACCESS_TOKEN PrimaryTokenInfo; BOOLEAN WasEnabled; HANDLE NullHandle; // // Set the process's primary token. This is actually much more complex // to implement in a single API, but we'll live with it. This MUST be // called when we are not impersonating! The client generally does *not* // have the SeAssignPrimary privilege // // // Enable the required privilege // if ( !AlreadyImpersonating ) { Status = RtlImpersonateSelf( SecurityImpersonation ); } else { Status = STATUS_SUCCESS ; } if ( NT_SUCCESS( Status ) ) { // // We now allow restricted tokens to passed in, so we don't // fail if the privilege isn't held. Let the kernel deal with // the possibilities. // Status = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, TRUE, TRUE, &WasEnabled); if ( !NT_SUCCESS( Status ) ) { WasEnabled = TRUE ; // Don't try to restore it. } PrimaryTokenInfo.Token = hTokenToAssign; PrimaryTokenInfo.Thread = hThread; Status = NtSetInformationProcess( hProcess, ProcessAccessToken, (PVOID)&PrimaryTokenInfo, (ULONG)sizeof(PROCESS_ACCESS_TOKEN) ); // // Restore the privilege to its previous state // if (!WasEnabled) { AdjustStatus = RtlAdjustPrivilege(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE, WasEnabled, TRUE, &WasEnabled); if (NT_SUCCESS(Status)) { Status = AdjustStatus; } } // // Revert back to process. // if ( !AlreadyImpersonating ) { NullHandle = NULL; AdjustStatus = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID) &NullHandle, sizeof( HANDLE ) ); if ( NT_SUCCESS( Status ) ) { Status = AdjustStatus; } } } else { NOTHING; } if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); } return (NT_SUCCESS(Status)); } //+--------------------------------------------------------------------------- // // Function: L32SetProcessQuotas // // Synopsis: Updates the quotas for the process // // Arguments: [hProcess] -- // // History: 4-25-95 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL L32SetProcessQuotas( HANDLE hProcess, BOOL AlreadyImpersonating ) { NTSTATUS Status = STATUS_SUCCESS; NTSTATUS AdjustStatus = STATUS_SUCCESS; QUOTA_LIMITS RequestedLimits; BOOLEAN WasEnabled; HANDLE NullHandle; RequestedLimits = Logon32QuotaLimits; RequestedLimits.MinimumWorkingSetSize = 0; RequestedLimits.MaximumWorkingSetSize = 0; // // Set the process's quota. This MUST be // called when we are not impersonating! The client generally does *not* // have the SeIncreaseQuota privilege. // if ( !AlreadyImpersonating ) { Status = RtlImpersonateSelf( SecurityImpersonation ); } if ( NT_SUCCESS( Status ) ) { if (RequestedLimits.PagedPoolLimit != 0) { Status = RtlAdjustPrivilege(SE_INCREASE_QUOTA_PRIVILEGE, TRUE, TRUE, &WasEnabled); if ( NT_SUCCESS( Status ) ) { Status = NtSetInformationProcess( hProcess, ProcessQuotaLimits, (PVOID)&RequestedLimits, (ULONG)sizeof(QUOTA_LIMITS) ); if (!WasEnabled) { AdjustStatus = RtlAdjustPrivilege(SE_INCREASE_QUOTA_PRIVILEGE, WasEnabled, FALSE, &WasEnabled); if (NT_SUCCESS(Status)) { Status = AdjustStatus; } } } } if ( !AlreadyImpersonating ) { NullHandle = NULL; AdjustStatus = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, (PVOID) &NullHandle, sizeof( HANDLE ) ); if ( NT_SUCCESS( Status ) ) { Status = AdjustStatus; } } } if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return(FALSE); } return(TRUE); } BOOL L32CreateTokenForNewProcess( PSECURITY_DESCRIPTOR psd, HANDLE hToken, PHANDLE phTokenToAssign ) { OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; // // Check for a NULL token. (No need to do anything) // The process will run in the parent process's context and inherit // the default ACL from the parent process's token. // if (hToken == NULL) { *phTokenToAssign = NULL; return TRUE; } // // A primary token can only be assigned to one process. // Duplicate the logon token so we can assign one to the new // process. // InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, psd ); Status = NtDuplicateToken( hToken, // Duplicate this token 0, // Same desired access &ObjectAttributes, FALSE, // EffectiveOnly TokenPrimary, // TokenType phTokenToAssign // Duplicate token handle stored here ); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return FALSE; } return TRUE; } HANDLE L32RevertOpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId ) /*+ A revert to self wrapper around OpenProcess -*/ { HANDLE hThreadToken = NULL; HANDLE hRevertToken = NULL; HANDLE hProcess = NULL; BOOL bImp = FALSE; NTSTATUS Status = STATUS_SUCCESS; // // If we are impersonating we must revert. // Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hThreadToken ); if (NT_SUCCESS(Status)) { bImp = TRUE; // // Stop impersonating. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hRevertToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); goto Cleanup; } else { bImp = FALSE; } } else if (Status == STATUS_NO_TOKEN) { hThreadToken = NULL; bImp = FALSE; } else { // // Why couldn't we open the thread token? // BaseSetLastNTError(Status); ASSERT(NT_SUCCESS(Status)); goto Cleanup; } // // OpenProcess without impersonating. // hProcess = OpenProcess( dwDesiredAccess, bInheritHandle, dwProcessId ); if (hThreadToken) { // // Continue to impersonate. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hThreadToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); goto Cleanup; } else { bImp = TRUE; } } Cleanup: if (hThreadToken) { if (!bImp) { // // Continue to impersonate. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hThreadToken, sizeof(HANDLE) ); ASSERT(NT_SUCCESS(Status)); } NtClose(hThreadToken); } return hProcess; } BOOL L32CommonCreate( DWORD CreateFlags, HANDLE hToken, LPPROCESS_INFORMATION lpProcessInfo, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes ) /*+ This will do several tasks. 1 create new security descriptors for the process / thread / token. 2 create a new token for the new process 3 assign security to that token 4 put this new token on the new process 5 assign security to the process and thread 6 adjust quotas on the new process -*/ { NTSTATUS Status = STATUS_SUCCESS; BOOL b = TRUE; PISECURITY_DESCRIPTOR pProcessSd = NULL; PISECURITY_DESCRIPTOR pThreadSd = NULL; PISECURITY_DESCRIPTOR pTokenSd = NULL; TOKEN_TYPE Type; DWORD dwLength; BOOL bUsingThreadToken = FALSE; BOOL bUsingImpToken = FALSE; HANDLE hThreadToken = NULL; // the initial thread token, if any HANDLE hNull = NULL; // token handle for reverting HANDLE hTokenToAssign = NULL; // primary token to place on new process HANDLE hImpToken = NULL; // impersonation version of hTokenToAssign OBJECT_ATTRIBUTES ObjectAttributes = {0}; // // Please forgive me. // GENERIC_MAPPING ProcessMapping = { STANDARD_RIGHTS_READ |PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, STANDARD_RIGHTS_WRITE |PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD |PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE |PROCESS_TERMINATE | PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | PROCESS_SET_PORT, STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE, PROCESS_ALL_ACCESS }; GENERIC_MAPPING ThreadMapping = { STANDARD_RIGHTS_READ |THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, STANDARD_RIGHTS_WRITE |THREAD_TERMINATE | THREAD_SUSPEND_RESUME | THREAD_ALERT |THREAD_SET_INFORMATION | THREAD_SET_CONTEXT, STANDARD_RIGHTS_EXECUTE |SYNCHRONIZE, THREAD_ALL_ACCESS }; GENERIC_MAPPING TokenMapping = { TOKEN_READ, TOKEN_WRITE, TOKEN_EXECUTE, TOKEN_ALL_ACCESS }; // // Sanity. // if (lpProcessInfo->hProcess == NULL) { b = FALSE; BaseSetLastNTError(STATUS_INVALID_HANDLE); goto Cleanup; } #ifdef ALLOW_IMPERSONATION_TOKENS HANDLE hTempToken = NULL; #endif // // Determine type of token, since a non primary token will not work // on a process. Now, we could duplicate it into a primary token, // and whack it into the process, but that leaves the process possibly // without credentials. // Status = NtQueryInformationToken( hToken, TokenType, (PUCHAR) &Type, sizeof(Type), &dwLength ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } if (Type != TokenPrimary) { #ifdef ALLOW_IMPERSONATION_TOKENS // // Make this a primary token. // InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL); SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken( hToken, TOKEN_IMPERSONATE | TOKEN_QUERY, &ObjectAttributes, FALSE, TokenPrimary, &hTempToken ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } hToken = hTempToken; #else // !ALLOW_IMPERSONATION_TOKENS b = FALSE; Status = STATUS_BAD_TOKEN_TYPE; BaseSetLastNTError(Status); goto Cleanup; #endif } // // Make our security descriptors grant ownership and permissions to the principal // represented by hToken. We need a SD for the process, thread, and the token // that is getting placed on the new process. // // // If we are impersonating we must revert because CreatePrivateObjectSecurityEx will // call RtlpGetDefaultsSubjectContext, which will try to open the process token. // The thread token (if it exists) will most likely not have this access. // Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hThreadToken ); if (NT_SUCCESS(Status)) { // // Stop impersonating. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingThreadToken = FALSE; } else if (Status == STATUS_NO_TOKEN) { hThreadToken = NULL; bUsingThreadToken = FALSE; } else { // // Why couldn't we open the thread token? // ASSERT(NT_SUCCESS(Status)); b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } // // We should not be impersonating. // ASSERT(!bUsingThreadToken && !bUsingImpToken); // // Ignore the owner check as the owner in the passed SD may not be assignable as an owner // given our current security context. // b = CreatePrivateObjectSecurityEx( NULL, lpProcessAttributes ? lpProcessAttributes->lpSecurityDescriptor : NULL, &pProcessSd, NULL, FALSE, SEF_AVOID_OWNER_CHECK, hToken, &ProcessMapping ); if (!b) { goto Cleanup; } b = CreatePrivateObjectSecurityEx( NULL, lpThreadAttributes ? lpThreadAttributes->lpSecurityDescriptor : NULL, &pThreadSd, NULL, FALSE, SEF_AVOID_OWNER_CHECK, hToken, &ThreadMapping ); if (!b) { goto Cleanup; } b = CreatePrivateObjectSecurityEx( NULL, NULL, &pTokenSd, NULL, FALSE, SEF_AVOID_OWNER_CHECK, hToken, &TokenMapping ); if (!b) { goto Cleanup; } // // We need an impersonation version of hToken so that we can later assign // these SDs to the process and threads. The SDs we created specify hToken // as the owner (assuming no passed SDs with owners), so only a thread // impersonating as hToken can assign them to objects successfully // (else we fail with INVALID_OWNER). // b = DuplicateTokenEx( hToken, TOKEN_QUERY | TOKEN_IMPERSONATE, NULL, SecurityImpersonation, TokenImpersonation, &hImpToken ); if (!b) { goto Cleanup; } // // Create a new token to put on the process. Make this a duplicate of // the passed hToken. We are not impersonating here. // ASSERT(!bUsingThreadToken && !bUsingImpToken); b = L32CreateTokenForNewProcess( pTokenSd, hToken, &hTokenToAssign ); if (!b) { // // Try again under impersonation: if the Owner in pTokenSd isn't assignable // when we run as the process, it will work with the new impersonation // version of hToken (since pTokenSd states that the hToken principal is to // be assigned as the Owner of hTokenToAssign). // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hImpToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingImpToken = TRUE; b = L32CreateTokenForNewProcess( pTokenSd, hToken, &hTokenToAssign ); if (!b) { goto Cleanup; } // // Revert. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingImpToken = FALSE; } // // Now hTokenToAssign exists as a duplicate of hToken and it has proper security // which grants access and ownership to the hToken principal. Set the primary token // of the new process to be hTokenToAssign. Try this first without impersonating, // since the current process currently owns this new process. // ASSERT(!bUsingThreadToken && !bUsingImpToken); b = L32SetProcessToken( lpProcessInfo->hProcess, lpProcessInfo->hThread, hTokenToAssign, FALSE ); if (!b) { if (hThreadToken) { // // Try again as the original thread principal. We aren't trying as the // hToken principal because that would allow anyone to create a process // as any user with the correct privileges (assuming that they could get // ahold of said user's token). If the thread token had the assign primary // privilege then we will succeed. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hThreadToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingThreadToken = TRUE; b = L32SetProcessToken( lpProcessInfo->hProcess, lpProcessInfo->hThread, hTokenToAssign, TRUE ); if (!b) { goto Cleanup; } // // Revert. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingThreadToken = FALSE; } else { // // The process doesn't have rights to assign the new process // a primary token, and we weren't called with an impersonation // context. We must give up. // goto Cleanup; } } // // Adjust the quota to something reasonable. // ASSERT(!bUsingThreadToken && !bUsingImpToken); b = L32SetProcessQuotas( lpProcessInfo->hProcess, FALSE ); if (!b) { if (hThreadToken) { // // If we failed to adjust quota as the process then try // while impersonating as the original thread token. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hThreadToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingThreadToken = TRUE; b = L32SetProcessQuotas( lpProcessInfo->hProcess, TRUE ); if (!b) { goto Cleanup; } // // Revert. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingThreadToken = FALSE; } else { // // We cannot adjust the quota as the process, and we were // not called while impersonating. Fail. // goto Cleanup; } } // // We should not be impersonating here. // ASSERT(!bUsingThreadToken && !bUsingImpToken); // // Now put the correct SD on the process / thread. // b = SetKernelObjectSecurity( lpProcessInfo->hProcess, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, pProcessSd ); if (!b) { // // If we failed above the cause was most likely because the owner ID in // the SD does not exist as an assignable owner ID in the current process token. // Impersonating as hImpToken will take care of this. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hImpToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingImpToken = TRUE; // // Try again as hImpToken. // b = SetKernelObjectSecurity( lpProcessInfo->hProcess, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, pProcessSd ); if (!b) { goto Cleanup; } // // Revert. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingImpToken = FALSE; } // // Now put it on the thread. // b = SetKernelObjectSecurity( lpProcessInfo->hThread, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, pThreadSd ); if (!b) { // // If we failed above the cause was most likely because the owner ID in // the SD does not exist as an assignable owner ID in the current process token. // Impersonating as hImpToken will take care of this. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hImpToken, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingImpToken = TRUE; // // Try again as hImpToken. // b = SetKernelObjectSecurity( lpProcessInfo->hThread, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, pThreadSd ); if (!b) { goto Cleanup; } // // Revert. // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); if (!NT_SUCCESS(Status)) { b = FALSE; BaseSetLastNTError(Status); goto Cleanup; } bUsingImpToken = FALSE; } // // If we're not supposed to leave it suspended then resume the // thread and let it run. // if ((CreateFlags & COMMON_CREATE_SUSPENDED) == 0) { ResumeThread(lpProcessInfo->hThread); } // // That's it! // goto Cleanup; Cleanup: #ifdef ALLOW_IMPERSONATION_TOKENS if (hTempToken) { NtClose(hTempToken); } #endif // // Free our new security descriptors. // if (pTokenSd) { DestroyPrivateObjectSecurity(&pTokenSd); } if (pProcessSd) { DestroyPrivateObjectSecurity(&pProcessSd); } if (pThreadSd) { DestroyPrivateObjectSecurity(&pThreadSd); } if (hTokenToAssign) { NtClose(hTokenToAssign); } // // If we are using the newly created impersonation token // then revert. // if (hImpToken) { if (bUsingImpToken) { Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hNull, sizeof(HANDLE) ); // // We should only ASSERT here because we don't want to // overwrite our real error codes. // ASSERT(NT_SUCCESS(Status)); } NtClose(hImpToken); } // // Resume original impersonation if that is how we were called. // if (hThreadToken) { if (!bUsingThreadToken) { Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hThreadToken, sizeof(HANDLE) ); // // We should only ASSERT here because we don't want to // overwrite our real error codes. // ASSERT(NT_SUCCESS(Status)); } NtClose(hThreadToken); } if (!b) { if (lpProcessInfo->hProcess) { NtTerminateProcess( lpProcessInfo->hProcess, ERROR_ACCESS_DENIED ); NtClose(lpProcessInfo->hProcess); } if (lpProcessInfo->hThread) { NtClose(lpProcessInfo->hThread); } RtlZeroMemory( lpProcessInfo, sizeof(PROCESS_INFORMATION) ); } return b; } //+--------------------------------------------------------------------------- // // Function: SaferiReplaceProcessThreadTokens // // Synopsis: // Provides a privately exported function to replace the access token // of a process and its primary thread of a new process before its // execution has begun. The process is left in a suspended state // after the token modification has been performed. // // Effects: // // Arguments: [NewTokenHandle] -- Primary token to use // [ProcessHandle] -- Process handle // [ThreadHandle] -- Handle of process's primary Thread // // History: 8-25-2000 JLawson Created // // Notes: // This is merely a wrapper function that calls L32CommonCreate. // //---------------------------------------------------------------------------- BOOL WINAPI SaferiReplaceProcessThreadTokens( IN HANDLE NewTokenHandle, IN HANDLE ProcessHandle, IN HANDLE ThreadHandle ) { PROCESS_INFORMATION TempProcessInfo; RtlZeroMemory( &TempProcessInfo, sizeof( PROCESS_INFORMATION ) ); TempProcessInfo.hProcess = ProcessHandle; TempProcessInfo.hThread = ThreadHandle; return (L32CommonCreate( COMMON_CREATE_PROCESSSD | COMMON_CREATE_THREADSD | COMMON_CREATE_SUSPENDED, NewTokenHandle, &TempProcessInfo, NULL, NULL)); } //+--------------------------------------------------------------------------- // // MarshallString // // Marshall in a UNICODE_NULL terminated WCHAR string // // ENTRY: // pSource (input) // Pointer to source string // // pBase (input) // Base buffer pointer for normalizing the string pointer // // MaxSize (input) // Maximum buffer size available // // ppPtr (input/output) // Pointer to the current context pointer in the marshall buffer. // This is updated as data is marshalled into the buffer // // pCount (input/output) // Current count of data in the marshall buffer. // This is updated as data is marshalled into the buffer // // EXIT: // NULL - Error // !=NULL "normalized" pointer to the string in reference to pBase // //+--------------------------------------------------------------------------- PWCHAR MarshallString( PCWSTR pSource, PCHAR pBase, ULONG MaxSize, PCHAR *ppPtr, PULONG pCount ) { ULONG Len; PCHAR ptr; Len = wcslen( pSource ); Len++; // include the NULL; Len *= sizeof(WCHAR); // convert to bytes if( (*pCount + Len) > MaxSize ) { return( NULL ); } RtlMoveMemory( *ppPtr, pSource, Len ); // // the normalized ptr is the current count // // Sundown note: ptr is a zero-extension of *pCount. ptr = (PCHAR)ULongToPtr(*pCount); *ppPtr += Len; *pCount += Len; return((PWCHAR)ptr); } #if DBG void DumpOutLastErrorString() { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); // // Process any inserts in lpMsgBuf. // ... // Display the string. // KdPrint(("%s\n", (LPCTSTR)lpMsgBuf )); // // Free the buffer. // LocalFree( lpMsgBuf ); } #endif #ifdef DBG #define DBG_DumpOutLastError DumpOutLastErrorString(); #else #define DBG_DumpOutLastError #endif //+--------------------------------------------------------------------------- // // This function was originally defined in \nt\private\ole32\dcomss\olescm\execclt.cxx // // CreateRemoteSessionProcessW() // // Create a process on the given Terminal Server Session. This is in UNICODE // // ENTRY: // SessionId (input) // SessionId of Session to create process on // // Param1 (input/output) // Comments // // Comments // The security attribs are not used by the session, they are set to NULL // We may consider to extend this feature in the future, assuming there is a // need for it. // // EXIT: // STATUS_SUCCESS - no error //+--------------------------------------------------------------------------- BOOL CreateRemoteSessionProcessW( ULONG SessionId, BOOL System, HANDLE hToken, PCWSTR lpszImageName, PCWSTR lpszCommandLine, PSECURITY_ATTRIBUTES psaProcess, // these are ignored on the session side, set to NULL PSECURITY_ATTRIBUTES psaThread, // these are ignored on the session side, set to NULL BOOL fInheritHandles, DWORD fdwCreate, LPVOID lpvEnvionment, LPCWSTR lpszCurDir, LPSTARTUPINFOW pStartInfo, LPPROCESS_INFORMATION pProcInfo ) { BOOL Result = TRUE; HANDLE hPipe = NULL; WCHAR szPipeName[EXECSRVPIPENAMELEN]; PCHAR ptr; ULONG Count, AmountWrote, AmountRead; DWORD MyProcId; PEXECSRV_REQUEST pReq; EXECSRV_REPLY Rep; CHAR Buf[EXECSRV_BUFFER_SIZE]; ULONG MaxSize = EXECSRV_BUFFER_SIZE; DWORD rc; LPVOID lpMsgBuf; ULONG envSize=0; // size of the lpEnvironemt, if any PWCHAR lpEnv; ULONG ReturnLen; #if DBG if( lpszImageName ) KdPrint(("logon32.c: CreateRemoteSessionProcessW: lpszImageName %ws\n",lpszImageName)); if( lpszCommandLine ) KdPrint(("logon32.c: CreateRemoteSessionProcessW: lpszCommandLine %ws\n",lpszCommandLine)); #endif // // Winlogon handles all now. System flag tells it what to do // Result = WinStationQueryInformation( NULL, SessionId, WinStationExecSrvSystemPipe, szPipeName, sizeof(szPipeName), &ReturnLen ); if ( !Result ) { KdPrint(("logon32.c: WinStationQueryInformation for the EXECSRV pipe name failed\n")); return(FALSE); } while ( TRUE ) { hPipe = CreateFileW( szPipeName, GENERIC_READ|GENERIC_WRITE, 0, // File share mode NULL, // default security OPEN_EXISTING, 0, // Attrs and flags NULL // template file handle ); if( hPipe == INVALID_HANDLE_VALUE ) { if (GetLastError() == ERROR_PIPE_BUSY) { if (!WaitNamedPipeW( szPipeName, 30000 )) { // 30 sec KdPrint(("logon32.c: Waited too long for pipe name %ws\n", szPipeName)); return(FALSE); } } else { DBG_DumpOutLastError; KdPrint(("logon32.c: Could not create pipe name %ws\n", szPipeName)); return(FALSE); } } else { break; } } // // Get the handle to the current process // MyProcId = GetCurrentProcessId(); // // setup the marshalling // ptr = Buf; Count = 0; pReq = (PEXECSRV_REQUEST)ptr; ptr += sizeof(EXECSRV_REQUEST); Count += sizeof(EXECSRV_REQUEST); // // set the basic parameters // pReq->System = System; pReq->hToken = hToken; pReq->RequestingProcessId = MyProcId; pReq->fInheritHandles = fInheritHandles; pReq->fdwCreate = fdwCreate; // // marshall the ImageName string // if( lpszImageName ) { pReq->lpszImageName = MarshallString( lpszImageName, Buf, MaxSize, &ptr, &Count ); if (! pReq->lpszImageName) { Result = FALSE; goto Cleanup; } } else { pReq->lpszImageName = NULL; } // // marshall in the CommandLine string // if( lpszCommandLine ) { pReq->lpszCommandLine = MarshallString( lpszCommandLine, Buf, MaxSize, &ptr, &Count ); if ( ! pReq->lpszCommandLine ) { Result = FALSE; goto Cleanup; } } else { pReq->lpszCommandLine = NULL; } // // marshall in the CurDir string // if( lpszCurDir ) { pReq->lpszCurDir = MarshallString( lpszCurDir, Buf, MaxSize, &ptr, &Count ); if ( ! pReq->lpszCurDir ) { Result = FALSE; goto Cleanup; } } else { pReq->lpszCurDir = NULL; } // // marshall in the StartupInfo structure // RtlMoveMemory( &pReq->StartInfo, pStartInfo, sizeof(STARTUPINFO) ); // // Now marshall the strings in STARTUPINFO // if( pStartInfo->lpDesktop ) { pReq->StartInfo.lpDesktop = MarshallString( pStartInfo->lpDesktop, Buf, MaxSize, &ptr, &Count ); if (! pReq->StartInfo.lpDesktop ) { Result = FALSE; goto Cleanup; } } else { pReq->StartInfo.lpDesktop = NULL; } if( pStartInfo->lpTitle ) { pReq->StartInfo.lpTitle = MarshallString( pStartInfo->lpTitle, Buf, MaxSize, &ptr, &Count ); if ( !pReq->StartInfo.lpTitle ) { Result = FALSE; goto Cleanup; } } else { pReq->StartInfo.lpTitle = NULL; } // // WARNING: This version does not pass the following: // // Also saProcess and saThread are ignored right now and use // the users default security on the remote WinStation // // Set things that are always NULL // pReq->StartInfo.lpReserved = NULL; // always NULL if ( lpvEnvionment) { for ( lpEnv = (PWCHAR) lpvEnvionment; (*lpEnv ) && (envSize + Count < MaxSize ) ; lpEnv++) { while( *lpEnv ) { lpEnv++; envSize += 2; // we are dealing with wide chars if ( envSize+Count >= MaxSize ) { // we have too many // vars in the user's profile. KdPrint(("\tEnv length too big = %d \n", envSize)); break; } } // this is the null which marked the end of the last env var. envSize +=2; } envSize += 2; // this is the final NULL if ( Count + envSize < MaxSize ) { RtlMoveMemory( (PCHAR)&Buf[Count] ,lpvEnvionment, envSize ); // SUNDOWN: Count is zero-extended and store in lpvEnvironment. // This zero-extension is valid. The consuming code [see tsext\notify\execsrv.c] // considers lpvEnvironment as an offset (<2GB). pReq->lpvEnvironment = (PCHAR)ULongToPtr(Count); ptr += envSize; // for the next guy Count += envSize; // the count used so far } else // no room left to make a complete copy { pReq->lpvEnvironment = NULL; } } else { pReq->lpvEnvironment = NULL; } // // now fill in the total count // pReq->Size = Count; #if DBG KdPrint(("pReq->Size = %d, envSize = %d \n", pReq->Size , envSize )); #endif // // Now send the buffer out to the server // Result = WriteFile( hPipe, Buf, Count, &AmountWrote, NULL ); if( !Result ) { KdPrint(("logon32.c: Error %d sending request\n",GetLastError() )); goto Cleanup; } // // Now read the reply // Result = ReadFile( hPipe, &Rep, sizeof(Rep), &AmountRead, NULL ); if( !Result ) { KdPrint(("logon32.c: Error %d reading reply\n",GetLastError())); goto Cleanup; } // // Check the result // if( !Rep.Result ) { KdPrint(("logon32.c: Error %d in reply\n",Rep.LastError)); // // set the error in the current thread to the returned error // Result = Rep.Result; SetLastError( Rep.LastError ); goto Cleanup; } // // We copy the PROCESS_INFO structure from the reply // to the caller. // // The remote site has duplicated the handles into our // process space for hProcess and hThread so that they will // behave like CreateProcessW() // RtlMoveMemory( pProcInfo, &Rep.ProcInfo, sizeof( PROCESS_INFORMATION ) ); Cleanup: CloseHandle(hPipe); KdPrint(("logon32.c:: Result 0x%x\n", Result)); return(Result); } //+--------------------------------------------------------------------------- // // Function: CreateProcessAsUserW // // Synopsis: Creates a process running as the user in hToken. // // Arguments: [hToken] -- Handle to a Primary Token to use // [lpApplicationName] -- as CreateProcess() q.v. // [lpCommandLine] -- // [lpProcessAttributes] -- // [lpThreadAttributes] -- // [bInheritHandles] -- // [dwCreationFlags] -- // [lpEnvironment] -- // [lpCurrentDirectory] -- // [lpStartupInfo] -- // [lpProcessInformation] -- // // Return Values // If the function succeeds, the return value is nonzero. // If the function fails, the return value is zero. To get extended error information, call GetLastError. // // History: 4-25-95 RichardW Created // 1-14-98 AraBern add changes for Hydra // Notes: // // //---------------------------------------------------------------------------- BOOL WINAPI CreateProcessAsUserW( HANDLE hToken, LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { DWORD CreateFlags; DWORD clientSessionID=0; DWORD currentSessionID=0; DWORD resultLength; HANDLE hTmpToken; DWORD curProcId ; NTSTATUS Status ; CreateFlags = (dwCreationFlags & CREATE_SUSPENDED ? COMMON_CREATE_SUSPENDED : 0); // // get the sessionID (if zero then it means that we are on the console). // currentSessionID = NtCurrentPeb()->SessionId; if ( !GetTokenInformation ( hToken, TokenSessionId , &clientSessionID,sizeof( DWORD), &resultLength ) ) { // // get the access token for the client of this call // get token instead of process since the client might have only // impersonated the thread, not the process // DBG_DumpOutLastError; ASSERT( FALSE ); currentSessionID = 0; // // We should probably return FALSE here, but at this time we don't want to alter the // non-Hydra code-execution-flow at all. // } // KdPrint(("logon32.c: CreateProcessAsUserW(): clientSessionID = %d, currentSessionID = %d \n", // clientSessionID, currentSessionID )); if ( clientSessionID != currentSessionID ) { // // If the client session ID is not the same as the current session ID, then, we are attempting // to create a process on a remote session from the current session. // This block of code is used to accomplish such process creation, it is Terminal-Server specific // BOOL bHaveImpersonated; HANDLE hCurrentThread; HANDLE hPrevToken = NULL; DWORD rc; TOKEN_TYPE tokenType; // // We must send the request to the remote session // of the requestor // // NOTE: The current WinStationCreateProcessW() does not use // the supplied security descriptor, but creates the // process under the account of the logged on user. // // // Stop impersonating before doing the WinStationCreateProcess. // The remote winstation exec thread will launch the app under // the users context. We must not be impersonating because this // call only lets SYSTEM request the remote execute. // // // Handle Inheritance is not allowed for cross session process creation // if (bInheritHandles) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } hCurrentThread = GetCurrentThread(); // // Init bHaveImpersonated to the FALSE state // bHaveImpersonated = FALSE; // // Since the caller of this function (runas-> SecLogon service ) has already // impersonated the new (target) user, we do the OpenThreadToken with // OpenAsSelf = TRUE // if ( OpenThreadToken( hCurrentThread, TOKEN_QUERY | TOKEN_IMPERSONATE , TRUE, &hPrevToken ) ) { bHaveImpersonated = TRUE; if ( !RevertToSelf() ) { return FALSE; } } // // else, we are not impersonating, as reflected by the init value of bHaveImpersonated // rc = CreateRemoteSessionProcessW( clientSessionID, FALSE, // not creating a process for System hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation) ; // // Undo the effect of RevertToSelf() if we had impersoanted // if ( bHaveImpersonated ) { Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hPrevToken, sizeof( hPrevToken ) ); ASSERT( NT_SUCCESS(Status ) ); NtClose( hPrevToken ); } if ( rc ) { return TRUE; } else { return FALSE; } } else // // this is the standard non-Hydra related call block // { HANDLE hRestrictedToken = NULL; BOOL b = FALSE; if (!CreateProcessInternalW(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags | CREATE_SUSPENDED , lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, &hRestrictedToken)) { // // The internal routine might return a token even in the failure case // since it uses try-finally. Free the token if needed. // if (hRestrictedToken != NULL) { NtClose(hRestrictedToken); } return(FALSE); } CreateFlags |= (lpProcessAttributes ? 0 : COMMON_CREATE_PROCESSSD); CreateFlags |= (lpThreadAttributes ? 0 : COMMON_CREATE_THREADSD); if(lpProcessInformation->dwProcessId != 0) { HANDLE VdmWaitHandle = NULL; // // Check if it is a shared wow being started // if((ULONG_PTR)lpProcessInformation->hProcess & 0x2) { VdmWaitHandle = lpProcessInformation->hProcess; lpProcessInformation->hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lpProcessInformation->dwProcessId); if (lpProcessInformation->hProcess == NULL) { // // Couldn't open it. Try reverting since the new process gets security // from the process token. // lpProcessInformation->hProcess = L32RevertOpenProcess(PROCESS_ALL_ACCESS, FALSE, lpProcessInformation->dwProcessId); ASSERT(lpProcessInformation->hProcess); } } // // If a restricted token was returned, set it on the process. // Else use the token provided by the caller. // if (hRestrictedToken == NULL) { b = (L32CommonCreate(CreateFlags, hToken, lpProcessInformation, lpProcessAttributes, lpThreadAttributes)); } else { b = (L32CommonCreate(CreateFlags, hRestrictedToken, lpProcessInformation, lpProcessAttributes, lpThreadAttributes)); } // // if L32CommonCreate didn't succeed, it closes lpProcessInformation->hProcess and // zeros out lpProcessInformation, so we shouldn't be closing it again if it didn't // succeed. if(b && VdmWaitHandle) { if(lpProcessInformation->hProcess) { NtClose(lpProcessInformation->hProcess); } lpProcessInformation->hProcess = VdmWaitHandle; } } else { b = TRUE; } if (hRestrictedToken) { NtClose(hRestrictedToken); } return b; } } /***************************************************************************\ * OemToCharW * * OemToCharW(pSrc, pDst) - Translates the OEM string at pSrc into * the Unicode string at pDst. pSrc == pDst is not legal. * * History: * This function was copied from NT\windows\Core\ntuser\client\oemxlate.c * \***************************************************************************/ BOOL WINAPI ConvertOemToCharW( LPCSTR pSrc, LPWSTR pDst) { int cch; if (pSrc == NULL || pDst == NULL) { return FALSE; } else if (pSrc == (LPCSTR)pDst) { /* * MultiByteToWideChar() requires pSrc != pDst: fail this call. * LATER: Is this really true? */ return FALSE; } cch = strlen(pSrc) + 1; MultiByteToWideChar( CP_OEMCP, // Unicode -> OEM MB_PRECOMPOSED | MB_USEGLYPHCHARS, // visual map to precomposed (LPSTR)pSrc, cch, // source & length pDst, // destination cch); // max poss. precomposed length return TRUE; } //---------------------------------------------------------------------------- // // Function: OemToCharW_WithAllocation() // // Synopsis: This func will allocated memory for the string ppDst which // must be then deallocatd thru a call to LocalFree(). // If the passed in ansi string is NULL, then no memory // is allocated, and a NULL is returned // // Arguments: // LPCSTR [in] ansi string for which we want the wide version // *LPWSTR [out] the wide version of ansi string // Return: // BOOL : TRUE if no errors. // BOOL : FALSE if unable to allocated memory. // //---------------------------------------------------------------------------- BOOL WINAPI OemToCharW_WithAllocation( LPCSTR pSrc, LPWSTR *ppDst) { DWORD size; if (pSrc) { size = strlen( pSrc ); *ppDst = ( WCHAR *) LocalAlloc(LMEM_FIXED, ( size + 1 ) * sizeof( WCHAR ) ); if ( ppDst ) { ConvertOemToCharW( pSrc, *ppDst ); return TRUE; } else return FALSE; } else { *ppDst = NULL; return TRUE; } } // ANSI wrapper for CreateRemoteSessionProcessW() // BOOL CreateRemoteSessionProcessA( ULONG SessionId, BOOL System, HANDLE hToken, LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { NTSTATUS st; BOOL rc,rc2; STARTUPINFOW WCHAR_StartupInfo; PWCHAR pWCHAR_AppName, pWCHAR_CommandLine, pWCHAR_CurDir, pWCHAR_Title, pWCHAR_Desktop; pWCHAR_AppName = pWCHAR_CommandLine = pWCHAR_CurDir = pWCHAR_Title = pWCHAR_Desktop = NULL; // in case there is a premature return from this function. rc2 = FALSE; if ( !( rc = OemToCharW_WithAllocation( lpApplicationName , &pWCHAR_AppName ) )) { goto Cleanup; } if ( !( rc = OemToCharW_WithAllocation( lpCommandLine , &pWCHAR_CommandLine ) )) { goto Cleanup; } if ( !( rc = OemToCharW_WithAllocation( lpCurrentDirectory , &pWCHAR_CurDir ) )) { goto Cleanup; } if ( !( rc = OemToCharW_WithAllocation( lpStartupInfo->lpTitle , &pWCHAR_Title ) )) { goto Cleanup; } if ( !( rc = OemToCharW_WithAllocation( lpStartupInfo->lpDesktop , &pWCHAR_Desktop ) )) { goto Cleanup; } WCHAR_StartupInfo.cb = lpStartupInfo->cb ; WCHAR_StartupInfo.cbReserved2 = lpStartupInfo->cbReserved2; WCHAR_StartupInfo.dwFillAttribute = lpStartupInfo->dwFillAttribute; WCHAR_StartupInfo.dwFlags = lpStartupInfo->dwFlags; WCHAR_StartupInfo.dwX = lpStartupInfo->dwX; WCHAR_StartupInfo.dwXCountChars = lpStartupInfo->dwXCountChars; WCHAR_StartupInfo.dwXSize = lpStartupInfo->dwXSize; WCHAR_StartupInfo.dwY = lpStartupInfo->dwY; WCHAR_StartupInfo.dwYCountChars = lpStartupInfo->dwYCountChars; WCHAR_StartupInfo.dwYSize = lpStartupInfo->dwYSize; WCHAR_StartupInfo.hStdError = lpStartupInfo->hStdError; WCHAR_StartupInfo.hStdInput = lpStartupInfo->hStdInput; WCHAR_StartupInfo.hStdOutput = lpStartupInfo->hStdOutput; WCHAR_StartupInfo.lpReserved2 = lpStartupInfo->lpReserved2; WCHAR_StartupInfo.wShowWindow = lpStartupInfo->wShowWindow; WCHAR_StartupInfo.lpDesktop = pWCHAR_Desktop; WCHAR_StartupInfo.lpReserved = NULL; WCHAR_StartupInfo.lpTitle = pWCHAR_Title; rc2 = CreateRemoteSessionProcessW( SessionId, System, hToken, pWCHAR_AppName , pWCHAR_CommandLine, lpProcessAttributes, lpThreadAttributes , bInheritHandles, dwCreationFlags, lpEnvironment, pWCHAR_CurDir, &WCHAR_StartupInfo, lpProcessInformation ); Cleanup: if ( !rc ) // rc is set to FALSE if an attempted memory allocation has failed. { BaseSetLastNTError(STATUS_NO_MEMORY); } if (pWCHAR_AppName) { LocalFree( pWCHAR_AppName ); } if (pWCHAR_CommandLine) { LocalFree( pWCHAR_CommandLine ); } if (pWCHAR_CurDir) { LocalFree( pWCHAR_CurDir ); } if (pWCHAR_Title) { LocalFree( pWCHAR_Title ); } if (pWCHAR_Desktop) { LocalFree( pWCHAR_Desktop ); } return rc2; } //+--------------------------------------------------------------------------- // // Function: CreateProcessAsUserA // // Synopsis: ANSI wrapper for CreateProcessAsUserW // // Arguments: [hToken] -- // [lpApplicationName] -- // [lpCommandLine] -- // [lpProcessAttributes] -- // [lpThreadAttributes] -- // [bInheritHandles] -- // [dwCreationFlags] -- // [lpEnvironment] -- // [lpCurrentDirectory] -- // [lpStartupInfo] -- // [lpProcessInformation] -- // // Return Values // If the function succeeds, the return value is nonzero. // If the function fails, the return value is zero. To get extended error information, call GetLastError. // // History: 4-25-95 RichardW Created // 1-14-98 AraBern add changes for Hydra // // Notes: // //---------------------------------------------------------------------------- BOOL WINAPI CreateProcessAsUserA( HANDLE hToken, LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { DWORD CreateFlags; DWORD clientSessionID=0; DWORD currentSessionID=0; DWORD resultLength; HANDLE hTmpToken; DWORD curProcId ; NTSTATUS Status ; CreateFlags = (dwCreationFlags & CREATE_SUSPENDED ? COMMON_CREATE_SUSPENDED : 0); // // get the session if (zero means console). // currentSessionID = NtCurrentPeb()->SessionId; if ( !GetTokenInformation ( hToken, TokenSessionId , &clientSessionID,sizeof( DWORD), &resultLength ) ) { // // get the access token for the client of this call // use get token instead of process since the client might have only // impersonated the thread, not the process // DBG_DumpOutLastError; ASSERT( FALSE ); currentSessionID = 0; // // We should probably return FALSE here, but at this time we don't want to alter the // non-Hydra code-execution-flow at all. // } KdPrint(("logon32.c: CreateProcessAsUserA(): clientSessionID = %d, currentSessionID = %d \n", clientSessionID, currentSessionID )); if ( ( clientSessionID != currentSessionID )) { // // If the client session ID is not the same as the current session ID, then, we are attempting // to create a process on a remote session from the current session. // This block of code is used to accomplish such process creation, it is Terminal-Server specific // BOOL bHaveImpersonated; HANDLE hCurrentThread; HANDLE hPrevToken = NULL; DWORD rc; TOKEN_TYPE tokenType; // // We must send the request to the remote WinStation // of the requestor // // NOTE: The current WinStationCreateProcessW() does not use // the supplied security descriptor, but creates the // process under the account of the logged on user. // // // Stop impersonating before doing the WinStationCreateProcess. // The remote winstation exec thread will launch the app under // the users context. We must not be impersonating because this // call only lets SYSTEM request the remote execute. // hCurrentThread = GetCurrentThread(); // // Init bHaveImpersonated to the FALSE state // bHaveImpersonated = FALSE; // // Since the caller of this function (runas-> SecLogon service ) has already // impersonated the new (target) user, we do the OpenThreadToken with // OpenAsSelf = TRUE // if ( OpenThreadToken( hCurrentThread, TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hPrevToken ) ) { bHaveImpersonated = TRUE; if ( !RevertToSelf() ) { return FALSE; } } // // else, we are not impersonating, as reflected by the init value of bHaveImpersonated // rc = CreateRemoteSessionProcessA( clientSessionID, FALSE, // not creating a process for System hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation) ; // // Undo the effect of RevertToSelf() if we had impersoanted // if ( bHaveImpersonated ) { Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &hPrevToken, sizeof( hPrevToken ) ); ASSERT( NT_SUCCESS(Status ) ); NtClose( hPrevToken ); } if ( rc ) { return TRUE; } else { return FALSE; } } else // // this is the standard non-Hydra related call block // { HANDLE hRestrictedToken = NULL; BOOL b = FALSE; if (!CreateProcessInternalA(hToken, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags | CREATE_SUSPENDED, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, &hRestrictedToken)) { // // The internal routine might return a token even in the failure case // since it uses try-finally. Free the token if needed. // if (hRestrictedToken != NULL) { NtClose(hRestrictedToken); } return(FALSE); } CreateFlags |= (lpProcessAttributes ? 0 : COMMON_CREATE_PROCESSSD); CreateFlags |= (lpThreadAttributes ? 0 : COMMON_CREATE_THREADSD); if(lpProcessInformation->dwProcessId != 0) { HANDLE VdmWaitHandle = NULL; // // Check if it is a shared wow being started // if((ULONG_PTR)lpProcessInformation->hProcess & 0x2) { VdmWaitHandle = lpProcessInformation->hProcess; lpProcessInformation->hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lpProcessInformation->dwProcessId); if (lpProcessInformation->hProcess == NULL) { // // Couldn't open it. Try reverting since the new process gets security // from the process token. // lpProcessInformation->hProcess = L32RevertOpenProcess(PROCESS_ALL_ACCESS, FALSE, lpProcessInformation->dwProcessId); ASSERT(lpProcessInformation->hProcess); } } // // If a restricted token was returned, set it on the process. // Else use the token provided by the caller. // if (hRestrictedToken == NULL) { b = (L32CommonCreate(CreateFlags, hToken, lpProcessInformation, lpProcessAttributes, lpThreadAttributes)); } else { b = (L32CommonCreate(CreateFlags, hRestrictedToken, lpProcessInformation, lpProcessAttributes, lpThreadAttributes)); } // // if L32CommonCreate didn't succeed, it closes lpProcessInformation->hProcess and // zeros out lpProcessInformation, so we shouldn't be closing it again if it didn't // succeed. if(b && VdmWaitHandle) { if(lpProcessInformation->hProcess) { NtClose(lpProcessInformation->hProcess); } lpProcessInformation->hProcess = VdmWaitHandle; } } else { b = TRUE; } if (hRestrictedToken) { NtClose(hRestrictedToken); } return b; } }