You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4118 lines
116 KiB
4118 lines
116 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1993.
|
|
//
|
|
// File: logon32.c
|
|
//
|
|
// Contents:
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 9-30-94 RichardW Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
#include "advapi.h"
|
|
#include <crypt.h>
|
|
#include <mpr.h>
|
|
#include <ntlsa.h>
|
|
#include <ntmsv1_0.h>
|
|
#include <wchar.h>
|
|
#include <stdlib.h>
|
|
#include <lmcons.h>
|
|
|
|
#define SECURITY_WIN32
|
|
#include <security.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#include <winbase.h>
|
|
#include <winbasep.h>
|
|
#include <execsrv.h>
|
|
#include <winsta.h>
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
}
|
|
|