#include "precomp.h" #pragma hdrstop /*++ Copyright (c) 1990 Microsoft Corporation Module Name: security.c Abstract: Security related functions in the setupdll Detect Routines: 1. GetUserAccounts. Lists down all the user accounts in the system 2. GetComputerName. Finds the computer name. Install Routines Workers: 1. CheckPrivilegeExistsWorker. 2. EnablePrivilegeWorker. General Subroutines: 1. AdjustPrivilege. 2. RestorePrivilege. Author: Sunil Pai (sunilp) April 1992 --*/ // // Routines which are used to force deletion of a file by taking ownership // of the file // BOOL AssertTakeOwnership( HANDLE TokenHandle, PTOKEN_PRIVILEGES OldPrivs ); BOOL GetTokenHandle( PHANDLE TokenHandle ); VOID RestoreTakeOwnership( HANDLE TokenHandle, PTOKEN_PRIVILEGES OldPrivs ); BOOL FForceDeleteFile( LPSTR szPath ); //====================== // DETECT ROUTINES //======================= // // Get current users account name // CB GetMyUserName( IN RGSZ Args, IN USHORT cArgs, OUT SZ ReturnBuffer, IN CB cbReturnBuffer ) /*++ Routine Description: DetectRoutine for GetUserName. This finds out the username of the logged in account. Arguments: Args - C argument list to this detect routine (None exist) cArgs - Number of arguments. ReturnBuffer - Buffer in which detected value is returned. cbReturnBuffer - Buffer Size. Return value: Returns length of detected value. --*/ { CHAR UserName[MAX_PATH]; CHAR DomainName[MAX_PATH]; DWORD cbUserName = MAX_PATH; DWORD cbDomainName = MAX_PATH; SID_NAME_USE peUse; BOOL bStatus = FALSE; HANDLE hToken = NULL; TOKEN_USER *ptu = NULL; DWORD cbTokenBuffer = 0; CB Length; #define DEFAULT_USERNAME "" Unused(Args); Unused(cArgs); Unused(cbReturnBuffer); if( !OpenProcessToken( GetCurrentProcess(), TOKEN_READ, &hToken ) ) { goto err; } // // Get space needed for process token information // if( !GetTokenInformation( hToken, TokenUser, (LPVOID)NULL, 0, &cbTokenBuffer) ) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { goto err; } } // // Allocate space for token user information // if ( (ptu = (TOKEN_USER *)SAlloc(cbTokenBuffer)) == NULL ) { goto err; } // // Query token user information again // if( !GetTokenInformation( hToken, TokenUser, (LPVOID)ptu, cbTokenBuffer, &cbTokenBuffer) ) { goto err; } // // Query the user name and return it // if( LookupAccountSid( NULL, ptu->User.Sid, UserName, &cbUserName , DomainName, &cbDomainName, &peUse) ) { lstrcpy( ReturnBuffer, DomainName ); lstrcat( ReturnBuffer, "\\" ); lstrcat( ReturnBuffer, UserName ); Length = lstrlen( ReturnBuffer ) + 1; bStatus = TRUE; } err: if( !bStatus ) { lstrcpy( ReturnBuffer, DEFAULT_USERNAME ); Length = lstrlen( DEFAULT_USERNAME ) + 1; } if( hToken != NULL ) { CloseHandle( hToken ); } if( ptu ) { SFree( ptu ); } return( Length ); } // // Get User Accounts // CB GetUserAccounts( IN RGSZ Args, IN USHORT cArgs, OUT SZ ReturnBuffer, IN CB cbReturnBuffer ) /*++ Routine Description: DetectRoutine for UserAccounts. This routine enumerates all the user accounts under HKEY_USERS and returns the tuplet for every user found. The detected value will have the following form: { {sid-string1, user-name1}, {sid-string2, user-name2} ... } Arguments: Args - C argument list to this detect routine (None exist) cArgs - Number of arguments. ReturnBuffer - Buffer in which detected value is returned. cbReturnBuffer - Buffer Size. Return value: Returns length of detected value. --*/ { HKEY hProfile, hSubKey; CHAR SubKeyName[MAX_PATH]; CHAR UserName[MAX_PATH]; CHAR DomainName[MAX_PATH]; CHAR ProfilePath[MAX_PATH]; CHAR Class[MAX_PATH]; DWORD cbSubKeyName; DWORD cbUserName; DWORD cbDomainName; DWORD cbProfilePath; DWORD cbClass; FILETIME FileTime; UINT Index; LONG Status; BOOL bStatus; RGSZ rgszUsers, rgszCurrent; SZ sz; CB Length; CHAR UnknownUser[MAX_PATH]; DWORD UnknownUserNum; CHAR UnknownUserChar[10]; PSID pSid; SID_NAME_USE peUse; Unused(Args); Unused(cArgs); Unused(cbReturnBuffer); #define NO_ACCOUNTS "{}" // // Load the string to use as the unknown user string. We will append // it with numbers // LoadString( MyDllModuleHandle, IDS_STRING_UNKNOWN_USER, UnknownUser, MAX_PATH ); UnknownUserNum = 1; // // Enumerate keys under HKEY_USERS, for each user see if it is a // a .Default (reject this), get the sid value and convert the // sid to a user name. Add the subkey (this is a sid-string) and // the username as an account. // // // Intialise users list to no users // rgszUsers = RgszAlloc(1); // // open the key to the profile tree // Status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList", 0, KEY_READ, &hProfile ); if (Status == ERROR_SUCCESS) { // // Profile key exists, enumerate the profiles under this key // for ( Index = 0 ; ; Index++ ) { // // Get the current sub-key // cbSubKeyName = MAX_PATH; cbClass = MAX_PATH; cbUserName = MAX_PATH; cbDomainName = MAX_PATH; cbProfilePath = MAX_PATH; Status = RegEnumKeyEx( hProfile, Index, SubKeyName, &cbSubKeyName, NULL, Class, &cbClass, &FileTime ); if ( Status != ERROR_SUCCESS ) { break; } // // Open the subkey // Status = RegOpenKeyEx( hProfile, SubKeyName, 0, KEY_READ, &hSubKey ); if ( Status != ERROR_SUCCESS) { continue; } if( !lstrcmpi( SubKeyName, "THE_USER" ) ) { lstrcpy( UserName, "THE_USER" ); goto skip_1; } // // Get the User name for this profile, by looking up the sid // value in the user key and then looking up the sid. // pSid = (PSID)GetValueEntry( hSubKey, "Sid" ); if (!pSid) { RegCloseKey( hSubKey ); continue; } // // Convert the Sid into Username // bStatus = LookupAccountSid( NULL, pSid, UserName, &cbUserName, DomainName, &cbDomainName, &peUse ); SFree( pSid ); if( !bStatus ) { RegCloseKey( hSubKey ); continue; } lstrcat( DomainName, "\\" ); if(!lstrcmpi(UserName, "")) { lstrcat(DomainName, UnknownUser); _ultoa( UnknownUserNum, UnknownUserChar, 10 ); lstrcat(DomainName, UnknownUserChar); UnknownUserNum++; } else { lstrcat(DomainName, UserName); } skip_1: // // Get the profilepath for this subkey, check to see if profilepath // exists // bStatus = HUserKeyToProfilePath( hSubKey, ProfilePath, &cbProfilePath ); if( !bStatus ) { RegCloseKey( hSubKey ); continue; } RegCloseKey( hSubKey ); // // Form the list entry for this user // rgszCurrent = RgszAlloc(4); rgszCurrent[0] = SzDup ( SubKeyName ); rgszCurrent[1] = SzDup ( DomainName ); rgszCurrent[2] = SzDup ( ProfilePath ); rgszCurrent[3] = NULL; // // Add this user to the list of users // sz = SzListValueFromRgsz( rgszCurrent ); if ( sz ) { if( !RgszAdd ( &rgszUsers, sz ) ) { SFree( sz ); } } RgszFree ( rgszCurrent ); } RegCloseKey( hProfile ); } sz = SzListValueFromRgsz( rgszUsers ); RgszFree( rgszUsers ); if ( sz ) { lstrcpy( ReturnBuffer, sz ); Length = lstrlen( sz ) + 1; SFree ( sz ); } else { lstrcpy( ReturnBuffer, NO_ACCOUNTS ); Length = lstrlen( NO_ACCOUNTS ) + 1; } return ( Length ); } //======================== // INSTALL ROUTINE WORKERS //======================== BOOL CheckPrivilegeExistsWorker( IN LPSTR PrivilegeType ) /*++ Routine Description: Routine to determine whether we have a particular privilege Arguments: PrivilegeType - Name of the privilege to enable / disable Return value: TRUE if CheckTakeOwnerPrivilege succeeds, FALSE otherwise. If TRUE: ReturnTextBuffer has "YES" if privilege exists, "NO" otherwise. If FALSE: ReturnTextBuffer has error text. --*/ { LONG Privilege; TOKEN_PRIVILEGES PrevState; ULONG ReturnLength = sizeof( TOKEN_PRIVILEGES ); if ( !lstrcmpi( PrivilegeType, "SeTakeOwnershipPrivilege" ) ) { Privilege = SE_TAKE_OWNERSHIP_PRIVILEGE; } else if ( !lstrcmpi( PrivilegeType, "SeSystemEnvironmentPrivilege" ) ) { Privilege = SE_SYSTEM_ENVIRONMENT_PRIVILEGE; } else { SetErrorText(IDS_ERROR_UNSUPPORTEDPRIV); return ( FALSE ); } if ( AdjustPrivilege( Privilege, ENABLE_PRIVILEGE, &PrevState, &ReturnLength ) ) { SetReturnText( "YES" ); RestorePrivilege( &PrevState ); return ( TRUE ); } else { SetReturnText( "NO" ); return ( TRUE ); } } BOOL EnablePrivilegeWorker( LPSTR PrivilegeType, LPSTR Action ) /*++ Routine Description: Install routine to enable / disable the SE_SYSTEM_ENVIRONMENT_PRIVILEGE Arguments: PrivilegeType - Name of the privilege to enable / disable Action - Whether to enable / disable Return value: TRUE if Enable / Disable succeeds, FALSE otherwise. ReturnTextBuffer gets initialised to error text if FALSE. --*/ { ULONG Privilege; INT AdjustAction; if ( !lstrcmpi( PrivilegeType, "SeTakeOwnershipPrivilege" ) ) { Privilege = SE_TAKE_OWNERSHIP_PRIVILEGE; } else if ( !lstrcmpi( PrivilegeType, "SeSystemEnvironmentPrivilege" ) ) { Privilege = SE_SYSTEM_ENVIRONMENT_PRIVILEGE; } else { SetErrorText(IDS_ERROR_UNSUPPORTEDPRIV); return ( FALSE ); } // // Check Arg[1] .. Whether to enable / disable // if (!lstrcmpi(Action, "ENABLE")) { AdjustAction = ENABLE_PRIVILEGE; } else if (!lstrcmpi(Action, "DISABLE")) { AdjustAction = DISABLE_PRIVILEGE; } else { SetErrorText(IDS_ERROR_BADARGS); return(FALSE); } if ( !AdjustPrivilege( Privilege, AdjustAction, NULL, NULL ) ) { SetErrorText(IDS_ERROR_ADJUSTPRIVILEGE); return ( FALSE ); } else { return ( TRUE ); } } //====================================================================== // General security subroutines //====================================================================== BOOL AdjustPrivilege( IN LONG PrivilegeType, IN INT Action, IN PTOKEN_PRIVILEGES PrevState, OPTIONAL IN PULONG ReturnLength OPTIONAL ) /*++ Routine Description: Routine to enable or disable a particular privilege Arguments: PrivilegeType - Name of the privilege to enable / disable Action - ENABLE_PRIVILEGE | DISABLE_PRIVILEGE PrevState - Optional pointer to TOKEN_PRIVILEGES structure to receive the previous state of privilege. ReturnLength - Optional pointer to a ULONG to receive the length of the PrevState returned. Return value: TRUE if succeeded, FALSE otherwise. --*/ { NTSTATUS NtStatus; HANDLE Token; LUID Privilege; TOKEN_PRIVILEGES NewState; ULONG BufferLength = 0; // // Get Privilege LUID // Privilege = RtlConvertLongToLuid(PrivilegeType); NewState.PrivilegeCount = 1; NewState.Privileges[0].Luid = Privilege; // // Look at action and determine the attributes // switch( Action ) { case ENABLE_PRIVILEGE: NewState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; break; case DISABLE_PRIVILEGE: NewState.Privileges[0].Attributes = 0; break; default: return ( FALSE ); } // // Open our own token // NtStatus = NtOpenProcessToken( NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Token ); if (!NT_SUCCESS(NtStatus)) { return( FALSE ); } // // See if return buffer is present and accordingly set the parameter // of buffer length // if ( PrevState && ReturnLength ) { BufferLength = *ReturnLength; } // // Set the state of the privilege // NtStatus = NtAdjustPrivilegesToken( Token, // TokenHandle FALSE, // DisableAllPrivileges &NewState, // NewState BufferLength, // BufferLength PrevState, // PreviousState (OPTIONAL) ReturnLength // ReturnLength (OPTIONAL) ); if ( NT_SUCCESS( NtStatus ) ) { NtClose( Token ); return( TRUE ); } else { NtClose( Token ); return( FALSE ); } } BOOL RestorePrivilege( IN PTOKEN_PRIVILEGES PrevState ) /*++ Routine Description: To restore a privilege to its previous state Arguments: PrevState - Pointer to token privileges returned from an earlier AdjustPrivileges call. Return value: TRUE on success, FALSE otherwise --*/ { NTSTATUS NtStatus; HANDLE Token; // // Parameter checking // if ( !PrevState ) { return ( FALSE ); } // // Open our own token // NtStatus = NtOpenProcessToken( NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Token ); if (!NT_SUCCESS(NtStatus)) { return( FALSE ); } // // Set the state of the privilege // NtStatus = NtAdjustPrivilegesToken( Token, // TokenHandle FALSE, // DisableAllPrivileges PrevState, // NewState 0, // BufferLength NULL, // PreviousState (OPTIONAL) NULL // ReturnLength (OPTIONAL) ); if ( NT_SUCCESS( NtStatus ) ) { NtClose( Token ); return( TRUE ); } else { NtClose( Token ); return( FALSE ); } } BOOL HUserKeyToProfilePath( IN HKEY hUserKey, OUT LPSTR lpProfilePath, IN OUT LPDWORD lpcbProfilePath ) /*++ Routine Description: This finds out the ProfilePath corresponding to a user account key handle sees if the file exists and then returns the path to the profile. Arguments: hUserKey - Handle to a user account profile key lpProfilePath - Pointer to a profile path buffer which will receive the queried path. lpcbProfilePath - Pointer to the size of the profile path buffer. Input value is the size of the name buffer. Output value is the actual size of the username queried Return value: Returns TRUE for success, FALSE for failure. If TRUE lpProfilePath and lpcbProfilePath are initialized. --*/ { LONG Status; CHAR szValue[ MAX_PATH ]; DWORD dwSize = MAX_PATH; // // Get the profile path value // Status = RegQueryValueEx( hUserKey, "ProfileImagePath", NULL, NULL, szValue, &dwSize ); if( Status != ERROR_SUCCESS ) { return( FALSE ); } *lpcbProfilePath = ExpandEnvironmentStrings( (LPCSTR)szValue, lpProfilePath, *lpcbProfilePath ); // // Check if profile path exists // if ( FFileExist( lpProfilePath ) ) { return( TRUE ); } else { return( FALSE ); } } BOOL FForceDeleteFile( LPSTR szPath ) { BOOL Result; SECURITY_DESCRIPTOR SecurityDescriptor; HANDLE TokenHandle; TOKEN_PRIVILEGES OldPrivs; PSID AliasAdminsSid = NULL; SID_IDENTIFIER_AUTHORITY SepNtAuthority = SECURITY_NT_AUTHORITY; Result = AllocateAndInitializeSid( &SepNtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AliasAdminsSid ); if ( !Result ) { return( FALSE ); } Result = GetTokenHandle( &TokenHandle ); if ( !Result ) { return( FALSE ); } // // Create the security descritor with NULL DACL and Administrator as owner // InitializeSecurityDescriptor( &SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION ); Result = SetSecurityDescriptorDacl ( &SecurityDescriptor, TRUE, NULL, FALSE ); if ( !Result ) { CloseHandle( TokenHandle ); return( FALSE ); } Result = SetSecurityDescriptorOwner ( &SecurityDescriptor, AliasAdminsSid, FALSE ); if ( !Result ) { CloseHandle( TokenHandle ); return FALSE; } // // Assert TakeOwnership privilege. // Result = AssertTakeOwnership( TokenHandle, &OldPrivs ); if ( !Result ) { CloseHandle( TokenHandle ); return FALSE; } // // Make Administrator the owner of the file. // Result = SetFileSecurity( szPath, OWNER_SECURITY_INFORMATION, &SecurityDescriptor ); RestoreTakeOwnership( TokenHandle, &OldPrivs ); if ( !Result ) { CloseHandle( TokenHandle ); return( FALSE ); } // // We are now the owner, put a benign DACL onto the file // Result = SetFileSecurity( szPath, DACL_SECURITY_INFORMATION, &SecurityDescriptor ); if ( !Result ) { CloseHandle( TokenHandle ); return( FALSE ); } return( TRUE ); } BOOL GetTokenHandle( PHANDLE TokenHandle ) // // This routine will open the current process and return // a handle to its token. // // These handles will be closed for us when the process // exits. // { HANDLE ProcessHandle; BOOL Result; ProcessHandle = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId() ); if ( ProcessHandle == NULL ) { // // This should not happen // return( FALSE ); } Result = OpenProcessToken ( ProcessHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, TokenHandle ); if ( !Result ) { // // This should not happen // return( FALSE ); } return( TRUE ); } BOOL AssertTakeOwnership( HANDLE TokenHandle, PTOKEN_PRIVILEGES OldPrivs ) // // This routine turns on SeTakeOwnershipPrivilege in the current // token. Once that has been accomplished, we can open the file // for WRITE_OWNER even if we are denied that access by the ACL // on the file. { LUID TakeOwnershipValue; BOOL Result; TOKEN_PRIVILEGES TokenPrivileges; DWORD ReturnLength; // // First, find out the value of TakeOwnershipPrivilege // Result = LookupPrivilegeValue( NULL, "SeTakeOwnershipPrivilege", &TakeOwnershipValue ); if ( !Result ) { // // This should not happen // return FALSE; } // // Set up the privilege set we will need // TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.Privileges[0].Luid = TakeOwnershipValue; TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; ReturnLength = sizeof( TOKEN_PRIVILEGES ); (VOID) AdjustTokenPrivileges ( TokenHandle, FALSE, &TokenPrivileges, sizeof( TOKEN_PRIVILEGES ), OldPrivs, &ReturnLength ); if ( GetLastError() != NO_ERROR ) { return( FALSE ); } else { return( TRUE ); } } VOID RestoreTakeOwnership( HANDLE TokenHandle, PTOKEN_PRIVILEGES OldPrivs ) { (VOID) AdjustTokenPrivileges ( TokenHandle, FALSE, OldPrivs, sizeof( TOKEN_PRIVILEGES ), NULL, NULL ); }