/* Copyright (c) 2000 Microsoft Corporation File name: patch.c Author: Adrian Marinescu (adrmarin) Wed Nov 14 2001 */ #include "nt.h" #include "ntrtl.h" #include "nturtl.h" #include #include #include #include #include #include #include // // Global constants // #define PATCH_OC_INSTALL 1 #define PATCH_OC_UNINSTALL 2 #define PATCH_OC_REPLACE_FILE 3 ULONG OperationCode = 0; // // System file protection utilities // typedef HANDLE (WINAPI *CONNECTTOSFCSERVER)(PCWSTR); typedef DWORD (WINAPI *SFCFILEEXCEPTION)(HANDLE, PCWSTR, DWORD); typedef VOID (WINAPI * SFCCLOSE)(HANDLE); SFCFILEEXCEPTION pSfcFileException = NULL; CONNECTTOSFCSERVER pConnectToSfcServer = NULL; SFCCLOSE pSfcClose = NULL; HANDLE LoadSfcLibrary() { HANDLE hLibSfc; hLibSfc = LoadLibrary("SFC.DLL"); if ( hLibSfc != NULL ) { pConnectToSfcServer = (CONNECTTOSFCSERVER)GetProcAddress( hLibSfc, (LPCSTR)0x00000003 ); pSfcClose = (SFCCLOSE)GetProcAddress( hLibSfc, (LPCSTR)0x00000004 ); pSfcFileException = (SFCFILEEXCEPTION)GetProcAddress( hLibSfc, (LPCSTR)0x00000005 ); } return hLibSfc; } NTSTATUS RemoveKnownDll ( PWSTR FileName ) { WCHAR Buffer[DOS_MAX_PATH_LENGTH + 1]; int BytesCopied; UNICODE_STRING Unicode; NTSTATUS Status; OBJECT_ATTRIBUTES Obja; HANDLE Section; BytesCopied = _snwprintf( Buffer, DOS_MAX_PATH_LENGTH, L"\\KnownDlls\\%ws", FileName ); if ( BytesCopied < 0 ) { return STATUS_BUFFER_TOO_SMALL; } RtlInitUnicodeString(&Unicode, Buffer); // // open the section object // InitializeObjectAttributes (&Obja, &Unicode, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenSection (&Section, SECTION_ALL_ACCESS, &Obja); if ( !NT_SUCCESS(Status) ) { printf("%ws is not a known dll\n", FileName); return Status; } printf("%ws is a known dll. Deleting ...\n", FileName); Status = NtMakeTemporaryObject(Section); if ( !NT_SUCCESS(Status) ) { printf("NtMakeTemporaryObject failed with status %lx\n", Status); } NtClose(Section); return Status; } NTSTATUS RemoveDelayedRename( IN PUNICODE_STRING OldFileName, IN PUNICODE_STRING NewFileName, IN ULONG Index ) /*++ Routine Description: Appends the given delayed move file operation to the registry value that contains the list of move file operations to be performed on the next boot. Arguments: OldFileName - Supplies the old file name NewFileName - Supplies the new file name Return Value: NTSTATUS --*/ { OBJECT_ATTRIBUTES Obja; UNICODE_STRING KeyName; UNICODE_STRING ValueName; HANDLE KeyHandle; PWSTR ValueData, s; PKEY_VALUE_PARTIAL_INFORMATION ValueInfo; ULONG ValueLength = 1024; ULONG ReturnedLength; WCHAR ValueNameBuf[64]; NTSTATUS Status; RtlInitUnicodeString( &KeyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager" ); if ( Index == 1 ) { RtlInitUnicodeString( &ValueName, L"PendingFileRenameOperations" ); } else { swprintf(ValueNameBuf,L"PendingFileRenameOperations%d",Index); RtlInitUnicodeString( &ValueName, ValueNameBuf ); } InitializeObjectAttributes( &Obja, &KeyName, OBJ_OPENIF | OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = NtCreateKey( &KeyHandle, GENERIC_READ | GENERIC_WRITE, &Obja, 0, NULL, 0, NULL ); if ( Status == STATUS_ACCESS_DENIED ) { Status = NtCreateKey( &KeyHandle, GENERIC_READ | GENERIC_WRITE, &Obja, 0, NULL, REG_OPTION_BACKUP_RESTORE, NULL ); } if ( !NT_SUCCESS( Status ) ) { return Status; } while ( TRUE ) { ValueInfo = RtlAllocateHeap(RtlProcessHeap(), 0, ValueLength); if ( ValueInfo == NULL ) { NtClose(KeyHandle); return(STATUS_NO_MEMORY); } // // File rename operations are stored in the registry in a // single MULTI_SZ value. This allows the renames to be // performed in the same order that they were originally // requested. Each rename operation consists of a pair of // NULL-terminated strings. // Status = NtQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, ValueInfo, ValueLength, &ReturnedLength); if ( Status != STATUS_BUFFER_OVERFLOW ) { break; } // // The existing value is too large for our buffer. // Retry with a larger buffer. // ValueLength = ReturnedLength; RtlFreeHeap(RtlProcessHeap(), 0, ValueInfo); } if ( NT_SUCCESS(Status) ) { // // A value already exists, append our two strings to the // MULTI_SZ. // ValueData = (PWSTR)(&ValueInfo->Data); s = (PWSTR)((PCHAR)ValueData + ValueInfo->DataLength) - 1; while ( ValueData < s ) { UNICODE_STRING CrtString; PWSTR Base; ULONG RemovedBytes; Base = ValueData; RtlInitUnicodeString(&CrtString, ValueData); RemovedBytes = CrtString.Length + sizeof(WCHAR); if ( RtlEqualUnicodeString( &CrtString, OldFileName, TRUE ) ) { ValueData += CrtString.Length / sizeof(WCHAR) + 1; RtlInitUnicodeString(&CrtString, ValueData + 1); if ( RtlEqualUnicodeString( &CrtString, NewFileName, TRUE ) ) { RemovedBytes += CrtString.Length + 2 * sizeof(WCHAR); // NULL + ! printf("Removing delayed entry %ws -> %ws\n", Base, ValueData); ValueData += CrtString.Length / sizeof(WCHAR) + 1; MoveMemory(Base, ValueData, (ULONG_PTR)s - (ULONG_PTR)ValueData); printf("Deleting delayed rename key (%ld bytes left)\n", (ULONG)ValueInfo->DataLength - RemovedBytes); Status = NtSetValueKey(KeyHandle, &ValueName, 0, REG_MULTI_SZ, &ValueInfo->Data, (ULONG)ValueInfo->DataLength - RemovedBytes); break; } } else { ValueData += CrtString.Length / sizeof(WCHAR) + 1; } } } else { NtClose(KeyHandle); RtlFreeHeap(RtlProcessHeap(), 0, ValueInfo); return(Status); } NtClose(KeyHandle); RtlFreeHeap(RtlProcessHeap(), 0, ValueInfo); return(Status); } NTSTATUS ReplaceSystemFile( PWSTR TargetPath, PWSTR ReplacedFileName, PWSTR ReplacementFile ) { WCHAR FullOriginalName[DOS_MAX_PATH_LENGTH + 1]; WCHAR TmpReplacementFile[DOS_MAX_PATH_LENGTH + 1]; WCHAR TmpOrigName[DOS_MAX_PATH_LENGTH + 1]; int BytesCopied; UNICODE_STRING ReplacedUnicodeString, NewUnicodeString, TempOrigFile; HANDLE hSfp; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatus; HANDLE ReplacedFileHandle = NULL, NewFileHandle = NULL; PFILE_RENAME_INFORMATION RenameInfo1 = NULL, RenameInfo2 = NULL; NTSTATUS Status = STATUS_SUCCESS; DWORD Result; ULONG i; int ThreadPriority; RtlInitUnicodeString(&ReplacedUnicodeString, NULL); RtlInitUnicodeString(&NewUnicodeString, NULL); BytesCopied = _snwprintf( FullOriginalName, DOS_MAX_PATH_LENGTH, L"%ws\\%ws", TargetPath, ReplacedFileName ); if ( BytesCopied < 0 ) { return STATUS_BUFFER_TOO_SMALL; } if ( !RtlDosPathNameToNtPathName_U( FullOriginalName, &ReplacedUnicodeString, NULL, NULL ) ) { printf("RtlDosPathNameToNtPathName_U failed\n"); Status = STATUS_UNSUCCESSFUL; goto cleanup; } // // Open the target file and keep a handle opened to it // InitializeObjectAttributes (&ObjectAttributes, &ReplacedUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenFile( &ReplacedFileHandle, SYNCHRONIZE | DELETE | FILE_GENERIC_READ, &ObjectAttributes, &IoStatus, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT); if ( !NT_SUCCESS(Status) ) { printf( "Opening the \"%ws\" file failed %lx (IOStatus = %lx)\n", FullOriginalName, Status, IoStatus); goto cleanup; } // // If a knowndll file, then remove it from the system known dll directory // Status = RemoveKnownDll( ReplacedFileName ); if ( !NT_SUCCESS(Status) && (Status != STATUS_OBJECT_NAME_NOT_FOUND) ) { printf( "Removing the KnownDll entry for \"%ws\" failed %lx\n", ReplacedFileName, Status); goto cleanup; } // // Unprotect the replaced file // hSfp = (pConnectToSfcServer)( NULL ); if ( hSfp ) { if ( SfcIsFileProtected(hSfp, FullOriginalName) ) { printf("Replacing protected file \"%ws\"\n", FullOriginalName); Result = (pSfcFileException)( hSfp, (PWSTR) FullOriginalName, (DWORD) -1 ); if ( Result != NO_ERROR ) { printf("Unprotect file \"%ws\" failed, ec = %d\n", FullOriginalName, Result); (pSfcClose)(hSfp); Status = STATUS_UNSUCCESSFUL; goto cleanup; } } else { printf("Replacing unprotected file \"%ws\"\n", FullOriginalName); } (pSfcClose)(hSfp); } else { printf("SfcConnectToServer failed\n"); Status = STATUS_UNSUCCESSFUL; goto cleanup; } if ( GetTempFileNameW(TargetPath, L"HOTP", 0, TmpReplacementFile) == 0 ) { printf("GetTempFileNameW failed\n"); Status = STATUS_UNSUCCESSFUL; goto cleanup; } if ( !DeleteFileW(TmpReplacementFile) ) { printf("DeleteFile \"%ws\" failed with error %ld\n", TmpReplacementFile, GetLastError()); Status = STATUS_UNSUCCESSFUL; goto cleanup; } if ( !CopyFileW ( ReplacementFile, TmpReplacementFile, TRUE ) ) { printf("CopyFileW \"%ws\" failed with error %ld\n", TmpReplacementFile, GetLastError()); Status = STATUS_UNSUCCESSFUL; goto cleanup; } if ( !RtlDosPathNameToNtPathName_U( TmpReplacementFile, &NewUnicodeString, NULL, NULL ) ) { printf("RtlDosPathNameToNtPathName_U failed\n"); Status = STATUS_UNSUCCESSFUL; goto cleanup; } // // Open the new file and keep a handle opened to it // InitializeObjectAttributes (&ObjectAttributes, &NewUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenFile( &NewFileHandle, SYNCHRONIZE | DELETE | FILE_GENERIC_READ, &ObjectAttributes, &IoStatus, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT); if ( !NT_SUCCESS(Status) ) { printf("Opening the temporary file \"%ws\" failed %lx (IOStatus = %lx)\n", TmpReplacementFile, Status, IoStatus); goto cleanup; } // // Prepare the rename info for the original file // It will be a temporary // if ( GetTempFileNameW(TargetPath, L"HPO", 0, TmpOrigName) == 0 ) { printf("GetTempFileNameW failed\n"); Status = STATUS_UNSUCCESSFUL; goto cleanup; } if ( !DeleteFileW(TmpOrigName) ) { printf("DeleteFile \"%ws\" failed with error %ld\n", TmpOrigName, GetLastError()); Status = STATUS_UNSUCCESSFUL; goto cleanup; } if ( !RtlDosPathNameToNtPathName_U( TmpOrigName, &TempOrigFile, NULL, NULL ) ) { printf("RtlDosPathNameToNtPathName_U failed\n"); Status = STATUS_UNSUCCESSFUL; goto cleanup; } RenameInfo1 = RtlAllocateHeap( RtlProcessHeap(), 0, TempOrigFile.Length+sizeof(*RenameInfo1)); if ( RenameInfo1 == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; } RtlCopyMemory( RenameInfo1->FileName, TempOrigFile.Buffer, TempOrigFile.Length ); RenameInfo1->ReplaceIfExists = TRUE; RenameInfo1->RootDirectory = NULL; RenameInfo1->FileNameLength = TempOrigFile.Length; RenameInfo2 = RtlAllocateHeap( RtlProcessHeap(), 0, ReplacedUnicodeString.Length+sizeof(*RenameInfo2)); if ( RenameInfo2 == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; } RtlCopyMemory( RenameInfo2->FileName, ReplacedUnicodeString.Buffer, ReplacedUnicodeString.Length ); RenameInfo2->ReplaceIfExists = TRUE; RenameInfo2->RootDirectory = NULL; RenameInfo2->FileNameLength = ReplacedUnicodeString.Length; // // We have everything set to do the two rename operations. However if // the machine crashes before the second rename operation the system may not recover at boot // We queue a delayed rename so smss will do the job at next boot. If we succeed, // then smss will not find the file so it will skip that step. // if ( !MoveFileExW( TmpReplacementFile, FullOriginalName, MOVEFILE_REPLACE_EXISTING | MOVEFILE_DELAY_UNTIL_REBOOT) ) { // // We cannot queue the rename operation, so we cannot recover if // the machine crashes between the two renames below. Better refuse to apply the patch // printf("Failed to queue the rename operation for the temporary file (%ld)\n", GetLastError()); Status = STATUS_UNSUCCESSFUL; goto cleanup; } ThreadPriority = GetThreadPriority(GetCurrentThread()); if ( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) ) { printf("SetThreadPriority failed\n"); } Status = NtSetInformationFile( ReplacedFileHandle, &IoStatus, RenameInfo1, TempOrigFile.Length+sizeof(*RenameInfo1), FileRenameInformation); if ( !NT_SUCCESS(Status) ) { printf("NtSetInformationFile failed for the original file %lx %lx\n", Status, IoStatus); goto cleanup; } Status = NtSetInformationFile( NewFileHandle, &IoStatus, RenameInfo2, ReplacedUnicodeString.Length+sizeof(*RenameInfo1), FileRenameInformation); if ( !NT_SUCCESS(Status) ) { printf("NtSetInformationFile failed for the new file %lx (IOStatus %lx). Restoring the original.\n", Status, IoStatus); // // Restore the original file // Status = NtSetInformationFile( ReplacedFileHandle, &IoStatus, RenameInfo2, ReplacedUnicodeString.Length+sizeof(*RenameInfo1), FileRenameInformation); goto cleanup; } if ( !SetThreadPriority(GetCurrentThread(), ThreadPriority) ) { printf("Restoring the thread priority failed\n"); } if ( NT_SUCCESS(Status) ) { for ( i = 0; i < 3; i++ ) { Status = RemoveDelayedRename( &NewUnicodeString, &ReplacedUnicodeString, i ); if ( NT_SUCCESS(Status) ) { break; } } } if ( ReplacedFileHandle ) { NtClose(ReplacedFileHandle); } if ( NewFileHandle ) { NtClose(NewFileHandle); } if ( !DeleteFileW(TmpOrigName) ) { printf("Queueing the temp file deletion for \"%ws\" \n", TmpOrigName); // // Detele the temporary file after next reboot // if ( !MoveFileExW( TmpOrigName, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) ) { // // We cannot queue the delete operation operation // printf("Failed to queue the delete operation for the temporary file (%ld)\n", GetLastError()); Status = STATUS_UNSUCCESSFUL; goto cleanup; } Status = STATUS_UNSUCCESSFUL; goto cleanup; } cleanup: if ( RenameInfo1 ) { RtlFreeHeap( RtlProcessHeap(), 0, RenameInfo1 ); } if ( RenameInfo2 ) { RtlFreeHeap( RtlProcessHeap(), 0, RenameInfo2 ); } RtlFreeUnicodeString(&NewUnicodeString); RtlFreeUnicodeString(&ReplacedUnicodeString); return Status; } BOOL PSTRToUnicodeString( OUT PUNICODE_STRING UnicodeString, IN LPCSTR lpSourceString ) /*++ Routine Description: Captures and converts a 8-bit (OEM or ANSI) string into a heap-allocated UNICODE string Arguments: UnicodeString - location where UNICODE_STRING is stored lpSourceString - string in OEM or ANSI Return Value: TRUE if string is correctly stored, FALSE if an error occurred. In the error case, the last error is correctly set. --*/ { ANSI_STRING AnsiString; NTSTATUS Status; // // Convert input into dynamic unicode string // RtlInitString( &AnsiString, lpSourceString ); RtlAnsiStringToUnicodeString(UnicodeString, &AnsiString, TRUE); return TRUE; } BOOLEAN InitializeAsDebugger(VOID) { HANDLE Token; PTOKEN_PRIVILEGES NewPrivileges; BYTE OldPriv[1024]; PBYTE pbOldPriv; ULONG cbNeeded; BOOLEAN bRet = FALSE; LUID LuidPrivilege, LoadDriverPrivilege; DWORD PID = 0; // // Make sure we have access to adjust and to get the old token privileges // if ( !OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Token) ) { printf("Cannot open process token %ld\n", GetLastError()); return( FALSE ); } cbNeeded = 0; // // Initialize the privilege adjustment structure // LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &LuidPrivilege ); LookupPrivilegeValue(NULL, SE_LOAD_DRIVER_NAME, &LoadDriverPrivilege ); NewPrivileges = (PTOKEN_PRIVILEGES)calloc( 1, sizeof(TOKEN_PRIVILEGES) + (2 - ANYSIZE_ARRAY) * sizeof(LUID_AND_ATTRIBUTES)); if ( NewPrivileges == NULL ) { CloseHandle(Token); return( bRet ); } NewPrivileges->PrivilegeCount = 2; NewPrivileges->Privileges[0].Luid = LuidPrivilege; NewPrivileges->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; NewPrivileges->Privileges[1].Luid = LoadDriverPrivilege; NewPrivileges->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED; // // Enable the privilege // pbOldPriv = OldPriv; bRet = (BOOLEAN)AdjustTokenPrivileges( Token, FALSE, NewPrivileges, 1024, (PTOKEN_PRIVILEGES)pbOldPriv, &cbNeeded ); if ( !bRet ) { // // If the stack was too small to hold the privileges // then allocate off the heap // printf("AdjustTokenPrivileges returned %ld\n", GetLastError()); if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { pbOldPriv = calloc(1,cbNeeded); if ( pbOldPriv == NULL ) { CloseHandle(Token); return( bRet); } bRet = (BOOLEAN)AdjustTokenPrivileges( Token, FALSE, NewPrivileges, cbNeeded, (PTOKEN_PRIVILEGES)pbOldPriv, &cbNeeded ); } else { printf("Cannot adjust token privileges %ld\n", GetLastError()); } } CloseHandle( Token ); return(bRet); } void Usage () { printf("Usage:\n"); printf(" patch -k [-i|-u] pach_file\n"); printf(" Apply a patch to a system driver.\n"); printf(" -i Enable patch\n"); printf(" -u Disable patch\n\n"); printf(" patch -i pach_file [PID|image_name]\n"); printf(" Apply a patch to a process.\n"); printf(" If Image name is missing, all existing processes will be patched.\n\n"); printf(" patch -u pach_file [PID|image_name] \n"); printf(" Uninstall an existing patch.\n"); printf(" If Image name is missing, all existing processes will be patched.\n\n"); printf(" patch -r TargetPath TargetBinary SourcePath\n"); printf(" Replaces the TargetPath\\TargetBinary with SourcePath\n"); printf(" \n"); printf(" \n"); } PVOID MapPatchFile( HANDLE ProcessHandle, LPCTSTR wPatchName, ULONG PatchFlags ) { PSYSTEM_HOTPATCH_CODE_INFORMATION RemoteInfo; SYSTEM_HOTPATCH_CODE_INFORMATION LocaLRemoteInfo; CANSI_STRING AnsiString; WCHAR Buffer[1024]; SIZE_T Size; UNICODE_STRING DestinationString; DestinationString.Buffer = Buffer; DestinationString.Length = 0; DestinationString.MaximumLength = sizeof(Buffer); RtlInitAnsiString(&AnsiString, wPatchName); RtlAnsiStringToUnicodeString( &DestinationString, &AnsiString, FALSE ); RemoteInfo = VirtualAllocEx( ProcessHandle, NULL, 4096 * 16, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if ( RemoteInfo == NULL ) { printf("VirtualAllocEx failed %ld\n", GetLastError()); return NULL; } LocaLRemoteInfo.Flags = PatchFlags | FLG_HOTPATCH_NAME_INFO; LocaLRemoteInfo.NameInfo.NameLength = DestinationString.Length; LocaLRemoteInfo.NameInfo.NameOffset = sizeof(LocaLRemoteInfo); LocaLRemoteInfo.InfoSize = LocaLRemoteInfo.NameInfo.NameLength + LocaLRemoteInfo.NameInfo.NameOffset; if ( !WriteProcessMemory(ProcessHandle, RemoteInfo, &LocaLRemoteInfo, sizeof(LocaLRemoteInfo), &Size) ) { printf("Write process memory failed %ld\n", GetLastError()); return NULL; } if ( !WriteProcessMemory(ProcessHandle, (RemoteInfo + 1), DestinationString.Buffer, DestinationString.Length + sizeof(LocaLRemoteInfo), &Size) ) { printf("Write process memory failed %ld\n", GetLastError()); return NULL; } return RemoteInfo; } PSYSTEM_HOTPATCH_CODE_INFORMATION InitializeKernelPatchData( LPCTSTR wPatchName, ULONG PatchFlags ) { PSYSTEM_HOTPATCH_CODE_INFORMATION KernelPatch; CANSI_STRING AnsiString; WCHAR Buffer[1024]; SIZE_T Size; UNICODE_STRING DestinationString; DestinationString.Buffer = Buffer; DestinationString.Length = 0; DestinationString.MaximumLength = sizeof(Buffer); RtlInitAnsiString(&AnsiString, wPatchName); RtlAnsiStringToUnicodeString( &DestinationString, &AnsiString, FALSE ); if ( !RtlDosPathNameToNtPathName_U( Buffer, &DestinationString, NULL, NULL ) ) { printf("RtlDosPathNameToNtPathName_U failed\n"); return NULL; } KernelPatch = malloc(sizeof(SYSTEM_HOTPATCH_CODE_INFORMATION) + DestinationString.Length); if ( KernelPatch == NULL ) { printf("Not enough memory\n"); RtlFreeUnicodeString(&DestinationString); return NULL; } KernelPatch->Flags = PatchFlags | FLG_HOTPATCH_NAME_INFO; KernelPatch->NameInfo.NameOffset = sizeof(SYSTEM_HOTPATCH_CODE_INFORMATION); KernelPatch->NameInfo.NameLength = DestinationString.Length; KernelPatch->InfoSize = sizeof(SYSTEM_HOTPATCH_CODE_INFORMATION) + DestinationString.Length; memcpy( (KernelPatch + 1), DestinationString.Buffer, DestinationString.Length); RtlFreeUnicodeString(&DestinationString); return KernelPatch; } int ApplyPatchToProcess( DWORD PID, PCHAR PatchFile) { DWORD ThreadId; HANDLE ProcessHandle = NULL; HANDLE RemoteThread = NULL; PVOID ThreadParam = NULL; HANDLE PortHandle = NULL; HMODULE NtDllHandle; LPTHREAD_START_ROUTINE PatchRoutine; DWORD ExitStatus; NTSTATUS Status; // // User mode patch // ProcessHandle = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, PID ); if ( ProcessHandle == NULL ) { printf("Cannot open process. Error %ld\n", GetLastError()); return EXIT_FAILURE; } ThreadParam = MapPatchFile( ProcessHandle, PatchFile, ((OperationCode == PATCH_OC_INSTALL) ? 1 : 0) ); if ( ThreadParam == NULL ) { return EXIT_FAILURE; } NtDllHandle = GetModuleHandle("ntdll.dll"); if ( NtDllHandle == NULL ) { printf("Cannot get ntdll.dll handle\n"); return EXIT_FAILURE; } PatchRoutine = (LPTHREAD_START_ROUTINE)GetProcAddress(NtDllHandle, "LdrHotPatchRoutine"); if ( PatchRoutine == NULL ) { printf("Cannot get LdrHotPatchRoutine\n"); return EXIT_FAILURE; } // // Use the Rtl version of create remote thread since the win32 version // does not work cross-session // Status = RtlCreateUserThread (ProcessHandle, NULL, FALSE, 0, 0, 0, (PUSER_THREAD_START_ROUTINE) PatchRoutine, ThreadParam, &RemoteThread, NULL); if (!NT_SUCCESS (Status)) { printf("Cannot create user thread. Error %ld\n", GetLastError()); VirtualFreeEx( ProcessHandle, ThreadParam, 0, MEM_RELEASE ); return EXIT_FAILURE; } WaitForSingleObject(RemoteThread, INFINITE); VirtualFreeEx( ProcessHandle, ThreadParam, 0, MEM_RELEASE ); if ( GetExitCodeThread(RemoteThread, &ExitStatus) ) { if ( ExitStatus ) { printf("Error 0x%lx\n", ExitStatus); } else { printf("OK\n"); } } return EXIT_SUCCESS; } BOOLEAN UpdateProcessList(PCHAR ProcName, PCHAR PatchFile) { DWORD CrtSize, cbNeeded, cProcesses; PDWORD aProcesses; DWORD i; CrtSize = cbNeeded = 1 * sizeof(DWORD); aProcesses = malloc(CrtSize); if ( aProcesses == NULL ) { exit(EXIT_FAILURE); } for ( ;; ) { if ( !EnumProcesses( aProcesses, CrtSize, &cbNeeded ) ) return FALSE; if ( CrtSize > cbNeeded ) { break; } free(aProcesses); CrtSize = cbNeeded + 1024; aProcesses = malloc(CrtSize); if ( aProcesses == NULL ) { exit(EXIT_FAILURE); } } // Calculate how many process identifiers were returned. cProcesses = cbNeeded / sizeof(DWORD); // Print the name and process identifier for each process. for ( i = 0; i < cProcesses; i++ ) { char szProcessName[MAX_PATH] = ""; DWORD processID = aProcesses[i]; // Get a handle to the process. HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID ); // Get the process name. if ( hProcess ) { HMODULE hMod; DWORD cbNeeded; if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) ) { GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName) ); if ( (ProcName == NULL) || _stricmp(szProcessName, ProcName) == 0 ) { printf("Patching %s : ", szProcessName); ApplyPatchToProcess(processID, PatchFile); } } CloseHandle( hProcess ); } } return TRUE; } int __cdecl main (int argc, char ** argv) { LONG i; DWORD id; DWORD PID; NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; PCHAR PatchFile = NULL; BOOLEAN KernelPatch = FALSE; char * ProgramName = NULL; // // By default the tool instals the patch // OperationCode = 0; PID = 0; for ( i = 1; i < argc; i++ ) { PCHAR CrtArg = argv[i]; if ( *CrtArg == '-' ) { CrtArg++; switch ( toupper(*CrtArg) ) { case 'I': if ( OperationCode != 0 ) { printf("Invalid argument %s\n", CrtArg); exit(0); } OperationCode = PATCH_OC_INSTALL; break; case 'U': if ( OperationCode != 0 ) { printf("Invalid argument %s\n", CrtArg); exit(0); } OperationCode = PATCH_OC_UNINSTALL; break; case 'K': KernelPatch = TRUE; break; case 'R': if ( OperationCode != 0 ) { printf("Invalid argument %s\n", CrtArg); exit(0); } OperationCode = PATCH_OC_REPLACE_FILE; break; default: Usage(); return EXIT_FAILURE; } } else { if ( KernelPatch ) { PatchFile = CrtArg; } else { if ( PatchFile == NULL ) { PatchFile = CrtArg; } else { sscanf(CrtArg, "%ld", &PID); if ( !PID ) { ProgramName = CrtArg; //printf("Program %s\n", CrtArg); } } } } } if (OperationCode == 0) { Usage(); return EXIT_FAILURE; } if ( OperationCode == PATCH_OC_REPLACE_FILE ) { // // Replace a binary file to a target path // HANDLE SfcLibrary; if ( argc <= 4 ) { Usage(); return EXIT_FAILURE; } SfcLibrary = LoadSfcLibrary(); if ( SfcLibrary ) { UNICODE_STRING p1, p2, p3; PSTRToUnicodeString(&p1, argv[2]); PSTRToUnicodeString(&p2, argv[3]); PSTRToUnicodeString(&p3, argv[4]); ReplaceSystemFile(p1.Buffer, p2.Buffer, p3.Buffer); FreeLibrary(SfcLibrary); if ( _stricmp(argv[3], "ntdll.dll") == 0 ) { SYSTEM_HOTPATCH_CODE_INFORMATION KernelPatch; NTSTATUS Status; printf("Replacing system ntdll.dll section\n"); if ( !InitializeAsDebugger() ) { printf("Cannot initialize as debugger\n"); return EXIT_FAILURE; } KernelPatch.Flags = FLG_HOTPATCH_RELOAD_NTDLL; Status = NtSetSystemInformation( SystemHotpatchInformation, (PVOID)&KernelPatch, sizeof(KernelPatch) + 100 ); if ( !NT_SUCCESS(Status) ) { printf("SystemHotpatchInformation failed with %08lx\n", Status); } } } } else { if ( !InitializeAsDebugger() ) { printf("Cannot initialize as debugger\n"); return EXIT_FAILURE; } if ( KernelPatch ) { PSYSTEM_HOTPATCH_CODE_INFORMATION KernelPatchData = InitializeKernelPatchData( PatchFile, ((OperationCode == PATCH_OC_INSTALL) ? 1 : 0) ); if ( KernelPatchData == NULL ) { return EXIT_FAILURE; } KernelPatchData->Flags |= FLG_HOTPATCH_KERNEL; Status = NtSetSystemInformation( SystemHotpatchInformation, (PVOID)KernelPatchData, KernelPatchData->InfoSize ); free(KernelPatchData); if ( !NT_SUCCESS(Status) ) { printf("Patching kernel driver failed with status 0x%lx\n", Status); return EXIT_FAILURE; } return EXIT_SUCCESS; } else { // // Use-mode patching. // if ( PID != 0 ) { return ApplyPatchToProcess(PID, PatchFile); } return UpdateProcessList( ProgramName, PatchFile); } } return EXIT_SUCCESS; }