/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: WindowsFileProtection.cpp Abstract: This AppVerifier shim hooks the file I/O APIs that could potentially change a file under Windows File Protection. When one of the files is accessed or modified, an event is written to the log. Notes: This is a general purpose shim. History: 06/25/2001 rparsons Created 11/26/2001 rparsons Remove unused local variables. Make SHFileOperation more efficent. --*/ #include "precomp.h" #include "rtlutils.h" #include "sfc.h" IMPLEMENT_SHIM_BEGIN(WindowsFileProtection) #include "ShimHookMacro.h" // // verifier log entries // BEGIN_DEFINE_VERIFIER_LOG(WindowFileProtection) VERIFIER_LOG_ENTRY(VLOG_WFP_COPYFILE) VERIFIER_LOG_ENTRY(VLOG_WFP_MOVEFILE) VERIFIER_LOG_ENTRY(VLOG_WFP_DELETEFILE) VERIFIER_LOG_ENTRY(VLOG_WFP_REPLACEFILE) VERIFIER_LOG_ENTRY(VLOG_WFP_WRITEFILE) VERIFIER_LOG_ENTRY(VLOG_WFP_OPENFILE) VERIFIER_LOG_ENTRY(VLOG_WFP_SHFILEOP) END_DEFINE_VERIFIER_LOG(WindowFileProtection) INIT_VERIFIER_LOG(WindowFileProtection); APIHOOK_ENUM_BEGIN APIHOOK_ENUM_ENTRY(CreateFileA) APIHOOK_ENUM_ENTRY(CreateFileW) APIHOOK_ENUM_ENTRY(OpenFile) APIHOOK_ENUM_ENTRY(CopyFileA) APIHOOK_ENUM_ENTRY(CopyFileW) APIHOOK_ENUM_ENTRY(CopyFileExA) APIHOOK_ENUM_ENTRY(CopyFileExW) APIHOOK_ENUM_ENTRY(DeleteFileA) APIHOOK_ENUM_ENTRY(DeleteFileW) APIHOOK_ENUM_ENTRY(MoveFileA) APIHOOK_ENUM_ENTRY(MoveFileW) APIHOOK_ENUM_ENTRY(MoveFileExA) APIHOOK_ENUM_ENTRY(MoveFileExW) APIHOOK_ENUM_ENTRY(MoveFileWithProgressA) APIHOOK_ENUM_ENTRY(MoveFileWithProgressW) APIHOOK_ENUM_ENTRY(ReplaceFileA) APIHOOK_ENUM_ENTRY(ReplaceFileW) APIHOOK_ENUM_ENTRY(SHFileOperationA) APIHOOK_ENUM_ENTRY(SHFileOperationW) APIHOOK_ENUM_ENTRY(_lcreat) APIHOOK_ENUM_ENTRY(_lopen) APIHOOK_ENUM_ENTRY(NtCreateFile) APIHOOK_ENUM_ENTRY(NtOpenFile) APIHOOK_ENUM_END /*++ ANSI wrapper for SfcIsFileProtected. --*/ BOOL IsFileProtected( LPCSTR pszFileName ) { LPWSTR pwszWideFileName = NULL; int nLen; BOOL bReturn = FALSE; // // Convert from ANSI to Unicode. // nLen = lstrlenA(pszFileName) + 1; if (nLen) { pwszWideFileName = (LPWSTR)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, nLen * sizeof(WCHAR)); if (!pwszWideFileName) { DPFN(eDbgLevelError, "[IsFileProtected] Failed to allocate memory"); return FALSE; } if (!MultiByteToWideChar(CP_ACP, 0, pszFileName, -1, pwszWideFileName, nLen)) { DPFN(eDbgLevelError, "[IsFileProtected] ANSI -> Unicode failed"); goto cleanup; } bReturn = SfcIsFileProtected(NULL, pwszWideFileName); } cleanup: if (pwszWideFileName) { RtlFreeHeap(RtlProcessHeap(), 0, pwszWideFileName); } return bReturn; } /*++ Wraps SfcIsFileProtected for NT path names. --*/ BOOL IsNtFileProtected( IN PUNICODE_STRING pstrNtFileName ) { NTSTATUS status; RTL_UNICODE_STRING_BUFFER DosPath; BOOL fReturn = FALSE; UCHAR DosPathBuffer[MAX_PATH * 2]; if (!pstrNtFileName) { DPFN(eDbgLevelError, "[IsNtFileProtected] Invalid parameter"); return FALSE; } // // Convert from an NT path to a DOS path. // RtlInitUnicodeStringBuffer(&DosPath, DosPathBuffer, sizeof(DosPathBuffer)); status = ShimAssignUnicodeStringBuffer(&DosPath, pstrNtFileName); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[IsNtFileProtected] Failed to initialize DOS path buffer"); return fReturn; } status = ShimNtPathNameToDosPathName(0, &DosPath, NULL, NULL); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[IsNtFileProtected] Failed to convert NT \"%ls\" to DOS path", pstrNtFileName->Buffer); goto cleanup; } // // Now check for a protected file. // if (SfcIsFileProtected(NULL, DosPath.String.Buffer)) { fReturn = TRUE; } cleanup: RtlFreeUnicodeStringBuffer(&DosPath); return fReturn; } BOOL APIHOOK(CopyFileA)( LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists ) { // // Ensure that the destination is not protected. // if (!bFailIfExists && IsFileProtected(lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_COPYFILE, "API: CopyFileA Filename: %s", lpNewFileName); } return ORIGINAL_API(CopyFileA)(lpExistingFileName, lpNewFileName, bFailIfExists); } BOOL APIHOOK(CopyFileW)( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, BOOL bFailIfExists ) { // // Ensure that the destination is not protected. // if (!bFailIfExists && SfcIsFileProtected(NULL, lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_COPYFILE, "API: CopyFileW Filename: %ls", lpNewFileName); } return ORIGINAL_API(CopyFileW)(lpExistingFileName, lpNewFileName, bFailIfExists); } BOOL APIHOOK(CopyFileExA)( LPCSTR lpExistingFileName, LPCSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, LPBOOL pbCancel, DWORD dwCopyFlags ) { // // Ensure that the destination is not protected. // if (!(dwCopyFlags & COPY_FILE_FAIL_IF_EXISTS) && IsFileProtected(lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_COPYFILE, "API: CopyFileExA Filename: %s", lpNewFileName); } return ORIGINAL_API(CopyFileExA)(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, pbCancel, dwCopyFlags); } BOOL APIHOOK(CopyFileExW)( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, LPBOOL pbCancel, DWORD dwCopyFlags ) { // // Ensure that the destination is not protected. // if (!(dwCopyFlags & COPY_FILE_FAIL_IF_EXISTS) && SfcIsFileProtected(NULL, lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_COPYFILE, "API: CopyFileExW Filename: %ls", lpNewFileName); } return ORIGINAL_API(CopyFileExW)(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, pbCancel, dwCopyFlags); } BOOL APIHOOK(DeleteFileA)( LPCSTR lpFileName ) { if (IsFileProtected(lpFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_DELETEFILE, "API: DeleteFileA Filename: %s", lpFileName); } return ORIGINAL_API(DeleteFileA)(lpFileName); } BOOL APIHOOK(DeleteFileW)( LPCWSTR lpFileName ) { if (SfcIsFileProtected(NULL, lpFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_DELETEFILE, "API: DeleteFileW Filename: %ls", lpFileName); } return ORIGINAL_API(DeleteFileW)(lpFileName); } BOOL APIHOOK(MoveFileA)( LPCSTR lpExistingFileName, LPCSTR lpNewFileName ) { // // Ensure that the source or destination is not protected. // if (IsFileProtected(lpExistingFileName) || IsFileProtected(lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_MOVEFILE, "API: MoveFileA Filename: %s Filename: %s", lpExistingFileName, lpNewFileName); } return ORIGINAL_API(MoveFileA)(lpExistingFileName, lpNewFileName); } BOOL APIHOOK(MoveFileW)( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName ) { // // Ensure that the source or destination is not protected. // if (SfcIsFileProtected(NULL, lpExistingFileName) || SfcIsFileProtected(NULL, lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_MOVEFILE, "API: MoveFileW Filename: %ls Filename: %ls", lpExistingFileName, lpNewFileName); } return ORIGINAL_API(MoveFileW)(lpExistingFileName, lpNewFileName); } BOOL APIHOOK(MoveFileExA)( LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags ) { // // Ensure that the source or destination is not protected. // if (IsFileProtected(lpExistingFileName) || IsFileProtected(lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_MOVEFILE, "API: MoveFileExA Filename: %s Filename: %s", lpExistingFileName, lpNewFileName); } return ORIGINAL_API(MoveFileExA)(lpExistingFileName, lpNewFileName, dwFlags); } BOOL APIHOOK(MoveFileExW)( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, DWORD dwFlags ) { // // Ensure that the source or destination is not protected. // if (SfcIsFileProtected(NULL, lpExistingFileName) || SfcIsFileProtected(NULL, lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_MOVEFILE, "API: MoveFileExW Filename: %ls Filename: %ls", lpExistingFileName, lpNewFileName); } return ORIGINAL_API(MoveFileExW)(lpExistingFileName, lpNewFileName, dwFlags); } BOOL APIHOOK(MoveFileWithProgressA)( LPCSTR lpExistingFileName, LPCSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, DWORD dwFlags ) { // // Ensure that the source or destination is not protected. // if (IsFileProtected(lpExistingFileName) || IsFileProtected(lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_MOVEFILE, "API: MoveFileWithProgressA Filename: %s Filename: %s", lpExistingFileName, lpNewFileName); } return ORIGINAL_API(MoveFileWithProgressA)(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, dwFlags); } BOOL APIHOOK(MoveFileWithProgressW)( LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, DWORD dwFlags ) { // // Ensure that the source or destination is not protected. // if (SfcIsFileProtected(NULL, lpExistingFileName) || SfcIsFileProtected(NULL, lpNewFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_MOVEFILE, "API: MoveFileWithProgressW Filename: %ls Filename: %ls", lpExistingFileName, lpNewFileName); } return ORIGINAL_API(MoveFileWithProgressW)(lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, dwFlags); } BOOL APIHOOK(ReplaceFileA)( LPCSTR lpReplacedFileName, LPCSTR lpReplacementFileName, LPCSTR lpBackupFileName, DWORD dwReplaceFlags, LPVOID lpExclude, LPVOID lpReserved ) { // // Ensure that the destination is not protected. // if (IsFileProtected(lpReplacedFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_REPLACEFILE, "API: ReplaceFileA Filename: %s", lpReplacedFileName); } return ORIGINAL_API(ReplaceFileA)(lpReplacedFileName, lpReplacementFileName, lpBackupFileName, dwReplaceFlags, lpExclude, lpReserved); } BOOL APIHOOK(ReplaceFileW)( LPCWSTR lpReplacedFileName, LPCWSTR lpReplacementFileName, LPCWSTR lpBackupFileName, DWORD dwReplaceFlags, LPVOID lpExclude, LPVOID lpReserved ) { // // Ensure that the destination is not protected. // if (SfcIsFileProtected(NULL, lpReplacedFileName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_REPLACEFILE, "API: ReplaceFileW Filename: %ls", lpReplacedFileName); } return ORIGINAL_API(ReplaceFileW)(lpReplacedFileName, lpReplacementFileName, lpBackupFileName, dwReplaceFlags, lpExclude, lpReserved); } void ReportProtectedFileA( LPCSTR pszFilePath ) { UINT uSize = 0; if (pszFilePath) { while (TRUE) { if (IsFileProtected(pszFilePath)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_SHFILEOP, "API: SHFileOperationA Filename: %s", pszFilePath); } uSize = lstrlenA(pszFilePath) + 1; pszFilePath += uSize; if (*pszFilePath == '\0') { break; } } } } void ReportProtectedFileW( LPCWSTR pwszFilePath ) { UINT uSize = 0; if (pwszFilePath) { while (TRUE) { if (SfcIsFileProtected(NULL, pwszFilePath)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_SHFILEOP, "API: SHFileOperationW Filename: %ls", pwszFilePath); } uSize = lstrlenW(pwszFilePath) + 1; pwszFilePath += uSize; if (*pwszFilePath == '\0') { break; } } } } int APIHOOK(SHFileOperationA)( LPSHFILEOPSTRUCTA lpFileOp ) { // // If they're going to rename files on collision, don't bother. // if (!(lpFileOp->fFlags & FOF_RENAMEONCOLLISION)) { ReportProtectedFileA(lpFileOp->pFrom); ReportProtectedFileA(lpFileOp->pTo); } return ORIGINAL_API(SHFileOperationA)(lpFileOp); } int APIHOOK(SHFileOperationW)( LPSHFILEOPSTRUCTW lpFileOp ) { // // If they're going to rename files on collision, don't bother. // if (!(lpFileOp->fFlags & FOF_RENAMEONCOLLISION)) { ReportProtectedFileW(lpFileOp->pFrom); ReportProtectedFileW(lpFileOp->pTo); } return ORIGINAL_API(SHFileOperationW)(lpFileOp); } HANDLE APIHOOK(CreateFileA)( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) { if (IsFileProtected(lpFileName) && (dwDesiredAccess & GENERIC_WRITE || dwDesiredAccess & GENERIC_READ)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: CreateFileA Filename: %s", lpFileName); } return ORIGINAL_API(CreateFileA)(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } HANDLE APIHOOK(CreateFileW)( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) { if (SfcIsFileProtected(NULL, lpFileName) && (dwDesiredAccess & GENERIC_WRITE || dwDesiredAccess & GENERIC_READ)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: CreateFileW Filename: %ls", lpFileName); } return ORIGINAL_API(CreateFileW)(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } HFILE APIHOOK(OpenFile)( LPCSTR lpFileName, LPOFSTRUCT lpReOpenBuff, UINT uStyle ) { if (IsFileProtected(lpFileName) && (uStyle & OF_READWRITE || uStyle & OF_CREATE || uStyle & OF_DELETE)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: OpenFile Filename: %s", lpFileName); } return ORIGINAL_API(OpenFile)(lpFileName, lpReOpenBuff, uStyle); } HFILE APIHOOK(_lopen)( LPCSTR lpPathName, int iReadWrite ) { if (IsFileProtected(lpPathName) && (iReadWrite & OF_READWRITE || iReadWrite & OF_WRITE)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: _lopen Filename: %s", lpPathName); } return ORIGINAL_API(_lopen)(lpPathName, iReadWrite); } HFILE APIHOOK(_lcreat)( LPCSTR lpPathName, int iAttribute ) { if (IsFileProtected(lpPathName) && iAttribute != 1) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: _lcreat Filename: %s", lpPathName); } return ORIGINAL_API(_lcreat)(lpPathName, iAttribute); } NTSTATUS APIHOOK(NtCreateFile)( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength ) { if (IsNtFileProtected(ObjectAttributes->ObjectName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: NtCreateFile Filename: %ls", ObjectAttributes->ObjectName->Buffer); } return ORIGINAL_API(NtCreateFile)(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength); } NTSTATUS APIHOOK(NtOpenFile)( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess, ULONG OpenOptions ) { if (IsNtFileProtected(ObjectAttributes->ObjectName)) { VLOG(VLOG_LEVEL_ERROR, VLOG_WFP_OPENFILE, "API: NtOpenFile Filename: %ls", ObjectAttributes->ObjectName->Buffer); } return ORIGINAL_API(NtOpenFile)(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions); } SHIM_INFO_BEGIN() SHIM_INFO_DESCRIPTION(AVS_WINFILEPROTECT_DESC) SHIM_INFO_FRIENDLY_NAME(AVS_WINFILEPROTECT_FRIENDLY) SHIM_INFO_VERSION(1, 5) SHIM_INFO_FLAGS(AVRF_FLAG_EXTERNAL_ONLY) SHIM_INFO_INCLUDE_EXCLUDE("E:rpcrt4.dll kernel32.dll") SHIM_INFO_END() /*++ Register hooked functions. --*/ HOOK_BEGIN DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_COPYFILE, AVS_WFP_COPYFILE, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_MOVEFILE, AVS_WFP_MOVEFILE, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_DELETEFILE, AVS_WFP_DELETEFILE, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_REPLACEFILE, AVS_WFP_REPLACEFILE, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_WRITEFILE, AVS_WFP_WRITEFILE, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_OPENFILE, AVS_WFP_OPENFILE, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) DUMP_VERIFIER_LOG_ENTRY(VLOG_WFP_SHFILEOP, AVS_WFP_SHFILEOP, AVS_WFP_GENERAL_R, AVS_WFP_GENERAL_URL) APIHOOK_ENTRY(KERNEL32.DLL, CreateFileA) APIHOOK_ENTRY(KERNEL32.DLL, CreateFileW) APIHOOK_ENTRY(KERNEL32.DLL, OpenFile) APIHOOK_ENTRY(KERNEL32.DLL, CopyFileA) APIHOOK_ENTRY(KERNEL32.DLL, CopyFileW) APIHOOK_ENTRY(KERNEL32.DLL, CopyFileExA) APIHOOK_ENTRY(KERNEL32.DLL, CopyFileExW) APIHOOK_ENTRY(KERNEL32.DLL, DeleteFileA) APIHOOK_ENTRY(KERNEL32.DLL, DeleteFileW) APIHOOK_ENTRY(KERNEL32.DLL, MoveFileA) APIHOOK_ENTRY(KERNEL32.DLL, MoveFileW) APIHOOK_ENTRY(KERNEL32.DLL, MoveFileExA) APIHOOK_ENTRY(KERNEL32.DLL, MoveFileExW) APIHOOK_ENTRY(KERNEL32.DLL, MoveFileWithProgressA) APIHOOK_ENTRY(KERNEL32.DLL, MoveFileWithProgressW) APIHOOK_ENTRY(KERNEL32.DLL, ReplaceFileA) APIHOOK_ENTRY(KERNEL32.DLL, ReplaceFileW) APIHOOK_ENTRY(SHELL32.DLL, SHFileOperationA) APIHOOK_ENTRY(SHELL32.DLL, SHFileOperationW) // 16-bit compatibility file routines. APIHOOK_ENTRY(KERNEL32.DLL, _lopen) APIHOOK_ENTRY(KERNEL32.DLL, _lcreat) APIHOOK_ENTRY(NTDLL.DLL, NtCreateFile) APIHOOK_ENTRY(NTDLL.DLL, NtOpenFile) HOOK_END IMPLEMENT_SHIM_END