/*++ Copyright (c) 1999 Microsoft Corporation Module Name : appsecdll.c Abstract : Exports a function CreateProcessNotify - this function decides whether the new process can be created. Revision History : Sep 2000 - added support for Short File Names; PowerUsers not affected by AppSec - SriramSa Author : Sriram Sampath (SriramSa) June 1999 --*/ #include "pch.h" #pragma hdrstop #include "appsecdll.h" BOOL APIENTRY DllMain ( HANDLE hInst, DWORD ul_reason, LPVOID lpReserved ) { switch (ul_reason) { case DLL_PROCESS_ATTACH : // Disable Thread Lib calls - performance optimisation DisableThreadLibraryCalls (hInst); break ; case DLL_PROCESS_DETACH : break ; } // end of switch return 1 ; UNREFERENCED_PARAMETER(hInst) ; UNREFERENCED_PARAMETER(lpReserved) ; } /*++ Routine Description : This routine determines if a process can be created based on whether it is a system process and if the user is an admin or not. Arguments : lpApplicationName - process name Reason - the reason this CreateProcessNotify is called Return Value : STATUS_SUCCESS if the process can be created ; STATUS_ACCESS_DEINIED if the process cannot be created. --*/ NTSTATUS CreateProcessNotify ( LPCWSTR lpApplicationName, ULONG Reason ) { INT size ; HKEY TSkey, list_key, learn_key ; WCHAR g_szSystemRoot[MAX_PATH] ; WCHAR CurrentProcessName[MAX_PATH] ; WCHAR LongApplicationName[MAX_PATH] ; WCHAR CorrectAppName[MAX_PATH] ; WCHAR ResolvedAppName[MAX_PATH] ; BOOL is_taskman = FALSE , is_system = FALSE ; BOOL check_flag = FALSE, taskman_flag = FALSE, add_status ; BOOL IsAppSecEnabled = TRUE ; DWORD is_enabled = 0, learn_enabled = 0, PowerUserEnabled = 0; DWORD dw, disp, error_code, CurrentSessionId, RetValue, dwTimeOut = 1000; HANDLE TokenHandle; UCHAR TokenInformation[ sizeof( TOKEN_STATISTICS ) ]; ULONG ReturnLength; LUID CurrentLUID = { 0, 0 }; LUID SystemLUID = SYSTEM_LUID; NTSTATUS Status, QueryStatus; BOOL IsMember, IsAnAdmin = FALSE; SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; PSID AdminSid = FALSE ; if ( Reason != APPCERT_IMAGE_OK_TO_RUN ) { return STATUS_SUCCESS ; } // First Check if the fEnabled key to see if Security is Enabled // This is done by checking the fEnabled key in the Registry if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, APPS_REGKEY, 0, KEY_READ, &TSkey ) != ERROR_SUCCESS ) { return STATUS_SUCCESS ; } size = sizeof(DWORD) ; if ( RegQueryValueEx( TSkey, FENABLED_KEY, NULL, NULL, (LPBYTE) &is_enabled, &size ) != ERROR_SUCCESS ) { goto error_cleanup ; } if (is_enabled == 0) { // Security is not Enabled IsAppSecEnabled = FALSE ; } // Check if the PowerUsers key in the registry is Enabled or not if ( RegQueryValueEx( TSkey, POWER_USERS_KEY, NULL, NULL, (LPBYTE) &PowerUserEnabled, &size ) != ERROR_SUCCESS ) { PowerUserEnabled = 0; } // // Check if the process which is trying to launch the new process is a system process. // This is done by querying the Token information of the current process and // comparing it's LUID with the LUID of a Process running under system context. // Status = NtOpenProcessToken( NtCurrentProcess(), TOKEN_QUERY, &TokenHandle ); if ( !NT_SUCCESS(Status) ) { is_system = TRUE ; } if ( ! is_system ) { QueryStatus = NtQueryInformationToken( TokenHandle, TokenStatistics, &TokenInformation, sizeof(TokenInformation), &ReturnLength ); if ( !NT_SUCCESS(QueryStatus) ) { goto error_cleanup ; } NtClose(TokenHandle); RtlCopyLuid( &CurrentLUID, &(((PTOKEN_STATISTICS)TokenInformation)->AuthenticationId) ); // // If the process is running in System context, // we allow it to be created without further check // The only exception to this is, we do not allow WinLogon to launch TaskManager // unless it is in the authorized list // if ( RtlEqualLuid( &CurrentLUID, &SystemLUID ) ) { is_system = TRUE ; } } // Check if Task Manager is spawned by a System Process if (is_system) { GetEnvironmentVariable( L"SystemRoot", g_szSystemRoot, MAX_PATH ) ; swprintf(CurrentProcessName, L"%s\\System32\\taskmgr.exe", g_szSystemRoot ) ; if ( _wcsicmp( CurrentProcessName, lpApplicationName ) != 0 ) { goto error_cleanup ; } } // // if not a system Process check if the user is a Administrator // This is done by comparing the SID of the current user to that of an Admin // if ( NT_SUCCESS( RtlAllocateAndInitializeSid( &SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdminSid ) ) ) { if ( CheckTokenMembership( NULL, AdminSid, &IsAnAdmin ) == 0 ) { goto error_cleanup ; } RtlFreeSid(AdminSid); } // // If the user is an Admin, see if we are in the Tracking mode // We are in Tracking mode if the LearnEnabled Flag in Registry contains the Current Session ID // if (IsAnAdmin == TRUE ) { // Check the LearnEnabled flag to see if Tracking mode if ( RegOpenKeyEx( HKEY_CURRENT_USER, LIST_REGKEY, 0, KEY_READ, &learn_key ) != ERROR_SUCCESS ) { goto error_cleanup ; } if ( RegQueryValueEx( learn_key, LEARN_ENABLED_KEY, NULL, NULL, (LPBYTE) &learn_enabled, &size ) != ERROR_SUCCESS ) { RegCloseKey(learn_key) ; goto error_cleanup ; } RegCloseKey(learn_key) ; if (learn_enabled == -1) { // Tracking is not enabled goto error_cleanup ; } else { // Tracking is enabled // now get current session and see if it is the same as // the one in which tracking is enabled // Get CurrentSessionId if ( ProcessIdToSessionId( GetCurrentProcessId(), &CurrentSessionId ) == 0 ) { goto error_cleanup ; } if (learn_enabled != CurrentSessionId) { // dont add to the list of tracked applications goto error_cleanup ; } // Tracking phase is enabled - build the list // add this process name to the AppList registry // Create the Mutex for Synchronization when adding to list g_hMutex = CreateMutex( NULL, FALSE, MUTEX_NAME ) ; if (g_hMutex == NULL) { goto error_cleanup ; } // Wait to Enter the Critical Section - wait for a max of 1 minute dw = WaitForSingleObject(g_hMutex, dwTimeOut) ; if (dw == WAIT_OBJECT_0) { // // Create the Registry Key which will hold the applications tracked // during tracking period // if ( RegCreateKeyEx( HKEY_CURRENT_USER, LIST_REGKEY, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &list_key, &disp ) != ERROR_SUCCESS) { ReleaseMutex(g_hMutex) ; CloseHandle(g_hMutex) ; goto error_cleanup ; } // Add this application name to the list in registry add_status = add_to_list ( list_key, lpApplicationName ) ; } // Done adding to the list ReleaseMutex(g_hMutex) ; // Out of the Critical Section CloseHandle(g_hMutex) ; RegCloseKey(list_key) ; goto error_cleanup ; } // ending of Tracking phase } // User is an admin // Check if user is a PowerUser if ((PowerUserEnabled == 1) && (IsPowerUser())) { goto error_cleanup ; } // User is not an admin - also it is not a system process // Check if AppSec is enabled - if yes check the authorized list of apps if (IsAppSecEnabled == FALSE) { // AppSec is not enabled - so no need to check the authorized list of apps goto error_cleanup ; } // The filename may be in a short form - first convert it into the long form RetValue = GetLongPathNameW( (LPCWSTR) lpApplicationName, LongApplicationName, MAX_PATH) ; if (RetValue == 0) { // error - so use the original app name, not the long one wcscpy(CorrectAppName, lpApplicationName) ; } else { wcscpy(CorrectAppName, LongApplicationName) ; } // // Resolve Application name - if may reside in a remote server and share // ResolveName( CorrectAppName, ResolvedAppName ); // Read the AuthorizedApplications List and compare with current Appname check_flag = check_list( TSkey, ResolvedAppName ) ; RegCloseKey(TSkey) ; // // If the current AppName is not in authorized list return ACCESS_DENIED if (check_flag == FALSE) { return STATUS_ACCESS_DENIED ; } else { return STATUS_SUCCESS ; } // // Error cleanup code // Close the Registry Key where we store authorized apps and return SUCCESS // error_cleanup : RegCloseKey(TSkey) ; return STATUS_SUCCESS; } // end of CreateProcessNotify /*++ Routine Description : This routine checks if a process name is in a specified list of authorised applications in the registry. Arguments : hkey - The handle to the registry key which has the list of authorised applications. appname - name of the process Return Value : TRUE if process is in the list of authorised applications. FALSE otherwise. --*/ BOOL check_list( HKEY hkey, LPWSTR appname ) { WCHAR c ; INT i, j = 0 ; DWORD error_code ; DWORD RetValue ; LONG value,size = 0 ; BOOL found = FALSE ; WCHAR *buffer_sent, *app ; WCHAR LongAppName[MAX_PATH] ; WCHAR AppToCompare[MAX_PATH] ; // First find out size of buffer to allocate // This buffer will hold the authorized list of apps if ( RegQueryValueEx( hkey, AUTHORIZED_APPS_LIST_KEY, NULL, NULL, (LPBYTE) NULL, &size ) != ERROR_SUCCESS ) { return TRUE ; } buffer_sent = (WCHAR *) malloc ( size * sizeof(WCHAR)) ; if (buffer_sent == NULL) { return TRUE ; } app = (WCHAR *) malloc ( size * sizeof(WCHAR)) ; if (app == NULL) { free(buffer_sent) ; return TRUE ; } memset(buffer_sent, 0, size * sizeof(WCHAR) ) ; memset(app, 0, size * sizeof(WCHAR) ) ; // Get the List of Authorized applications from the Registry if ( RegQueryValueEx( hkey, AUTHORIZED_APPS_LIST_KEY, NULL, NULL, (LPBYTE) buffer_sent, &size ) != ERROR_SUCCESS ) { free(buffer_sent) ; free(app) ; return TRUE ; } // check if the process is present in the Authorized List for(i=0 ; i <= size-1 ; i++ ) { // check for end of list if ( (buffer_sent[i] == L'\0') && (buffer_sent[i+1] == L'\0') ) { break ; } while ( buffer_sent[i] != L'\0' ) { app[j++] = buffer_sent[i++] ; } app[j++] = L'\0' ; // The filename may be in a short form - first convert it into the long form RetValue = GetLongPathNameW( (LPCWSTR) app, LongAppName, MAX_PATH) ; if (RetValue == 0) { // GetLongPathNameW failed for an app in the authorized list // maybe the file in the authorized list doesn't exist anymore wcscpy( AppToCompare, app) ; } else { wcscpy(AppToCompare, LongAppName) ; } // Compare if this app is the one that is being queried now if ( _wcsicmp(appname, AppToCompare) == 0 ) { // this process is present in the Authorized List found = TRUE ; break ; } j = 0 ; } // end of for loop free(buffer_sent) ; free(app) ; return(found) ; } // end of function /*++ Routine Description : This routine appends a process name to a list maintained in Registry Key - used in Tracking mode. Arguments : hkey - The handle to the registry key which has the list of applications tracked. appname - name of the process Return Value : TRUE if process is appended successfully. FALSE otherwise. --*/ BOOL add_to_list( HKEY hkey, LPCWSTR appname ) { WCHAR c ; INT i, j = 0 ; UINT k ; DWORD error_code ; BOOL status = FALSE ; LONG value, size = 0, new_size ; WCHAR *buffer_got, *buffer_sent ; // First find out size of buffer to allocate // This buffer will hold the applications which are tracked if ( RegQueryValueEx( hkey, TRACK_LIST_KEY, NULL, NULL, (LPBYTE) NULL, &size ) != ERROR_SUCCESS ) { return (status) ; } buffer_got = (WCHAR *) malloc ( size * sizeof(WCHAR)) ; if (buffer_got == NULL) { return (status); } memset(buffer_got, 0, size * sizeof(WCHAR) ) ; // Get the present list of tracked processes in buffer_got if ( RegQueryValueEx( hkey, TRACK_LIST_KEY, NULL, NULL, (LPBYTE) buffer_got, &size ) != ERROR_SUCCESS ) { free(buffer_got) ; return (status) ; } // Append the present process to the track list // Prepare buffer to hold it // Size of new buffer will be the sum of the old buffer size // and the size of the new application + one byte for the terminating NULL char (in bytes) // new_size = size + (wcslen(appname) + 1) * sizeof(WCHAR) ; buffer_sent = (WCHAR *) malloc (new_size) ; if (buffer_sent == NULL) { free(buffer_got) ; return (status); } memset( buffer_sent, 0, new_size ) ; // check if this is the FIRST entry // If so size will be 2 - corresponding to 2 NULL chars in a empty list if ( size == 2 ) { // this is the first entry wcscpy(buffer_sent,appname) ; j = wcslen(buffer_sent) ; j++ ; buffer_sent[j] = L'\0' ; } else { // size > 2 - append this process to the end of track list for(i=0 ; i <= size-1 ; i++ ) { if ( (buffer_got[i] == L'\0') && (buffer_got[i+1] == L'\0') ) { break ; } buffer_sent[j++] = buffer_got[i] ; } // end of for loop buffer_sent[j++] = L'\0' ; for(k=0 ; k <= wcslen(appname) - 1 ; k++) { buffer_sent[j++] = (WCHAR) appname[k] ; } buffer_sent[j++] = L'\0' ; buffer_sent[j] = L'\0' ; } // size > 2 // write the new track list into registry if ( RegSetValueEx( hkey, L"ApplicationList", 0, REG_MULTI_SZ, (CONST BYTE *) buffer_sent, (j+1) * sizeof(WCHAR) ) != ERROR_SUCCESS ) { // Free all the buffers which were allocated free(buffer_got) ; free(buffer_sent) ; return (status) ; } status = TRUE ; // Free the buffers allocated free(buffer_got) ; free(buffer_sent) ; return(status) ; } // end of function /*++ Routine Description : This Routine checks if the application resides in a local drive or a remote network share. If it is a remote share, the UNC path of the application is returned. Arguments : appname - name of the application Return Value : The UNC path of the appname if it resides in a remote server share. The same appname if it resides in a local drive. --*/ VOID ResolveName( LPCWSTR appname, WCHAR *ResolvedName ) { UINT i ; INT length ; WCHAR LocalName[3] ; WCHAR RootPathName[4] ; WCHAR RemoteName[MAX_PATH] ; DWORD size = MAX_PATH ; DWORD DriveType, error_status ; // // ResolvedName will hold the name of the UNC path of the appname if it is in // a remote server and share memset(ResolvedName, 0, MAX_PATH * sizeof(WCHAR)) ; // check if appname is a app in local drive or remote server share // Parse the first 3 chars in appname to get the root directory of the drive // where it resides wcsncpy(RootPathName, appname, 3 ) ; RootPathName[3] = L'\0'; // Find the type of the Drive where the app is DriveType = GetDriveType(RootPathName) ; if (DriveType == DRIVE_REMOTE) { // Use WNetGetConnection to get the name of the remote share // Parse the first two chars of the appname to get the local drive // which is mapped onto the remote server and share wcsncpy(LocalName, appname, 2 ) ; LocalName[2] = L'\0' ; error_status = WNetGetConnection ( LocalName, RemoteName, &size ) ; if (error_status != NO_ERROR) { wcscpy(ResolvedName,appname) ; return ; } // // Prepare ResolvedName - it will contain the Remote Server and Share name // followed by a \ and then the appname // wcscpy( ResolvedName, RemoteName ) ; length = wcslen(ResolvedName) ; ResolvedName[length++] = L'\\' ; for (i = 3 ; i <= wcslen(appname) ; i++ ) { ResolvedName[length++] = appname[i] ; } ResolvedName[length] = L'\0' ; return ; } else { // This application is in local drive and not in a remote server and share // Just send the appname back to the calling function wcscpy(ResolvedName,appname) ; return ; } } /*++ Routine Description - This function checks if the present User belongs to the group of PowerUser. Arguments - none Return Value - TRUE is the User belongs to the Group of PowerUser FALSE if not. --*/ BOOL IsPowerUser(VOID) { BOOL IsMember, IsAnPower; SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; PSID PowerSid; if (RtlAllocateAndInitializeSid( &SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0, 0, &PowerSid ) != STATUS_SUCCESS) { IsAnPower = FALSE; } else { if (!CheckTokenMembership( NULL, PowerSid, &IsMember)) { IsAnPower = FALSE; } else { IsAnPower = IsMember; } RtlFreeSid(PowerSid); } return IsAnPower; }// end of function IsPowerUser