diff --git a/README.md b/README.md index 3dcf413..c0d2e61 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,18 @@ thread context manipulation. A supporting tool 'injector' is a sample program doing that type of code injection. -A related blog entory can be found [here](http://standa-note.blogspot.ca/2015/03/section-based-code-injection-and-its.html). +A related blog entory can be found here: + + http://standa-note.blogspot.ca/2015/03/section-based-code-injection-and-its.html). Installation and Uninstallation ----------------- -Use the 'sc' command, for example, for installation: +Get an archive file for compiled files form this link: + + https://github.com/tandasat/RemoteWriteMonitor/releases/latest + +Then use the 'sc' command. For installation: >sc create rwmon type= kernel binPath= C:\Users\user\Desktop\RemoteWriteMonitor.sys >sc start rwmon @@ -23,6 +29,16 @@ For uninstallation: >sc stop rwmon >sc delete rwmon + +On the x64 bit platform, you have to enable test signing to install the driver. +To do that, open the command prompt with the administrator privilege and type +the following command: + + >bcdedit /set {current} testsigning on + +Then, reboot the system to activate the change. You also have to disable the +Kernel Patch Protection (PatchGuard), and Google helps you do that work. + Usage ------- @@ -36,8 +52,8 @@ what was written or mapped into the remote process. Output can be seen with DebugView and are all saved under the C:\Windows\RemoteWriteMonitor\ directory. Written and mapped data is stored as \.bin apart from a log file. -'injector' could be used to test the driver's function. Injecting and executing code into -notepad.exe could be done by the following commands: +'injector' could be used to test the driver's function. Injecting and executing +code into notepad.exe can be done by the following commands: >notepad && tasklist | findstr notepad notepad.exe 3368 Console 1 4,564 K @@ -54,6 +70,9 @@ notepad.exe could be done by the following commands: Output on DebugView would look like this: ![DebugView](/img/injector.png) +Note that the injector only works against 32 bit processes. + + Caveats ------- - It reports all those API calls regardless of its memory protection, contents @@ -62,7 +81,7 @@ output related to the sample you are analyzing as it reports a lot of legit activities too. - It was designed so because it is far more difficult to track all written -regions and reports only when it is executed (I wrote [it](https://sites.google.com/site/tandasat/home/egg) long time ago, and it was hell). +regions and reports only when it is executed. - It does not monitor any of processes existed when the driver was installed. Thus, the second injection will not be reported if the sample injects code @@ -79,7 +98,7 @@ may be happening. Supported Platform(s) ----------------- -- Windows 7 SP1 x86 +- Windows 7 SP1 and 8.1 (x86/x64) License diff --git a/RemoteWriteMonitor/Release/RemoteWriteMonitor.sys b/RemoteWriteMonitor/Release/RemoteWriteMonitor.sys deleted file mode 100644 index 65a9b68..0000000 Binary files a/RemoteWriteMonitor/Release/RemoteWriteMonitor.sys and /dev/null differ diff --git a/RemoteWriteMonitor/Release/injector.exe b/RemoteWriteMonitor/Release/injector.exe deleted file mode 100644 index a5a530f..0000000 Binary files a/RemoteWriteMonitor/Release/injector.exe and /dev/null differ diff --git a/RemoteWriteMonitor/RemoteWriteMonitor.sln b/RemoteWriteMonitor/RemoteWriteMonitor.sln index dfc3f72..3c90055 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor.sln +++ b/RemoteWriteMonitor/RemoteWriteMonitor.sln @@ -1,28 +1,71 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RemoteWriteMonitor", "RemoteWriteMonitor\RemoteWriteMonitor.vcxproj", "{287B2687-2894-4AA5-A5A9-686AE6C5F34A}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "injector", "injector\injector.vcxproj", "{FEE34C62-A273-4557-BF93-360BDA2855E5}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {287B2687-2894-4AA5-A5A9-686AE6C5F34A}.Debug|Win32.ActiveCfg = Debug|Win32 - {287B2687-2894-4AA5-A5A9-686AE6C5F34A}.Debug|Win32.Build.0 = Debug|Win32 - {287B2687-2894-4AA5-A5A9-686AE6C5F34A}.Release|Win32.ActiveCfg = Release|Win32 - {287B2687-2894-4AA5-A5A9-686AE6C5F34A}.Release|Win32.Build.0 = Release|Win32 - {FEE34C62-A273-4557-BF93-360BDA2855E5}.Debug|Win32.ActiveCfg = Debug|Win32 - {FEE34C62-A273-4557-BF93-360BDA2855E5}.Debug|Win32.Build.0 = Debug|Win32 - {FEE34C62-A273-4557-BF93-360BDA2855E5}.Release|Win32.ActiveCfg = Release|Win32 - {FEE34C62-A273-4557-BF93-360BDA2855E5}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RemoteWriteMonitor", "RemoteWriteMonitor\RemoteWriteMonitor.vcxproj", "{987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "injector", "injector\injector.vcxproj", "{FEE34C62-A273-4557-BF93-360BDA2855E5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{94B67B9C-4EA6-4F4D-A1B2-51035E1CF277}" + ProjectSection(SolutionItems) = preProject + ..\README.md = ..\README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Win7 Debug|Win32 = Win7 Debug|Win32 + Win7 Debug|x64 = Win7 Debug|x64 + Win7 Release|Win32 = Win7 Release|Win32 + Win7 Release|x64 = Win7 Release|x64 + Win8.1 Debug|Win32 = Win8.1 Debug|Win32 + Win8.1 Debug|x64 = Win8.1 Debug|x64 + Win8.1 Release|Win32 = Win8.1 Release|Win32 + Win8.1 Release|x64 = Win8.1 Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Debug|Win32.ActiveCfg = Win7 Debug|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Debug|Win32.Build.0 = Win7 Debug|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Debug|Win32.Deploy.0 = Win7 Debug|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Debug|x64.ActiveCfg = Win7 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Debug|x64.Build.0 = Win7 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Debug|x64.Deploy.0 = Win7 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Release|Win32.ActiveCfg = Win7 Release|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Release|Win32.Build.0 = Win7 Release|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Release|Win32.Deploy.0 = Win7 Release|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Release|x64.ActiveCfg = Win7 Release|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Release|x64.Build.0 = Win7 Release|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win7 Release|x64.Deploy.0 = Win7 Release|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Debug|Win32.ActiveCfg = Win8.1 Debug|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Debug|Win32.Build.0 = Win8.1 Debug|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Debug|Win32.Deploy.0 = Win8.1 Debug|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Debug|x64.ActiveCfg = Win8.1 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Debug|x64.Build.0 = Win8.1 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Debug|x64.Deploy.0 = Win8.1 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Release|Win32.ActiveCfg = Win8.1 Release|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Release|Win32.Build.0 = Win8.1 Release|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Release|Win32.Deploy.0 = Win8.1 Release|Win32 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Release|x64.ActiveCfg = Win8.1 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Release|x64.Build.0 = Win8.1 Debug|x64 + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082}.Win8.1 Release|x64.Deploy.0 = Win8.1 Debug|x64 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Debug|Win32.ActiveCfg = Debug|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Debug|Win32.Build.0 = Debug|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Debug|Win32.Deploy.0 = Debug|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Debug|x64.ActiveCfg = Debug|x64 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Release|Win32.ActiveCfg = Release|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Release|Win32.Build.0 = Release|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Release|Win32.Deploy.0 = Release|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win7 Release|x64.ActiveCfg = Release|x64 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Debug|Win32.ActiveCfg = Debug|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Debug|Win32.Build.0 = Debug|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Debug|Win32.Deploy.0 = Debug|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Debug|x64.ActiveCfg = Debug|x64 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Release|Win32.ActiveCfg = Release|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Release|Win32.Build.0 = Release|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Release|Win32.Deploy.0 = Release|Win32 + {FEE34C62-A273-4557-BF93-360BDA2855E5}.Win8.1 Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/Arch/AMD64/amd64.asm b/RemoteWriteMonitor/RemoteWriteMonitor/Arch/AMD64/amd64.asm new file mode 100644 index 0000000..4129a8f --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/Arch/AMD64/amd64.asm @@ -0,0 +1,68 @@ +; +; This module implements the lowest part of hook handlers +; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.CONST + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.DATA + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.CODE + + +; Implements jump to an arbitrary location without modifying registers. +; 0ffffffffffffffffh is used as a mark to be replaced with a correct address. +JMP_TEMPLATE MACRO + nop ; This is space for implanting int 3 for debugging + jmp qword ptr [jmp_address] +jmp_address: + dq 0ffffffffffffffffh +ENDM + + + +AsmNtMapViewOfSection_Win81_7 PROC + mov qword ptr [rsp+10h], rbx + mov qword ptr [rsp+18h], rsi + mov qword ptr [rsp+8h], rcx + push rdi + JMP_TEMPLATE +AsmNtMapViewOfSection_Win81_7 ENDP +AsmNtMapViewOfSection_Win81_7End PROC + nop +AsmNtMapViewOfSection_Win81_7End ENDP + + +; For Win 8.1 +AsmNtWriteVirtualMemory_Win81 PROC + sub rsp, 38h + mov rax, [rsp+60h] + mov dword ptr [rsp+28h], 20h + mov [rsp+20h], rax + JMP_TEMPLATE +AsmNtWriteVirtualMemory_Win81 ENDP +AsmNtWriteVirtualMemory_Win81End PROC + nop +AsmNtWriteVirtualMemory_Win81End ENDP + + +; For Win 7 +AsmNtWriteVirtualMemory_Win7 PROC + mov rax, rsp + mov qword ptr [rax+8h], rbx + mov qword ptr [rax+10h], rsi + mov qword ptr [rax+18h], rdi + mov qword ptr [rax+20h], r12 + JMP_TEMPLATE +AsmNtWriteVirtualMemory_Win7 ENDP +AsmNtWriteVirtualMemory_Win7End PROC + nop +AsmNtWriteVirtualMemory_Win7End ENDP + + + +END diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/Arch/x86/asm.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/Arch/x86/asm.cpp new file mode 100644 index 0000000..eed0eae --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/Arch/x86/asm.cpp @@ -0,0 +1,51 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// +// +#include "stdafx.h" +#include "../../asm.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +EXTERN_C void AsmNtMapViewOfSection_Win81_7(){}; + +EXTERN_C void AsmNtMapViewOfSection_Win81_7End(){}; + +EXTERN_C void AsmNtWriteVirtualMemory_Win81(){}; + +EXTERN_C void AsmNtWriteVirtualMemory_Win81End(){}; + +EXTERN_C void AsmNtWriteVirtualMemory_Win7(){}; + +EXTERN_C void AsmNtWriteVirtualMemory_Win7End(){}; diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.cpp index 636afb8..b68b8df 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.cpp +++ b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.cpp @@ -1,704 +1,423 @@ -// Copyright (c) 2015, tandasat. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -// -// This module implements an entry point of the driver and initializes other -// components in this module. -// -#include "stdafx.h" -#include "log.h" - -namespace stdexp = std::experimental; - -//////////////////////////////////////////////////////////////////////////////// -// -// macro utilities -// - -//////////////////////////////////////////////////////////////////////////////// -// -// constants and macros -// - -static const auto RWMONP_WHITELIST_ARRAY_SIZE = 1000; - -static const wchar_t RWMONP_OUT_DIRECTORY_PATH[] = - L"\\SystemRoot\\RemoteWriteMonitor"; -static const wchar_t RWMONP_LOG_FILE_PATH[] = - L"\\SystemRoot\\RemoteWriteMonitor\\RemoteWriteMonitor.log"; - -#if DBG -static const auto RWMONP_LOG_LEVEL = LOG_PUT_LEVEL_DEBUG; -#else -static const auto RWMONP_LOG_LEVEL = LOG_PUT_LEVEL_INFO; -#endif - -//////////////////////////////////////////////////////////////////////////////// -// -// types -// - -struct SERVICE_DESCRIPTOR_TABLE { - PVOID *ServiceTable; - PULONG CounterTable; - ULONG TableSize; - PUCHAR ArgumentTable; -}; - -union CR0_REGISTER { - ULONG_PTR Value; - struct { - unsigned PE : 1; // [0] Protected Mode Enabled - unsigned MP : 1; // [1] Monitor Coprocessor FLAG - unsigned EM : 1; // [2] Emulate FLAG - unsigned TS : 1; // [3] Task Switched FLAG - unsigned ET : 1; // [4] Extension Type FLAG - unsigned NE : 1; // [5] Numeric Error - unsigned Reserved1 : 10; // [6-15] - unsigned WP : 1; // [16] Write Protect - unsigned Reserved2 : 1; // [17] - unsigned AM : 1; // [18] Alignment Mask - unsigned Reserved3 : 10; // [19-28] - unsigned NW : 1; // [29] Not Write-Through - unsigned CD : 1; // [30] Cache Disable - unsigned PG : 1; // [31] Paging Enabled - } Fields; -}; -static_assert(sizeof(CR0_REGISTER) == sizeof(void *), "Size check"); - -struct SYSTEM_PROCESS_INFORMATION { - ULONG NextEntryOffset; - ULONG NumberOfThreads; - BYTE Reserved1[48]; - PVOID Reserved2[3]; - HANDLE UniqueProcessId; - PVOID Reserved3; - ULONG HandleCount; - BYTE Reserved4[4]; - PVOID Reserved5[11]; - SIZE_T PeakPagefileUsage; - SIZE_T PrivatePageCount; - LARGE_INTEGER Reserved6[6]; -}; - -enum SYSTEM_INFORMATION_CLASS { - SystemProcessInformation = 5, -}; - -//////////////////////////////////////////////////////////////////////////////// -// -// prototypes -// - -EXTERN_C NTKERNELAPI UCHAR *NTAPI -PsGetProcessImageFileName(_In_ PEPROCESS Process); - -EXTERN_C NTSTATUS NTAPI -ZwQuerySystemInformation(_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, - _Inout_ PVOID SystemInformation, - _In_ ULONG SystemInformationLength, - _Out_opt_ PULONG ReturnLength); - -EXTERN_C -NTSTATUS NTAPI NtWriteVirtualMemory(_In_ HANDLE ProcessHandle, - _In_ PVOID BaseAddress, _In_ PVOID Buffer, - _In_ ULONG BytesToWrite, - _Out_opt_ PULONG BytesWritten); -EXTERN_C -NTSTATUS NTAPI -NtMapViewOfSection(_In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, - _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, - _In_ SIZE_T CommitSize, - _Inout_opt_ PLARGE_INTEGER SectionOffset, - _Inout_ PSIZE_T ViewSize, - _In_ SECTION_INHERIT InheritDisposition, - _In_ ULONG AllocationType, _In_ ULONG Win32Protect); - -using NtWriteVirtualMemoryPtrType = decltype(&NtWriteVirtualMemory); -using NtMapViewOfSectionPtrType = decltype(&NtMapViewOfSection); - -EXTERN_C DRIVER_INITIALIZE DriverEntry; - -EXTERN_C static NTSTATUS RWMonpCreateDirectory(_In_ const wchar_t *PathW); - -EXTERN_C static NTSTATUS RWMonpForEachProcess( - _In_ bool (*Callback)(_In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, - _In_opt_ void *Context), - _In_opt_ void *Context); - -EXTERN_C static bool RWMonpSaveExistingPID( - _In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, _In_ void *Context); - -EXTERN_C static DRIVER_UNLOAD RWMonpDriverUnload; - -EXTERN_C static NTSTATUS RWMonpSleep(_In_ LONG Millisecond); - -EXTERN_C static NTSTATUS RWMonpSetMonitorHooks(_In_ bool Enable); - -EXTERN_C static void RWMonpDisableWriteProtect(); - -EXTERN_C static void RWMonpEnableWriteProtect(); - -EXTERN_C static void RWMonpHookSSDT(_In_ ULONG Index, _In_ void *HookRoutine, - _Out_opt_ void **OriginalRoutine); - -EXTERN_C static NTSTATUS NTAPI -RWMonpNtWriteVirtualMemory_Hook(_In_ HANDLE ProcessHandle, - _In_ PVOID BaseAddress, _In_ PVOID Buffer, - _In_ ULONG BytesToWrite, - _Out_opt_ PULONG BytesWritten); - -EXTERN_C static NTSTATUS NTAPI RWMonpNtMapViewOfSection_Hook( - _In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, - _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, - _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, - _In_ SECTION_INHERIT InheritDisposition, _In_ ULONG AllocationType, - _In_ ULONG Win32Protect); - -EXTERN_C static bool RWMonpCheckData(_In_ HANDLE ProcessHandle, - _In_ void *RemoteAddress, - _In_opt_ void *Contents, - _In_ ULONG DataSize); - -_Success_(return == true) EXTERN_C - static bool RWMonpIsInterprocessWrite(_In_ HANDLE ProcessHandle, - _Out_ PEPROCESS *TargetProcess); - -EXTERN_C static NTSTATUS RWMonpCopyDataFromUserSpace( - _Out_ void *Buffer, _In_ const void *BaseAddress, _In_ ULONG DataSize, - _In_opt_ PEPROCESS TargetProcess); - -EXTERN_C static NTSTATUS RWMonpCopyMemoryWithSEH(_Out_ void *Destionation, - _In_ const void *Source, - _In_ SIZE_T Length); - -_Success_(return == true) EXTERN_C - static bool RWMonpGetSha1(_Out_ UCHAR(&Sha1Hash)[20], _In_ void *Data, - _In_ SIZE_T DataSize); - -EXTERN_C static NTSTATUS RWMonpWriteFile(_In_ const wchar_t *OutPathW, - _In_ void *Buffer, - _In_ ULONG BufferSize, - _In_ ACCESS_MASK DesiredAccess, - _In_ ULONG CreateDisposition); - -//////////////////////////////////////////////////////////////////////////////// -// -// variables -// - -EXTERN_C SERVICE_DESCRIPTOR_TABLE *KeServiceDescriptorTable; - -static auto g_RWMonpNtMapViewOfSection_Index = 0; -static auto g_RWMonpNtWriteVirtualMemory_Index = 0; -static NtWriteVirtualMemoryPtrType g_RWMonpNtWriteVirtualMemory_Orig = nullptr; -static NtMapViewOfSectionPtrType g_RWMonpNtMapViewOfSection_Orig = nullptr; - -static HANDLE g_RWMonpWhiteListedProcessIDs[RWMONP_WHITELIST_ARRAY_SIZE] = {}; -static BCRYPT_ALG_HANDLE g_RWMonpSha1AlgorithmHandle = nullptr; - -//////////////////////////////////////////////////////////////////////////////// -// -// implementations -// - -// -// INIT section begin -// -ALLOC_TEXT(INIT, DriverEntry) -EXTERN_C NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, - _In_ PUNICODE_STRING RegistryPath) { - PAGED_CODE(); - UNREFERENCED_PARAMETER(RegistryPath); - auto status = STATUS_UNSUCCESSFUL; - - DriverObject->DriverUnload = RWMonpDriverUnload; - DBG_BREAK(); - - // Create a directory for a log file and dumped files before initializing - // the Log system - status = RWMonpCreateDirectory(RWMONP_OUT_DIRECTORY_PATH); - if (!NT_SUCCESS(status)) { - return status; - } - - // Initialize the Log system - status = LogInitialization( - RWMONP_LOG_LEVEL | LOG_OPT_DISABLE_TIME | LOG_OPT_DISABLE_FUNCTION_NAME, - RWMONP_LOG_FILE_PATH, nullptr); - if (!NT_SUCCESS(status)) { - return status; - } - auto scopedLogTermination = - stdexp::make_scope_exit([] { LogTermination(nullptr); }); - - // Check the OS version and initialize right indexes for SSDT hook. - RTL_OSVERSIONINFOW osVersion = {sizeof(osVersion)}; - status = RtlGetVersion(&osVersion); - if (!NT_SUCCESS(status)) { - LOG_ERROR("RtlGetVersion failed (%08x)", status); - return status; - } - if (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion == 1) { - g_RWMonpNtMapViewOfSection_Index = 0xA8; - g_RWMonpNtWriteVirtualMemory_Index = 0x18F; - } else if (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion == 3) { - g_RWMonpNtMapViewOfSection_Index = 0xF6; - g_RWMonpNtWriteVirtualMemory_Index = 0x3; - } else { - LOG_ERROR("Unsupported OS version"); - return STATUS_DEVICE_CONFIGURATION_ERROR; - } - - // Save existing processes' IDs in a white list - auto index = 0; - status = RWMonpForEachProcess(RWMonpSaveExistingPID, &index); - if (!NT_SUCCESS(status)) { - LOG_ERROR("ForEachProcess failed (%08x)", status); - return status; - } - - // Initialize the crypt APIs. - status = BCryptOpenAlgorithmProvider(&g_RWMonpSha1AlgorithmHandle, - BCRYPT_SHA1_ALGORITHM, nullptr, 0); - if (!NT_SUCCESS(status)) { - LOG_ERROR("BCryptOpenAlgorithmProvider failed (%08x)", status); - return status; - } - - // Install SSDT hooks - RWMonpSetMonitorHooks(true); - scopedLogTermination.release(); - LOG_INFO("RemoteWriteMonitor installed"); - return status; -} - -// Create a directory -ALLOC_TEXT(INIT, RWMonpCreateDirectory) -EXTERN_C static NTSTATUS RWMonpCreateDirectory(_In_ const wchar_t *PathW) { - PAGED_CODE(); - - UNICODE_STRING path = {}; - RtlInitUnicodeString(&path, PathW); - OBJECT_ATTRIBUTES objAttr = RTL_INIT_OBJECT_ATTRIBUTES( - &path, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE); - - IO_STATUS_BLOCK ioStatus = {}; - HANDLE directory = nullptr; - NTSTATUS status = ZwCreateFile( - &directory, GENERIC_WRITE, &objAttr, &ioStatus, nullptr, - FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, - FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, nullptr, 0); - if (NT_SUCCESS(status)) { - ZwClose(directory); - } - - return status; -} - -// Apply Callback for each process. Enumeration can be discontinued by returning -// false from Callback. -ALLOC_TEXT(INIT, RWMonpForEachProcess) -EXTERN_C static NTSTATUS RWMonpForEachProcess( - _In_ bool (*Callback)(_In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, - _In_opt_ void *Context), - _In_opt_ void *Context) { - PAGED_CODE(); - - auto processInfo = reinterpret_cast( - ExAllocatePoolWithTag(PagedPool, 0x10000, RWMON_POOL_TAG_NAME)); - if (!processInfo) { - return STATUS_MEMORY_NOT_ALLOCATED; - } - - ULONG returnLength = 0; - auto status = ZwQuerySystemInformation(SystemProcessInformation, processInfo, - 0x10000, &returnLength); - if (!NT_SUCCESS(status) && returnLength) { - ExFreePoolWithTag(processInfo, RWMON_POOL_TAG_NAME); - processInfo = - reinterpret_cast(ExAllocatePoolWithTag( - PagedPool, returnLength + PAGE_SIZE, RWMON_POOL_TAG_NAME)); - if (!processInfo) { - return STATUS_MEMORY_NOT_ALLOCATED; - } - - status = - ZwQuerySystemInformation(SystemProcessInformation, processInfo, - (returnLength + PAGE_SIZE), &returnLength); - } - const auto scopedExFreePoolWithTag = stdexp::make_scope_exit( - [processInfo] { ExFreePoolWithTag(processInfo, RWMON_POOL_TAG_NAME); }); - if (!NT_SUCCESS(status)) { - return status; - } - - for (auto current = processInfo; current; /**/) { - if (!Callback(current, Context)) { - break; - } - - if (!current->NextEntryOffset) { - break; - } - current = reinterpret_cast( - reinterpret_cast(current) + current->NextEntryOffset); - } - - return status; -} - -// A callback routine saving existing processes' IDs into a white list. -ALLOC_TEXT(INIT, RWMonpSaveExistingPID) -EXTERN_C static bool RWMonpSaveExistingPID( - _In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, _In_ void *Context) { - PAGED_CODE(); - - auto &index = *static_cast(Context); - if (index >= - RWMONP_WHITELIST_ARRAY_SIZE - 1) { // -1 to have 0 at the end at least - return false; - } - if (ProcessInfo->UniqueProcessId) { - g_RWMonpWhiteListedProcessIDs[index++] = ProcessInfo->UniqueProcessId; - } - return true; -} - -// -// Unloading Functions -// - -// Unloading the driver. Close and restore everything. -ALLOC_TEXT(PAGED, RWMonpDriverUnload) -EXTERN_C static void RWMonpDriverUnload(_In_ PDRIVER_OBJECT DriverObject) { - PAGED_CODE(); - UNREFERENCED_PARAMETER(DriverObject); - - LOG_DEBUG("Being terminated."); - // DBG_BREAK(); - - RWMonpSetMonitorHooks(false); - RWMonpSleep(1000); - BCryptCloseAlgorithmProvider(g_RWMonpSha1AlgorithmHandle, 0); - LogTermination(nullptr); -} - -// Sleep. -ALLOC_TEXT(PAGED, RWMonpSleep) -EXTERN_C static NTSTATUS RWMonpSleep(_In_ LONG Millisecond) { - PAGED_CODE(); - - LARGE_INTEGER interval = {}; - interval.QuadPart = -(10000 * Millisecond); // msec - return KeDelayExecutionThread(KernelMode, FALSE, &interval); -} - -// -// Common -// - -// Install or Uninstall necessary SSDT hooks. -EXTERN_C static NTSTATUS RWMonpSetMonitorHooks(_In_ bool Enable) { - // Need to rise IRQL not to allow the system to change an execution processor - // during the operation because this code changes a state of processor (CR0). - KIRQL oldIrql = 0; - KeRaiseIrql(DISPATCH_LEVEL, &oldIrql); - const auto scopedIrql = - stdexp::make_scope_exit([oldIrql]() { KeLowerIrql(oldIrql); }); - - RWMonpDisableWriteProtect(); - const auto scopedWriteProtection = - stdexp::make_scope_exit([] { RWMonpEnableWriteProtect(); }); - - if (Enable) { - // Install - RWMonpHookSSDT(g_RWMonpNtMapViewOfSection_Index, - RWMonpNtMapViewOfSection_Hook, - reinterpret_cast(&g_RWMonpNtMapViewOfSection_Orig)); - RWMonpHookSSDT( - g_RWMonpNtWriteVirtualMemory_Index, RWMonpNtWriteVirtualMemory_Hook, - reinterpret_cast(&g_RWMonpNtWriteVirtualMemory_Orig)); - } else { - // Uninstall - RWMonpHookSSDT(g_RWMonpNtMapViewOfSection_Index, - g_RWMonpNtMapViewOfSection_Orig, nullptr); - RWMonpHookSSDT(g_RWMonpNtWriteVirtualMemory_Index, - g_RWMonpNtWriteVirtualMemory_Orig, nullptr); - } - return STATUS_SUCCESS; -} - -// Disable the write protection -EXTERN_C static void RWMonpDisableWriteProtect() { - CR0_REGISTER cr0 = {__readcr0()}; - cr0.Fields.WP = false; - __writecr0(cr0.Value); -} - -// Enable the write protection -EXTERN_C static void RWMonpEnableWriteProtect() { - CR0_REGISTER cr0 = {__readcr0()}; - cr0.Fields.WP = true; - __writecr0(cr0.Value); -} - -// Get an original value of the SSDT and replace it with a new value. -EXTERN_C static void RWMonpHookSSDT(_In_ ULONG Index, _In_ void *HookRoutine, - _Out_opt_ void **OriginalRoutine) { - if (OriginalRoutine) { - *OriginalRoutine = KeServiceDescriptorTable->ServiceTable[Index]; - } - KeServiceDescriptorTable->ServiceTable[Index] = HookRoutine; -} - -// -// Hook Handlers -// - -// A hook handler for NtWriteVirtualMemory -ALLOC_TEXT(PAGED, RWMonpNtWriteVirtualMemory_Hook) -EXTERN_C static NTSTATUS NTAPI -RWMonpNtWriteVirtualMemory_Hook(_In_ HANDLE ProcessHandle, - _In_ PVOID BaseAddress, _In_ PVOID Buffer, - _In_ ULONG BytesToWrite, - _Out_opt_ PULONG BytesWritten) { - PAGED_CODE(); - - const auto result = g_RWMonpNtWriteVirtualMemory_Orig( - ProcessHandle, BaseAddress, Buffer, BytesToWrite, BytesWritten); - if (NT_SUCCESS(result)) { - RWMonpCheckData(ProcessHandle, BaseAddress, Buffer, BytesToWrite); - } - return result; -} - -// A hook handler for NtMapViewOfSection -ALLOC_TEXT(PAGED, RWMonpNtMapViewOfSection_Hook) -EXTERN_C static NTSTATUS NTAPI RWMonpNtMapViewOfSection_Hook( - _In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, - _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, - _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, - _In_ SECTION_INHERIT InheritDisposition, _In_ ULONG AllocationType, - _In_ ULONG Win32Protect) { - PAGED_CODE(); - - const auto result = g_RWMonpNtMapViewOfSection_Orig( - SectionHandle, ProcessHandle, BaseAddress, ZeroBits, CommitSize, - SectionOffset, ViewSize, InheritDisposition, AllocationType, - Win32Protect); - if (NT_SUCCESS(result)) { - RWMonpCheckData(ProcessHandle, *BaseAddress, nullptr, *ViewSize); - } - return result; -} - -// Check if the call is inter-process write, and log it if so. -ALLOC_TEXT(PAGED, RWMonpCheckData) -EXTERN_C static bool RWMonpCheckData(_In_ HANDLE ProcessHandle, - _In_ void *RemoteAddress, - _In_opt_ void *Contents, - _In_ ULONG DataSize) { - PAGED_CODE(); - - const auto isWriteVirtualMemory = (Contents != nullptr); - - // Check if it is a interprocess operation - PEPROCESS targetProcess = nullptr; - if (!RWMonpIsInterprocessWrite(ProcessHandle, &targetProcess)) { - return false; - } - const auto scopedDereference = stdexp::make_scope_exit( - [targetProcess] { ObDereferenceObject(targetProcess); }); - - // Allocate a memory to copy written data - auto data = stdexp::make_unique_resource( - ExAllocatePoolWithTag(PagedPool, DataSize, RWMON_POOL_TAG_NAME), - [](void *p) { ExFreePoolWithTag(p, RWMON_POOL_TAG_NAME); }); - if (!data) { - return false; - } - - // Copy the written data - auto status = STATUS_SUCCESS; - if (Contents) { - status = - RWMonpCopyDataFromUserSpace(data.get(), Contents, DataSize, nullptr); - } else { - status = RWMonpCopyDataFromUserSpace(data.get(), RemoteAddress, DataSize, - targetProcess); - } - if (!NT_SUCCESS(status)) { - LOG_ERROR_SAFE("CopyDataFromUserSpace failed (%08x)", status); - return false; - } - - // Calculate SHA1 of the written data - UCHAR sha1Hash[20] = {}; - if (!RWMonpGetSha1(sha1Hash, data.get(), DataSize)) { - return false; - } - wchar_t sha1HashW[41] = {}; - for (auto i = 0; i < RTL_NUMBER_OF(sha1Hash); ++i) { - const auto outW = sha1HashW + i * 2; - RtlStringCchPrintfW(outW, 3, L"%02x", sha1Hash[i]); - } - - // Save it to a file - wchar_t outPathW[260]; - status = RtlStringCchPrintfW(outPathW, RTL_NUMBER_OF(outPathW), L"%s\\%s.bin", - RWMONP_OUT_DIRECTORY_PATH, sha1HashW); - if (!NT_SUCCESS(status)) { - LOG_ERROR_SAFE("RtlStringCchPrintfW failed (%08x)", status); - return false; - } - status = RWMonpWriteFile(outPathW, data.get(), DataSize, GENERIC_WRITE, - FILE_CREATE); - if (!NT_SUCCESS(status) && status != STATUS_OBJECT_NAME_COLLISION) { - LOG_ERROR_SAFE("WriteFile failed (%08x)", status); - return false; - } - - // Log it - LOG_INFO_SAFE("Remote %s onto %5lu (%-15s) at %p (saved as %S, %lu bytes)", - (isWriteVirtualMemory) ? "write" : "map ", - PsGetProcessId(targetProcess), - PsGetProcessImageFileName(targetProcess), RemoteAddress, - sha1HashW, DataSize); - return true; -} - -// Check if the write operation is interprocess and from a not white listed -// process -ALLOC_TEXT(PAGED, RWMonpIsInterprocessWrite) -_Success_(return == true) EXTERN_C - static bool RWMonpIsInterprocessWrite(_In_ HANDLE ProcessHandle, - _Out_ PEPROCESS *TargetProcess) { - PAGED_CODE(); - - if (ProcessHandle == ZwCurrentProcess()) { - return false; - } - - const auto pid = PsGetCurrentProcessId(); - for (auto i = 0; g_RWMonpWhiteListedProcessIDs[i]; ++i) { - if (g_RWMonpWhiteListedProcessIDs[i] == pid) { - return false; - } - } - - auto status = ObReferenceObjectByHandle( - ProcessHandle, 0, *PsProcessType, UserMode, - reinterpret_cast(TargetProcess), nullptr); - if (!NT_SUCCESS(status)) { - LOG_ERROR_SAFE("ObReferenceObjectByHandle failed (%08x)", status); - return false; - } - - if (*TargetProcess == PsGetCurrentProcess()) { - ObDereferenceObject(*TargetProcess); - return false; - } - return true; -} - -// Copy data from user-space -ALLOC_TEXT(PAGED, RWMonpCopyDataFromUserSpace) -EXTERN_C static NTSTATUS RWMonpCopyDataFromUserSpace( - _Out_ void *Buffer, _In_ const void *BaseAddress, _In_ ULONG DataSize, - _In_opt_ PEPROCESS TargetProcess) { - PAGED_CODE(); - - if (TargetProcess) { - // Need to switch to another process memory space to access the data - KAPC_STATE apcState = {}; - KeStackAttachProcess(TargetProcess, &apcState); - const auto scopedKeUnstackDetachProcess = stdexp::make_scope_exit( - [&apcState] { KeUnstackDetachProcess(&apcState); }); - return RWMonpCopyMemoryWithSEH(Buffer, BaseAddress, DataSize); - } else { - // The current process contains the data - return RWMonpCopyMemoryWithSEH(Buffer, BaseAddress, DataSize); - } -} - -// RtlCopyMemory wrapped with SEH -ALLOC_TEXT(PAGED, RWMonpCopyMemoryWithSEH) -EXTERN_C static NTSTATUS RWMonpCopyMemoryWithSEH(_Out_ void *Destionation, - _In_ const void *Source, - _In_ SIZE_T Length) { - PAGED_CODE(); - - auto status = STATUS_SUCCESS; - __try { - RtlCopyMemory(Destionation, Source, Length); - } __except (status = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER) { - } - return status; -} - -// Calculate SHA1 -ALLOC_TEXT(PAGED, RWMonpGetSha1) -_Success_(return == true) EXTERN_C - static bool RWMonpGetSha1(_Out_ UCHAR(&Sha1Hash)[20], _In_ void *Data, - _In_ SIZE_T DataSize) { - PAGED_CODE(); - - BCRYPT_HASH_HANDLE hashHandle = nullptr; - auto status = BCryptCreateHash(g_RWMonpSha1AlgorithmHandle, &hashHandle, - nullptr, 0, nullptr, 0, 0); - if (!NT_SUCCESS(status)) { - LOG_ERROR_SAFE("BCryptCreateHash failed (%08x)", status); - return false; - } - const auto scopedBCryptDestroyHash = - stdexp::make_scope_exit([hashHandle] { BCryptDestroyHash(hashHandle); }); - - status = BCryptHashData(hashHandle, static_cast(Data), DataSize, 0); - if (!NT_SUCCESS(status)) { - LOG_ERROR_SAFE("BCryptHashData failed (%08x)", status); - return false; - } - - static_assert(sizeof(Sha1Hash) == 20, "Size check"); - status = BCryptFinishHash(hashHandle, Sha1Hash, sizeof(Sha1Hash), 0); - if (!NT_SUCCESS(status)) { - LOG_ERROR_SAFE("BCryptFinishHash failed (%08x)", status); - return false; - } - - return true; -} - -// Write data to a file -ALLOC_TEXT(PAGED, RWMonpWriteFile) -EXTERN_C static NTSTATUS RWMonpWriteFile(_In_ const wchar_t *OutPathW, - _In_ void *Buffer, - _In_ ULONG BufferSize, - _In_ ACCESS_MASK DesiredAccess, - _In_ ULONG CreateDisposition) { - PAGED_CODE(); - - UNICODE_STRING outPath = {}; - RtlInitUnicodeString(&outPath, OutPathW); - OBJECT_ATTRIBUTES objAttr = RTL_INIT_OBJECT_ATTRIBUTES( - &outPath, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE); - - IO_STATUS_BLOCK ioStatus = {}; - HANDLE file = nullptr; - auto status = ZwCreateFile( - &file, DesiredAccess, &objAttr, &ioStatus, nullptr, FILE_ATTRIBUTE_NORMAL, - FILE_SHARE_READ | FILE_SHARE_WRITE, CreateDisposition, - FILE_SEQUENTIAL_ONLY | FILE_SYNCHRONOUS_IO_NONALERT | - FILE_NON_DIRECTORY_FILE, - nullptr, 0); - if (!NT_SUCCESS(status)) { - return status; - } - - status = ZwWriteFile(file, nullptr, nullptr, nullptr, &ioStatus, Buffer, - BufferSize, nullptr, nullptr); - ZwClose(file); - return status; -} +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module implements an entry point of the driver and initializes other +// components in this module. +// +#include "stdafx.h" +#include "log.h" +#include "asm.h" +#include "inline.h" +#include "check.h" +#include "ssdt.h" +#include "util.h" + +namespace stdexp = std::experimental; + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +static const wchar_t RWMONP_OUT_DIRECTORY_PATH[] = + L"\\SystemRoot\\RemoteWriteMonitor"; +static const wchar_t RWMONP_LOG_FILE_PATH[] = + L"\\SystemRoot\\RemoteWriteMonitor\\RemoteWriteMonitor.log"; + +#if DBG +static const auto RWMONP_LOG_LEVEL = LOG_PUT_LEVEL_DEBUG; +#else +static const auto RWMONP_LOG_LEVEL = LOG_PUT_LEVEL_INFO; +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C +NTSTATUS NTAPI +NtMapViewOfSection(_In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, + _In_ SIZE_T CommitSize, + _Inout_opt_ PLARGE_INTEGER SectionOffset, + _Inout_ PSIZE_T ViewSize, + _In_ SECTION_INHERIT InheritDisposition, + _In_ ULONG AllocationType, _In_ ULONG Win32Protect); +using NtMapViewOfSectionType = decltype(&NtMapViewOfSection); + +EXTERN_C +NTSTATUS NTAPI NtWriteVirtualMemory(_In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, _In_ PVOID Buffer, + _In_ ULONG BytesToWrite, + _Out_opt_ PULONG BytesWritten); + +using NtWriteVirtualMemoryType = decltype(&NtWriteVirtualMemory); + +EXTERN_C DRIVER_INITIALIZE DriverEntry; + +EXTERN_C static NTSTATUS RWMonpCreateDirectory(_In_ const wchar_t *PathW); + +EXTERN_C static NTSTATUS RWMonpInitVersionDependentValues(); + +EXTERN_C static DRIVER_UNLOAD RWMonpDriverUnload; + +EXTERN_C static NTSTATUS RWMonpSleep(_In_ LONG Millisecond); + +EXTERN_C static NTSTATUS RWMonpInstallHooks(); + +EXTERN_C static NTSTATUS RWMonpUninstallHooks(); + +EXTERN_C static NTSTATUS NTAPI +RWMonpNtWriteVirtualMemory_Hook(_In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, _In_ PVOID Buffer, + _In_ ULONG BytesToWrite, + _Out_opt_ PULONG BytesWritten); + +EXTERN_C static NTSTATUS NTAPI RWMonpNtMapViewOfSection_Hook( + _In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, + _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, + _In_ SECTION_INHERIT InheritDisposition, _In_ ULONG AllocationType, + _In_ ULONG Win32Protect); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +static HookInfo g_RWMonpNtMapViewOfSectionInfo = {}; +static HookInfo g_RWMonpNtWriteVirtualMemoryInfo = {}; + +static NtMapViewOfSectionType g_RWMonpNtMapViewOfSectionOriginal = nullptr; +static NtWriteVirtualMemoryType g_RWMonpNtWriteVirtualMemoryOriginal = nullptr; + +static ULONG g_RWMonpNtMapViewOfSectionSSDTIndex = 0; +static ULONG g_RWMonpNtWriteVirtualMemorySSDTIndex = 0; + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +// +// INIT section begin +// +ALLOC_TEXT(INIT, DriverEntry) +EXTERN_C NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath) { + PAGED_CODE(); + UNREFERENCED_PARAMETER(RegistryPath); + auto status = STATUS_UNSUCCESSFUL; + + DriverObject->DriverUnload = RWMonpDriverUnload; + DBG_BREAK(); + + // Create a directory for a log file and dumped files before initializing + // the Log system + status = RWMonpCreateDirectory(RWMONP_OUT_DIRECTORY_PATH); + if (!NT_SUCCESS(status)) { + return status; + } + + // Initialize the Log system + status = LogInitialization( + RWMONP_LOG_LEVEL | LOG_OPT_DISABLE_TIME | LOG_OPT_DISABLE_FUNCTION_NAME, + RWMONP_LOG_FILE_PATH, nullptr); + if (!NT_SUCCESS(status)) { + return status; + } + auto scopedLogTermination = + stdexp::make_scope_exit([] { LogTermination(nullptr); }); + + // Init SSDT + status = SSDTInitialization(); + if (!NT_SUCCESS(status)) { + return status; + } + auto scopedSSDTTermination = + stdexp::make_scope_exit([] { SSDTTermination(); }); + + // Init globals + status = RWMonpInitVersionDependentValues(); + if (!NT_SUCCESS(status)) { + return status; + } + + // Init the Check subsystem + status = CheckInitialization(RWMONP_OUT_DIRECTORY_PATH); + if (!NT_SUCCESS(status)) { + return status; + } + auto scopedCheckTermination = + stdexp::make_scope_exit([] { CheckTermination(); }); + + status = RWMonpInstallHooks(); + if (!NT_SUCCESS(status)) { + return status; + } + + scopedCheckTermination.release(); + scopedSSDTTermination.release(); + scopedLogTermination.release(); + LOG_INFO("RemoteWriteMonitor installed"); + return status; +} + +ALLOC_TEXT(INIT, RWMonpInitVersionDependentValues) +EXTERN_C static NTSTATUS RWMonpInitVersionDependentValues() { + PAGED_CODE(); + + // Check the OS version and initialize right indexes for SSDT hook. + RTL_OSVERSIONINFOW osVersion = {sizeof(osVersion)}; + auto status = RtlGetVersion(&osVersion); + if (!NT_SUCCESS(status)) { + LOG_ERROR("RtlGetVersion failed (%08x)", status); + return status; + } + + if (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion == 1) { + // Windows 7 + if (IsX64()) { + // x64 + g_RWMonpNtMapViewOfSectionSSDTIndex = 0x25; + g_RWMonpNtMapViewOfSectionOriginal = + reinterpret_cast( + AsmNtMapViewOfSection_Win81_7); + status = InlineInitHookInfo( + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtMapViewOfSectionSSDTIndex)), + reinterpret_cast(RWMonpNtMapViewOfSection_Hook), + reinterpret_cast(AsmNtMapViewOfSection_Win81_7), + reinterpret_cast(AsmNtMapViewOfSection_Win81_7End), + &g_RWMonpNtMapViewOfSectionInfo); + if (!NT_SUCCESS(status)) { + return status; + } + + g_RWMonpNtWriteVirtualMemorySSDTIndex = 0x37; + g_RWMonpNtWriteVirtualMemoryOriginal = + reinterpret_cast( + AsmNtWriteVirtualMemory_Win7); + status = InlineInitHookInfo( + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtWriteVirtualMemorySSDTIndex)), + reinterpret_cast(RWMonpNtWriteVirtualMemory_Hook), + reinterpret_cast(AsmNtWriteVirtualMemory_Win7), + reinterpret_cast(AsmNtWriteVirtualMemory_Win7End), + &g_RWMonpNtWriteVirtualMemoryInfo); + if (!NT_SUCCESS(status)) { + return status; + } + } else { + // x86 + g_RWMonpNtMapViewOfSectionSSDTIndex = 0xa8; + g_RWMonpNtMapViewOfSectionOriginal = + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtMapViewOfSectionSSDTIndex)); + + g_RWMonpNtWriteVirtualMemorySSDTIndex = 0x18f; + g_RWMonpNtWriteVirtualMemoryOriginal = + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtWriteVirtualMemorySSDTIndex)); + } + } else if (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion == 3) { + // Windows 8.1 + if (IsX64()) { + // x64 + g_RWMonpNtMapViewOfSectionSSDTIndex = 0x27; + g_RWMonpNtMapViewOfSectionOriginal = + reinterpret_cast( + AsmNtMapViewOfSection_Win81_7); + status = InlineInitHookInfo( + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtMapViewOfSectionSSDTIndex)), + reinterpret_cast(RWMonpNtMapViewOfSection_Hook), + reinterpret_cast(AsmNtMapViewOfSection_Win81_7), + reinterpret_cast(AsmNtMapViewOfSection_Win81_7End), + &g_RWMonpNtMapViewOfSectionInfo); + if (!NT_SUCCESS(status)) { + return status; + } + + g_RWMonpNtWriteVirtualMemorySSDTIndex = 0x39; + g_RWMonpNtWriteVirtualMemoryOriginal = + reinterpret_cast( + AsmNtWriteVirtualMemory_Win81); + status = InlineInitHookInfo( + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtWriteVirtualMemorySSDTIndex)), + reinterpret_cast(RWMonpNtWriteVirtualMemory_Hook), + reinterpret_cast(AsmNtWriteVirtualMemory_Win81), + reinterpret_cast(AsmNtWriteVirtualMemory_Win81End), + &g_RWMonpNtWriteVirtualMemoryInfo); + if (!NT_SUCCESS(status)) { + return status; + } + + } else { + // x86 + g_RWMonpNtMapViewOfSectionSSDTIndex = 0xf6; + g_RWMonpNtMapViewOfSectionOriginal = + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtMapViewOfSectionSSDTIndex)); + + g_RWMonpNtWriteVirtualMemorySSDTIndex = 0x3; + g_RWMonpNtWriteVirtualMemoryOriginal = + reinterpret_cast( + SSDTGetProcAdderss(g_RWMonpNtWriteVirtualMemorySSDTIndex)); + } + } else { + LOG_ERROR("Unsupported OS version"); + return STATUS_DEVICE_CONFIGURATION_ERROR; + } + return status; +} + +// Create a directory +ALLOC_TEXT(INIT, RWMonpCreateDirectory) +EXTERN_C static NTSTATUS RWMonpCreateDirectory(_In_ const wchar_t *PathW) { + PAGED_CODE(); + + UNICODE_STRING path = {}; + RtlInitUnicodeString(&path, PathW); + OBJECT_ATTRIBUTES objAttr = RTL_INIT_OBJECT_ATTRIBUTES( + &path, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE); + + IO_STATUS_BLOCK ioStatus = {}; + HANDLE directory = nullptr; + NTSTATUS status = ZwCreateFile( + &directory, GENERIC_WRITE, &objAttr, &ioStatus, nullptr, + FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, nullptr, 0); + if (NT_SUCCESS(status)) { + ZwClose(directory); + } + + return status; +} + +// Unloading the driver. Close and restore everything. +ALLOC_TEXT(PAGED, RWMonpDriverUnload) +EXTERN_C static void RWMonpDriverUnload(_In_ PDRIVER_OBJECT DriverObject) { + PAGED_CODE(); + UNREFERENCED_PARAMETER(DriverObject); + + LOG_DEBUG("Being terminated."); + // DBG_BREAK(); + + RWMonpUninstallHooks(); + RWMonpSleep(1000); + CheckTermination(); + SSDTTermination(); + LogTermination(nullptr); +} + +// Sleep. +ALLOC_TEXT(PAGED, RWMonpSleep) +EXTERN_C static NTSTATUS RWMonpSleep(_In_ LONG Millisecond) { + PAGED_CODE(); + + LARGE_INTEGER interval = {}; + interval.QuadPart = -(10000 * Millisecond); // msec + return KeDelayExecutionThread(KernelMode, FALSE, &interval); +} + +ALLOC_TEXT(INIT, RWMonpInstallHooks) +EXTERN_C static NTSTATUS RWMonpInstallHooks() { + PAGED_CODE(); + + auto status = STATUS_SUCCESS; + if (IsX64()) { + status = InlineInstallHook(g_RWMonpNtMapViewOfSectionInfo); + if (!NT_SUCCESS(status)) { + return status; + } + status = InlineInstallHook(g_RWMonpNtWriteVirtualMemoryInfo); + if (!NT_SUCCESS(status)) { + InlineUninstallHook(g_RWMonpNtMapViewOfSectionInfo); + return status; + } + } else { + SSDTSetProcAdderss( + g_RWMonpNtMapViewOfSectionSSDTIndex, + reinterpret_cast(RWMonpNtMapViewOfSection_Hook)); + SSDTSetProcAdderss( + g_RWMonpNtWriteVirtualMemorySSDTIndex, + reinterpret_cast(RWMonpNtWriteVirtualMemory_Hook)); + } + return status; +} + +ALLOC_TEXT(PAGED, RWMonpUninstallHooks) +EXTERN_C static NTSTATUS RWMonpUninstallHooks() { + PAGED_CODE(); + + auto status = STATUS_SUCCESS; + if (IsX64()) { + status = InlineUninstallHook(g_RWMonpNtWriteVirtualMemoryInfo); + status = InlineUninstallHook(g_RWMonpNtMapViewOfSectionInfo); + } else { + SSDTSetProcAdderss( + g_RWMonpNtMapViewOfSectionSSDTIndex, + reinterpret_cast(g_RWMonpNtMapViewOfSectionOriginal)); + SSDTSetProcAdderss( + g_RWMonpNtWriteVirtualMemorySSDTIndex, + reinterpret_cast(g_RWMonpNtWriteVirtualMemoryOriginal)); + } + return status; +} + +// +// Hook Handlers +// + +// A hook handler for NtMapViewOfSection +ALLOC_TEXT(PAGED, RWMonpNtMapViewOfSection_Hook) +EXTERN_C static NTSTATUS NTAPI RWMonpNtMapViewOfSection_Hook( + _In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, + _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, + _In_ SECTION_INHERIT InheritDisposition, _In_ ULONG AllocationType, + _In_ ULONG Win32Protect) { + PAGED_CODE(); + + const auto result = g_RWMonpNtMapViewOfSectionOriginal( + SectionHandle, ProcessHandle, BaseAddress, ZeroBits, CommitSize, + SectionOffset, ViewSize, InheritDisposition, AllocationType, + Win32Protect); + if (NT_SUCCESS(result)) { + CheckData(ProcessHandle, *BaseAddress, nullptr, + static_cast(*ViewSize)); + } + return result; +} + +// A hook handler for NtWriteVirtualMemory +ALLOC_TEXT(PAGED, RWMonpNtWriteVirtualMemory_Hook) +EXTERN_C static NTSTATUS NTAPI +RWMonpNtWriteVirtualMemory_Hook(_In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, _In_ PVOID Buffer, + _In_ ULONG BytesToWrite, + _Out_opt_ PULONG BytesWritten) { + PAGED_CODE(); + + const auto result = g_RWMonpNtWriteVirtualMemoryOriginal( + ProcessHandle, BaseAddress, Buffer, BytesToWrite, BytesWritten); + if (NT_SUCCESS(result)) { + CheckData(ProcessHandle, BaseAddress, Buffer, BytesToWrite); + } + return result; +} \ No newline at end of file diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj index 687d074..0e38377 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj +++ b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj @@ -1,115 +1,280 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {287B2687-2894-4AA5-A5A9-686AE6C5F34A} - Win32Proj - RemoteWriteMonitor - - - - Application - true - WindowsKernelModeDriver8.1 - NotSet - - - Application - false - WindowsKernelModeDriver8.1 - true - NotSet - - - - - - - - - - - - - false - .sys - false - - - false - .sys - false - - - - Use - Level4 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;RemoteWriteMonitor_EXPORTS;%(PreprocessorDefinitions) - true - $(WindowsSdkDir)Include\km - false - false - - - Native - true - $(WindowsSdkDir)Lib\winv6.3\km\$(PlatformTarget);$(WindowsSdkDir)Lib\winv6.3\um\$(PlatformTarget);%(AdditionalLibraryDirectories) - ntoskrnl.lib;wdm.lib;wmilib.lib;hal.lib;Aux_klib.lib;wdmsec.lib;ksecdd.lib;BufferOverflowK.lib - true - Driver - GsDriverEntry - - - - - Level4 - Use - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;RemoteWriteMonitor_EXPORTS;%(PreprocessorDefinitions) - true - $(WindowsSdkDir)Include\km - false - false - - - Native - true - true - true - $(WindowsSdkDir)Lib\winv6.3\km\$(PlatformTarget);$(WindowsSdkDir)Lib\winv6.3\um\$(PlatformTarget);%(AdditionalLibraryDirectories) - ntoskrnl.lib;wdm.lib;wmilib.lib;hal.lib;Aux_klib.lib;wdmsec.lib;ksecdd.lib;BufferOverflowK.lib - true - Driver - GsDriverEntry - - - - - - - - - - - - - Create - Create - - - - - + + + + + Win8.1 Debug + Win32 + + + Win8.1 Release + Win32 + + + Win7 Debug + Win32 + + + Win7 Release + Win32 + + + Win8.1 Debug + x64 + + + Win8.1 Release + x64 + + + Win7 Debug + x64 + + + Win7 Release + x64 + + + + {987A0E0D-CF4E-4DC8-A5FE-1CCCC3D75082} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 11.0 + Win8.1 Debug + Win32 + RemoteWriteMonitor + + + + WindowsV6.3 + true + WindowsKernelModeDriver8.1 + Driver + KMDF + + + WindowsV6.3 + false + WindowsKernelModeDriver8.1 + Driver + KMDF + + + Windows7 + true + WindowsKernelModeDriver8.1 + Driver + KMDF + + + Windows7 + false + WindowsKernelModeDriver8.1 + Driver + KMDF + + + WindowsV6.3 + true + WindowsKernelModeDriver8.1 + Driver + KMDF + + + WindowsV6.3 + false + WindowsKernelModeDriver8.1 + Driver + KMDF + + + Windows7 + true + WindowsKernelModeDriver8.1 + Driver + KMDF + + + Windows7 + false + WindowsKernelModeDriver8.1 + Driver + KMDF + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + false + trace.h + true + $(VCInstallDir)\include;%(AdditionalIncludeDirectories) + Use + + + Ksecdd.lib;%(AdditionalDependencies) + + + + + + + + + true + true + true + true + + + + + + + + Create + Create + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + Document + true + true + true + true + + + + + \ No newline at end of file diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.filters b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.filters index 776c765..171019c 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.filters +++ b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.filters @@ -1,48 +1,80 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {e24d7c43-af6b-4430-9f6f-7e9907caa1b0} - - - {44082a1d-9f05-42e9-b7cd-db78af9eb8ed} - - - - - Header Files - - - Header Files\../Common\ScopedResource - - - Header Files\../Common\ScopedResource - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {92ec6841-3b89-43a6-8ce1-3dd70898e636} + + + {50b9ecfc-b33b-4adb-8bcf-6cf305042be6} + + + {e6866ce5-f1d2-47bd-b2b5-763276207d0c} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\Arch\x86 + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + \ No newline at end of file diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.user b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.user index a839d78..8bd5638 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.user +++ b/RemoteWriteMonitor/RemoteWriteMonitor/RemoteWriteMonitor.vcxproj.user @@ -1,5 +1,31 @@ - - - - + + + + $(ProjectDir)satoshitanda.pfx + + + TestSign + $(ProjectDir)satoshitanda.pfx + + + $(ProjectDir)satoshitanda.pfx + + + TestSign + $(ProjectDir)satoshitanda.pfx + + + $(ProjectDir)satoshitanda.pfx + + + TestSign + $(ProjectDir)satoshitanda.pfx + + + $(ProjectDir)satoshitanda.pfx + + + TestSign + $(ProjectDir)satoshitanda.pfx + \ No newline at end of file diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/asm.h b/RemoteWriteMonitor/RemoteWriteMonitor/asm.h new file mode 100644 index 0000000..0cf8575 --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/asm.h @@ -0,0 +1,50 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module declares interfaces to functions written in assembler. +// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C void AsmNtMapViewOfSection_Win81_7(); + +EXTERN_C void AsmNtMapViewOfSection_Win81_7End(); + +EXTERN_C void AsmNtWriteVirtualMemory_Win81(); + +EXTERN_C void AsmNtWriteVirtualMemory_Win81End(); + +EXTERN_C void AsmNtWriteVirtualMemory_Win7(); + +EXTERN_C void AsmNtWriteVirtualMemory_Win7End(); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/check.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/check.cpp new file mode 100644 index 0000000..ec123bd --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/check.cpp @@ -0,0 +1,416 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module implements an entry point of the driver and initializes other +// components in this module. +// +#include "stdafx.h" +#include "check.h" +#include "log.h" + +namespace stdexp = std::experimental; + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +static const auto CHECKP_WHITELIST_ARRAY_SIZE = 1000; + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// +struct SYSTEM_PROCESS_INFORMATION { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + BYTE Reserved1[48]; + PVOID Reserved2[3]; + HANDLE UniqueProcessId; + PVOID Reserved3; + ULONG HandleCount; + BYTE Reserved4[4]; + PVOID Reserved5[11]; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER Reserved6[6]; +}; + +enum SYSTEM_INFORMATION_CLASS { + SystemProcessInformation = 5, +}; +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C NTKERNELAPI UCHAR *NTAPI +PsGetProcessImageFileName(_In_ PEPROCESS Process); + +EXTERN_C NTSTATUS NTAPI +ZwQuerySystemInformation(_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _Inout_ PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength); + +EXTERN_C static NTSTATUS CheckpForEachProcess( + _In_ bool (*Callback)(_In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, + _In_opt_ void *Context), + _In_opt_ void *Context); + +EXTERN_C static bool CheckpSaveExistingPID( + _In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, _In_ void *Context); + +_Success_(return == true) EXTERN_C + static bool CheckpIsInterprocessWrite(_In_ HANDLE ProcessHandle, + _Out_ PEPROCESS *TargetProcess); + +EXTERN_C static NTSTATUS CheckpCopyDataFromUserSpace( + _Out_ void *Buffer, _In_ const void *BaseAddress, _In_ ULONG DataSize, + _In_opt_ PEPROCESS TargetProcess); + +EXTERN_C static NTSTATUS CheckpTryCopyMemory(_Out_ void *Destionation, + _In_ const void *Source, + _In_ SIZE_T Length); + +_Success_(return == true) EXTERN_C + static bool CheckpGetSha1(_Out_ UCHAR(&Sha1Hash)[20], _In_ void *Data, + _In_ ULONG DataSize); + +EXTERN_C static NTSTATUS CheckpWriteFile(_In_ const wchar_t *OutPathW, + _In_ void *Buffer, + _In_ ULONG BufferSize, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG CreateDisposition); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +static wchar_t g_CheckpLogDirecotry[MAX_PATH]; +static HANDLE g_CheckpWhiteListedProcessIDs[CHECKP_WHITELIST_ARRAY_SIZE] = {}; +static BCRYPT_ALG_HANDLE g_CheckpSha1AlgorithmHandle = nullptr; + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +ALLOC_TEXT(INIT, CheckInitialization) +EXTERN_C NTSTATUS CheckInitialization(_In_ const wchar_t *LogDirectry) { + PAGED_CODE(); + + auto status = RtlStringCchCopyW( + g_CheckpLogDirecotry, RTL_NUMBER_OF(g_CheckpLogDirecotry), LogDirectry); + if (!NT_SUCCESS(status)) { + LOG_ERROR("RtlStringCchCopyW failed (%08x)", status); + return status; + } + + // Save existing processes' IDs in a white list + auto index = 0; + status = CheckpForEachProcess(CheckpSaveExistingPID, &index); + if (!NT_SUCCESS(status)) { + LOG_ERROR("ForEachProcess failed (%08x)", status); + return status; + } + + // Initialize the crypt APIs. + status = BCryptOpenAlgorithmProvider(&g_CheckpSha1AlgorithmHandle, + BCRYPT_SHA1_ALGORITHM, nullptr, 0); + if (!NT_SUCCESS(status)) { + LOG_ERROR("BCryptOpenAlgorithmProvider failed (%08x)", status); + return status; + } + return status; +} + +ALLOC_TEXT(PAGED, CheckTermination) +EXTERN_C void CheckTermination() { + PAGED_CODE(); + BCryptCloseAlgorithmProvider(g_CheckpSha1AlgorithmHandle, 0); +} + +// Apply Callback for each process. Enumeration can be discontinued by returning +// false from Callback. +ALLOC_TEXT(INIT, CheckpForEachProcess) +EXTERN_C static NTSTATUS CheckpForEachProcess( + _In_ bool (*Callback)(_In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, + _In_opt_ void *Context), + _In_opt_ void *Context) { + PAGED_CODE(); + + auto processInfo = reinterpret_cast( + ExAllocatePoolWithTag(PagedPool, 0x10000, RWMON_POOL_TAG_NAME)); + if (!processInfo) { + return STATUS_MEMORY_NOT_ALLOCATED; + } + + ULONG returnLength = 0; + auto status = ZwQuerySystemInformation(SystemProcessInformation, processInfo, + 0x10000, &returnLength); + if (!NT_SUCCESS(status) && returnLength) { + ExFreePoolWithTag(processInfo, RWMON_POOL_TAG_NAME); + processInfo = + reinterpret_cast(ExAllocatePoolWithTag( + PagedPool, returnLength + PAGE_SIZE, RWMON_POOL_TAG_NAME)); + if (!processInfo) { + return STATUS_MEMORY_NOT_ALLOCATED; + } + + status = + ZwQuerySystemInformation(SystemProcessInformation, processInfo, + (returnLength + PAGE_SIZE), &returnLength); + } + const auto scopedExFreePoolWithTag = stdexp::make_scope_exit( + [processInfo] { ExFreePoolWithTag(processInfo, RWMON_POOL_TAG_NAME); }); + if (!NT_SUCCESS(status)) { + return status; + } + + for (auto current = processInfo; current; /**/) { + if (!Callback(current, Context)) { + break; + } + + if (!current->NextEntryOffset) { + break; + } + current = reinterpret_cast( + reinterpret_cast(current) + current->NextEntryOffset); + } + + return status; +} + +// A callback routine saving existing processes' IDs into a white list. +ALLOC_TEXT(INIT, CheckpSaveExistingPID) +EXTERN_C static bool CheckpSaveExistingPID( + _In_ const SYSTEM_PROCESS_INFORMATION *ProcessInfo, _In_ void *Context) { + PAGED_CODE(); + + auto &index = *static_cast(Context); + if (index >= + CHECKP_WHITELIST_ARRAY_SIZE - 1) { // -1 to have 0 at the end at least + return false; + } + if (ProcessInfo->UniqueProcessId) { + g_CheckpWhiteListedProcessIDs[index++] = ProcessInfo->UniqueProcessId; + } + return true; +} + +// Check if the call is inter-process write, and log it if so. +ALLOC_TEXT(PAGED, CheckData) +EXTERN_C bool CheckData(_In_ HANDLE ProcessHandle, _In_ void *RemoteAddress, + _In_opt_ void *Contents, _In_ ULONG DataSize) { + PAGED_CODE(); + + const auto isWriteVirtualMemory = (Contents != nullptr); + + // Check if it is a interprocess operation + PEPROCESS targetProcess = nullptr; + if (!CheckpIsInterprocessWrite(ProcessHandle, &targetProcess)) { + return false; + } + const auto scopedDereference = stdexp::make_scope_exit( + [targetProcess] { ObDereferenceObject(targetProcess); }); + + // Allocate a memory to copy written data + auto data = stdexp::make_unique_resource( + ExAllocatePoolWithTag(PagedPool, DataSize, RWMON_POOL_TAG_NAME), + [](void *p) { ExFreePoolWithTag(p, RWMON_POOL_TAG_NAME); }); + if (!data) { + return false; + } + + // Copy the written data + auto status = STATUS_SUCCESS; + if (Contents) { + status = + CheckpCopyDataFromUserSpace(data.get(), Contents, DataSize, nullptr); + } else { + status = CheckpCopyDataFromUserSpace(data.get(), RemoteAddress, DataSize, + targetProcess); + } + if (!NT_SUCCESS(status)) { + LOG_ERROR_SAFE("CopyDataFromUserSpace failed (%08x)", status); + return false; + } + + // Calculate SHA1 of the written data + UCHAR sha1Hash[20] = {}; + if (!CheckpGetSha1(sha1Hash, data.get(), DataSize)) { + return false; + } + wchar_t sha1HashW[41] = {}; + for (auto i = 0; i < RTL_NUMBER_OF(sha1Hash); ++i) { + const auto outW = sha1HashW + i * 2; + RtlStringCchPrintfW(outW, 3, L"%02x", sha1Hash[i]); + } + + // Save it to a file + wchar_t outPathW[260]; + status = RtlStringCchPrintfW(outPathW, RTL_NUMBER_OF(outPathW), L"%s\\%s.bin", + g_CheckpLogDirecotry, sha1HashW); + if (!NT_SUCCESS(status)) { + LOG_ERROR_SAFE("RtlStringCchPrintfW failed (%08x)", status); + return false; + } + status = CheckpWriteFile(outPathW, data.get(), DataSize, GENERIC_WRITE, + FILE_CREATE); + if (!NT_SUCCESS(status) && status != STATUS_OBJECT_NAME_COLLISION) { + LOG_ERROR_SAFE("WriteFile failed (%08x)", status); + return false; + } + + // Log it + LOG_INFO_SAFE("Remote %s onto %5lu (%-15s) at %p (saved as %S, %lu bytes)", + (isWriteVirtualMemory) ? "write" : "map ", + PsGetProcessId(targetProcess), + PsGetProcessImageFileName(targetProcess), RemoteAddress, + sha1HashW, DataSize); + return true; +} + +// Check if the write operation is interprocess and from a not white listed +// process +ALLOC_TEXT(PAGED, CheckpIsInterprocessWrite) +_Success_(return == true) EXTERN_C + static bool CheckpIsInterprocessWrite(_In_ HANDLE ProcessHandle, + _Out_ PEPROCESS *TargetProcess) { + PAGED_CODE(); + + if (ProcessHandle == ZwCurrentProcess()) { + return false; + } + + const auto pid = PsGetCurrentProcessId(); + for (auto i = 0; g_CheckpWhiteListedProcessIDs[i]; ++i) { + if (g_CheckpWhiteListedProcessIDs[i] == pid) { + return false; + } + } + + auto status = ObReferenceObjectByHandle( + ProcessHandle, 0, *PsProcessType, UserMode, + reinterpret_cast(TargetProcess), nullptr); + if (!NT_SUCCESS(status)) { + LOG_ERROR_SAFE("ObReferenceObjectByHandle failed (%08x)", status); + return false; + } + + if (*TargetProcess == PsGetCurrentProcess()) { + ObDereferenceObject(*TargetProcess); + return false; + } + return true; +} + +// Copy data from user-space +ALLOC_TEXT(PAGED, CheckpCopyDataFromUserSpace) +EXTERN_C static NTSTATUS CheckpCopyDataFromUserSpace( + _Out_ void *Buffer, _In_ const void *BaseAddress, _In_ ULONG DataSize, + _In_opt_ PEPROCESS TargetProcess) { + PAGED_CODE(); + + if (TargetProcess) { + // Need to switch to another process memory space to access the data + KAPC_STATE apcState = {}; + KeStackAttachProcess(TargetProcess, &apcState); + const auto scopedKeUnstackDetachProcess = stdexp::make_scope_exit( + [&apcState] { KeUnstackDetachProcess(&apcState); }); + return CheckpTryCopyMemory(Buffer, BaseAddress, DataSize); + } else { + // The current process contains the data + return CheckpTryCopyMemory(Buffer, BaseAddress, DataSize); + } +} + +// RtlCopyMemory wrapped with SEH +ALLOC_TEXT(PAGED, CheckpTryCopyMemory) +EXTERN_C static NTSTATUS CheckpTryCopyMemory(_Out_ void *Destionation, + _In_ const void *Source, + _In_ SIZE_T Length) { + PAGED_CODE(); + + auto status = STATUS_SUCCESS; + __try { + RtlCopyMemory(Destionation, Source, Length); + } __except (status = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER) { + } + return status; +} + +// Calculate SHA1 +ALLOC_TEXT(PAGED, CheckpGetSha1) +_Success_(return == true) EXTERN_C + static bool CheckpGetSha1(_Out_ UCHAR(&Sha1Hash)[20], _In_ void *Data, + _In_ ULONG DataSize) { + PAGED_CODE(); + + BCRYPT_HASH_HANDLE hashHandle = nullptr; + auto status = BCryptCreateHash(g_CheckpSha1AlgorithmHandle, &hashHandle, + nullptr, 0, nullptr, 0, 0); + if (!NT_SUCCESS(status)) { + LOG_ERROR_SAFE("BCryptCreateHash failed (%08x)", status); + return false; + } + const auto scopedBCryptDestroyHash = + stdexp::make_scope_exit([hashHandle] { BCryptDestroyHash(hashHandle); }); + + status = BCryptHashData(hashHandle, static_cast(Data), DataSize, 0); + if (!NT_SUCCESS(status)) { + LOG_ERROR_SAFE("BCryptHashData failed (%08x)", status); + return false; + } + + static_assert(sizeof(Sha1Hash) == 20, "Size check"); + status = BCryptFinishHash(hashHandle, Sha1Hash, sizeof(Sha1Hash), 0); + if (!NT_SUCCESS(status)) { + LOG_ERROR_SAFE("BCryptFinishHash failed (%08x)", status); + return false; + } + + return true; +} + +// Write data to a file +ALLOC_TEXT(PAGED, CheckpWriteFile) +EXTERN_C static NTSTATUS CheckpWriteFile(_In_ const wchar_t *OutPathW, + _In_ void *Buffer, + _In_ ULONG BufferSize, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG CreateDisposition) { + PAGED_CODE(); + + UNICODE_STRING outPath = {}; + RtlInitUnicodeString(&outPath, OutPathW); + OBJECT_ATTRIBUTES objAttr = RTL_INIT_OBJECT_ATTRIBUTES( + &outPath, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE); + + IO_STATUS_BLOCK ioStatus = {}; + HANDLE file = nullptr; + auto status = ZwCreateFile( + &file, DesiredAccess, &objAttr, &ioStatus, nullptr, FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, CreateDisposition, + FILE_SEQUENTIAL_ONLY | FILE_SYNCHRONOUS_IO_NONALERT | + FILE_NON_DIRECTORY_FILE, + nullptr, 0); + if (!NT_SUCCESS(status)) { + return status; + } + + status = ZwWriteFile(file, nullptr, nullptr, nullptr, &ioStatus, Buffer, + BufferSize, nullptr, nullptr); + ZwClose(file); + return status; +} diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/check.h b/RemoteWriteMonitor/RemoteWriteMonitor/check.h new file mode 100644 index 0000000..7353b6e --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/check.h @@ -0,0 +1,45 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// +// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C NTSTATUS CheckInitialization(_In_ const wchar_t *LogDirectry); + +EXTERN_C void CheckTermination(); + +EXTERN_C bool CheckData(_In_ HANDLE ProcessHandle, _In_ void *RemoteAddress, + _In_opt_ void *Contents, _In_ ULONG DataSize); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/inline.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/inline.cpp new file mode 100644 index 0000000..b4f2fae --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/inline.cpp @@ -0,0 +1,158 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module implements an entry point of the driver and initializes other +// components in this module. +// +#include "stdafx.h" +#include "inline.h" +#include "log.h" +#include "util.h" + +namespace stdexp = std::experimental; + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +// A structure reflects inline hook code. +#include +struct TrampolineCode { + UCHAR jmp[6]; + FARPROC FunctionAddress; +}; +static const auto DISPGP_MININUM_EPILOGUE_LENGTH = sizeof(TrampolineCode); +static_assert(sizeof(TrampolineCode) == DISPGP_MININUM_EPILOGUE_LENGTH, + "Size check"); +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C +NTSTATUS static InlinepFixupAsmCode(_In_ UCHAR *OriginalRoutine, + _In_ FARPROC AsmHandler, + _In_ FARPROC AsmHandlerEnd); + +EXTERN_C static TrampolineCode InlinepMakeTrampolineCode( + _In_ UCHAR *HookAddress, _In_ FARPROC HookHandler); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +// Fill out HookInfo in order to hook the begging of the function. This is not +// designed to execute original code like what DispgpSetEpilogueHookInfo() does. +ALLOC_TEXT(INIT, InlineInitHookInfo) +EXTERN_C NTSTATUS +InlineInitHookInfo(_In_ UCHAR *HookAddress, _In_ FARPROC HookHandler, + _In_ FARPROC AsmHandler, _In_ FARPROC AsmHandlerEnd, + _Out_ HookInfo *Info) { + PAGED_CODE(); + + NT_ASSERT(HookHandler); + NT_ASSERT(AsmHandler); + NT_ASSERT(AsmHandlerEnd); + NT_ASSERT(Info); + + if (!HookAddress) { + return STATUS_INVALID_PARAMETER; + } + + Info->HookHandler = HookHandler; + Info->HookAddress = HookAddress; + Info->OriginalCodeSize = DISPGP_MININUM_EPILOGUE_LENGTH; + memcpy(Info->OriginalCode, Info->HookAddress, Info->OriginalCodeSize); + + auto status = InlinepFixupAsmCode(HookAddress, AsmHandler, AsmHandlerEnd); + if (!NT_SUCCESS(status)) { + return status; + } + + LOG_DEBUG("HookHandler= %p, HookAddress= %p, OriginalCodeSize= %d", + Info->HookHandler, Info->HookAddress, Info->OriginalCodeSize); + + return status; +} + +// Build and return trampoline code. +ALLOC_TEXT(PAGED, InlinepMakeTrampolineCode) +EXTERN_C static TrampolineCode InlinepMakeTrampolineCode( + _In_ UCHAR *HookAddress, _In_ FARPROC HookHandler) { + PAGED_CODE(); + // jmp qword ptr [nextline] + // nextline: + // dq HookHandler + UNREFERENCED_PARAMETER(HookAddress); + return { + { + 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, + }, + HookHandler, + }; +} + +// Replaces placeholder (0xffffffffffffffff) in AsmHandler with a given +// ReturnAddress. AsmHandler does not has to be writable. Race condition between +// multiple processors should be taken care of by a programmer it exists; this +// function does not care about it. +ALLOC_TEXT(PAGED, InlinepFixupAsmCode) +EXTERN_C +NTSTATUS static InlinepFixupAsmCode(_In_ UCHAR *OriginalRoutine, + _In_ FARPROC AsmHandler, + _In_ FARPROC AsmHandlerEnd) { + PAGED_CODE(); + ASSERT(AsmHandlerEnd > AsmHandler); + SIZE_T asmHandlerSize = reinterpret_cast(AsmHandlerEnd) - + reinterpret_cast(AsmHandler); + + ULONG64 pattern = 0xffffffffffffffff; + auto addressOfMarker = UtilMemMem(reinterpret_cast(AsmHandler), + asmHandlerSize, &pattern, sizeof(pattern)); + ASSERT(addressOfMarker); + auto destinationAddress = + reinterpret_cast(OriginalRoutine + asmHandlerSize - 15); + return UtilForceMemCpy(addressOfMarker, &destinationAddress, + sizeof(destinationAddress)); +} + +// Install a inline hook (modify code) using HookInfo. +ALLOC_TEXT(PAGED, InlineInstallHook) +EXTERN_C NTSTATUS InlineInstallHook(_In_ const HookInfo &Info) { + PAGED_CODE(); + LOG_DEBUG("%p => %p", Info.HookAddress, Info.HookHandler); + auto newCode = InlinepMakeTrampolineCode(Info.HookAddress, Info.HookHandler); + auto status = UtilForceMemCpy(Info.HookAddress, newCode.jmp, sizeof(newCode)); + UtilInvalidateInstructionCache(Info.HookAddress, sizeof(newCode)); + return status; +} + +ALLOC_TEXT(PAGED, InlineUninstallHook) +EXTERN_C NTSTATUS InlineUninstallHook(_In_ const HookInfo &Info) { + PAGED_CODE(); + auto status = UtilForceMemCpy(Info.HookAddress, Info.OriginalCode, + Info.OriginalCodeSize); + UtilInvalidateInstructionCache(Info.HookAddress, Info.OriginalCodeSize); + return status; +} diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/inline.h b/RemoteWriteMonitor/RemoteWriteMonitor/inline.h new file mode 100644 index 0000000..75a69e6 --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/inline.h @@ -0,0 +1,60 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// +// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +// Acceptable the minimum function epilogue size in bytes for inline hooking. +// It limits the length to 32 bytes due to a size of a backup area allocated by +// a macro NOP_32. +static const ULONG DISPGP_MAXIMUM_EPILOGUE_LENGTH = 32; + +// Holds a necessary context for installing and uninstalling inline hook. +struct HookInfo { + UCHAR *HookAddress; // An address to install inline hook + FARPROC HookHandler; // A hook handler to be called instead + SIZE_T OriginalCodeSize; // A size of saved original code + UCHAR OriginalCode[DISPGP_MAXIMUM_EPILOGUE_LENGTH]; // A saved original code +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C NTSTATUS +InlineInitHookInfo(_In_ UCHAR *HookAddress, _In_ FARPROC HookHandler, + _In_ FARPROC AsmHandler, _In_ FARPROC AsmHandlerEnd, + _Out_ HookInfo *Info); + +EXTERN_C NTSTATUS InlineInstallHook(_In_ const HookInfo &Info); + +EXTERN_C NTSTATUS InlineUninstallHook(_In_ const HookInfo &Info); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/log.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/log.cpp index 9a67599..e80aa65 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/log.cpp +++ b/RemoteWriteMonitor/RemoteWriteMonitor/log.cpp @@ -1,640 +1,640 @@ -// Copyright (c) 2015, tandasat. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -// -// This module implements logging functions. -// -#include "stdafx.h" -#include "log.h" - -//////////////////////////////////////////////////////////////////////////////// -// -// macro utilities -// - -//////////////////////////////////////////////////////////////////////////////// -// -// constant and macro -// - -// A size for log buffer in NonPagedPool. Two buffers are allocated with this -// size. Exceeded logs are ignored silently. Make it bigger if a buffered log -// size often reach this size. -static const auto LOGP_BUFFER_SIZE_IN_PAGES = 5ul; - -// An actual log buffer size in bytes. -static const auto LOGP_BUFFER_SIZE = PAGE_SIZE * LOGP_BUFFER_SIZE_IN_PAGES; - -// A size that is usable for logging. Minus one because the last byte is kept -// for \0. -static const auto LOGP_BUFFER_USABLE_SIZE = LOGP_BUFFER_SIZE - 1; - -// An interval to flush buffered log entries into a log file. -static const auto LOGP_AUTO_FLUSH_INTERVAL_MSEC = 50; - -static const ULONG LOGP_POOL_TAG_NAME = ' gol'; - -//////////////////////////////////////////////////////////////////////////////// -// -// types -// - -struct LogBufferInfo { - volatile char *LogBufferHead; // A pointer to a buffer currently used. - // It is either LogBuffer1 or LogBuffer2. - volatile char *LogBufferTail; // A pointer to where the next log should - // be written. - char *LogBuffer1; - char *LogBuffer2; - SIZE_T LogMaximumUsage; // Holds the biggest buffer usage to - // determine a necessary buffer size. - HANDLE LogFileHandle; - KSPIN_LOCK SpinLock; - ERESOURCE Resource; - volatile bool BufferFlushThreadShouldBeAlive; - HANDLE BufferFlushThreadHandle; -}; - -//////////////////////////////////////////////////////////////////////////////// -// -// prototypes -// - -EXTERN_C NTKERNELAPI UCHAR *NTAPI -PsGetProcessImageFileName(_In_ PEPROCESS Process); - -EXTERN_C static NTSTATUS LogpInitializeBufferInfo( - _In_ const wchar_t *LogFilePath, _In_opt_ PDEVICE_OBJECT DeviceObject, - _Inout_ LogBufferInfo *Info); - -EXTERN_C static void LogpFinalizeBufferInfo(_In_opt_ PDEVICE_OBJECT - DeviceObject, - _In_ LogBufferInfo *Info); - -#ifdef _X86_ -_Requires_lock_not_held_(*SpinLock) _Acquires_lock_(*SpinLock) - _IRQL_requires_max_(DISPATCH_LEVEL) _IRQL_saves_ - _IRQL_raises_(DISPATCH_LEVEL) inline KIRQL - KeAcquireSpinLockRaiseToDpc(_Inout_ PKSPIN_LOCK SpinLock); -#endif - -EXTERN_C static NTSTATUS LogpMakePrefix(_In_ ULONG Level, - _In_ const char *FunctionName, - _In_ const char *LogMessage, - _Out_ char *LogBuffer, - _In_ size_t LogBufferLength); - -EXTERN_C static const char *LogpFindBaseFunctionName( - _In_ const char *FunctionName); - -EXTERN_C static NTSTATUS LogpPut(_In_ const char *Message, - _In_ ULONG Attribute); - -EXTERN_C static NTSTATUS LogpWriteLogBufferToFile(_In_opt_ LogBufferInfo *Info); - -EXTERN_C static NTSTATUS LogpWriteMessageToFile(_In_ const char *Message, - _In_ const LogBufferInfo &Info); - -EXTERN_C static NTSTATUS LogpBufferMessage(_In_ const char *Message, - _In_opt_ LogBufferInfo *Info); - -EXTERN_C static bool LogpIsLogFileEnabled(_In_ const LogBufferInfo &Info); - -EXTERN_C static bool LogpIsLogNeeded(_In_ ULONG Level); - -EXTERN_C static KSTART_ROUTINE LogpBufferFlushThreadRoutine; - -EXTERN_C static NTSTATUS LogpSleep(_In_ LONG Millisecond); - -//////////////////////////////////////////////////////////////////////////////// -// -// variables -// - -static auto g_LogpDebugFlag = LOG_PUT_LEVEL_DISABLE; -static LogBufferInfo g_LogpLogBufferInfo = {}; - -//////////////////////////////////////////////////////////////////////////////// -// -// implementations -// - -ALLOC_TEXT(INIT, LogInitialization) -EXTERN_C NTSTATUS LogInitialization(_In_ ULONG Flag, - _In_opt_ const wchar_t *LogFilePath, - _In_opt_ PDEVICE_OBJECT DeviceObject) { - PAGED_CODE(); - - auto status = STATUS_SUCCESS; - - g_LogpDebugFlag = Flag; - - if (DeviceObject && !LogFilePath) { - return STATUS_INVALID_PARAMETER; - } - - // Initialize a log file if a log file path is specified. - if (LogFilePath) { - status = LogpInitializeBufferInfo(LogFilePath, DeviceObject, - &g_LogpLogBufferInfo); - if (!NT_SUCCESS(status)) { - return status; - } - } - - // Test the log. - status = LOG_INFO( - "Log system was initialized (Flag= %08x, Buffer= %p %p, File= %S).", Flag, - g_LogpLogBufferInfo.LogBuffer1, g_LogpLogBufferInfo.LogBuffer2, - LogFilePath); - if (!NT_SUCCESS(status)) { - goto Fail; - } - return status; - -Fail: - if (LogFilePath) { - LogpFinalizeBufferInfo(DeviceObject, &g_LogpLogBufferInfo); - } - return status; -} - -// Initialize a log file related code such as a flushing thread. -ALLOC_TEXT(INIT, LogpInitializeBufferInfo) -EXTERN_C static NTSTATUS LogpInitializeBufferInfo( - _In_ const wchar_t *LogFilePath, _In_opt_ PDEVICE_OBJECT DeviceObject, - _Inout_ LogBufferInfo *Info) { - NT_ASSERT(LogFilePath); - NT_ASSERT(Info); - - KeInitializeSpinLock(&Info->SpinLock); - - auto status = ExInitializeResourceLite(&Info->Resource); - if (!NT_SUCCESS(status)) { - return status; - } - - if (DeviceObject) { - // We can handle IRP_MJ_SHUTDOWN in order to flush buffered log entries. - status = IoRegisterShutdownNotification(DeviceObject); - if (!NT_SUCCESS(status)) { - LogpFinalizeBufferInfo(DeviceObject, Info); - return status; - } - } - - // Allocate two log buffers on NonPagedPool. - Info->LogBuffer1 = reinterpret_cast(ExAllocatePoolWithTag( - NonPagedPool, LOGP_BUFFER_SIZE, LOGP_POOL_TAG_NAME)); - if (!Info->LogBuffer1) { - LogpFinalizeBufferInfo(DeviceObject, Info); - return STATUS_INSUFFICIENT_RESOURCES; - } - - Info->LogBuffer2 = reinterpret_cast(ExAllocatePoolWithTag( - NonPagedPool, LOGP_BUFFER_SIZE, LOGP_POOL_TAG_NAME)); - if (!Info->LogBuffer2) { - LogpFinalizeBufferInfo(DeviceObject, Info); - return STATUS_INSUFFICIENT_RESOURCES; - } - - // Initialize these buffers - RtlFillMemory(Info->LogBuffer1, LOGP_BUFFER_SIZE, 0xff); // for debug - Info->LogBuffer1[0] = '\0'; - Info->LogBuffer1[LOGP_BUFFER_SIZE - 1] = '\0'; // at the end - - RtlFillMemory(Info->LogBuffer2, LOGP_BUFFER_SIZE, 0xff); // for debug - Info->LogBuffer2[0] = '\0'; - Info->LogBuffer2[LOGP_BUFFER_SIZE - 1] = '\0'; // at the end - - // Buffer should be used is LogBuffer1, and location should be written logs - // is the head of the buffer. - Info->LogBufferHead = Info->LogBuffer1; - Info->LogBufferTail = Info->LogBuffer1; - - // Initialize a log file - UNICODE_STRING logFilePathU = {}; - RtlInitUnicodeString(&logFilePathU, LogFilePath); - - OBJECT_ATTRIBUTES oa = {}; - InitializeObjectAttributes(&oa, &logFilePathU, - OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr, - nullptr); - - IO_STATUS_BLOCK ioStatus = {}; - status = ZwCreateFile( - &Info->LogFileHandle, FILE_APPEND_DATA | SYNCHRONIZE, &oa, &ioStatus, - nullptr, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, - FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, nullptr, 0); - if (!NT_SUCCESS(status)) { - LogpFinalizeBufferInfo(DeviceObject, Info); - return status; - } - - // Initialize a log buffer flush thread. - Info->BufferFlushThreadShouldBeAlive = true; - status = PsCreateSystemThread(&Info->BufferFlushThreadHandle, GENERIC_ALL, - nullptr, nullptr, nullptr, - LogpBufferFlushThreadRoutine, Info); - if (!NT_SUCCESS(status)) { - LogpFinalizeBufferInfo(DeviceObject, Info); - return status; - } - - return status; -} - -// Terminates the log functions without releasing resources. -ALLOC_TEXT(PAGED, LogIrpShutdownHandler) -EXTERN_C void LogIrpShutdownHandler() { - PAGED_CODE(); - - LOG_DEBUG("Flushing... (Max log usage = %08x bytes)", - g_LogpLogBufferInfo.LogMaximumUsage); - LOG_INFO("Bye!"); - g_LogpDebugFlag = LOG_PUT_LEVEL_DISABLE; - - // Wait until the log buffer is emptied. - auto &info = g_LogpLogBufferInfo; - while (info.LogBufferHead[0]) { - LogpSleep(LOGP_AUTO_FLUSH_INTERVAL_MSEC); - } -} - -// Terminates the log functions. -ALLOC_TEXT(PAGED, LogTermination) -EXTERN_C void LogTermination(_In_opt_ PDEVICE_OBJECT DeviceObject) { - PAGED_CODE(); - - LOG_DEBUG("Finalizing... (Max log usage = %08x bytes)", - g_LogpLogBufferInfo.LogMaximumUsage); - LOG_INFO("Bye!"); - g_LogpDebugFlag = LOG_PUT_LEVEL_DISABLE; - LogpFinalizeBufferInfo(DeviceObject, &g_LogpLogBufferInfo); -} - -// Terminates a log file related code. -ALLOC_TEXT(PAGED, LogpFinalizeBufferInfo) -EXTERN_C static void LogpFinalizeBufferInfo(_In_opt_ PDEVICE_OBJECT - DeviceObject, - _In_ LogBufferInfo *Info) { - PAGED_CODE(); - NT_ASSERT(Info); - - // Closing the log buffer flush thread. - if (Info->BufferFlushThreadHandle) { - Info->BufferFlushThreadShouldBeAlive = false; - auto status = - ZwWaitForSingleObject(Info->BufferFlushThreadHandle, FALSE, nullptr); - if (!NT_SUCCESS(status)) { - DBG_BREAK(); - } - ZwClose(Info->BufferFlushThreadHandle); - Info->BufferFlushThreadHandle = nullptr; - } - - // Cleaning up other things. - if (Info->LogFileHandle) { - ZwClose(Info->LogFileHandle); - Info->LogFileHandle = nullptr; - } - if (Info->LogBuffer2) { - ExFreePoolWithTag(Info->LogBuffer2, LOGP_POOL_TAG_NAME); - Info->LogBuffer2 = nullptr; - } - if (Info->LogBuffer1) { - ExFreePoolWithTag(Info->LogBuffer1, LOGP_POOL_TAG_NAME); - Info->LogBuffer1 = nullptr; - } - - if (DeviceObject) { - IoUnregisterShutdownNotification(DeviceObject); - } - ExDeleteResourceLite(&Info->Resource); -} - -#ifdef _X86_ -_Requires_lock_not_held_(*SpinLock) _Acquires_lock_(*SpinLock) - _IRQL_requires_max_(DISPATCH_LEVEL) _IRQL_saves_ - _IRQL_raises_(DISPATCH_LEVEL) inline KIRQL - KeAcquireSpinLockRaiseToDpc(_Inout_ PKSPIN_LOCK SpinLock) { - KIRQL irql = {}; - KeAcquireSpinLock(SpinLock, &irql); - return irql; -} -#endif - -// Actual implementation of logging API. -EXTERN_C NTSTATUS LogPrint(_In_ ULONG Level, _In_ const char *FunctionName, - _In_ const char *Format, ...) { - auto status = STATUS_SUCCESS; - - if (!LogpIsLogNeeded(Level)) { - return status; - } - - va_list args; - va_start(args, Format); - char logMessage[300]; - status = - RtlStringCchVPrintfA(logMessage, RTL_NUMBER_OF(logMessage), Format, args); - va_end(args); - if (!NT_SUCCESS(status)) { - return status; - } - if (logMessage[0] == '\0') { - return STATUS_INVALID_PARAMETER; - } - - const auto pureLevel = Level & 0xf0; - const auto attribute = Level & 0x0f; - - // A single entry of log should not exceed 512 bytes. See - // Reading and Filtering Debugging Messages in MSDN for details. - char message[100 + RTL_NUMBER_OF(logMessage)]; - static_assert(RTL_NUMBER_OF(message) <= 512, - "One log message should not exceed 512 bytes."); - status = LogpMakePrefix(pureLevel, FunctionName, logMessage, message, - RTL_NUMBER_OF(message)); - if (!NT_SUCCESS(status)) { - return status; - } - - return LogpPut(message, attribute); -} - -// Concatenates meta information such as the current time and a process ID to -// user given log message. -EXTERN_C static NTSTATUS LogpMakePrefix(_In_ ULONG Level, - _In_ const char *FunctionName, - _In_ const char *LogMessage, - _Out_ char *LogBuffer, - _In_ size_t LogBufferLength) { - char const *levelString = nullptr; - switch (Level) { - case LOGP_LEVEL_DEBUG: - levelString = "DBG"; - break; - case LOGP_LEVEL_INFO: - levelString = "INF"; - break; - case LOGP_LEVEL_WARN: - levelString = "WRN"; - break; - case LOGP_LEVEL_ERROR: - levelString = "ERR"; - break; - default: - return STATUS_INVALID_PARAMETER; - } - - auto status = STATUS_SUCCESS; - - char timeBuffer[20] = {}; - if ((g_LogpDebugFlag & LOG_OPT_DISABLE_TIME) == 0) { - // Want the current time. - TIME_FIELDS timeFields; - LARGE_INTEGER systemTime, localTime; - KeQuerySystemTime(&systemTime); - ExSystemTimeToLocalTime(&systemTime, &localTime); - RtlTimeToTimeFields(&localTime, &timeFields); - - status = RtlStringCchPrintfA(timeBuffer, RTL_NUMBER_OF(timeBuffer), - "%02u:%02u:%02u.%03u\t", timeFields.Hour, - timeFields.Minute, timeFields.Second, - timeFields.Milliseconds); - if (!NT_SUCCESS(status)) { - return status; - } - } - - char functionNameBuffer[50] = {}; - if ((g_LogpDebugFlag & LOG_OPT_DISABLE_FUNCTION_NAME) == 0) { - // Want the function name - const auto baseFunctionName = LogpFindBaseFunctionName(FunctionName); - status = RtlStringCchPrintfA(functionNameBuffer, - RTL_NUMBER_OF(functionNameBuffer), "%-40s\t", - baseFunctionName); - if (!NT_SUCCESS(status)) { - return status; - } - } - - // - // It uses PsGetProcessId(PsGetCurrentProcess()) instead of - // PsGetCurrentThreadProcessId() because the later sometimes returns - // unwanted value, for example: - // PID == 4 but its image name != ntoskrnl.exe - // The author is guessing that it is related to attaching processes but - // not quite sure. The former way works as expected. - // - status = RtlStringCchPrintfA( - LogBuffer, LogBufferLength, "%s%s\t%5lu\t%5lu\t%-15s\t%s%s\r\n", - timeBuffer, levelString, - reinterpret_cast(PsGetProcessId(PsGetCurrentProcess())), - reinterpret_cast(PsGetCurrentThreadId()), - PsGetProcessImageFileName(PsGetCurrentProcess()), functionNameBuffer, - LogMessage); - return status; -} - -// Returns the function's base name, for example, -// NamespaceName::ClassName::MethodName will be returned as MethodName. -EXTERN_C static const char *LogpFindBaseFunctionName( - _In_ const char *FunctionName) { - if (!FunctionName) { - return nullptr; - } - - auto ptr = FunctionName; - auto name = FunctionName; - while (*(ptr++)) { - if (*ptr == ':') { - name = ptr + 1; - } - } - return name; -} - -// Logs the entry according to Attribute and the thread condition. -EXTERN_C static NTSTATUS LogpPut(_In_ const char *Message, - _In_ ULONG Attribute) { - auto status = STATUS_SUCCESS; - - // Log the entry to a file or buffer. - auto &info = g_LogpLogBufferInfo; - if (LogpIsLogFileEnabled(info)) { - // Can it log it to a file now? - if (((Attribute & LOGP_LEVEL_OPT_SAFE) == 0) && - KeGetCurrentIrql() == PASSIVE_LEVEL && !KeAreAllApcsDisabled()) { - // Yes, it can. Do it. - LogpWriteLogBufferToFile(&info); - status = LogpWriteMessageToFile(Message, info); - } else { - // No, it cannot. Buffer it. - status = LogpBufferMessage(Message, &info); - } - } - - // Can it safely be printed? - if (KeGetCurrentIrql() >= CLOCK_LEVEL) { - return STATUS_UNSUCCESSFUL; - } - - DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "%s", Message); - return status; -} - -// Switch the current log buffer and save the contents of old buffer to the log -// file. This function does not flush the log file, so code should call -// LogpWriteMessageToFile() or ZwFlushBuffersFile() later. -EXTERN_C static NTSTATUS LogpWriteLogBufferToFile( - _In_opt_ LogBufferInfo *Info) { - NT_ASSERT(Info); - auto status = STATUS_SUCCESS; - - // Enter a critical section and acquire a reader lock for Info in order to - // write a log file safely. - ExEnterCriticalRegionAndAcquireResourceExclusive(&Info->Resource); - - // Acquire a spin lock for Info.LogBuffer(s) in order to switch its head - // safely. - const auto irql = KeAcquireSpinLockRaiseToDpc(&Info->SpinLock); - auto oldLogBuffer = const_cast(Info->LogBufferHead); - if (oldLogBuffer[0]) { - Info->LogBufferHead = (oldLogBuffer == Info->LogBuffer1) ? Info->LogBuffer2 - : Info->LogBuffer1; - Info->LogBufferHead[0] = '\0'; - Info->LogBufferTail = Info->LogBufferHead; - } - KeReleaseSpinLock(&Info->SpinLock, irql); - - // Write all log entries in old log buffer. - IO_STATUS_BLOCK ioStatus = {}; - for (auto currentLogEntry = oldLogBuffer; currentLogEntry[0]; /**/) { - const auto currentLogEntryLength = strlen(currentLogEntry); - status = - ZwWriteFile(Info->LogFileHandle, nullptr, nullptr, nullptr, &ioStatus, - currentLogEntry, static_cast(currentLogEntryLength), - nullptr, nullptr); - if (!NT_SUCCESS(status)) { - // It could happen when you did not register IRP_SHUTDOWN and call - // LogIrpShutdownHandler() and the system tried to log to a file after - // a filesystem was unmounted. - DBG_BREAK(); - } - - currentLogEntry += currentLogEntryLength + 1; - } - oldLogBuffer[0] = '\0'; - - ExReleaseResourceAndLeaveCriticalRegion(&Info->Resource); - return status; -} - -// Logs the current log entry to and flush the log file. -EXTERN_C static NTSTATUS LogpWriteMessageToFile( - _In_ const char *Message, _In_ const LogBufferInfo &Info) { - IO_STATUS_BLOCK ioStatus = {}; - auto status = - ZwWriteFile(Info.LogFileHandle, nullptr, nullptr, nullptr, &ioStatus, - const_cast(Message), - static_cast(strlen(Message)), nullptr, nullptr); - if (!NT_SUCCESS(status)) { - // It could happen when you did not register IRP_SHUTDOWN and call - // LogIrpShutdownHandler() and the system tried to log to a file after - // a filesystem was unmounted. - DBG_BREAK(); - } - status = ZwFlushBuffersFile(Info.LogFileHandle, &ioStatus); - return status; -} - -// Buffer the log entry to the log buffer. -EXTERN_C static NTSTATUS LogpBufferMessage(_In_ const char *Message, - _In_opt_ LogBufferInfo *Info) { - NT_ASSERT(Info); - - // Acquire a spin lock to add the log safely. - const auto irql = KeAcquireSpinLockRaiseToDpc(&Info->SpinLock); - - // Copy the current log to the buffer. - size_t usedBufferSize = Info->LogBufferTail - Info->LogBufferHead; - auto status = - RtlStringCchCopyA(const_cast(Info->LogBufferTail), - LOGP_BUFFER_USABLE_SIZE - usedBufferSize, Message); - - // Update Info.LogMaximumUsage if necessary. - if (NT_SUCCESS(status)) { - const auto messageLength = strlen(Message) + 1; - Info->LogBufferTail += messageLength; - usedBufferSize += messageLength; - if (usedBufferSize > Info->LogMaximumUsage) { - Info->LogMaximumUsage = usedBufferSize; // Update - } - } else { - Info->LogMaximumUsage = LOGP_BUFFER_SIZE; // Indicates overflow - } - *Info->LogBufferTail = '\0'; - - KeReleaseSpinLock(&Info->SpinLock, irql); - return status; -} - -// Returns true when a log file is enabled. -EXTERN_C static bool LogpIsLogFileEnabled(_In_ const LogBufferInfo &Info) { - if (Info.LogFileHandle) { - NT_ASSERT(Info.LogBuffer1); - NT_ASSERT(Info.LogBuffer2); - NT_ASSERT(Info.LogBufferHead); - NT_ASSERT(Info.LogBufferTail); - return true; - } - NT_ASSERT(!Info.LogBuffer1); - NT_ASSERT(!Info.LogBuffer2); - NT_ASSERT(!Info.LogBufferHead); - NT_ASSERT(!Info.LogBufferTail); - return false; -} - -// Returns true when logging is necessary according to the log's severity and -// a set log level. -EXTERN_C static bool LogpIsLogNeeded(_In_ ULONG Level) { - return !!(g_LogpDebugFlag & Level); -} - -// A thread runs as long as info.BufferFlushThreadShouldBeAlive is true and -// flushes a log buffer to a log file every LOGP_AUTO_FLUSH_INTERVAL_MSEC msec. -ALLOC_TEXT(PAGED, LogpBufferFlushThreadRoutine) -EXTERN_C static VOID LogpBufferFlushThreadRoutine(_In_ void *StartContext) { - PAGED_CODE(); - auto status = STATUS_SUCCESS; - auto info = reinterpret_cast(StartContext); - LOG_DEBUG("Log thread started."); - NT_ASSERT(LogpIsLogFileEnabled(*info)); - - while (info->BufferFlushThreadShouldBeAlive) { - if (info->LogBufferHead[0]) { - NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); - NT_ASSERT(!KeAreAllApcsDisabled()); - status = LogpWriteLogBufferToFile(info); - // Do not flush the file for overall performance. Even a case of - // bug check, we should be able to recover logs by looking at both - // log buffers. - } - LogpSleep(LOGP_AUTO_FLUSH_INTERVAL_MSEC); - } - LOG_DEBUG("Log thread is ending."); - PsTerminateSystemThread(status); -} - -// Sleep the current thread's execution for Millisecond milli-seconds. -ALLOC_TEXT(PAGED, LogpSleep) -EXTERN_C static NTSTATUS LogpSleep(_In_ LONG Millisecond) { - PAGED_CODE(); - - LARGE_INTEGER interval = {}; - interval.QuadPart = -(10000 * Millisecond); // msec - return KeDelayExecutionThread(KernelMode, FALSE, &interval); -} +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module implements logging functions. +// +#include "stdafx.h" +#include "log.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constant and macro +// + +// A size for log buffer in NonPagedPool. Two buffers are allocated with this +// size. Exceeded logs are ignored silently. Make it bigger if a buffered log +// size often reach this size. +static const auto LOGP_BUFFER_SIZE_IN_PAGES = 5ul; + +// An actual log buffer size in bytes. +static const auto LOGP_BUFFER_SIZE = PAGE_SIZE * LOGP_BUFFER_SIZE_IN_PAGES; + +// A size that is usable for logging. Minus one because the last byte is kept +// for \0. +static const auto LOGP_BUFFER_USABLE_SIZE = LOGP_BUFFER_SIZE - 1; + +// An interval to flush buffered log entries into a log file. +static const auto LOGP_AUTO_FLUSH_INTERVAL_MSEC = 50; + +static const ULONG LOGP_POOL_TAG_NAME = ' gol'; + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +struct LogBufferInfo { + volatile char *LogBufferHead; // A pointer to a buffer currently used. + // It is either LogBuffer1 or LogBuffer2. + volatile char *LogBufferTail; // A pointer to where the next log should + // be written. + char *LogBuffer1; + char *LogBuffer2; + SIZE_T LogMaximumUsage; // Holds the biggest buffer usage to + // determine a necessary buffer size. + HANDLE LogFileHandle; + KSPIN_LOCK SpinLock; + ERESOURCE Resource; + volatile bool BufferFlushThreadShouldBeAlive; + HANDLE BufferFlushThreadHandle; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C NTKERNELAPI UCHAR *NTAPI +PsGetProcessImageFileName(_In_ PEPROCESS Process); + +EXTERN_C static NTSTATUS LogpInitializeBufferInfo( + _In_ const wchar_t *LogFilePath, _In_opt_ PDEVICE_OBJECT DeviceObject, + _Inout_ LogBufferInfo *Info); + +EXTERN_C static void LogpFinalizeBufferInfo(_In_opt_ PDEVICE_OBJECT + DeviceObject, + _In_ LogBufferInfo *Info); + +#ifdef _X86_ +_Requires_lock_not_held_(*SpinLock) _Acquires_lock_(*SpinLock) + _IRQL_requires_max_(DISPATCH_LEVEL) _IRQL_saves_ + _IRQL_raises_(DISPATCH_LEVEL) inline KIRQL + KeAcquireSpinLockRaiseToDpc(_Inout_ PKSPIN_LOCK SpinLock); +#endif + +EXTERN_C static NTSTATUS LogpMakePrefix(_In_ ULONG Level, + _In_ const char *FunctionName, + _In_ const char *LogMessage, + _Out_ char *LogBuffer, + _In_ size_t LogBufferLength); + +EXTERN_C static const char *LogpFindBaseFunctionName( + _In_ const char *FunctionName); + +EXTERN_C static NTSTATUS LogpPut(_In_ const char *Message, + _In_ ULONG Attribute); + +EXTERN_C static NTSTATUS LogpWriteLogBufferToFile(_In_opt_ LogBufferInfo *Info); + +EXTERN_C static NTSTATUS LogpWriteMessageToFile(_In_ const char *Message, + _In_ const LogBufferInfo &Info); + +EXTERN_C static NTSTATUS LogpBufferMessage(_In_ const char *Message, + _In_opt_ LogBufferInfo *Info); + +EXTERN_C static bool LogpIsLogFileEnabled(_In_ const LogBufferInfo &Info); + +EXTERN_C static bool LogpIsLogNeeded(_In_ ULONG Level); + +EXTERN_C static KSTART_ROUTINE LogpBufferFlushThreadRoutine; + +EXTERN_C static NTSTATUS LogpSleep(_In_ LONG Millisecond); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +static auto g_LogpDebugFlag = LOG_PUT_LEVEL_DISABLE; +static LogBufferInfo g_LogpLogBufferInfo = {}; + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +ALLOC_TEXT(INIT, LogInitialization) +EXTERN_C NTSTATUS LogInitialization(_In_ ULONG Flag, + _In_opt_ const wchar_t *LogFilePath, + _In_opt_ PDEVICE_OBJECT DeviceObject) { + PAGED_CODE(); + + auto status = STATUS_SUCCESS; + + g_LogpDebugFlag = Flag; + + if (DeviceObject && !LogFilePath) { + return STATUS_INVALID_PARAMETER; + } + + // Initialize a log file if a log file path is specified. + if (LogFilePath) { + status = LogpInitializeBufferInfo(LogFilePath, DeviceObject, + &g_LogpLogBufferInfo); + if (!NT_SUCCESS(status)) { + return status; + } + } + + // Test the log. + status = LOG_INFO( + "Log system was initialized (Flag= %08x, Buffer= %p %p, File= %S).", Flag, + g_LogpLogBufferInfo.LogBuffer1, g_LogpLogBufferInfo.LogBuffer2, + LogFilePath); + if (!NT_SUCCESS(status)) { + goto Fail; + } + return status; + +Fail: + if (LogFilePath) { + LogpFinalizeBufferInfo(DeviceObject, &g_LogpLogBufferInfo); + } + return status; +} + +// Initialize a log file related code such as a flushing thread. +ALLOC_TEXT(INIT, LogpInitializeBufferInfo) +EXTERN_C static NTSTATUS LogpInitializeBufferInfo( + _In_ const wchar_t *LogFilePath, _In_opt_ PDEVICE_OBJECT DeviceObject, + _Inout_ LogBufferInfo *Info) { + NT_ASSERT(LogFilePath); + NT_ASSERT(Info); + + KeInitializeSpinLock(&Info->SpinLock); + + auto status = ExInitializeResourceLite(&Info->Resource); + if (!NT_SUCCESS(status)) { + return status; + } + + if (DeviceObject) { + // We can handle IRP_MJ_SHUTDOWN in order to flush buffered log entries. + status = IoRegisterShutdownNotification(DeviceObject); + if (!NT_SUCCESS(status)) { + LogpFinalizeBufferInfo(DeviceObject, Info); + return status; + } + } + + // Allocate two log buffers on NonPagedPool. + Info->LogBuffer1 = reinterpret_cast(ExAllocatePoolWithTag( + NonPagedPool, LOGP_BUFFER_SIZE, LOGP_POOL_TAG_NAME)); + if (!Info->LogBuffer1) { + LogpFinalizeBufferInfo(DeviceObject, Info); + return STATUS_INSUFFICIENT_RESOURCES; + } + + Info->LogBuffer2 = reinterpret_cast(ExAllocatePoolWithTag( + NonPagedPool, LOGP_BUFFER_SIZE, LOGP_POOL_TAG_NAME)); + if (!Info->LogBuffer2) { + LogpFinalizeBufferInfo(DeviceObject, Info); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Initialize these buffers + RtlFillMemory(Info->LogBuffer1, LOGP_BUFFER_SIZE, 0xff); // for debug + Info->LogBuffer1[0] = '\0'; + Info->LogBuffer1[LOGP_BUFFER_SIZE - 1] = '\0'; // at the end + + RtlFillMemory(Info->LogBuffer2, LOGP_BUFFER_SIZE, 0xff); // for debug + Info->LogBuffer2[0] = '\0'; + Info->LogBuffer2[LOGP_BUFFER_SIZE - 1] = '\0'; // at the end + + // Buffer should be used is LogBuffer1, and location should be written logs + // is the head of the buffer. + Info->LogBufferHead = Info->LogBuffer1; + Info->LogBufferTail = Info->LogBuffer1; + + // Initialize a log file + UNICODE_STRING logFilePathU = {}; + RtlInitUnicodeString(&logFilePathU, LogFilePath); + + OBJECT_ATTRIBUTES oa = {}; + InitializeObjectAttributes(&oa, &logFilePathU, + OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr, + nullptr); + + IO_STATUS_BLOCK ioStatus = {}; + status = ZwCreateFile( + &Info->LogFileHandle, FILE_APPEND_DATA | SYNCHRONIZE, &oa, &ioStatus, + nullptr, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, nullptr, 0); + if (!NT_SUCCESS(status)) { + LogpFinalizeBufferInfo(DeviceObject, Info); + return status; + } + + // Initialize a log buffer flush thread. + Info->BufferFlushThreadShouldBeAlive = true; + status = PsCreateSystemThread(&Info->BufferFlushThreadHandle, GENERIC_ALL, + nullptr, nullptr, nullptr, + LogpBufferFlushThreadRoutine, Info); + if (!NT_SUCCESS(status)) { + LogpFinalizeBufferInfo(DeviceObject, Info); + return status; + } + + return status; +} + +// Terminates the log functions without releasing resources. +ALLOC_TEXT(PAGED, LogIrpShutdownHandler) +EXTERN_C void LogIrpShutdownHandler() { + PAGED_CODE(); + + LOG_DEBUG("Flushing... (Max log usage = %08x bytes)", + g_LogpLogBufferInfo.LogMaximumUsage); + LOG_INFO("Bye!"); + g_LogpDebugFlag = LOG_PUT_LEVEL_DISABLE; + + // Wait until the log buffer is emptied. + auto &info = g_LogpLogBufferInfo; + while (info.LogBufferHead[0]) { + LogpSleep(LOGP_AUTO_FLUSH_INTERVAL_MSEC); + } +} + +// Terminates the log functions. +ALLOC_TEXT(PAGED, LogTermination) +EXTERN_C void LogTermination(_In_opt_ PDEVICE_OBJECT DeviceObject) { + PAGED_CODE(); + + LOG_DEBUG("Finalizing... (Max log usage = %08x bytes)", + g_LogpLogBufferInfo.LogMaximumUsage); + LOG_INFO("Bye!"); + g_LogpDebugFlag = LOG_PUT_LEVEL_DISABLE; + LogpFinalizeBufferInfo(DeviceObject, &g_LogpLogBufferInfo); +} + +// Terminates a log file related code. +ALLOC_TEXT(PAGED, LogpFinalizeBufferInfo) +EXTERN_C static void LogpFinalizeBufferInfo(_In_opt_ PDEVICE_OBJECT + DeviceObject, + _In_ LogBufferInfo *Info) { + PAGED_CODE(); + NT_ASSERT(Info); + + // Closing the log buffer flush thread. + if (Info->BufferFlushThreadHandle) { + Info->BufferFlushThreadShouldBeAlive = false; + auto status = + ZwWaitForSingleObject(Info->BufferFlushThreadHandle, FALSE, nullptr); + if (!NT_SUCCESS(status)) { + DBG_BREAK(); + } + ZwClose(Info->BufferFlushThreadHandle); + Info->BufferFlushThreadHandle = nullptr; + } + + // Cleaning up other things. + if (Info->LogFileHandle) { + ZwClose(Info->LogFileHandle); + Info->LogFileHandle = nullptr; + } + if (Info->LogBuffer2) { + ExFreePoolWithTag(Info->LogBuffer2, LOGP_POOL_TAG_NAME); + Info->LogBuffer2 = nullptr; + } + if (Info->LogBuffer1) { + ExFreePoolWithTag(Info->LogBuffer1, LOGP_POOL_TAG_NAME); + Info->LogBuffer1 = nullptr; + } + + if (DeviceObject) { + IoUnregisterShutdownNotification(DeviceObject); + } + ExDeleteResourceLite(&Info->Resource); +} + +#ifdef _X86_ +_Requires_lock_not_held_(*SpinLock) _Acquires_lock_(*SpinLock) + _IRQL_requires_max_(DISPATCH_LEVEL) _IRQL_saves_ + _IRQL_raises_(DISPATCH_LEVEL) inline KIRQL + KeAcquireSpinLockRaiseToDpc(_Inout_ PKSPIN_LOCK SpinLock) { + KIRQL irql = {}; + KeAcquireSpinLock(SpinLock, &irql); + return irql; +} +#endif + +// Actual implementation of logging API. +EXTERN_C NTSTATUS LogpPrint(_In_ ULONG Level, _In_ const char *FunctionName, + _In_ const char *Format, ...) { + auto status = STATUS_SUCCESS; + + if (!LogpIsLogNeeded(Level)) { + return status; + } + + va_list args; + va_start(args, Format); + char logMessage[300]; + status = + RtlStringCchVPrintfA(logMessage, RTL_NUMBER_OF(logMessage), Format, args); + va_end(args); + if (!NT_SUCCESS(status)) { + return status; + } + if (logMessage[0] == '\0') { + return STATUS_INVALID_PARAMETER; + } + + const auto pureLevel = Level & 0xf0; + const auto attribute = Level & 0x0f; + + // A single entry of log should not exceed 512 bytes. See + // Reading and Filtering Debugging Messages in MSDN for details. + char message[100 + RTL_NUMBER_OF(logMessage)]; + static_assert(RTL_NUMBER_OF(message) <= 512, + "One log message should not exceed 512 bytes."); + status = LogpMakePrefix(pureLevel, FunctionName, logMessage, message, + RTL_NUMBER_OF(message)); + if (!NT_SUCCESS(status)) { + return status; + } + + return LogpPut(message, attribute); +} + +// Concatenates meta information such as the current time and a process ID to +// user given log message. +EXTERN_C static NTSTATUS LogpMakePrefix(_In_ ULONG Level, + _In_ const char *FunctionName, + _In_ const char *LogMessage, + _Out_ char *LogBuffer, + _In_ size_t LogBufferLength) { + char const *levelString = nullptr; + switch (Level) { + case LOGP_LEVEL_DEBUG: + levelString = "DBG"; + break; + case LOGP_LEVEL_INFO: + levelString = "INF"; + break; + case LOGP_LEVEL_WARN: + levelString = "WRN"; + break; + case LOGP_LEVEL_ERROR: + levelString = "ERR"; + break; + default: + return STATUS_INVALID_PARAMETER; + } + + auto status = STATUS_SUCCESS; + + char timeBuffer[20] = {}; + if ((g_LogpDebugFlag & LOG_OPT_DISABLE_TIME) == 0) { + // Want the current time. + TIME_FIELDS timeFields; + LARGE_INTEGER systemTime, localTime; + KeQuerySystemTime(&systemTime); + ExSystemTimeToLocalTime(&systemTime, &localTime); + RtlTimeToTimeFields(&localTime, &timeFields); + + status = RtlStringCchPrintfA(timeBuffer, RTL_NUMBER_OF(timeBuffer), + "%02u:%02u:%02u.%03u\t", timeFields.Hour, + timeFields.Minute, timeFields.Second, + timeFields.Milliseconds); + if (!NT_SUCCESS(status)) { + return status; + } + } + + char functionNameBuffer[50] = {}; + if ((g_LogpDebugFlag & LOG_OPT_DISABLE_FUNCTION_NAME) == 0) { + // Want the function name + const auto baseFunctionName = LogpFindBaseFunctionName(FunctionName); + status = RtlStringCchPrintfA(functionNameBuffer, + RTL_NUMBER_OF(functionNameBuffer), "%-40s\t", + baseFunctionName); + if (!NT_SUCCESS(status)) { + return status; + } + } + + // + // It uses PsGetProcessId(PsGetCurrentProcess()) instead of + // PsGetCurrentThreadProcessId() because the later sometimes returns + // unwanted value, for example: + // PID == 4 but its image name != ntoskrnl.exe + // The author is guessing that it is related to attaching processes but + // not quite sure. The former way works as expected. + // + status = RtlStringCchPrintfA( + LogBuffer, LogBufferLength, "%s%s\t%5lu\t%5lu\t%-15s\t%s%s\r\n", + timeBuffer, levelString, + reinterpret_cast(PsGetProcessId(PsGetCurrentProcess())), + reinterpret_cast(PsGetCurrentThreadId()), + PsGetProcessImageFileName(PsGetCurrentProcess()), functionNameBuffer, + LogMessage); + return status; +} + +// Returns the function's base name, for example, +// NamespaceName::ClassName::MethodName will be returned as MethodName. +EXTERN_C static const char *LogpFindBaseFunctionName( + _In_ const char *FunctionName) { + if (!FunctionName) { + return nullptr; + } + + auto ptr = FunctionName; + auto name = FunctionName; + while (*(ptr++)) { + if (*ptr == ':') { + name = ptr + 1; + } + } + return name; +} + +// Logs the entry according to Attribute and the thread condition. +EXTERN_C static NTSTATUS LogpPut(_In_ const char *Message, + _In_ ULONG Attribute) { + auto status = STATUS_SUCCESS; + + // Log the entry to a file or buffer. + auto &info = g_LogpLogBufferInfo; + if (LogpIsLogFileEnabled(info)) { + // Can it log it to a file now? + if (((Attribute & LOGP_LEVEL_OPT_SAFE) == 0) && + KeGetCurrentIrql() == PASSIVE_LEVEL && !KeAreAllApcsDisabled()) { + // Yes, it can. Do it. + LogpWriteLogBufferToFile(&info); + status = LogpWriteMessageToFile(Message, info); + } else { + // No, it cannot. Buffer it. + status = LogpBufferMessage(Message, &info); + } + } + + // Can it safely be printed? + if (KeGetCurrentIrql() >= CLOCK_LEVEL) { + return STATUS_UNSUCCESSFUL; + } + + DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "%s", Message); + return status; +} + +// Switch the current log buffer and save the contents of old buffer to the log +// file. This function does not flush the log file, so code should call +// LogpWriteMessageToFile() or ZwFlushBuffersFile() later. +EXTERN_C static NTSTATUS LogpWriteLogBufferToFile( + _In_opt_ LogBufferInfo *Info) { + NT_ASSERT(Info); + auto status = STATUS_SUCCESS; + + // Enter a critical section and acquire a reader lock for Info in order to + // write a log file safely. + ExEnterCriticalRegionAndAcquireResourceExclusive(&Info->Resource); + + // Acquire a spin lock for Info.LogBuffer(s) in order to switch its head + // safely. + const auto irql = KeAcquireSpinLockRaiseToDpc(&Info->SpinLock); + auto oldLogBuffer = const_cast(Info->LogBufferHead); + if (oldLogBuffer[0]) { + Info->LogBufferHead = (oldLogBuffer == Info->LogBuffer1) ? Info->LogBuffer2 + : Info->LogBuffer1; + Info->LogBufferHead[0] = '\0'; + Info->LogBufferTail = Info->LogBufferHead; + } + KeReleaseSpinLock(&Info->SpinLock, irql); + + // Write all log entries in old log buffer. + IO_STATUS_BLOCK ioStatus = {}; + for (auto currentLogEntry = oldLogBuffer; currentLogEntry[0]; /**/) { + const auto currentLogEntryLength = strlen(currentLogEntry); + status = + ZwWriteFile(Info->LogFileHandle, nullptr, nullptr, nullptr, &ioStatus, + currentLogEntry, static_cast(currentLogEntryLength), + nullptr, nullptr); + if (!NT_SUCCESS(status)) { + // It could happen when you did not register IRP_SHUTDOWN and call + // LogIrpShutdownHandler() and the system tried to log to a file after + // a filesystem was unmounted. + DBG_BREAK(); + } + + currentLogEntry += currentLogEntryLength + 1; + } + oldLogBuffer[0] = '\0'; + + ExReleaseResourceAndLeaveCriticalRegion(&Info->Resource); + return status; +} + +// Logs the current log entry to and flush the log file. +EXTERN_C static NTSTATUS LogpWriteMessageToFile( + _In_ const char *Message, _In_ const LogBufferInfo &Info) { + IO_STATUS_BLOCK ioStatus = {}; + auto status = + ZwWriteFile(Info.LogFileHandle, nullptr, nullptr, nullptr, &ioStatus, + const_cast(Message), + static_cast(strlen(Message)), nullptr, nullptr); + if (!NT_SUCCESS(status)) { + // It could happen when you did not register IRP_SHUTDOWN and call + // LogIrpShutdownHandler() and the system tried to log to a file after + // a filesystem was unmounted. + DBG_BREAK(); + } + status = ZwFlushBuffersFile(Info.LogFileHandle, &ioStatus); + return status; +} + +// Buffer the log entry to the log buffer. +EXTERN_C static NTSTATUS LogpBufferMessage(_In_ const char *Message, + _In_opt_ LogBufferInfo *Info) { + NT_ASSERT(Info); + + // Acquire a spin lock to add the log safely. + const auto irql = KeAcquireSpinLockRaiseToDpc(&Info->SpinLock); + + // Copy the current log to the buffer. + size_t usedBufferSize = Info->LogBufferTail - Info->LogBufferHead; + auto status = + RtlStringCchCopyA(const_cast(Info->LogBufferTail), + LOGP_BUFFER_USABLE_SIZE - usedBufferSize, Message); + + // Update Info.LogMaximumUsage if necessary. + if (NT_SUCCESS(status)) { + const auto messageLength = strlen(Message) + 1; + Info->LogBufferTail += messageLength; + usedBufferSize += messageLength; + if (usedBufferSize > Info->LogMaximumUsage) { + Info->LogMaximumUsage = usedBufferSize; // Update + } + } else { + Info->LogMaximumUsage = LOGP_BUFFER_SIZE; // Indicates overflow + } + *Info->LogBufferTail = '\0'; + + KeReleaseSpinLock(&Info->SpinLock, irql); + return status; +} + +// Returns true when a log file is enabled. +EXTERN_C static bool LogpIsLogFileEnabled(_In_ const LogBufferInfo &Info) { + if (Info.LogFileHandle) { + NT_ASSERT(Info.LogBuffer1); + NT_ASSERT(Info.LogBuffer2); + NT_ASSERT(Info.LogBufferHead); + NT_ASSERT(Info.LogBufferTail); + return true; + } + NT_ASSERT(!Info.LogBuffer1); + NT_ASSERT(!Info.LogBuffer2); + NT_ASSERT(!Info.LogBufferHead); + NT_ASSERT(!Info.LogBufferTail); + return false; +} + +// Returns true when logging is necessary according to the log's severity and +// a set log level. +EXTERN_C static bool LogpIsLogNeeded(_In_ ULONG Level) { + return !!(g_LogpDebugFlag & Level); +} + +// A thread runs as long as info.BufferFlushThreadShouldBeAlive is true and +// flushes a log buffer to a log file every LOGP_AUTO_FLUSH_INTERVAL_MSEC msec. +ALLOC_TEXT(PAGED, LogpBufferFlushThreadRoutine) +EXTERN_C static VOID LogpBufferFlushThreadRoutine(_In_ void *StartContext) { + PAGED_CODE(); + auto status = STATUS_SUCCESS; + auto info = reinterpret_cast(StartContext); + LOG_DEBUG("Log thread started."); + NT_ASSERT(LogpIsLogFileEnabled(*info)); + + while (info->BufferFlushThreadShouldBeAlive) { + if (info->LogBufferHead[0]) { + NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); + NT_ASSERT(!KeAreAllApcsDisabled()); + status = LogpWriteLogBufferToFile(info); + // Do not flush the file for overall performance. Even a case of + // bug check, we should be able to recover logs by looking at both + // log buffers. + } + LogpSleep(LOGP_AUTO_FLUSH_INTERVAL_MSEC); + } + LOG_DEBUG("Log thread is ending."); + PsTerminateSystemThread(status); +} + +// Sleep the current thread's execution for Millisecond milli-seconds. +ALLOC_TEXT(PAGED, LogpSleep) +EXTERN_C static NTSTATUS LogpSleep(_In_ LONG Millisecond) { + PAGED_CODE(); + + LARGE_INTEGER interval = {}; + interval.QuadPart = -(10000 * Millisecond); // msec + return KeDelayExecutionThread(KernelMode, FALSE, &interval); +} diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/log.h b/RemoteWriteMonitor/RemoteWriteMonitor/log.h index a694ecb..076c266 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/log.h +++ b/RemoteWriteMonitor/RemoteWriteMonitor/log.h @@ -1,106 +1,106 @@ -// Copyright (c) 2015, tandasat. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -// -// This module declares interfaces to logging functions. -// -#pragma once - -//////////////////////////////////////////////////////////////////////////////// -// -// macro utilities -// - -// -// Does log with respective severities. Here are some ideas to decide which -// level is appropriate: -// DEBUG: For developers. -// INFO: For all. -// WARN: For all. It may require some attention but does not prevent the -// program working properly. -// ERROR: For all. It stops the program working properly. -// -#define LOG_DEBUG(format, ...) \ - LogPrint(LOGP_LEVEL_DEBUG, __FUNCTION__, (format), __VA_ARGS__) -#define LOG_INFO(format, ...) \ - LogPrint(LOGP_LEVEL_INFO, __FUNCTION__, (format), __VA_ARGS__) -#define LOG_WARN(format, ...) \ - LogPrint(LOGP_LEVEL_WARN, __FUNCTION__, (format), __VA_ARGS__) -#define LOG_ERROR(format, ...) \ - LogPrint(LOGP_LEVEL_ERROR, __FUNCTION__, (format), __VA_ARGS__) - -// Buffers the log to buffer. It is recommended to use it when a status of -// callee is no predictable in order to avoid bug checks. -#define LOG_DEBUG_SAFE(format, ...) \ - LogPrint(LOGP_LEVEL_DEBUG | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ - __VA_ARGS__) -#define LOG_INFO_SAFE(format, ...) \ - LogPrint(LOGP_LEVEL_INFO | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ - __VA_ARGS__) -#define LOG_WARN_SAFE(format, ...) \ - LogPrint(LOGP_LEVEL_WARN | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ - __VA_ARGS__) -#define LOG_ERROR_SAFE(format, ...) \ - LogPrint(LOGP_LEVEL_ERROR | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ - __VA_ARGS__) - -//////////////////////////////////////////////////////////////////////////////// -// -// constants and macros -// - -// (internal) Save this log to buffer and not try to write to a log file. -static const auto LOGP_LEVEL_OPT_SAFE = 0x1ul; - -// (internal) Log levels. -static const auto LOGP_LEVEL_DEBUG = 0x10ul; -static const auto LOGP_LEVEL_INFO = 0x20ul; -static const auto LOGP_LEVEL_WARN = 0x40ul; -static const auto LOGP_LEVEL_ERROR = 0x80ul; - -// For LogInitialization(). Specifies what level of verbosity is needed. -static const auto LOG_PUT_LEVEL_DEBUG = - LOGP_LEVEL_ERROR | LOGP_LEVEL_WARN | LOGP_LEVEL_INFO | LOGP_LEVEL_DEBUG; -static const auto LOG_PUT_LEVEL_INFO = - LOGP_LEVEL_ERROR | LOGP_LEVEL_WARN | LOGP_LEVEL_INFO; -static const auto LOG_PUT_LEVEL_WARN = LOGP_LEVEL_ERROR | LOGP_LEVEL_WARN; -static const auto LOG_PUT_LEVEL_ERROR = LOGP_LEVEL_ERROR; -static const auto LOG_PUT_LEVEL_DISABLE = 0x00ul; - -// For LogInitialization(). Does not log a current time. -static const auto LOG_OPT_DISABLE_TIME = 0x100ul; - -// For LogInitialization(). Does not log a current function name. -static const auto LOG_OPT_DISABLE_FUNCTION_NAME = 0x200ul; - -//////////////////////////////////////////////////////////////////////////////// -// -// types -// - -//////////////////////////////////////////////////////////////////////////////// -// -// prototypes -// - -EXTERN_C NTSTATUS LogInitialization(_In_ ULONG Flag, - _In_opt_ const wchar_t *FilePath, - _In_opt_ PDEVICE_OBJECT DeviceObject); - -EXTERN_C void LogIrpShutdownHandler(); - -EXTERN_C void LogTermination(_In_opt_ PDEVICE_OBJECT DeviceObject); - -EXTERN_C NTSTATUS LogPrint(_In_ ULONG Level, _In_ const char *FunctionName, - _In_ const char *Format, ...); - -//////////////////////////////////////////////////////////////////////////////// -// -// variables -// - -//////////////////////////////////////////////////////////////////////////////// -// -// implementations -// +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module declares interfaces to logging functions. +// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +// +// Does log with respective severities. Here are some ideas to decide which +// level is appropriate: +// DEBUG: For developers. +// INFO: For all. +// WARN: For all. It may require some attention but does not prevent the +// program working properly. +// ERROR: For all. It stops the program working properly. +// +#define LOG_DEBUG(format, ...) \ + LogpPrint(LOGP_LEVEL_DEBUG, __FUNCTION__, (format), __VA_ARGS__) +#define LOG_INFO(format, ...) \ + LogpPrint(LOGP_LEVEL_INFO, __FUNCTION__, (format), __VA_ARGS__) +#define LOG_WARN(format, ...) \ + LogpPrint(LOGP_LEVEL_WARN, __FUNCTION__, (format), __VA_ARGS__) +#define LOG_ERROR(format, ...) \ + LogpPrint(LOGP_LEVEL_ERROR, __FUNCTION__, (format), __VA_ARGS__) + +// Buffers the log to buffer. It is recommended to use it when a status of +// callee is no predictable in order to avoid bug checks. +#define LOG_DEBUG_SAFE(format, ...) \ + LogpPrint(LOGP_LEVEL_DEBUG | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ + __VA_ARGS__) +#define LOG_INFO_SAFE(format, ...) \ + LogpPrint(LOGP_LEVEL_INFO | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ + __VA_ARGS__) +#define LOG_WARN_SAFE(format, ...) \ + LogpPrint(LOGP_LEVEL_WARN | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ + __VA_ARGS__) +#define LOG_ERROR_SAFE(format, ...) \ + LogpPrint(LOGP_LEVEL_ERROR | LOGP_LEVEL_OPT_SAFE, __FUNCTION__, (format), \ + __VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +// (internal) Save this log to buffer and not try to write to a log file. +static const auto LOGP_LEVEL_OPT_SAFE = 0x1ul; + +// (internal) Log levels. +static const auto LOGP_LEVEL_DEBUG = 0x10ul; +static const auto LOGP_LEVEL_INFO = 0x20ul; +static const auto LOGP_LEVEL_WARN = 0x40ul; +static const auto LOGP_LEVEL_ERROR = 0x80ul; + +// For LogInitialization(). Specifies what level of verbosity is needed. +static const auto LOG_PUT_LEVEL_DEBUG = + LOGP_LEVEL_ERROR | LOGP_LEVEL_WARN | LOGP_LEVEL_INFO | LOGP_LEVEL_DEBUG; +static const auto LOG_PUT_LEVEL_INFO = + LOGP_LEVEL_ERROR | LOGP_LEVEL_WARN | LOGP_LEVEL_INFO; +static const auto LOG_PUT_LEVEL_WARN = LOGP_LEVEL_ERROR | LOGP_LEVEL_WARN; +static const auto LOG_PUT_LEVEL_ERROR = LOGP_LEVEL_ERROR; +static const auto LOG_PUT_LEVEL_DISABLE = 0x00ul; + +// For LogInitialization(). Does not log a current time. +static const auto LOG_OPT_DISABLE_TIME = 0x100ul; + +// For LogInitialization(). Does not log a current function name. +static const auto LOG_OPT_DISABLE_FUNCTION_NAME = 0x200ul; + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C NTSTATUS LogInitialization(_In_ ULONG Flag, + _In_opt_ const wchar_t *FilePath, + _In_opt_ PDEVICE_OBJECT DeviceObject); + +EXTERN_C void LogIrpShutdownHandler(); + +EXTERN_C void LogTermination(_In_opt_ PDEVICE_OBJECT DeviceObject); + +EXTERN_C NTSTATUS LogpPrint(_In_ ULONG Level, _In_ const char *FunctionName, + _In_ const char *Format, ...); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/satoshitanda.pfx b/RemoteWriteMonitor/RemoteWriteMonitor/satoshitanda.pfx new file mode 100644 index 0000000..d211d7e Binary files /dev/null and b/RemoteWriteMonitor/RemoteWriteMonitor/satoshitanda.pfx differ diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/ssdt.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/ssdt.cpp new file mode 100644 index 0000000..64027f9 --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/ssdt.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// This module implements an entry point of the driver and initializes other +// components in this module. +// +#include "stdafx.h" +#include "ssdt.h" +#include "log.h" +#include "util.h" + +namespace stdexp = std::experimental; + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +struct SERVICE_DESCRIPTOR_TABLE { + PULONG ServiceTable; + PULONG CounterTable; + ULONG_PTR TableSize; + PUCHAR ArgumentTable; +}; +static_assert(sizeof(SERVICE_DESCRIPTOR_TABLE) == sizeof(void *) * 4, + "Size check"); + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C PVOID NTAPI +RtlPcToFileHeader(_In_ PVOID PcValue, _Out_ PVOID *BaseOfImage); + +EXTERN_C static SERVICE_DESCRIPTOR_TABLE *SSDTpFindTable(); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +static SERVICE_DESCRIPTOR_TABLE *g_SSDTpTable = nullptr; + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +ALLOC_TEXT(INIT, SSDTInitialization) +EXTERN_C NTSTATUS SSDTInitialization() { + PAGED_CODE(); + + g_SSDTpTable = SSDTpFindTable(); + if (!g_SSDTpTable) { + return STATUS_UNSUCCESSFUL; + } + return STATUS_SUCCESS; +} + +ALLOC_TEXT(PAGED, SSDTTermination) +EXTERN_C void SSDTTermination() { PAGED_CODE(); } + +ALLOC_TEXT(PAGED, SSDTGetProcAdderss) +EXTERN_C FARPROC SSDTGetProcAdderss(_In_ ULONG Index) { + PAGED_CODE(); + + if (IsX64()) { + return reinterpret_cast( + (g_SSDTpTable->ServiceTable[Index] >> 4) + + reinterpret_cast(g_SSDTpTable->ServiceTable)); + } else { + return reinterpret_cast(g_SSDTpTable->ServiceTable[Index]); + } +} + +// Get an original value of the SSDT and replace it with a new value. +EXTERN_C void SSDTSetProcAdderss(_In_ ULONG Index, _In_ FARPROC HookRoutine) { + // Need to rise IRQL not to allow the system to change an execution processor + // during the operation because this code changes a state of processor (CR0). + KIRQL oldIrql = 0; + KeRaiseIrql(DISPATCH_LEVEL, &oldIrql); + const auto scopedIrql = + stdexp::make_scope_exit([oldIrql]() { KeLowerIrql(oldIrql); }); + + UtilDisableWriteProtect(); + const auto scopedWriteProtection = + stdexp::make_scope_exit([] { UtilEnableWriteProtect(); }); + g_SSDTpTable->ServiceTable[Index] = reinterpret_cast(HookRoutine); +} + +ALLOC_TEXT(INIT, SSDTpFindTable) +EXTERN_C static SERVICE_DESCRIPTOR_TABLE *SSDTpFindTable() { + PAGED_CODE(); + + if (!IsX64()) { + UNICODE_STRING name = RTL_CONSTANT_STRING(L"KeServiceDescriptorTable"); + return reinterpret_cast( + MmGetSystemRoutineAddress(&name)); + } + + UNICODE_STRING name = RTL_CONSTANT_STRING(L"KeAddSystemServiceTable"); + auto pKeAddSystemServiceTable = + reinterpret_cast(MmGetSystemRoutineAddress(&name)); + if (!pKeAddSystemServiceTable) { + return nullptr; + } + + UNICODE_STRING name2 = RTL_CONSTANT_STRING(L"RtlPcToFileHeader"); + auto pRtlPcToFileHeader = reinterpret_cast( + MmGetSystemRoutineAddress(&name2)); + if (!pRtlPcToFileHeader) { + return nullptr; + } + + ULONG offset = 0; + for (auto i = 0; i < 0x40; ++i) { + auto dwordBytes = *reinterpret_cast(pKeAddSystemServiceTable + i); + if ((dwordBytes & 0x00fffff0) == 0x00bc8340) { + offset = *reinterpret_cast(pKeAddSystemServiceTable + i + 4); + break; + } + } + if (!offset) { + return nullptr; + } + + UCHAR *base = nullptr; + if (!pRtlPcToFileHeader(pKeAddSystemServiceTable, + reinterpret_cast(&base))) { + return nullptr; + } + return reinterpret_cast(base + offset); +} diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/ssdt.h b/RemoteWriteMonitor/RemoteWriteMonitor/ssdt.h new file mode 100644 index 0000000..b8c465e --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/ssdt.h @@ -0,0 +1,46 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// +// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C NTSTATUS SSDTInitialization(); + +EXTERN_C void SSDTTermination(); + +EXTERN_C FARPROC SSDTGetProcAdderss(_In_ ULONG Index); + +EXTERN_C void SSDTSetProcAdderss(_In_ ULONG Index, _In_ FARPROC HookRoutine); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/stdafx.h b/RemoteWriteMonitor/RemoteWriteMonitor/stdafx.h index 0129f32..9a2e6ea 100644 --- a/RemoteWriteMonitor/RemoteWriteMonitor/stdafx.h +++ b/RemoteWriteMonitor/RemoteWriteMonitor/stdafx.h @@ -1,94 +1,111 @@ -// Copyright (c) 2015, tandasat. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -// -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#pragma once - -extern "C" { -#pragma warning(push, 0) -#include -#include -#include -#include -#include -#define NTSTRSAFE_NO_CB_FUNCTIONS -#include -#include -#include -#include -#include -#include -#include -#pragma warning(pop) -} - -#ifndef _HAS_EXCEPTIONS -#define _HAS_EXCEPTIONS 0 -#endif -#include "../Common/ScopedResource/unique_resource.h" -#include "../Common/ScopedResource/scope_exit.h" - -//////////////////////////////////////////////////////////////////////////////// -// -// macro utilities -// - -// Specifies where the code should be located -#ifdef ALLOC_PRAGMA -#define ALLOC_TEXT(Section, Name) __pragma(alloc_text(Section, Name)) -#else -#define ALLOC_TEXT(Section, Name) -#endif - -// Break point that works only when a debugger is enabled -#ifndef DBG_BREAK -#ifdef _ARM_ -// Nullify it since an ARM device never allow us to attach a debugger. -#define DBG_BREAK() -#else // _ARM_ -#define DBG_BREAK() \ - if (KD_DEBUGGER_ENABLED) { \ - __debugbreak(); \ - } else { \ - } \ - reinterpret_cast(0) -#endif // _ARM_ -#endif // DBG_BREAK - -// To explicitly suppress warnings -#ifndef UNREFERENCED_LOCAL_VARIABLE -#define UNREFERENCED_LOCAL_VARIABLE(x) (reinterpret_cast(!(x))) -#endif - -//////////////////////////////////////////////////////////////////////////////// -// -// constants and macros -// - -static const ULONG RWMON_POOL_TAG_NAME = 'nmwr'; - -//////////////////////////////////////////////////////////////////////////////// -// -// types -// - -//////////////////////////////////////////////////////////////////////////////// -// -// prototypes -// - -//////////////////////////////////////////////////////////////////////////////// -// -// variables -// - -//////////////////////////////////////////////////////////////////////////////// -// -// implementations -// +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +// +// NOTE: +// +// About building the driver: +// Visual Studio will pop up a dialog during a build process and require a +// password for code signing. Type "password" for it. +// + +#pragma once + +extern "C" { +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#define NTSTRSAFE_NO_CB_FUNCTIONS +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) +} + +#ifndef _HAS_EXCEPTIONS +#define _HAS_EXCEPTIONS 0 +#endif +#include "../Common/ScopedResource/unique_resource.h" +#include "../Common/ScopedResource/scope_exit.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +// Specifies where the code should be located +#ifdef ALLOC_PRAGMA +#define ALLOC_TEXT(Section, Name) __pragma(alloc_text(Section, Name)) +#else +#define ALLOC_TEXT(Section, Name) +#endif + +// Break point that works only when a debugger is enabled +#ifndef DBG_BREAK +#ifdef _ARM_ +// Nullify it since an ARM device never allow us to attach a debugger. +#define DBG_BREAK() +#else // _ARM_ +#define DBG_BREAK() \ + if (KD_DEBUGGER_ENABLED) { \ + __debugbreak(); \ + } else { \ + } \ + reinterpret_cast(0) +#endif // _ARM_ +#endif // DBG_BREAK + +// To explicitly suppress warnings +#ifndef UNREFERENCED_LOCAL_VARIABLE +#define UNREFERENCED_LOCAL_VARIABLE(x) (reinterpret_cast(!(x))) +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +static const ULONG RWMON_POOL_TAG_NAME = 'nmwr'; + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +// Returns true when it is running on the x64 system. +inline bool IsX64() { +#ifdef _AMD64_ + return true; +#else + return false; +#endif +} \ No newline at end of file diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/util.cpp b/RemoteWriteMonitor/RemoteWriteMonitor/util.cpp new file mode 100644 index 0000000..2d5ad85 --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/util.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// +// +#include "stdafx.h" +#include "util.h" + +namespace stdexp = std::experimental; + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +union CR0_REGISTER { + ULONG_PTR Value; + struct { + unsigned PE : 1; // [0] Protected Mode Enabled + unsigned MP : 1; // [1] Monitor Coprocessor FLAG + unsigned EM : 1; // [2] Emulate FLAG + unsigned TS : 1; // [3] Task Switched FLAG + unsigned ET : 1; // [4] Extension Type FLAG + unsigned NE : 1; // [5] Numeric Error + unsigned Reserved1 : 10; // [6-15] + unsigned WP : 1; // [16] Write Protect + unsigned Reserved2 : 1; // [17] + unsigned AM : 1; // [18] Alignment Mask + unsigned Reserved3 : 10; // [19-28] + unsigned NW : 1; // [29] Not Write-Through + unsigned CD : 1; // [30] Cache Disable + unsigned PG : 1; // [31] Paging Enabled + } Fields; +}; +static_assert(sizeof(CR0_REGISTER) == sizeof(void *), "Size check"); + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// + +// Disable the write protection +EXTERN_C void UtilDisableWriteProtect() { + CR0_REGISTER cr0 = {__readcr0()}; + cr0.Fields.WP = false; + __writecr0(cr0.Value); +} + +// Enable the write protection +EXTERN_C void UtilEnableWriteProtect() { + CR0_REGISTER cr0 = {__readcr0()}; + cr0.Fields.WP = true; + __writecr0(cr0.Value); +} + +// memmem(). +EXTERN_C void *UtilMemMem(_In_ const void *SearchBase, _In_ SIZE_T SearchSize, + _In_ const void *Pattern, _In_ SIZE_T PatternSize) { + if (PatternSize > SearchSize) { + return nullptr; + } + auto searchBase = static_cast(SearchBase); + for (size_t i = 0; i <= SearchSize - PatternSize; i++) { + if (!memcmp(Pattern, &searchBase[i], PatternSize)) { + return const_cast(&searchBase[i]); + } + } + return nullptr; +} + +// Does memcpy safely even if Destination is a read only region. +EXTERN_C NTSTATUS UtilForceMemCpy(_In_ void *Destination, + _In_ const void *Source, _In_ SIZE_T Length) { + auto mdl = std::experimental::make_unique_resource( + IoAllocateMdl(Destination, static_cast(Length), FALSE, FALSE, + nullptr), + &IoFreeMdl); + if (!mdl) { + return STATUS_INSUFFICIENT_RESOURCES; + } + MmBuildMdlForNonPagedPool(mdl.get()); + + // + // Following MmMapLockedPagesSpecifyCache() call causes bug check in case + // you are using Driver Verifier. The reason is explained as follows: + // + // A driver must not try to create more than one system-address-space + // mapping for an MDL. Additionally, because an MDL that is built by the + // MmBuildMdlForNonPagedPool routine is already mapped to the system + // address space, a driver must not try to map this MDL into the system + // address space again by using the MmMapLockedPagesSpecifyCache routine. + // -- MSDN + // + // This flag modification hacks Driver Verifier's check and prevent leading + // bug check. + // + mdl.get()->MdlFlags &= ~MDL_SOURCE_IS_NONPAGED_POOL; + mdl.get()->MdlFlags |= MDL_PAGES_LOCKED; + + auto writableDest = std::experimental::make_unique_resource( + MmMapLockedPagesSpecifyCache(mdl.get(), KernelMode, MmCached, nullptr, + FALSE, NormalPagePriority), + [&mdl](void *p) { MmUnmapLockedPages(p, mdl.get()); }); + if (!writableDest) { + return STATUS_INSUFFICIENT_RESOURCES; + } + memcpy(writableDest.get(), Source, Length); + return STATUS_SUCCESS; +} + +// Invalidates an instruction cache for the specified region. +EXTERN_C void UtilInvalidateInstructionCache(_In_ void *BaseAddress, + _In_ SIZE_T Length) { + UNREFERENCED_PARAMETER(BaseAddress); + UNREFERENCED_PARAMETER(Length); +#if _AMD64_ + __faststorefence(); +#else + _mm_sfence(); +#endif +} \ No newline at end of file diff --git a/RemoteWriteMonitor/RemoteWriteMonitor/util.h b/RemoteWriteMonitor/RemoteWriteMonitor/util.h new file mode 100644 index 0000000..e066aab --- /dev/null +++ b/RemoteWriteMonitor/RemoteWriteMonitor/util.h @@ -0,0 +1,51 @@ +// Copyright (c) 2015, tandasat. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// +// +// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +// +// macro utilities +// + +//////////////////////////////////////////////////////////////////////////////// +// +// constants and macros +// + +//////////////////////////////////////////////////////////////////////////////// +// +// types +// + +//////////////////////////////////////////////////////////////////////////////// +// +// prototypes +// + +EXTERN_C void UtilDisableWriteProtect(); + +EXTERN_C void UtilEnableWriteProtect(); + +EXTERN_C void *UtilMemMem(_In_ const void *SearchBase, _In_ SIZE_T SearchSize, + _In_ const void *Pattern, _In_ SIZE_T PatternSize); + +EXTERN_C NTSTATUS UtilForceMemCpy(_In_ void *Destination, + _In_ const void *Source, _In_ SIZE_T Length); + +EXTERN_C void UtilInvalidateInstructionCache(_In_ void *BaseAddress, + _In_ SIZE_T Length); + +//////////////////////////////////////////////////////////////////////////////// +// +// variables +// + +//////////////////////////////////////////////////////////////////////////////// +// +// implementations +// diff --git a/RemoteWriteMonitor/clean.bat b/RemoteWriteMonitor/clean.bat index f8ff1ba..63ed94e 100644 --- a/RemoteWriteMonitor/clean.bat +++ b/RemoteWriteMonitor/clean.bat @@ -3,9 +3,17 @@ del /a:h *.suo rmdir /s /q ipch rmdir /s /q Debug rmdir /s /q Release -rmdir /s /q RemoteWriteMonitor\Debug -rmdir /s /q RemoteWriteMonitor\Release +rmdir /s /q Win7Debug +rmdir /s /q Win7Release +rmdir /s /q Win8.1Debug +rmdir /s /q Win8.1Release +rmdir /s /q x64 rmdir /s /q injector\Debug rmdir /s /q injector\Release +rmdir /s /q RemoteWriteMonitor\Win7Debug +rmdir /s /q RemoteWriteMonitor\Win7Release +rmdir /s /q RemoteWriteMonitor\Win8.1Debug +rmdir /s /q RemoteWriteMonitor\Win8.1Release +rmdir /s /q RemoteWriteMonitor\x64 del /s *.aps pause diff --git a/RemoteWriteMonitor/clean_for_release.bat b/RemoteWriteMonitor/clean_for_release.bat new file mode 100644 index 0000000..57c8049 --- /dev/null +++ b/RemoteWriteMonitor/clean_for_release.bat @@ -0,0 +1,9 @@ +del /s *.pdb *.cer +mkdir x86 +move Win7Release x86 +move Win8.1Release x86 +mkdir bin +move x86 bin +move x64 bin +move Release bin +pause diff --git a/RemoteWriteMonitor/injector/injector.vcxproj b/RemoteWriteMonitor/injector/injector.vcxproj index e51ff85..7017af4 100644 --- a/RemoteWriteMonitor/injector/injector.vcxproj +++ b/RemoteWriteMonitor/injector/injector.vcxproj @@ -1,96 +1,159 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {FEE34C62-A273-4557-BF93-360BDA2855E5} - Win32Proj - injector - - - - Application - true - v120_xp - Unicode - - - Application - false - v120_xp - true - Unicode - - - - - - - - - - - - - true - - - false - - - - Use - Level4 - Disabled - WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - true - false - - - Console - true - - - - - Level4 - Use - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - true - false - - - Console - true - true - true - - - - - - - - - - - - Create - Create - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {FEE34C62-A273-4557-BF93-360BDA2855E5} + Win32Proj + injector + + + + Application + true + v120_xp + Unicode + + + Application + true + v120_xp + Unicode + + + Application + false + v120_xp + true + Unicode + + + Application + false + v120_xp + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level4 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + false + + + Console + true + + + + + Use + Level4 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + false + + + Console + true + + + + + Level4 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + false + + + Console + true + true + true + + + + + Level4 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + false + + + Console + true + true + true + + + + + + + + + + + + Create + Create + Create + Create + + + + + \ No newline at end of file diff --git a/RemoteWriteMonitor/injector/injector.vcxproj.user b/RemoteWriteMonitor/injector/injector.vcxproj.user index 63c37cc..153324e 100644 --- a/RemoteWriteMonitor/injector/injector.vcxproj.user +++ b/RemoteWriteMonitor/injector/injector.vcxproj.user @@ -1,11 +1,19 @@ - - - - 4468 section context - WindowsLocalDebugger - - - 4468 section context - WindowsLocalDebugger - + + + + 4468 section context + WindowsLocalDebugger + + + 4468 section context + WindowsLocalDebugger + + + 4468 section context + WindowsLocalDebugger + + + 4468 section context + WindowsLocalDebugger + \ No newline at end of file