Repository: D4stiny/PeaceMaker Branch: master Commit: c96669d226d7 Files: 60 Total size: 315.3 KB Directory structure: gitextract_zfage812/ ├── .gitignore ├── LICENSE ├── PeaceMaker CLI/ │ ├── IOCTLCommunicationUser.cpp │ ├── IOCTLCommunicationUser.h │ ├── PeaceMaker CLI.cpp │ ├── PeaceMaker CLI.vcxproj │ └── PeaceMaker CLI.vcxproj.filters ├── PeaceMaker Kernel/ │ ├── AlertQueue.cpp │ ├── AlertQueue.h │ ├── DetectionLogic.cpp │ ├── DetectionLogic.h │ ├── FSFilter.cpp │ ├── FSFilter.h │ ├── FilterTesting.cpp │ ├── IOCTLCommunication.cpp │ ├── IOCTLCommunication.h │ ├── ImageHistoryFilter.cpp │ ├── ImageHistoryFilter.h │ ├── PeaceMaker Kernel.inf │ ├── PeaceMaker Kernel.rc │ ├── PeaceMaker Kernel.vcxproj │ ├── PeaceMaker Kernel.vcxproj.filters │ ├── RCa19580 │ ├── RCa38200 │ ├── RegistryFilter.cpp │ ├── RegistryFilter.h │ ├── StackWalker.cpp │ ├── StackWalker.h │ ├── StringFilters.cpp │ ├── StringFilters.h │ ├── TamperGuard.cpp │ ├── TamperGuard.h │ ├── ThreadFilter.cpp │ ├── ThreadFilter.h │ ├── common.cpp │ ├── common.h │ ├── ntdef.h │ └── shared.h ├── PeaceMaker Kernel.vcxproj ├── PeaceMaker.sln ├── PeaceMakerGUI/ │ ├── AssetResources.qrc │ ├── ClickableTab.cpp │ ├── ClickableTab.h │ ├── InvestigateProcessWindow.cpp │ ├── InvestigateProcessWindow.h │ ├── InvestigateProcessWindow.ui │ ├── PeaceMakerGUI.pro │ ├── addfilterwindow.cpp │ ├── addfilterwindow.h │ ├── addfilterwindow.ui │ ├── configparser.cpp │ ├── configparser.h │ ├── detailedalertwindow.cpp │ ├── detailedalertwindow.h │ ├── detailedalertwindow.ui │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ └── mainwindow.ui └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Bill Demirkapi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: PeaceMaker CLI/IOCTLCommunicationUser.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "IOCTLCommunicationUser.h" /** Default constructor. */ IOCTLCommunication::IOCTLCommunication( VOID ) { } /** Disconnect from the driver. */ IOCTLCommunication::~IOCTLCommunication( VOID ) { CloseHandle(this->device); } /** Establish communication with the driver. @return Whether or not we successfully connected to the driver. */ BOOLEAN IOCTLCommunication::ConnectDevice( VOID ) { this->device = CreateFile(GLOBAL_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (this->device == INVALID_HANDLE_VALUE) { printf("IOCTLCommunication!IOCTLCommunication: Failed to connect to driver device with error %i.\n", GetLastError()); return FALSE; } printf("IOCTLCommunication!IOCTLCommunication: Established communication with driver.\n"); return TRUE; } /** Small wrapper around the DeviceIoControl call to the driver. @param IOCTLCode - The IOCTL code to pass to the driver. @param Input - The input buffer. @param InputLength - Size of the input buffer. @param Output - The output buffer. @param OutputLength - Size of the output buffer. @return Whether or not the call succeeded. */ BOOLEAN IOCTLCommunication::GenericQueryDriver ( _In_ DWORD IOCTLCode, _In_ PVOID Input, _In_ DWORD InputLength, _Out_ PVOID Output, _In_ DWORD OutputLength ) { DWORD bytesReturned; return DeviceIoControl(this->device, IOCTLCode, Input, InputLength, Output, OutputLength, &bytesReturned, NULL); } /** Query if there are alerts queued. @return Whether or not alerts are queued. */ BOOLEAN IOCTLCommunication::QueuedAlerts ( VOID ) { BOOLEAN queued; // // Query the driver passing in an output boolean. // if (this->GenericQueryDriver(IOCTL_ALERTS_QUEUED, NULL, 0, &queued, sizeof(queued)) == FALSE) { printf("IOCTLCommunication!QueuedAlerts: Failed to query driver with error code %i.\n", GetLastError()); return FALSE; } return queued; } /** Pop a queued alert from the Alerts queue. @return An allocated pointer to the alert or NULL if unable to pop an alert. Caller must free to prevent leak. */ PBASE_ALERT_INFO IOCTLCommunication::PopAlert( VOID ) { BOOLEAN success; PBASE_ALERT_INFO alert; success = FALSE; alert = RCAST(malloc(MAX_STACK_VIOLATION_ALERT_SIZE)); if (alert == NULL) { printf("IOCTLCommunication!PopAlert: Failed to allocate space for alert.\n"); goto Exit; } memset(alert, 0, MAX_STACK_VIOLATION_ALERT_SIZE); if (this->GenericQueryDriver(IOCTL_POP_ALERT, NULL, 0, alert, MAX_STACK_VIOLATION_ALERT_SIZE) == FALSE) { printf("IOCTLCommunication!PopAlert: Failed to query driver with error code %i.\n", GetLastError()); goto Exit; } success = TRUE; Exit: if (success == FALSE && alert) { free(alert); alert = NULL; } return alert; } /** Request a summary of the most recent processes up to RequestCount. @param SkipCount - How many processes to "skip" in iteration. @param RequestCount - How many proocesses to get the summary of. @return The response to the summary request if any, otherwise NULL. Caller must free to prevent leak. */ PPROCESS_SUMMARY_REQUEST IOCTLCommunication::RequestProcessSummary ( _In_ ULONG SkipCount, _In_ ULONG RequestCount ) { BOOLEAN success; PPROCESS_SUMMARY_REQUEST summaryRequest; ULONG summaryRequestSize; success = FALSE; summaryRequestSize = MAX_PROCESS_SUMMARY_REQUEST_SIZE_RAW(RequestCount); summaryRequest = RCAST(malloc(summaryRequestSize)); if (summaryRequest == NULL) { printf("IOCTLCommunication!RequestProcessSummary: Failed to allocate space for summaryRequest.\n"); goto Exit; } memset(summaryRequest, 0, summaryRequestSize); summaryRequest->SkipCount = SkipCount; summaryRequest->ProcessHistorySize = RequestCount; if (this->GenericQueryDriver(IOCTL_GET_PROCESSES, summaryRequest, summaryRequestSize, summaryRequest, summaryRequestSize) == FALSE) { printf("IOCTLCommunication!RequestProcessSummary: Failed to query driver with error code %i.\n", GetLastError()); goto Exit; } success = TRUE; Exit: if (success == FALSE && summaryRequest) { free(summaryRequest); summaryRequest = NULL; } return summaryRequest; } /** Request detailed information on a process. @param ProcessId - The subject process. @param EpochExecutionTime - The time the process was executed (in seconds since epoch). @param MaxImageSize - The maximum number of image entries to copy. @param MaxStackSize - The maximum number of stack entries to copy. @return The response to the detailed request if any, otherwise NULL. Caller must free to prevent leak. */ PPROCESS_DETAILED_REQUEST IOCTLCommunication::RequestDetailedProcess ( _In_ HANDLE ProcessId, _In_ ULONG EpochExecutionTime, _In_ ULONG MaxImageSize, _In_ ULONG MaxStackSize ) { BOOLEAN success; PPROCESS_DETAILED_REQUEST detailedRequest; ULONG detailedRequestSize; success = FALSE; detailedRequestSize = sizeof(PROCESS_DETAILED_REQUEST); // // Allocate the necessary members. // detailedRequest = RCAST(malloc(detailedRequestSize)); if (detailedRequest == NULL) { printf("IOCTLCommunication!RequestDetailedProcess: Failed to allocate space for detailedRequest.\n"); goto Exit; } memset(detailedRequest, 0, detailedRequestSize); detailedRequest->ImageSummary = RCAST(malloc(MaxImageSize * sizeof(IMAGE_SUMMARY))); if (detailedRequest->ImageSummary == NULL) { printf("IOCTLCommunication!RequestDetailedProcess: Failed to allocate space for detailedRequest->ImageSummary.\n"); goto Exit; } memset(detailedRequest->ImageSummary, 0, MaxImageSize * sizeof(IMAGE_SUMMARY)); detailedRequest->StackHistory = RCAST(malloc(MaxStackSize * sizeof(STACK_RETURN_INFO))); if (detailedRequest->StackHistory == NULL) { printf("IOCTLCommunication!RequestDetailedProcess: Failed to allocate space for detailedRequest->StackHistory.\n"); goto Exit; } memset(detailedRequest->StackHistory, 0, MaxStackSize * sizeof(STACK_RETURN_INFO)); detailedRequest->ProcessId = ProcessId; detailedRequest->EpochExecutionTime = EpochExecutionTime; detailedRequest->ImageSummarySize = MaxImageSize; detailedRequest->StackHistorySize = MaxStackSize; if (this->GenericQueryDriver(IOCTL_GET_PROCESS_DETAILED, detailedRequest, detailedRequestSize, detailedRequest, detailedRequestSize) == FALSE) { printf("IOCTLCommunication!RequestDetailedProcess: Failed to query driver with error code %i.\n", GetLastError()); goto Exit; } success = TRUE; Exit: if (success == FALSE && detailedRequest) { free(detailedRequest); detailedRequest = NULL; } return detailedRequest; } /** Register a filter with the driver. @param Type - The filter type. @param Flags - The filter flags (EXECUTE/DELETE/WRITE/ETC). @param Content - The content of the filter. @param ContentLength - The size of the content. @return The filter ID (if added successfully), otherwise 0. */ ULONG IOCTLCommunication::AddFilter ( _In_ STRING_FILTER_TYPE Type, _In_ ULONG Flags, _In_ PWCHAR Content, _In_ ULONG ContentLength ) { STRING_FILTER_REQUEST filterRequest; // // Fill out the struct. // filterRequest.FilterType = Type; filterRequest.Filter.Flags = Flags; memcpy_s(filterRequest.Filter.MatchString, sizeof(filterRequest.Filter.MatchString), Content, ContentLength * sizeof(WCHAR)); filterRequest.Filter.MatchStringSize = ContentLength; // // Query the driver passing in the filter request. // if (this->GenericQueryDriver(IOCTL_ADD_FILTER, &filterRequest, sizeof(filterRequest), &filterRequest, sizeof(filterRequest)) == FALSE) { printf("IOCTLCommunication!AddFilter: Failed to query driver with error code %i.\n", GetLastError()); return 0; } return filterRequest.Filter.Id; } /** Request a list of filters with a constant size of 10. Grab more by using the SkipCount argument. @param Type - The type of filters to grab. @param SkipCount - The number of filters to skip during iteration. @return The response to the request. */ LIST_FILTERS_REQUEST IOCTLCommunication::RequestFilters( _In_ STRING_FILTER_TYPE Type, _In_ ULONG SkipCount ) { LIST_FILTERS_REQUEST listRequest; listRequest.FilterType = Type; listRequest.SkipFilters = SkipCount; // // Query the driver passing in the list request. // if (this->GenericQueryDriver(IOCTL_LIST_FILTERS, &listRequest, sizeof(listRequest), &listRequest, sizeof(listRequest)) == FALSE) { printf("IOCTLCommunication!RequestFilters: Failed to query driver with error code %i.\n", GetLastError()); } return listRequest; } /** Get the dynamic sizes in a process entry from the driver. @param ProcessId - The target process ID. @param EpochExecutionTime - The time the target process was executed. @return The response. */ PROCESS_SIZES_REQUEST IOCTLCommunication::GetProcessSizes( _In_ HANDLE ProcessId, _In_ ULONG EpochExecutionTime ) { PROCESS_SIZES_REQUEST sizeRequest; sizeRequest.ProcessId = ProcessId; sizeRequest.EpochExecutionTime = EpochExecutionTime; // // Query the driver passing in the list request. // if (this->GenericQueryDriver(IOCTL_GET_PROCESS_SIZES, &sizeRequest, sizeof(sizeRequest), &sizeRequest, sizeof(sizeRequest)) == FALSE) { printf("IOCTLCommunication!GetProcessSizes: Failed to query driver with error code %i.\n", GetLastError()); } return sizeRequest; } /** Request detailed information on an image. @param ProcessId - The subject process. @param EpochExecutionTime - The time the process was executed (in seconds since epoch). @param MaxStackSize - The maximum number of stack entries to copy. @return The response to the detailed request if any, otherwise NULL. Caller must free to prevent leak. */ PIMAGE_DETAILED_REQUEST IOCTLCommunication::RequestDetailedImage ( _In_ HANDLE ProcessId, _In_ ULONG EpochExecutionTime, _In_ ULONG ImageIndex, _In_ ULONG MaxStackSize ) { BOOLEAN success; PIMAGE_DETAILED_REQUEST imageRequest; ULONG imageRequestSize; success = FALSE; imageRequestSize = MAX_IMAGE_DETAILED_REQUEST_SIZE_RAW(MaxStackSize); imageRequest = RCAST(malloc(imageRequestSize)); if (imageRequest == NULL) { printf("IOCTLCommunication!RequestDetailedImage: Failed to allocate space for imageRequest.\n"); goto Exit; } memset(imageRequest, 0, imageRequestSize); imageRequest->ProcessId = ProcessId; imageRequest->EpochExecutionTime = EpochExecutionTime; imageRequest->ImageIndex = ImageIndex; imageRequest->StackHistorySize = MaxStackSize; if (this->GenericQueryDriver(IOCTL_GET_IMAGE_DETAILED, imageRequest, imageRequestSize, imageRequest, imageRequestSize) == FALSE) { printf("IOCTLCommunication!RequestDetailedImage: Failed to query driver with error code %i.\n", GetLastError()); goto Exit; } success = TRUE; Exit: if (success == FALSE && imageRequest) { free(imageRequest); imageRequest = NULL; } return imageRequest; } /** Get global sizes from the kernel @return The various sizes of data stored in the kernel. */ GLOBAL_SIZES IOCTLCommunication::GetGlobalSizes ( VOID ) { GLOBAL_SIZES sizes; // // Query the driver passing in the size request. // if (this->GenericQueryDriver(IOCTL_GET_GLOBAL_SIZES, NULL, 0, &sizes, sizeof(sizes)) == FALSE) { printf("IOCTLCommunication!GetProcessSizes: Failed to query driver with error code %i.\n", GetLastError()); } return sizes; } /** Delete a filter. @param Filter - The filter to delete. @return Whether or not the filter was deleted. */ BOOLEAN IOCTLCommunication::DeleteFilter ( _In_ FILTER_INFO Filter ) { DELETE_FILTER_REQUEST deleteFilterRequest; deleteFilterRequest.FilterId = Filter.Id; deleteFilterRequest.FilterType = Filter.Type; // // Query the driver passing in the delete request. // if (this->GenericQueryDriver(IOCTL_DELETE_FILTER, &deleteFilterRequest, sizeof(deleteFilterRequest), &deleteFilterRequest, sizeof(deleteFilterRequest)) == FALSE) { printf("IOCTLCommunication!DeleteFilter: Failed to query driver with error code %i.\n", GetLastError()); } return deleteFilterRequest.Deleted; } ================================================ FILE: PeaceMaker CLI/IOCTLCommunicationUser.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include #include #include "shared.h" class IOCTLCommunication { HANDLE device; BOOLEAN GenericQueryDriver( _In_ DWORD IOCTLCode, _In_ PVOID Input, _In_ DWORD InputLength, _Out_ PVOID Output, _In_ DWORD OutputLength ); public: IOCTLCommunication(VOID); ~IOCTLCommunication(VOID); BOOLEAN ConnectDevice( VOID ); BOOLEAN QueuedAlerts( VOID ); PBASE_ALERT_INFO PopAlert( VOID ); PPROCESS_SUMMARY_REQUEST RequestProcessSummary( _In_ ULONG SkipCount, _In_ ULONG RequestCount ); PPROCESS_DETAILED_REQUEST RequestDetailedProcess( _In_ HANDLE ProcessId, _In_ ULONG EpochExecutionTime, _In_ ULONG MaxImageSize, _In_ ULONG MaxStackSize ); ULONG AddFilter( _In_ STRING_FILTER_TYPE Type, _In_ ULONG Flags, _In_ PWCHAR Content, _In_ ULONG ContentLength ); LIST_FILTERS_REQUEST RequestFilters( _In_ STRING_FILTER_TYPE Type, _In_ ULONG SkipCount ); PROCESS_SIZES_REQUEST GetProcessSizes( _In_ HANDLE ProcessId, _In_ ULONG EpochExecutionTime ); PIMAGE_DETAILED_REQUEST RequestDetailedImage( _In_ HANDLE ProcessId, _In_ ULONG EpochExecutionTime, _In_ ULONG ImageIndex, _In_ ULONG MaxStackSize ); GLOBAL_SIZES GetGlobalSizes( VOID ); BOOLEAN DeleteFilter( _In_ FILTER_INFO Filter ); }; ================================================ FILE: PeaceMaker CLI/PeaceMaker CLI.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include #include #include #include "IOCTLCommunicationUser.h" /** Print stack history. @param StackHistory - The history to display. @param StackHistorySize - The size of the StackHistory array. */ VOID DisplayStackHistory( _In_ PSTACK_RETURN_INFO StackHistory, _In_ ULONG StackHistorySize ) { ULONG i; printf("DisplayStackHistory: \tStack History:\n"); for (i = 0; i < StackHistorySize; i++) { if (StackHistory[i].MemoryInModule) { printf("DisplayStackHistory: \t\t%ws+0x%X\n", StackHistory[i].BinaryPath, StackHistory[i].BinaryOffset); } else { printf("DisplayStackHistory: \t\t0x%llx (Manual Mapped)\n", StackHistory[i].RawAddress); } } } /** Display an alert. Supports the various alert types. @param Alert - Alert to display. */ VOID DisplayAlert ( PBASE_ALERT_INFO Alert ) { PSTACK_VIOLATION_ALERT stackViolationAlert; PFILTER_VIOLATION_ALERT filterViolationAlert; // // Sanity check. // if (Alert == NULL) { printf("DisplayAlert: No alert found.\n"); return; } printf("DisplayAlert: Alert dump:\n"); switch (Alert->AlertSource) { case ProcessCreate: printf("DisplayAlert: \tSource: Process creation callback\n"); break; case ImageLoad: printf("DisplayAlert: \tSource: Image load callback\n"); break; case RegistryFilterMatch: printf("DisplayAlert: \tSource: Registry filter match\n"); break; case FileFilterMatch: printf("DisplayAlert: \tSource: File filter match\n"); break; case ThreadCreate: printf("DisplayAlert: \tSource: Thread creation callback\n"); break; } switch (Alert->AlertType) { case StackViolation: stackViolationAlert = RCAST(Alert); printf("DisplayAlert: \tAlert Type: Stack walk violation\n"); printf("DisplayAlert: \tViolating Address: 0x%llx\n", stackViolationAlert->ViolatingAddress); DisplayStackHistory(stackViolationAlert->StackHistory, stackViolationAlert->StackHistorySize); break; case FilterViolation: filterViolationAlert = RCAST(Alert); printf("DisplayAlert: \tAlert Type: Filter violation\n"); printf("DisplayAlert: \tFilter content: %ws\n", filterViolationAlert->ViolatedFilter.MatchString); printf("DisplayAlert: \tFilter flags: 0x%X\n", filterViolationAlert->ViolatedFilter.Flags); DisplayStackHistory(filterViolationAlert->StackHistory, filterViolationAlert->StackHistorySize); break; } } int main() { IOCTLCommunication communicator; std::string input; int choice; PBASE_ALERT_INFO alert; PPROCESS_SUMMARY_REQUEST processSummaries; PPROCESS_DETAILED_REQUEST processDetailed; PROCESS_SIZES_REQUEST processSizes; LIST_FILTERS_REQUEST filters; PIMAGE_DETAILED_REQUEST imageDetailed; ULONG i; int skipCount; int requestCount; HANDLE processID; ULONG executionTime; STRING_FILTER_TYPE filterType; ULONG filterFlags; std::wstring filterContent; ULONG filterId; ULONG imageIndex; choice = 7; printf("main: Initiating communication with the driver.\n"); if (communicator.ConnectDevice() == FALSE) { printf("main: Failed to connect to the device.\n"); goto Exit; } printf("main: Communication initiated.\n"); do { printf("main: PeaceMaker basic CLI utility\n"); printf("main: 1. Check if there are alerts queued.\n"); printf("main: 2. Pop an alert from the list of alerts.\n"); printf("main: 3. Request a list of process summaries.\n"); printf("main: 4. Request detailed information on a process.\n"); printf("main: 5. Request to add a filter.\n"); printf("main: 6. Request a list of filters.\n"); printf("main: 7. Request detailed information on an image in a process.\n"); printf("main: 8. Exit.\n"); std::cin >> input; choice = std::stoi(input); // // Depending on the user's choice, dispatch to the correct function. // switch (choice) { case 1: if (communicator.QueuedAlerts()) { printf("main: There are alerts queued.\n"); break; } printf("main: There are no alerts queued.\n"); break; case 2: alert = communicator.PopAlert(); DisplayAlert(alert); free(alert); alert = NULL; break; case 3: // // Ask for the necessary information. // printf("main: How many processes should we skip?\n"); std::cin >> input; skipCount = std::stoi(input); printf("main: How many processes should we request?\n"); std::cin >> input; requestCount = std::stoi(input); // // Get the summamries. // processSummaries = communicator.RequestProcessSummary(skipCount, requestCount); if (processSummaries == NULL) { printf("main: Failed to retrieve process summaries.\n"); break; } // // Print the processes in a "table" format. // printf("main: %-10s\t%-50s\t%-12s\n", "Process ID", "Path", "Execution Time"); for (i = 0; i < processSummaries->ProcessHistorySize; i++) { if (processSummaries->ProcessHistory[i].ProcessId == 0) { continue; } printf("main: %-10i\t%-50ws\t%-12i\n", processSummaries->ProcessHistory[i].ProcessId, processSummaries->ProcessHistory[i].ImageFileName, processSummaries->ProcessHistory[i].EpochExecutionTime); } free(processSummaries); processSummaries = NULL; break; case 4: // // Ask for the necessary information. // printf("main: What is the target process ID?\n"); std::cin >> input; processID = RCAST(std::stoi(input)); printf("main: What is the processes execution time in epoch?\n"); std::cin >> input; executionTime = std::stoi(input); processSizes = communicator.GetProcessSizes(processID, executionTime); printf("main: ImageSize = %i, StackSize = %i\n", processSizes.ImageSize, processSizes.StackSize); // // Request a detailed report on the process. // processDetailed = communicator.RequestDetailedProcess(processID, executionTime, processSizes.ImageSize, processSizes.StackSize); if (processDetailed == NULL || processDetailed->Populated == FALSE) { printf("main: Failed to retrieve a detailed process report.\n"); break; } printf("main: Process 0x%X:\n", processID); printf("main: \tProcess Path: %ws\n", processDetailed->ProcessPath); printf("main: \tCaller Process ID: 0x%X\n", processDetailed->CallerProcessId); printf("main: \tCaller Process Path: %ws\n", processDetailed->CallerProcessPath); printf("main: \tParent Process ID: 0x%X\n", processDetailed->ParentProcessId); printf("main: \tParent Process Path: %ws\n", processDetailed->ParentProcessPath); DisplayStackHistory(processDetailed->StackHistory, processDetailed->StackHistorySize); printf("main: \t%-3s\t%-100s:\n", "ID", "IMAGE PATH"); for (i = 0; i < processDetailed->ImageSummarySize; i++) { printf("main: \t\t%-3i\t%-100ws\n", i, processDetailed->ImageSummary[i].ImagePath); } free(processDetailed->ImageSummary); free(processDetailed->StackHistory); free(processDetailed); processDetailed = NULL; break; case 5: // // Ask for the necessary information. // printf("main: What type of filter to add (R/F)?\n"); std::cin >> input; switch (input[0]) { case 'R': case 'r': filterType = RegistryFilter; break; case 'F': case 'f': filterType = FilesystemFilter; break; } printf("main: What flags should the filter have (D/W/E, can enter multiple)?\n"); std::cin >> input; filterFlags = 0; for (char c : input) { switch (c) { case 'D': case 'd': filterFlags |= FILTER_FLAG_DELETE; break; case 'W': case 'w': filterFlags |= FILTER_FLAG_WRITE; break; case 'E': case 'e': filterFlags |= FILTER_FLAG_DELETE; break; } } printf("main: What should the filter content be?\n"); std::wcin >> filterContent; filterId = communicator.AddFilter(filterType, filterFlags, CCAST(filterContent.c_str()), filterContent.length()); if (filterId) { printf("main: Filter added with ID 0x%X.\n", filterId); break; } printf("main: Failed to add filter.\n"); break; case 6: // // Ask for the necessary information. // printf("main: What type of filter to list (R/F)?\n"); std::cin >> input; switch (input[0]) { case 'R': case 'r': filterType = RegistryFilter; break; case 'F': case 'f': filterType = FilesystemFilter; break; } printf("main: How many filters should we skip?\n"); std::cin >> input; skipCount = std::stoi(input); filters = communicator.RequestFilters(filterType, skipCount); for (i = 0; i < filters.CopiedFilters; i++) { printf("main: Filter 0x%X:\n"); switch (filters.Filters[i].Type) { case RegistryFilter: printf("main: \tFilter Type: Registry filter"); break; case FilesystemFilter: printf("main: \tFilter Type: Filesystem filter"); break; } printf("main: \tFilter Flags: "); if (FlagOn(filters.Filters[i].Flags, FILTER_FLAG_DELETE)) { printf("main: \t\tDELETE\n"); } if (FlagOn(filters.Filters[i].Flags, FILTER_FLAG_WRITE)) { printf("main: \t\tWRITE\n"); } if (FlagOn(filters.Filters[i].Flags, FILTER_FLAG_EXECUTE)) { printf("main: \t\tDELETE\n"); } printf("main: \tFilter Content: %ws\n", filters.Filters[i].MatchString); } break; case 7: // // Ask for the necessary information. // printf("main: What is the target process ID?\n"); std::cin >> input; processID = RCAST(std::stoi(input)); printf("main: What is the processes execution time in epoch?\n"); std::cin >> input; executionTime = std::stoi(input); processSizes = communicator.GetProcessSizes(processID, executionTime); printf("main: ImageSize = %i, StackSize = %i\n", processSizes.ImageSize, processSizes.StackSize); // // Request a detailed report on the process. // processDetailed = communicator.RequestDetailedProcess(processID, executionTime, processSizes.ImageSize, processSizes.StackSize); if (processDetailed == NULL || processDetailed->Populated == FALSE) { printf("main: Failed to retrieve a detailed process report.\n"); break; } printf("main: \t%-3s\t%-50s\n", "ID", "IMAGE PATH"); for (i = 0; i < processDetailed->ImageSummarySize; i++) { printf("main: \t%-3i\t%-50ws\n", i, processDetailed->ImageSummary[i].ImagePath); } printf("main: Enter the ID of the image to query.\n"); std::cin >> input; imageIndex = std::stoi(input); printf("main: Image stack size: %i\n", processDetailed->ImageSummary[imageIndex].StackSize); imageDetailed = communicator.RequestDetailedImage(processID, executionTime, imageIndex, processDetailed->ImageSummary[imageIndex].StackSize); if (imageDetailed == NULL || imageDetailed->Populated == FALSE) { printf("main: Failed to retrieve a detailed image report.\n"); break; } printf("main: Image %i:\n", imageIndex); printf("main: \tPath: %ws\n", imageDetailed->ImagePath); DisplayStackHistory(imageDetailed->StackHistory, imageDetailed->StackHistorySize); free(processDetailed); free(imageDetailed); break; case 8: // // No handling required, will exit when the while condition is checked. // break; default: printf("main: Unrecognized option %i.\n", choice); break; } } while (input != "8"); Exit: _fgetchar(); return 0; } ================================================ FILE: PeaceMaker CLI/PeaceMaker CLI.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 16.0 {A287D40E-AB7B-4FE9-AA84-44114766C79D} Win32Proj PeaceMakerCLI 10.0 Application true v142 Unicode Application false v142 true Unicode Application true v142 Unicode Application false v142 true Unicode false $(SolutionDir)\PeaceMaker Kernel;$(IncludePath) true true false Level3 MaxSpeed true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true true true Level3 Disabled true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 Disabled true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 MaxSpeed true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true true true ================================================ FILE: PeaceMaker CLI/PeaceMaker CLI.vcxproj.filters ================================================  {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;ipp;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 Source Files Source Files Header Files ================================================ FILE: PeaceMaker Kernel/AlertQueue.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "AlertQueue.h" /** Initialize basic members of the AlertQueue class. */ AlertQueue::AlertQueue() { this->alertsLock = RCAST(ExAllocatePoolWithTag(NonPagedPool, sizeof(KSPIN_LOCK), ALERT_LOCK_TAG)); NT_ASSERT(this->alertsLock); this->destroying = FALSE; KeInitializeSpinLock(this->alertsLock); InitializeListHead(RCAST(&this->alertsHead)); } /** Clear the queue of alerts. */ AlertQueue::~AlertQueue() { PLIST_ENTRY currentEntry; KIRQL oldIRQL; // // Make sure no one is doing operations on the AlertQueue. // this->destroying = TRUE; KeAcquireSpinLock(this->alertsLock, &oldIRQL); KeReleaseSpinLock(this->alertsLock, oldIRQL); while (IsListEmpty(RCAST(&this->alertsHead)) == FALSE) { currentEntry = RemoveHeadList(RCAST(&this->alertsHead)); // // Free the entry. // ExFreePoolWithTag(SCAST(currentEntry), ALERT_QUEUE_ENTRY_TAG); } ExFreePoolWithTag(this->alertsLock, ALERT_LOCK_TAG); } /** Push an alert to the queue. @param Alert - The alert to push. @return Whether or not pushing the alert was successful. */ VOID AlertQueue::PushAlert ( _In_ PBASE_ALERT_INFO Alert, _In_ ULONG AlertSize ) { PBASE_ALERT_INFO newAlert; if (this->destroying) { return; } // // Allocate space for the new alert and copy the details. // newAlert = RCAST(ExAllocatePoolWithTag(NonPagedPool, AlertSize, ALERT_QUEUE_ENTRY_TAG)); if (newAlert == NULL) { DBGPRINT("AlertQueue!PushAlert: Failed to allocate space for new alert."); return; } memset(newAlert, 0, AlertSize); memcpy(newAlert, Alert, AlertSize); newAlert->AlertSize = AlertSize; // // Queue the alert. // ExInterlockedInsertTailList(RCAST(&this->alertsHead), RCAST(newAlert), this->alertsLock); } /** Pop an alert from the queue of alerts. Follows FI-FO. @return The first in queued alert. */ PBASE_ALERT_INFO AlertQueue::PopAlert ( VOID ) { if (this->destroying) { return NULL; } return RCAST(ExInterlockedRemoveHeadList(RCAST(&this->alertsHead), this->alertsLock)); } /** Check if the queue of alerts is empty. @return Whether or not the alerts queue is empty. */ BOOLEAN AlertQueue::IsQueueEmpty ( VOID ) { BOOLEAN empty; KIRQL oldIrql; ExAcquireSpinLock(this->alertsLock, &oldIrql); empty = IsListEmpty(RCAST(&this->alertsHead)); ExReleaseSpinLock(this->alertsLock, oldIrql); return empty; } /** Free a previously pop'd alert. @param Alert - The alert to free. */ VOID AlertQueue::FreeAlert( _In_ PBASE_ALERT_INFO Alert ) { ExFreePoolWithTag(Alert, ALERT_QUEUE_ENTRY_TAG); } ================================================ FILE: PeaceMaker Kernel/AlertQueue.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "shared.h" typedef class AlertQueue { BASE_ALERT_INFO alertsHead; // The linked list of alerts. PKSPIN_LOCK alertsLock; // The lock protecting the linked-list of alerts. BOOLEAN destroying; // This boolean indicates to functions that a lock should not be held as we are in the process of destruction. public: AlertQueue(); ~AlertQueue(); VOID PushAlert ( _In_ PBASE_ALERT_INFO Alert, _In_ ULONG AlertSize ); PBASE_ALERT_INFO PopAlert ( VOID ); BOOLEAN IsQueueEmpty ( VOID ); VOID FreeAlert ( _In_ PBASE_ALERT_INFO Alert ); } ALERT_QUEUE, *PALERT_QUEUE; #define ALERT_LOCK_TAG 'lAmP' #define ALERT_QUEUE_ENTRY_TAG 'eAmP' ================================================ FILE: PeaceMaker Kernel/DetectionLogic.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "DetectionLogic.h" /** Initialize class members. */ DetectionLogic::DetectionLogic() { alerts = new (NonPagedPool, ALERT_QUEUE_TAG) AlertQueue(); } /** Deconstruct class members. */ DetectionLogic::~DetectionLogic() { alerts->~AlertQueue(); ExFreePoolWithTag(alerts, ALERT_QUEUE_TAG); } /** Get the alert queue for this detection logic instance. @return The Alert Queue. */ PALERT_QUEUE DetectionLogic::GetAlertQueue ( VOID ) { return this->alerts; } /** Audit a stack history for invalid code. @param DetectionSource - The filter we are checking the stack of. @param SourceProcessId - The source of the audit. @param SourcePath - The source path. @param TargetPath - The target path. @param StackHistory - A variable-length array of stack return history. @param StackHistorySize - Size of the StackHistory array. */ VOID DetectionLogic::AuditUserStackWalk ( _In_ DETECTION_SOURCE DetectionSource, _In_ HANDLE SourceProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ) { ULONG i; BOOLEAN stackViolation; PVOID firstViolatingAddress; stackViolation = FALSE; firstViolatingAddress = NULL; // // Check if any of the stack returns are to unmapped code. // for (i = 0; i < StackHistorySize; i++) { if (StackHistory[i].MemoryInModule == FALSE && StackHistory[i].ExecutableMemory && StackHistory[i].RawAddress != 0x0 && RCAST(StackHistory[i].RawAddress) < MmUserProbeAddress) { DBGPRINT("DetectionLogic!AuditUserStackWalk: Alert pid 0x%X, Violate 0x%llx, Source %i", PsGetCurrentProcessId(), StackHistory[i].RawAddress, DetectionSource); stackViolation = TRUE; firstViolatingAddress = StackHistory[i].RawAddress; break; } } if (stackViolation == FALSE) { return; } // // Push the alert. // this->PushStackViolationAlert(DetectionSource, firstViolatingAddress, SourceProcessId, SourcePath, TargetPath, StackHistory, StackHistorySize); } /** Create and push a stack violation alert. @param DetectionSource - The filter we are checking the stack of. @param ViolatingAddress - If the origin of this alert is from an audit address operation, log the specific address. @param SourceProcessId - The source of the audit. @param SourcePath - The source path. @param TargetPath - The target path. @param StackHistory - A variable-length array of stack return history. @param StackHistorySize - Size of the StackHistory array. */ VOID DetectionLogic::PushStackViolationAlert( _In_ DETECTION_SOURCE DetectionSource, _In_ PVOID ViolatingAddress, _In_ HANDLE SourceProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ) { ULONG stackHistoryBytes; PSTACK_VIOLATION_ALERT stackViolationAlert; // // Calculate the size of the StackHistory array in bytes. // stackHistoryBytes = sizeof(STACK_RETURN_INFO) * (StackHistorySize-1); // // Allocate space for the alert depending on the size of StackHistory. // stackViolationAlert = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(STACK_VIOLATION_ALERT) + stackHistoryBytes, STACK_VIOLATION_TAG)); if (stackViolationAlert == NULL) { DBGPRINT("DetectionLogic!PushStackViolationAlert: Failed to allocate space for the alert."); return; } memset(stackViolationAlert, 0, sizeof(STACK_VIOLATION_ALERT) + stackHistoryBytes); // // Fill the fields of the alert. // stackViolationAlert->AlertInformation.AlertType = StackViolation; stackViolationAlert->AlertInformation.AlertSource = DetectionSource; stackViolationAlert->ViolatingAddress = ViolatingAddress; stackViolationAlert->AlertInformation.SourceId = SourceProcessId; if (SourcePath) { RtlStringCbCopyUnicodeString(stackViolationAlert->AlertInformation.SourcePath, MAX_PATH, SourcePath); } if (TargetPath) { RtlStringCbCopyUnicodeString(stackViolationAlert->AlertInformation.TargetPath, MAX_PATH, TargetPath); } stackViolationAlert->StackHistorySize = StackHistorySize; memcpy(&stackViolationAlert->StackHistory, StackHistory, sizeof(STACK_RETURN_INFO) * StackHistorySize); // // Push the alert. // this->alerts->PushAlert(RCAST(stackViolationAlert), sizeof(STACK_VIOLATION_ALERT) + stackHistoryBytes); // // PushAlert copies the alert, so we can free our copy. // ExFreePoolWithTag(stackViolationAlert, STACK_VIOLATION_TAG); } /** Validate a user-mode pointer. @param DetectionSource - The filter we are checking the stack of. @param UserPtr - The pointer to check. @param SourceProcessId - The source of the audit. @param SourcePath - The source path. @param TargetPath - The target path. @param StackHistory - A variable-length array of stack return history. @param StackHistorySize - Size of the StackHistory array. */ VOID DetectionLogic::AuditUserPointer ( _In_ DETECTION_SOURCE DetectionSource, _In_ PVOID UserPtr, _In_ HANDLE SourceProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ) { STACK_RETURN_INFO info; info.RawAddress = UserPtr; // // Resolve basic information about the module. // resolver.ResolveAddressModule(UserPtr, &info); // // If the user pointer isn't mapped, something's wrong. // if (info.MemoryInModule == FALSE && info.ExecutableMemory && info.RawAddress != 0x0 && RCAST(info.RawAddress) < MmUserProbeAddress) { this->PushStackViolationAlert(DetectionSource, UserPtr, SourceProcessId, SourcePath, TargetPath, StackHistory, StackHistorySize); } } /** Check if an operation is on a remote process. This is called by suspicious operation callbacks such as Thread Creation. @param DetectionSource - The filter we are checking the stack of. @param UserPtr - The pointer to check. @param SourceProcessId - The source of the audit. @param SourceProcessId - The target of the operation. @param SourcePath - The source path. @param TargetPath - The target path. @param StackHistory - A variable-length array of stack return history. @param StackHistorySize - Size of the StackHistory array. */ VOID DetectionLogic::AuditCallerProcessId( _In_ DETECTION_SOURCE DetectionSource, _In_ HANDLE CallerProcessId, _In_ HANDLE TargetProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ) { ULONG stackHistoryBytes; PREMOTE_OPERATION_ALERT remoteOperationAlert; // // If the operation is on the current process, no problems! // if (CallerProcessId == TargetProcessId) { return; } // // Calculate the size of the StackHistory array in bytes. // stackHistoryBytes = sizeof(STACK_RETURN_INFO) * (StackHistorySize - 1); // // Allocate space for the alert depending on the size of StackHistory. // remoteOperationAlert = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(REMOTE_OPERATION_ALERT) + stackHistoryBytes, STACK_VIOLATION_TAG)); if (remoteOperationAlert == NULL) { DBGPRINT("DetectionLogic!PushStackViolationAlert: Failed to allocate space for the alert."); return; } memset(remoteOperationAlert, 0, sizeof(REMOTE_OPERATION_ALERT) + stackHistoryBytes); // // Fill the fields of the alert. // switch (DetectionSource) { case ProcessCreate: remoteOperationAlert->AlertInformation.AlertType = ParentProcessIdSpoofing; break; case ThreadCreate: remoteOperationAlert->AlertInformation.AlertType = RemoteThreadCreation; break; } remoteOperationAlert->AlertInformation.AlertSource = DetectionSource; remoteOperationAlert->AlertInformation.SourceId = CallerProcessId; remoteOperationAlert->RemoteTargetId = TargetProcessId; if (SourcePath) { RtlStringCbCopyUnicodeString(remoteOperationAlert->AlertInformation.SourcePath, MAX_PATH, SourcePath); } if (TargetPath) { RtlStringCbCopyUnicodeString(remoteOperationAlert->AlertInformation.TargetPath, MAX_PATH, TargetPath); } remoteOperationAlert->StackHistorySize = StackHistorySize; memcpy(&remoteOperationAlert->StackHistory, StackHistory, sizeof(STACK_RETURN_INFO) * StackHistorySize); // // Push the alert. // this->alerts->PushAlert(RCAST(remoteOperationAlert), sizeof(REMOTE_OPERATION_ALERT) + stackHistoryBytes); // // PushAlert copies the alert, so we can free our copy. // ExFreePoolWithTag(remoteOperationAlert, STACK_VIOLATION_TAG); } /** Report a filter violation. @param DetectionSource - The filter type that was violated. @param CallerProcessId - The process ID of the caller that violated the filter. @param CallerPath - The path of the caller process. @param ViolatingPath - The path that triggered the filter violation. @param StackHistory - A variable-length array of stack return history. @param StackHistorySize - Size of the StackHistory array. */ VOID DetectionLogic::ReportFilterViolation ( _In_ DETECTION_SOURCE DetectionSource, _In_ HANDLE CallerProcessId, _In_ PUNICODE_STRING CallerPath, _In_ PUNICODE_STRING ViolatingPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ) { ULONG stackHistoryBytes; PFILTER_VIOLATION_ALERT filterViolationAlert; // // Sanity check, sometimes stack history can be NULL if the stackwalk failed. // if (StackHistory == NULL || StackHistorySize == 0) { DBGPRINT("DetectionLogic!ReportFilterViolation: StackHistory was invalid!"); return; } // // Calculate the size of the StackHistory array in bytes. // stackHistoryBytes = sizeof(STACK_RETURN_INFO) * (StackHistorySize - 1); // // Allocate space for the alert depending on the size of StackHistory. // filterViolationAlert = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(FILTER_VIOLATION_ALERT) + stackHistoryBytes, STACK_VIOLATION_TAG)); if (filterViolationAlert == NULL) { DBGPRINT("DetectionLogic!ReportFilterViolation: Failed to allocate space for the alert."); return; } memset(filterViolationAlert, 0, sizeof(FILTER_VIOLATION_ALERT) + stackHistoryBytes); filterViolationAlert->AlertInformation.AlertType = FilterViolation; filterViolationAlert->AlertInformation.AlertSource = DetectionSource; filterViolationAlert->AlertInformation.SourceId = CallerProcessId; if (CallerPath) { RtlStringCbCopyUnicodeString(filterViolationAlert->AlertInformation.SourcePath, MAX_PATH, CallerPath); } if (ViolatingPath) { RtlStringCbCopyUnicodeString(filterViolationAlert->AlertInformation.TargetPath, MAX_PATH, ViolatingPath); } filterViolationAlert->StackHistorySize = StackHistorySize; memcpy(&filterViolationAlert->StackHistory, StackHistory, sizeof(STACK_RETURN_INFO) * StackHistorySize); // // Push the alert. // this->alerts->PushAlert(RCAST(filterViolationAlert), sizeof(FILTER_VIOLATION_ALERT) + stackHistoryBytes); // // PushAlert copies the alert, so we can free our copy. // ExFreePoolWithTag(filterViolationAlert, STACK_VIOLATION_TAG); } ================================================ FILE: PeaceMaker Kernel/DetectionLogic.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "AlertQueue.h" #include "StackWalker.h" #include "shared.h" typedef class DetectionLogic { PALERT_QUEUE alerts; StackWalker resolver; VOID PushStackViolationAlert ( _In_ DETECTION_SOURCE DetectionSource, _In_ PVOID ViolatingAddress, _In_ HANDLE SourceProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ); public: DetectionLogic(); ~DetectionLogic(); PALERT_QUEUE GetAlertQueue ( VOID ); VOID AuditUserStackWalk ( _In_ DETECTION_SOURCE DetectionSource, _In_ HANDLE SourceProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ); VOID AuditUserPointer ( _In_ DETECTION_SOURCE DetectionSource, _In_ PVOID UserPtr, _In_ HANDLE SourceProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ); VOID AuditCallerProcessId ( _In_ DETECTION_SOURCE DetectionSource, _In_ HANDLE CallerProcessId, _In_ HANDLE TargetProcessId, _In_ PUNICODE_STRING SourcePath, _In_ PUNICODE_STRING TargetPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ); VOID ReportFilterViolation ( _In_ DETECTION_SOURCE DetectionSource, _In_ HANDLE CallerProcessId, _In_ PUNICODE_STRING CallerPath, _In_ PUNICODE_STRING ViolatingPath, _In_ STACK_RETURN_INFO StackHistory[], _In_ ULONG StackHistorySize ); } DETECTION_LOGIC, *PDETECTION_LOGIC; #define ALERT_QUEUE_TAG 'qAmP' #define STACK_VIOLATION_TAG 'vSmP' ================================================ FILE: PeaceMaker Kernel/FSFilter.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "FSFilter.h" FLT_REGISTRATION FSBlockingFilter::FilterRegistration; PSTRING_FILTERS FSBlockingFilter::FileStringFilters; STACK_WALKER FSBlockingFilter::walker; PDETECTION_LOGIC FSBlockingFilter::detector; /** Initializes the necessary components of the filesystem filter. @param DriverObject - The object of the driver necessary for mini-filter initialization. @param RegistryPath - The registry path of the driver. @param UnloadRoutine - The function to call on the unload of the mini-filter. @param Detector - Detection instance used to analyze untrusted operations. @param InitializeStatus - Status of initialization. @param FilterHandle - The pointer to place the handle for the filter to. */ FSBlockingFilter::FSBlockingFilter ( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath, _In_ PFLT_FILTER_UNLOAD_CALLBACK UnloadRoutine, _In_ PDETECTION_LOGIC Detector, _Out_ NTSTATUS* InitializeStatus, _Out_ PFLT_FILTER* FilterHandle ) { FSBlockingFilter::FileStringFilters = new (PagedPool, STRING_FILE_FILTERS_TAG) StringFilters(FilesystemFilter, RegistryPath, L"FileFilterStore"); if (FSBlockingFilter::FileStringFilters == NULL) { DBGPRINT("FSBlockingFilter!FSBlockingFilter: Failed to allocate memory for string filters."); *InitializeStatus = STATUS_NO_MEMORY; return; } // // Restore existing filters. // FSBlockingFilter::FileStringFilters->RestoreFilters(); // // This isn't a constant because the unload routine changes. // FSBlockingFilter::FilterRegistration = { sizeof(FLT_REGISTRATION), // Size FLT_REGISTRATION_VERSION, // Version 0, // Flags NULL, // Context Callbacks, // Operation callbacks UnloadRoutine, FSBlockingFilter::HandleInstanceSetup, FSBlockingFilter::HandleInstanceQueryTeardown, FSBlockingFilter::HandleInstanceTeardownStart, FSBlockingFilter::HandleInstanceTeardownComplete, NULL, // GenerateFileName NULL, // GenerateDestinationFileName NULL // NormalizeNameComponent }; // // Register with FltMgr to tell it our callback routines. // *InitializeStatus = FltRegisterFilter(DriverObject, &FilterRegistration, FilterHandle); FLT_ASSERT(NT_SUCCESS(*InitializeStatus)); // // Start filtering. // *InitializeStatus = FltStartFiltering(*FilterHandle); // // If we can't start filtering, unregister the filter. // if (NT_SUCCESS(*InitializeStatus) == FALSE) { FltUnregisterFilter(*FilterHandle); } // // Set the detector. // FSBlockingFilter::detector = Detector; } /** Free data members that were dynamically allocated. */ FSBlockingFilter::~FSBlockingFilter() { DBGPRINT("FSBlockingFilter!~FSBlockingFilter: Deconstructing class."); // // Make sure to deconstruct the class. // if (FSBlockingFilter::FileStringFilters) { FSBlockingFilter::FileStringFilters->~StringFilters(); ExFreePoolWithTag(FSBlockingFilter::FileStringFilters, STRING_FILE_FILTERS_TAG); FSBlockingFilter::FileStringFilters = NULL; } } /** Get the pointer to the filters used by this filesystem filter. Useful if you want to add/remove filters. */ PSTRING_FILTERS FSBlockingFilter::GetStringFilters() { return FSBlockingFilter::FileStringFilters; } /** This function is called prior to a create operation. Data - The data associated with the current operation. FltObjects - Objects related to the filter, instance, and its associated volume. CompletionContext - Optional context to be passed to post operation callbacks. */ FLT_PREOP_CALLBACK_STATUS FSBlockingFilter::HandlePreCreateOperation( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ) { FLT_PREOP_CALLBACK_STATUS callbackStatus; PFLT_FILE_NAME_INFORMATION fileNameInfo; PUNICODE_STRING callerProcessPath; PSTACK_RETURN_INFO fileOperationStack; ULONG fileOperationStackSize; BOOLEAN reportOperation; UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(CompletionContext); reportOperation = FALSE; fileOperationStackSize = MAX_STACK_RETURN_HISTORY; fileNameInfo = NULL; callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; // // PeaceMaker is not designed to block kernel operations. // if (ExGetPreviousMode() == KernelMode) { return callbackStatus; } if (FlagOn(Data->Iopb->Parameters.Create.Options, FILE_DELETE_ON_CLOSE)) { if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &fileNameInfo))) { if (FSBlockingFilter::FileStringFilters->MatchesFilter(fileNameInfo->Name.Buffer, FILTER_FLAG_DELETE) != FALSE) { DBGPRINT("FSBlockingFilter!HandlePreCreateOperation: Detected FILE_DELETE_ON_CLOSE of %wZ. Prevented deletion!", fileNameInfo->Name); Data->Iopb->TargetFileObject->DeletePending = FALSE; Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; callbackStatus = FLT_PREOP_COMPLETE; reportOperation = TRUE; } } } if (Data->Iopb->Parameters.Create.SecurityContext && FlagOn(Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess, FILE_EXECUTE)) { if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &fileNameInfo))) { if (FSBlockingFilter::FileStringFilters->MatchesFilter(fileNameInfo->Name.Buffer, FILTER_FLAG_EXECUTE) != FALSE) { DBGPRINT("FSBlockingFilter!HandlePreCreateOperation: Detected FILE_EXECUTE desired access of %wZ. Prevented execute access!", fileNameInfo->Name); Data->Iopb->TargetFileObject->DeletePending = FALSE; Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; callbackStatus = FLT_PREOP_COMPLETE; reportOperation = TRUE; } } } if (reportOperation) { // // Grab the caller's path. // ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &callerProcessPath); // // Walk the stack. // FSBlockingFilter::walker.WalkAndResolveStack(&fileOperationStack, &fileOperationStackSize, STACK_HISTORY_TAG); NT_ASSERT(fileOperationStack); // // Only if we successfully walked the stack, report the violation. // if (fileOperationStack != NULL && fileOperationStackSize != 0) { // // Report the violation. // FSBlockingFilter::detector->ReportFilterViolation(FileFilterMatch, PsGetCurrentProcessId(), callerProcessPath, &fileNameInfo->Name, fileOperationStack, fileOperationStackSize); // // Clean up. // ExFreePoolWithTag(fileOperationStack, STACK_HISTORY_TAG); } ExFreePoolWithTag(callerProcessPath, IMAGE_NAME_TAG); } if (fileNameInfo) { FltReleaseFileNameInformation(fileNameInfo); } return callbackStatus; } /** This function is called prior to a write operation. Data - The data associated with the current operation. FltObjects - Objects related to the filter, instance, and its associated volume. CompletionContext - Optional context to be passed to post operation callbacks. */ FLT_PREOP_CALLBACK_STATUS FSBlockingFilter::HandlePreWriteOperation( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ) { FLT_PREOP_CALLBACK_STATUS callbackStatus; PFLT_FILE_NAME_INFORMATION fileNameInfo; PUNICODE_STRING callerProcessPath; PSTACK_RETURN_INFO fileOperationStack; ULONG fileOperationStackSize; BOOLEAN reportOperation; UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(CompletionContext); reportOperation = FALSE; fileOperationStackSize = MAX_STACK_RETURN_HISTORY; fileNameInfo = NULL; callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; // // PeaceMaker is not designed to block kernel operations. // if (ExGetPreviousMode() == KernelMode) { return callbackStatus; } if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &fileNameInfo))) { if (FSBlockingFilter::FileStringFilters->MatchesFilter(fileNameInfo->Name.Buffer, FILTER_FLAG_WRITE) != FALSE) { DBGPRINT("FSBlockingFilter!HandlePreWriteOperation: Detected write on %wZ. Prevented write!", fileNameInfo->Name); Data->Iopb->TargetFileObject->DeletePending = FALSE; Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; callbackStatus = FLT_PREOP_COMPLETE; reportOperation = TRUE; } } if (reportOperation) { // // Grab the caller's path. // ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &callerProcessPath); // // Walk the stack. // FSBlockingFilter::walker.WalkAndResolveStack(&fileOperationStack, &fileOperationStackSize, STACK_HISTORY_TAG); NT_ASSERT(fileOperationStack); // // Only if we successfully walked the stack, report the violation. // if (fileOperationStack != NULL && fileOperationStackSize != 0) { // // Report the violation. // FSBlockingFilter::detector->ReportFilterViolation(FileFilterMatch, PsGetCurrentProcessId(), callerProcessPath, &fileNameInfo->Name, fileOperationStack, fileOperationStackSize); // // Clean up. // ExFreePoolWithTag(fileOperationStack, STACK_HISTORY_TAG); } ExFreePoolWithTag(callerProcessPath, IMAGE_NAME_TAG); } if (fileNameInfo) { FltReleaseFileNameInformation(fileNameInfo); } return callbackStatus; } /** This function is called prior to a set information operation. Data - The data associated with the current operation. FltObjects - Objects related to the filter, instance, and its associated volume. CompletionContext - Optional context to be passed to post operation callbacks. */ FLT_PREOP_CALLBACK_STATUS FSBlockingFilter::HandlePreSetInfoOperation( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ) { FLT_PREOP_CALLBACK_STATUS callbackStatus; PFLT_FILE_NAME_INFORMATION fileNameInfo; PUNICODE_STRING callerProcessPath; PSTACK_RETURN_INFO fileOperationStack; ULONG fileOperationStackSize; BOOLEAN reportOperation; UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(CompletionContext); reportOperation = FALSE; fileOperationStackSize = MAX_STACK_RETURN_HISTORY; fileNameInfo = NULL; callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; // // PeaceMaker is not designed to block kernel operations. // if (ExGetPreviousMode() == KernelMode) { return callbackStatus; } switch (Data->Iopb->Parameters.SetFileInformation.FileInformationClass) { case FileDispositionInformation: case FileDispositionInformationEx: if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &fileNameInfo))) { if (FSBlockingFilter::FileStringFilters->MatchesFilter(fileNameInfo->Name.Buffer, FILTER_FLAG_DELETE) != FALSE) { DBGPRINT("FSBlockingFilter!HandlePreSetInfoOperation: Detected attempted file deletion of %wZ. Prevented deletion!", fileNameInfo->Name); Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; callbackStatus = FLT_PREOP_COMPLETE; reportOperation = TRUE; } } break; } if (reportOperation) { // // Grab the caller's path. // ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &callerProcessPath); // // Walk the stack. // FSBlockingFilter::walker.WalkAndResolveStack(&fileOperationStack, &fileOperationStackSize, STACK_HISTORY_TAG); NT_ASSERT(fileOperationStack); // // Only if we successfully walked the stack, report the violation. // if (fileOperationStack != NULL && fileOperationStackSize != 0) { // // Report the violation. // FSBlockingFilter::detector->ReportFilterViolation(FileFilterMatch, PsGetCurrentProcessId(), callerProcessPath, &fileNameInfo->Name, fileOperationStack, fileOperationStackSize); // // Clean up. // ExFreePoolWithTag(fileOperationStack, STACK_HISTORY_TAG); } ExFreePoolWithTag(callerProcessPath, IMAGE_NAME_TAG); } if (fileNameInfo) { FltReleaseFileNameInformation(fileNameInfo); } return callbackStatus; } /** This function determines whether or not the mini-filter should attach to the volume. FltObjects - Objects related to the filter, instance, and its associated volume. Flags - Flags that indicate the reason for the volume attach request. VolumeDeviceType - The device type of the specified volume. VolumeFilesystemType - The filesystem type of the specified volume. */ NTSTATUS FSBlockingFilter::HandleInstanceSetup( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_SETUP_FLAGS Flags, _In_ DEVICE_TYPE VolumeDeviceType, _In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType ) { NTSTATUS status = STATUS_SUCCESS; BOOLEAN isWritable = FALSE; UNREFERENCED_PARAMETER(Flags); UNREFERENCED_PARAMETER(VolumeDeviceType); status = FltIsVolumeWritable(FltObjects->Volume, &isWritable); if (!NT_SUCCESS(status)) { return STATUS_FLT_DO_NOT_ATTACH; } // // If you can't write to a volume... how can you delete a file in it? // if (isWritable) { switch (VolumeFilesystemType) { case FLT_FSTYPE_NTFS: case FLT_FSTYPE_REFS: status = STATUS_SUCCESS; break; default: return STATUS_FLT_DO_NOT_ATTACH; } } else { return STATUS_FLT_DO_NOT_ATTACH; } return status; } /** This function is called when an instance is being deleted. FltObjects - Objects related to the filter, instance, and its associated volume. Flags - Flags that indicate the reason for the detach request. */ NTSTATUS FSBlockingFilter::HandleInstanceQueryTeardown( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags ) { UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(Flags); return STATUS_SUCCESS; } /** This function is called at the start of an instance teardown. FltObjects - Objects related to the filter, instance, and its associated volume. Flags - Flags that indicate the reason for the deletion. */ VOID FSBlockingFilter::HandleInstanceTeardownStart( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags ) { UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(Flags); } /** This function is called at the end of an instance teardown. FltObjects - Objects related to the filter, instance, and its associated volume. Flags - Flags that indicate the reason for the deletion. */ VOID FSBlockingFilter::HandleInstanceTeardownComplete( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags ) { UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(Flags); } ================================================ FILE: PeaceMaker Kernel/FSFilter.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "StringFilters.h" #include "StackWalker.h" #include "ImageHistoryFilter.h" typedef class FSBlockingFilter { static FLT_PREOP_CALLBACK_STATUS HandlePreCreateOperation ( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ); static FLT_PREOP_CALLBACK_STATUS HandlePreWriteOperation ( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ); static FLT_PREOP_CALLBACK_STATUS HandlePreSetInfoOperation ( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ); static NTSTATUS HandleInstanceSetup ( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_SETUP_FLAGS Flags, _In_ DEVICE_TYPE VolumeDeviceType, _In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType ); static NTSTATUS HandleInstanceQueryTeardown ( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags ); static VOID HandleInstanceTeardownStart ( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags ); static VOID HandleInstanceTeardownComplete ( _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags ); // // Class callbacks for necessary filesystem operations. // static constexpr FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, 0, FSBlockingFilter::HandlePreCreateOperation, NULL }, { IRP_MJ_WRITE, 0, FSBlockingFilter::HandlePreWriteOperation, NULL }, { IRP_MJ_SET_INFORMATION, FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO, FSBlockingFilter::HandlePreSetInfoOperation, NULL }, { IRP_MJ_OPERATION_END } }; // // The registration context for the mini-filter. // static FLT_REGISTRATION FilterRegistration; // // Contains strings to block various filesystem operations. // static PSTRING_FILTERS FileStringFilters; static STACK_WALKER walker; static PDETECTION_LOGIC detector; public: FSBlockingFilter ( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath, _In_ PFLT_FILTER_UNLOAD_CALLBACK UnloadRoutine, _In_ PDETECTION_LOGIC Detector, _Out_ NTSTATUS* InitializeStatus, _Out_ PFLT_FILTER* FilterHandle ); ~FSBlockingFilter(); static PSTRING_FILTERS GetStringFilters(); } FS_BLOCKING_FILTER, *PFS_BLOCKING_FILTER; #define STRING_FILE_FILTERS_TAG 'fFmP' ================================================ FILE: PeaceMaker Kernel/FilterTesting.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "common.h" #include "IOCTLCommunication.h" PIOCTL_COMMUNICATION Communicator; #pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers") /************************************************************************* Prototypes *************************************************************************/ EXTERN_C_START DRIVER_INITIALIZE DriverEntry; NTSTATUS DriverEntry ( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ); NTSTATUS FilterUnload( _In_ FLT_FILTER_UNLOAD_FLAGS Flags ); EXTERN_C_END // // Assign text sections for each routine. // #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, DriverEntry) #pragma alloc_text(PAGE, FilterUnload) #endif /************************************************************************* MiniFilter initialization and unload routines. *************************************************************************/ /** Initialize the mini-filter driver. DriverObject - The driver's object. RegistryPath - The path to the driver's registry entry. */ NTSTATUS DriverEntry ( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { NTSTATUS status; status = STATUS_SUCCESS; DBGPRINT("FilterTesting!DriverEntry: Hello world."); Communicator = new (NonPagedPool, 'cImP') IOCTLCommunication(DriverObject, RegistryPath, NULL, &status); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("FilterTesting!DriverEntry: Failed to initialize communication with status 0x%X.", status); } return status; } /** This function handles unloading the mini-filter. @param Flags - Flags indicating whether or not this is a mandatory unload. */ NTSTATUS FilterUnload ( _In_ FLT_FILTER_UNLOAD_FLAGS Flags ) { UNREFERENCED_PARAMETER( Flags ); PAGED_CODE(); DBGPRINT("FilterTesting!FilterUnload: Unloading filter."); Communicator->~IOCTLCommunication(); ExFreePoolWithTag(Communicator, 'cImP'); return STATUS_SUCCESS; } ================================================ FILE: PeaceMaker Kernel/IOCTLCommunication.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "IOCTLCommunication.h" PDRIVER_OBJECT IOCTLCommunication::DriverObject; PDETECTION_LOGIC IOCTLCommunication::Detector; PIMAGE_HISTORY_FILTER IOCTLCommunication::ImageProcessFilter; PFLT_FILTER IOCTLCommunication::FileFilterHandle; PFS_BLOCKING_FILTER IOCTLCommunication::FilesystemMonitor; PREGISTRY_BLOCKING_FILTER IOCTLCommunication::RegistryMonitor; PTHREAD_FILTER IOCTLCommunication::ThreadOperationFilter; PTAMPER_GUARD IOCTLCommunication::TamperGuardFilter; /** Construct the IOCTLCommunication class by initializing the driver object and detector. @param DriverObject - The driver's object. @param RegistryPath - The registry path of the driver. @param UnloadRoutine - The routine to call when the filter is unloading. @param InitializeStatus - Status of initialization. */ IOCTLCommunication::IOCTLCommunication ( _In_ PDRIVER_OBJECT Driver, _In_ PUNICODE_STRING RegistryPath, _In_ PFLT_FILTER_UNLOAD_CALLBACK UnloadRoutine, _Inout_ NTSTATUS* InitializeStatus ) { this->DriverObject = Driver; *InitializeStatus = STATUS_SUCCESS; // // Initialize the class members. // this->Detector = new (NonPagedPool, DETECTION_LOGIC_TAG) DetectionLogic(); if (this->Detector == NULL) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to allocate space for detection logic."); *InitializeStatus = STATUS_NO_MEMORY; return; } this->ImageProcessFilter = new (NonPagedPool, IMAGE_HISTORY_FILTER_TAG) ImageHistoryFilter(this->Detector, InitializeStatus); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to initialize image process history filter with status 0x%X.", *InitializeStatus); return; } if (this->ImageProcessFilter == NULL) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to allocate space for image process history filter."); *InitializeStatus = STATUS_NO_MEMORY; return; } FilesystemMonitor = new (NonPagedPool, FILE_MONITOR_TAG) FSBlockingFilter(DriverObject, RegistryPath, UnloadRoutine, this->Detector, InitializeStatus, &FileFilterHandle); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to initialize the filesystem blocking filter with status 0x%X.", *InitializeStatus); return; } RegistryMonitor = new (NonPagedPool, REGISTRY_MONITOR_TAG) RegistryBlockingFilter(DriverObject, RegistryPath, this->Detector, InitializeStatus); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to initialize the registry blocking filter with status 0x%X.", *InitializeStatus); return; } this->ThreadOperationFilter = new (NonPagedPool, THREAD_FILTER_TAG) ThreadFilter(this->Detector, InitializeStatus); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to initialize thread operation filters with status 0x%X.", *InitializeStatus); return; } if (this->ThreadOperationFilter == NULL) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to allocate space for thread operation filters."); *InitializeStatus = STATUS_NO_MEMORY; return; } this->TamperGuardFilter = new (NonPagedPool, TAMPER_GUARD_TAG) TamperGuard(InitializeStatus); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("IOCTLCommunication!IOCTLCommunication: Failed to initialize tamper guard with status 0x%X.", *InitializeStatus); return; } InitializeDriverIOCTL(); } /** Deconstruct the IOCTLCommunication class. */ IOCTLCommunication::~IOCTLCommunication ( VOID ) { this->Detector->~DetectionLogic(); ExFreePoolWithTag(this->Detector, DETECTION_LOGIC_TAG); this->ImageProcessFilter->~ImageHistoryFilter(); ExFreePoolWithTag(this->ImageProcessFilter, IMAGE_HISTORY_FILTER_TAG); FltUnregisterFilter(FileFilterHandle); this->FilesystemMonitor->~FSBlockingFilter(); ExFreePoolWithTag(this->FilesystemMonitor, FILE_MONITOR_TAG); this->RegistryMonitor->~RegistryBlockingFilter(); ExFreePoolWithTag(this->RegistryMonitor, REGISTRY_MONITOR_TAG); this->ThreadOperationFilter->~ThreadFilter(); ExFreePoolWithTag(this->ThreadOperationFilter, THREAD_FILTER_TAG); this->TamperGuardFilter->~TamperGuard(); ExFreePoolWithTag(this->TamperGuardFilter, TAMPER_GUARD_TAG); UninitializeDriverIOCTL(); } /** Handle basic create / close of device, always return success with no change. @param DeviceObject - The driver's device object. @param Irp - The current IRP. */ NTSTATUS IOCTLCommunication::IOCTLCreateClose ( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp ) { UNREFERENCED_PARAMETER(DeviceObject); // // Just accept everyone for now. // TODO: Implement some sort of authentication? // Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } /** Handle IO controls for communication with the PeaceMaker user-mode interface. @param DeviceObject - The driver's device object. @param Irp - The current IRP. */ NTSTATUS IOCTLCommunication::IOCTLDeviceControl ( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp ) { NTSTATUS status; PIO_STACK_LOCATION irpStackLocation; ULONG ioctlCode; ULONG inputLength; ULONG outputLength; ULONG minimumLength; ULONG writtenLength; PBASE_ALERT_INFO poppedAlert; PPROCESS_SUMMARY_REQUEST processSummaryRequest; PPROCESS_DETAILED_REQUEST processDetailedRequest; PSTRING_FILTER_REQUEST filterAddRequest; PLIST_FILTERS_REQUEST listFiltersRequest; PIMAGE_DETAILED_REQUEST imageDetailedRequest; PDELETE_FILTER_REQUEST deleteFilterRequest; PGLOBAL_SIZES globalSizes; WCHAR temporaryFilterBuffer[MAX_PATH]; UNREFERENCED_PARAMETER(DeviceObject); status = STATUS_SUCCESS; irpStackLocation = IoGetCurrentIrpStackLocation(Irp); // // Grab basic information about the request. // ioctlCode = irpStackLocation->Parameters.DeviceIoControl.IoControlCode; inputLength = irpStackLocation->Parameters.DeviceIoControl.InputBufferLength; outputLength = irpStackLocation->Parameters.DeviceIoControl.OutputBufferLength; writtenLength = 0; // // Update the tamper guard. // IOCTLCommunication::TamperGuardFilter->UpdateProtectedProcess(PsGetCurrentProcessId()); DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: ioctlCode = 0x%X, inputLength = 0x%X, outputLength = 0x%X", ioctlCode, inputLength, outputLength); // // Handle the different IOCTL request types. // switch (ioctlCode) { case IOCTL_ALERTS_QUEUED: if (outputLength >= sizeof(BOOLEAN)) { // // Return the status of the Queue. // *RCAST(Irp->AssociatedIrp.SystemBuffer) = !IOCTLCommunication::Detector->GetAlertQueue()->IsQueueEmpty(); writtenLength = sizeof(BOOLEAN); } break; case IOCTL_POP_ALERT: if (outputLength < MAX_STACK_VIOLATION_ALERT_SIZE) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_POP_ALERT but output buffer with size 0x%X smaller then minimum 0x%X.", outputLength, MAX_STACK_VIOLATION_ALERT_SIZE); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // // Pop an alert from the queue. // poppedAlert = IOCTLCommunication::Detector->GetAlertQueue()->PopAlert(); if (poppedAlert == NULL) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_POP_ALERT but no alert to pop."); status = STATUS_NOT_FOUND; goto Exit; } DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Got alert 0x%llx for IOCTL_POP_ALERT with size 0x%llx.\n", poppedAlert, poppedAlert->AlertSize); // // Copy the alert. // memcpy_s(Irp->AssociatedIrp.SystemBuffer, outputLength, poppedAlert, poppedAlert->AlertSize); writtenLength = poppedAlert->AlertSize; // // Free the alert entry. // IOCTLCommunication::Detector->GetAlertQueue()->FreeAlert(poppedAlert); break; case IOCTL_GET_PROCESSES: if (inputLength < sizeof(PROCESS_SUMMARY_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESSES but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // // Verify the specified array size. // processSummaryRequest = RCAST(Irp->AssociatedIrp.SystemBuffer); if (processSummaryRequest->ProcessHistorySize <= 0 || outputLength < MAX_PROCESS_SUMMARY_REQUEST_SIZE(processSummaryRequest)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESSES but output buffer with size 0x%X smaller then minimum 0x%X.", outputLength, MAX_PROCESS_SUMMARY_REQUEST_SIZE(processSummaryRequest)); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // // Grab the history summaries. // processSummaryRequest->ProcessHistorySize = IOCTLCommunication::ImageProcessFilter->GetProcessHistorySummary(processSummaryRequest->SkipCount, RCAST(&processSummaryRequest->ProcessHistory[0]), processSummaryRequest->ProcessHistorySize); writtenLength = MAX_PROCESS_SUMMARY_REQUEST_SIZE(processSummaryRequest); DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: IOCTL_GET_PROCESSES found %i processes.", processSummaryRequest->ProcessHistorySize); break; case IOCTL_GET_PROCESS_DETAILED: if (inputLength < sizeof(PROCESS_DETAILED_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESS_DETAILED but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } processDetailedRequest = RCAST(Irp->AssociatedIrp.SystemBuffer); minimumLength = sizeof(PROCESS_DETAILED_REQUEST); // // Verify the specified array size. // if (outputLength < minimumLength) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESS_DETAILED but output buffer with size 0x%X smaller then minimum 0x%X.", outputLength, minimumLength); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // // Verify the user buffers. // __try { ProbeForWrite(processDetailedRequest->ImageSummary, processDetailedRequest->ImageSummarySize * sizeof(IMAGE_SUMMARY), sizeof(ULONG)); ProbeForWrite(processDetailedRequest->StackHistory, processDetailedRequest->StackHistorySize * sizeof(STACK_RETURN_INFO), sizeof(ULONG)); } __except (1) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESS_DETAILED but user buffers were invalid."); status = STATUS_BAD_DATA; goto Exit; } // // Populate the detailed request. // IOCTLCommunication::ImageProcessFilter->PopulateProcessDetailedRequest(processDetailedRequest); writtenLength = minimumLength; break; case IOCTL_ADD_FILTER: // // Validate the size of the input and output buffers. // if (inputLength < sizeof(STRING_FILTER_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_ADD_FILTER but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } if (outputLength < sizeof(STRING_FILTER_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_ADD_FILTER but output buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } filterAddRequest = RCAST(Irp->AssociatedIrp.SystemBuffer); // // Copy the filter content to a temporary string (ensures null-terminator). // status = RtlStringCchCopyNW(temporaryFilterBuffer, MAX_PATH, filterAddRequest->Filter.MatchString, MAX_PATH); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Failed to copy filter content to temporary buffer with status 0x%X.", status); goto Exit; } // // Sanity check. // if (wcsnlen_s(temporaryFilterBuffer, MAX_PATH) == 0) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Blocked empty filter."); goto Exit; } // // Depending on the type of filter, add the string. // switch (filterAddRequest->FilterType) { case FilesystemFilter: filterAddRequest->Filter.Id = FilesystemMonitor->GetStringFilters()->AddFilter(temporaryFilterBuffer, filterAddRequest->Filter.Flags); break; case RegistryFilter: filterAddRequest->Filter.Id = RegistryMonitor->GetStringFilters()->AddFilter(temporaryFilterBuffer, filterAddRequest->Filter.Flags); break; } writtenLength = sizeof(STRING_FILTER_REQUEST); break; case IOCTL_LIST_FILTERS: // // Validate the size of the input and output buffers. // if (inputLength < sizeof(LIST_FILTERS_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_LIST_FILTERS but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } if (outputLength < sizeof(LIST_FILTERS_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_LIST_FILTERS but output buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } listFiltersRequest = RCAST(Irp->AssociatedIrp.SystemBuffer); switch (listFiltersRequest->FilterType) { case FilesystemFilter: DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Retrieving filesystem filters."); listFiltersRequest->CopiedFilters = FilesystemMonitor->GetStringFilters()->GetFilters(listFiltersRequest->SkipFilters, RCAST(&listFiltersRequest->Filters), 10); break; case RegistryFilter: DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Retrieving registry filters."); listFiltersRequest->CopiedFilters = RegistryMonitor->GetStringFilters()->GetFilters(listFiltersRequest->SkipFilters, RCAST(&listFiltersRequest->Filters), 10); break; } writtenLength = sizeof(LIST_FILTERS_REQUEST); break; case IOCTL_GET_PROCESS_SIZES: // // Validate the size of the input and output buffers. // if (inputLength < sizeof(PROCESS_SIZES_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESS_SIZES but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } if (outputLength < sizeof(PROCESS_SIZES_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_PROCESS_SIZES but output buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } IOCTLCommunication::ImageProcessFilter->PopulateProcessSizes(RCAST(Irp->AssociatedIrp.SystemBuffer)); writtenLength = sizeof(PROCESS_SIZES_REQUEST); break; case IOCTL_GET_IMAGE_DETAILED: // // Validate the size of the input and output buffers. // if (inputLength < sizeof(IMAGE_DETAILED_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_IMAGE_DETAILED but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } imageDetailedRequest = RCAST(Irp->AssociatedIrp.SystemBuffer); minimumLength = MAX_IMAGE_DETAILED_REQUEST_SIZE(imageDetailedRequest); DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: IOCTL_GET_IMAGE_DETAILED minimumLength = 0x%X.", minimumLength); if (inputLength < minimumLength) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_IMAGE_DETAILED but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } if (outputLength < minimumLength) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_IMAGE_DETAILED but output buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } IOCTLCommunication::ImageProcessFilter->PopulateImageDetailedRequest(imageDetailedRequest); writtenLength = MAX_IMAGE_DETAILED_REQUEST_SIZE(imageDetailedRequest); break; case IOCTL_GET_GLOBAL_SIZES: // // Validate the size of the output buffer. // if (outputLength < sizeof(GLOBAL_SIZES)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_GET_GLOBAL_SIZES but output buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } globalSizes = RCAST(Irp->AssociatedIrp.SystemBuffer); globalSizes->ProcessHistorySize = ImageHistoryFilter::ProcessHistorySize; globalSizes->FilesystemFilterSize = FilesystemMonitor->GetStringFilters()->filtersCount; globalSizes->RegistryFilterSize = RegistryMonitor->GetStringFilters()->filtersCount; writtenLength = sizeof(GLOBAL_SIZES); break; case IOCTL_DELETE_FILTER: // // Validate the size of the input buffer. // if (inputLength < sizeof(DELETE_FILTER_REQUEST)) { DBGPRINT("IOCTLCommunication!IOCTLDeviceControl: Received IOCTL_DELETE_FILTER but input buffer is too small."); status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } deleteFilterRequest = RCAST(Irp->AssociatedIrp.SystemBuffer); switch (deleteFilterRequest->FilterType) { case FilesystemFilter: deleteFilterRequest->Deleted = FilesystemMonitor->GetStringFilters()->RemoveFilter(deleteFilterRequest->FilterId); break; case RegistryFilter: deleteFilterRequest->Deleted = RegistryMonitor->GetStringFilters()->RemoveFilter(deleteFilterRequest->FilterId); break; } writtenLength = sizeof(DELETE_FILTER_REQUEST); break; } Exit: Irp->IoStatus.Status = status; Irp->IoStatus.Information = writtenLength; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; } /** Initialize the driver object to support IOCTL communication. */ NTSTATUS IOCTLCommunication::InitializeDriverIOCTL ( VOID ) { NTSTATUS status; UNICODE_STRING ioctlDeviceName; UNICODE_STRING ioctlDosDevicesName; PDEVICE_OBJECT ioctlDevice; RtlInitUnicodeString(&ioctlDeviceName, NT_DEVICE_NAME); // // Create IO Device Object. // TODO: Implement secure device creation (with secure DACL). // status = IoCreateDevice(DriverObject, NULL, &ioctlDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, TRUE, &ioctlDevice); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("IOCTLCommunication!InitializeDriverIOCTL: Failed to create kernel device object with error 0x%X.", status); goto Exit; } // // Set the handlers for our IOCTL. // DriverObject->MajorFunction[IRP_MJ_CREATE] = IOCTLCreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = IOCTLCreateClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IOCTLDeviceControl; RtlInitUnicodeString(&ioctlDosDevicesName, DOS_DEVICE_NAME); status = IoCreateSymbolicLink(&ioctlDosDevicesName, &ioctlDeviceName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("IOCTLCommunication!InitializeDriverIOCTL: Failed to create symbolic link to device with error 0x%X.", status); IoDeleteDevice(ioctlDevice); goto Exit; } Exit: return status; } /** Undo everything done in InitializeDriverObject. @return The status of uninitialization. */ VOID IOCTLCommunication::UninitializeDriverIOCTL ( VOID ) { PDEVICE_OBJECT deviceObject; UNICODE_STRING ioctlDosDevicesName; deviceObject = DriverObject->DeviceObject; // // Initialize the unicode string of our DosDevices symlink. // RtlInitUnicodeString(&ioctlDosDevicesName, DOS_DEVICE_NAME); // // Delete IOCTL symlink because we're unloading. // IoDeleteSymbolicLink(&ioctlDosDevicesName); if (deviceObject != NULL) { // // Delete the device while unloading. // IoDeleteDevice(deviceObject); } } ================================================ FILE: PeaceMaker Kernel/IOCTLCommunication.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "DetectionLogic.h" #include "ImageHistoryFilter.h" #include "ThreadFilter.h" #include "FSFilter.h" #include "RegistryFilter.h" #include "TamperGuard.h" typedef class IOCTLCommunication { static PDRIVER_OBJECT DriverObject; static PDETECTION_LOGIC Detector; static PIMAGE_HISTORY_FILTER ImageProcessFilter; static PFLT_FILTER FileFilterHandle; static PFS_BLOCKING_FILTER FilesystemMonitor; static PREGISTRY_BLOCKING_FILTER RegistryMonitor; static PTHREAD_FILTER ThreadOperationFilter; static PTAMPER_GUARD TamperGuardFilter; NTSTATUS InitializeDriverIOCTL(VOID); VOID UninitializeDriverIOCTL(VOID); static NTSTATUS IOCTLCreateClose( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp ); static NTSTATUS IOCTLDeviceControl( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp ); public: IOCTLCommunication( _In_ PDRIVER_OBJECT Driver, _In_ PUNICODE_STRING RegistryPath, _In_ PFLT_FILTER_UNLOAD_CALLBACK UnloadRoutine, _Inout_ NTSTATUS* InitializeStatus ); ~IOCTLCommunication(VOID); } IOCTL_COMMUNICATION, *PIOCTL_COMMUNICATION; #define DETECTION_LOGIC_TAG 'lDmP' #define IMAGE_HISTORY_FILTER_TAG 'fImP' #define FILE_MONITOR_TAG 'mFmP' #define REGISTRY_MONITOR_TAG 'mRmP' #define THREAD_FILTER_TAG 'fTmP' #define TAMPER_GUARD_TAG 'gTmP' ================================================ FILE: PeaceMaker Kernel/ImageHistoryFilter.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "ImageHistoryFilter.h" StackWalker ImageHistoryFilter::walker; PPROCESS_HISTORY_ENTRY ImageHistoryFilter::ProcessHistoryHead; EX_PUSH_LOCK ImageHistoryFilter::ProcessHistoryLock; BOOLEAN ImageHistoryFilter::destroying; ULONG64 ImageHistoryFilter::ProcessHistorySize; PDETECTION_LOGIC ImageHistoryFilter::detector; /** Register the necessary notify routines. @param Detector - Detection instance used to analyze untrusted operations. @param InitializeStatus - Status of initialization. */ ImageHistoryFilter::ImageHistoryFilter ( _In_ PDETECTION_LOGIC Detector, _Out_ NTSTATUS* InitializeStatus ) { // // Set the create process notify routine. // *InitializeStatus = PsSetCreateProcessNotifyRoutineEx(ImageHistoryFilter::CreateProcessNotifyRoutine, FALSE); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("ImageHistoryFilter!ImageHistoryFilter: Failed to register create process notify routine with status 0x%X.", *InitializeStatus); return; } // // Set the load image notify routine. // *InitializeStatus = PsSetLoadImageNotifyRoutine(ImageHistoryFilter::LoadImageNotifyRoutine); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("ImageHistoryFilter!ImageHistoryFilter: Failed to register load image notify routine with status 0x%X.", *InitializeStatus); return; } FltInitializePushLock(&ImageHistoryFilter::ProcessHistoryLock); ImageHistoryFilter::ProcessHistoryHead = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(PROCESS_HISTORY_ENTRY), PROCESS_HISTORY_TAG)); if (ImageHistoryFilter::ProcessHistoryHead == NULL) { DBGPRINT("ImageHistoryFilter!ImageHistoryFilter: Failed to allocate the process history head."); *InitializeStatus = STATUS_NO_MEMORY; return; } memset(ImageHistoryFilter::ProcessHistoryHead, 0, sizeof(PROCESS_HISTORY_ENTRY)); InitializeListHead(RCAST(ImageHistoryFilter::ProcessHistoryHead)); this->ProcessHistorySize = 0; // // Set the detector. // ImageHistoryFilter::detector = Detector; } /** Clean up the process history linked-list. */ ImageHistoryFilter::~ImageHistoryFilter ( VOID ) { PPROCESS_HISTORY_ENTRY currentProcessHistory; PIMAGE_LOAD_HISTORY_ENTRY currentImageEntry; // // Set destroying to TRUE so that no other threads can get a lock. // ImageHistoryFilter::destroying = TRUE; // // Remove the notify routines. // PsSetCreateProcessNotifyRoutineEx(ImageHistoryFilter::CreateProcessNotifyRoutine, TRUE); PsRemoveLoadImageNotifyRoutine(ImageHistoryFilter::LoadImageNotifyRoutine); // // Acquire an exclusive lock to push out other threads. // FltAcquirePushLockExclusive(&ImageHistoryFilter::ProcessHistoryLock); // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); // // Delete the lock for the process history linked-list. // FltDeletePushLock(&ImageHistoryFilter::ProcessHistoryLock); // // Go through each process history and free it. // if (ImageHistoryFilter::ProcessHistoryHead) { while (IsListEmpty(RCAST(ImageHistoryFilter::ProcessHistoryHead)) == FALSE) { currentProcessHistory = RCAST(RemoveHeadList(RCAST(ImageHistoryFilter::ProcessHistoryHead))); // // Clear the images linked-list. // FltDeletePushLock(¤tProcessHistory->ImageLoadHistoryLock); if (currentProcessHistory->ImageLoadHistory) { while (IsListEmpty(RCAST(currentProcessHistory->ImageLoadHistory)) == FALSE) { currentImageEntry = RCAST(RemoveHeadList(RCAST(currentProcessHistory->ImageLoadHistory))); // // Free the image name. // if (currentImageEntry->ImageFileName.Buffer) { ExFreePoolWithTag(currentImageEntry->ImageFileName.Buffer, IMAGE_NAME_TAG); } if (currentImageEntry->CallerImageFileName) { ExFreePoolWithTag(currentImageEntry->CallerImageFileName, IMAGE_NAME_TAG); } // // Free the stack history. // ExFreePoolWithTag(currentImageEntry->CallerStackHistory, STACK_HISTORY_TAG); ExFreePoolWithTag(currentImageEntry, IMAGE_HISTORY_TAG); } // // Finally, free the list head. // ExFreePoolWithTag(currentProcessHistory->ImageLoadHistory, IMAGE_HISTORY_TAG); } // // Free the names. // if (currentProcessHistory->ProcessImageFileName) { ExFreePoolWithTag(currentProcessHistory->ProcessImageFileName, IMAGE_NAME_TAG); } if (currentProcessHistory->CallerImageFileName) { ExFreePoolWithTag(currentProcessHistory->CallerImageFileName, IMAGE_NAME_TAG); } if (currentProcessHistory->ParentImageFileName) { ExFreePoolWithTag(currentProcessHistory->ParentImageFileName, IMAGE_NAME_TAG); } if (currentProcessHistory->ProcessCommandLine) { ExFreePoolWithTag(currentProcessHistory->ProcessCommandLine, IMAGE_COMMMAND_TAG); } // // Free the stack history. // ExFreePoolWithTag(currentProcessHistory->CallerStackHistory, STACK_HISTORY_TAG); // // Free the process history. // ExFreePoolWithTag(currentProcessHistory, PROCESS_HISTORY_TAG); } // // Finally, free the list head. // ExFreePoolWithTag(ImageHistoryFilter::ProcessHistoryHead, PROCESS_HISTORY_TAG); } } /** Add a process to the linked-list of process history objects. This function attempts to add a history object regardless of failures. @param ProcessId - The process ID of the process to add. @param CreateInfo - Information about the process being created. */ VOID ImageHistoryFilter::AddProcessToHistory ( _In_ HANDLE ProcessId, _In_ PPS_CREATE_NOTIFY_INFO CreateInfo ) { NTSTATUS status; PPROCESS_HISTORY_ENTRY newProcessHistory; LARGE_INTEGER systemTime; LARGE_INTEGER localSystemTime; BOOLEAN processHistoryLockHeld; processHistoryLockHeld = FALSE; status = STATUS_SUCCESS; if (ImageHistoryFilter::destroying) { return; } newProcessHistory = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(PROCESS_HISTORY_ENTRY), PROCESS_HISTORY_TAG)); if (newProcessHistory == NULL) { DBGPRINT("ImageHistoryFilter!AddProcessToHistory: Failed to allocate space for the process history."); status = STATUS_NO_MEMORY; goto Exit; } memset(newProcessHistory, 0, sizeof(PROCESS_HISTORY_ENTRY)); // // Basic fields. // newProcessHistory->ProcessId = ProcessId; newProcessHistory->ParentId = CreateInfo->ParentProcessId; newProcessHistory->CallerId = PsGetCurrentProcessId(); newProcessHistory->ProcessTerminated = FALSE; newProcessHistory->ImageLoadHistorySize = 0; KeQuerySystemTime(&systemTime); ExSystemTimeToLocalTime(&systemTime, &localSystemTime); newProcessHistory->EpochExecutionTime = localSystemTime.QuadPart / TICKSPERSEC - SECS_1601_TO_1970; // // Image file name fields. // // // Allocate the necessary space. // newProcessHistory->ProcessImageFileName = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(UNICODE_STRING) + CreateInfo->ImageFileName->Length, IMAGE_NAME_TAG)); if (newProcessHistory->ProcessImageFileName == NULL) { DBGPRINT("ImageHistoryFilter!AddProcessToHistory: Failed to allocate space for process ImageFileName."); goto Exit; } newProcessHistory->ProcessImageFileName->Buffer = RCAST(RCAST(newProcessHistory->ProcessImageFileName) + sizeof(UNICODE_STRING)); newProcessHistory->ProcessImageFileName->Length = CreateInfo->ImageFileName->Length; newProcessHistory->ProcessImageFileName->MaximumLength = CreateInfo->ImageFileName->Length; // // Copy the image file name string. // RtlCopyUnicodeString(newProcessHistory->ProcessImageFileName, CreateInfo->ImageFileName); // // Allocate the necessary space. // if (CreateInfo->CommandLine) { newProcessHistory->ProcessCommandLine = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(UNICODE_STRING) + CreateInfo->CommandLine->Length, IMAGE_COMMMAND_TAG)); if (newProcessHistory->ProcessCommandLine == NULL) { DBGPRINT("ImageHistoryFilter!AddProcessToHistory: Failed to allocate space for process command line."); goto Exit; } newProcessHistory->ProcessCommandLine->Buffer = RCAST(RCAST(newProcessHistory->ProcessCommandLine) + sizeof(UNICODE_STRING)); newProcessHistory->ProcessCommandLine->Length = CreateInfo->CommandLine->Length; newProcessHistory->ProcessCommandLine->MaximumLength = CreateInfo->CommandLine->Length; // // Copy the command line string. // RtlCopyUnicodeString(newProcessHistory->ProcessCommandLine, CreateInfo->CommandLine); } // // These fields are optional. // ImageHistoryFilter::GetProcessImageFileName(CreateInfo->ParentProcessId, &newProcessHistory->ParentImageFileName); if (PsGetCurrentProcessId() != CreateInfo->ParentProcessId) { ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &newProcessHistory->CallerImageFileName); } // // Grab the user-mode stack. // newProcessHistory->CallerStackHistorySize = MAX_STACK_RETURN_HISTORY; // Will be updated in the resolve function. walker.WalkAndResolveStack(&newProcessHistory->CallerStackHistory, &newProcessHistory->CallerStackHistorySize, STACK_HISTORY_TAG); if (newProcessHistory->CallerStackHistory == NULL) { DBGPRINT("ImageHistoryFilter!AddProcessToHistory: Failed to allocate space for the stack history."); status = STATUS_NO_MEMORY; goto Exit; } newProcessHistory->ImageLoadHistory = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(IMAGE_LOAD_HISTORY_ENTRY), IMAGE_HISTORY_TAG)); if (newProcessHistory->ImageLoadHistory == NULL) { DBGPRINT("ImageHistoryFilter!AddProcessToHistory: Failed to allocate space for the image load history."); status = STATUS_NO_MEMORY; goto Exit; } memset(newProcessHistory->ImageLoadHistory, 0, sizeof(IMAGE_LOAD_HISTORY_ENTRY)); InitializeListHead(RCAST(newProcessHistory->ImageLoadHistory)); // // Initialize this last so we don't have to delete it if anything failed. // FltInitializePushLock(&newProcessHistory->ImageLoadHistoryLock); // // Grab a lock to add an entry. // FltAcquirePushLockExclusive(&ImageHistoryFilter::ProcessHistoryLock); InsertTailList(RCAST(ImageHistoryFilter::ProcessHistoryHead), RCAST(newProcessHistory)); ImageHistoryFilter::ProcessHistorySize++; FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); // // Audit the stack. // ImageHistoryFilter::detector->AuditUserStackWalk(ProcessCreate, newProcessHistory->ProcessId, newProcessHistory->ParentImageFileName, newProcessHistory->ProcessImageFileName, newProcessHistory->CallerStackHistory, newProcessHistory->CallerStackHistorySize); // // Check for parent process ID spoofing. // ImageHistoryFilter::detector->AuditCallerProcessId(ProcessCreate, PsGetCurrentProcessId(), CreateInfo->ParentProcessId, newProcessHistory->ParentImageFileName, newProcessHistory->ProcessImageFileName, newProcessHistory->CallerStackHistory, newProcessHistory->CallerStackHistorySize); Exit: if (newProcessHistory && NT_SUCCESS(status) == FALSE) { ExFreePoolWithTag(newProcessHistory, PROCESS_HISTORY_TAG); } } /** Set a process to terminated, still maintain the history. @param ProcessId - The process ID of the process being terminated. */ VOID ImageHistoryFilter::TerminateProcessInHistory ( _In_ HANDLE ProcessId ) { PPROCESS_HISTORY_ENTRY currentProcessHistory; if (ImageHistoryFilter::destroying) { return; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); // // Iterate histories for a match. // if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = ImageHistoryFilter::ProcessHistoryHead; do { // // Find the process history with the same PID and then set it to terminated. // if (currentProcessHistory->ProcessId == ProcessId) { currentProcessHistory->ProcessTerminated = TRUE; break; } currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Blink); } while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead); } // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); } /** Notify routine called on new process execution. @param Process - The EPROCESS structure of the new/terminating process. @param ProcessId - The new child's process ID. @param CreateInfo - Information about the process being created. */ VOID ImageHistoryFilter::CreateProcessNotifyRoutine ( _In_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_ PPS_CREATE_NOTIFY_INFO CreateInfo ) { UNREFERENCED_PARAMETER(Process); // // If a new process is being created, add it to the history of processes. // if (CreateInfo) { ImageHistoryFilter::AddProcessToHistory(ProcessId, CreateInfo); DBGPRINT("ImageHistoryFilter!CreateProcessNotifyRoutine: Registered process 0x%X.", ProcessId); } else { DBGPRINT("ImageHistoryFilter!CreateProcessNotifyRoutine: Terminating process 0x%X.", ProcessId); // // Set the process as "terminated". // ImageHistoryFilter::TerminateProcessInHistory(ProcessId); } } /** Retrieve the full image file name for a process. @param ProcessId - The process to get the name of. @param ProcessImageFileName - PUNICODE_STRING to fill with the image file name of the process. */ BOOLEAN ImageHistoryFilter::GetProcessImageFileName ( _In_ HANDLE ProcessId, _Inout_ PUNICODE_STRING* ImageFileName ) { NTSTATUS status; PEPROCESS processObject; HANDLE processHandle; ULONG returnLength; processHandle = NULL; *ImageFileName = NULL; returnLength = 0; // // Before we can open a handle to the process, we need its PEPROCESS object. // status = PsLookupProcessByProcessId(ProcessId, &processObject); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!GetProcessImageFileName: Failed to find process object with status 0x%X.", status); goto Exit; } // // Open a handle to the process. // status = ObOpenObjectByPointer(processObject, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, GENERIC_ALL, *PsProcessType, KernelMode, &processHandle); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!GetProcessImageFileName: Failed to open handle to process with status 0x%X.", status); goto Exit; } // // Query for the size of the UNICODE_STRING. // status = NtQueryInformationProcess(processHandle, ProcessImageFileName, NULL, 0, &returnLength); if (status != STATUS_INFO_LENGTH_MISMATCH && status != STATUS_BUFFER_TOO_SMALL && status != STATUS_BUFFER_OVERFLOW) { DBGPRINT("ImageHistoryFilter!GetProcessImageFileName: Failed to query size of process ImageFileName with status 0x%X.", status); goto Exit; } // // Allocate the necessary space. // *ImageFileName = RCAST(ExAllocatePoolWithTag(PagedPool, returnLength, IMAGE_NAME_TAG)); if (*ImageFileName == NULL) { DBGPRINT("ImageHistoryFilter!GetProcessImageFileName: Failed to allocate space for process ImageFileName."); goto Exit; } // // Query the image file name. // status = NtQueryInformationProcess(processHandle, ProcessImageFileName, *ImageFileName, returnLength, &returnLength); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!GetProcessImageFileName: Failed to query process ImageFileName with status 0x%X.", status); goto Exit; } Exit: if (processHandle) { ZwClose(processHandle); } if (NT_SUCCESS(status) == FALSE && *ImageFileName) { ExFreePoolWithTag(*ImageFileName, IMAGE_NAME_TAG); *ImageFileName = NULL; } return NT_SUCCESS(status); } /** Notify routine called when a new image is loaded into a process. Adds the image to the corresponding process history element. @param FullImageName - A PUNICODE_STRING that identifies the executable image file. Might be NULL. @param ProcessId - The process ID where this image is being mapped. @param ImageInfo - Structure containing a variety of properties about the image being loaded. */ VOID ImageHistoryFilter::LoadImageNotifyRoutine( _In_ PUNICODE_STRING FullImageName, _In_ HANDLE ProcessId, _In_ PIMAGE_INFO ImageInfo ) { NTSTATUS status; PPROCESS_HISTORY_ENTRY currentProcessHistory; PIMAGE_LOAD_HISTORY_ENTRY newImageLoadHistory; UNREFERENCED_PARAMETER(ImageInfo); currentProcessHistory = NULL; newImageLoadHistory = NULL; status = STATUS_SUCCESS; if (ImageHistoryFilter::destroying) { return; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); // // Iterate histories for a match. // if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = ImageHistoryFilter::ProcessHistoryHead; do { if (currentProcessHistory->ProcessId == ProcessId && currentProcessHistory->ProcessTerminated == FALSE) { break; } currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Blink); } while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead); } // // This might happen if we load on a running machine that already has processes. // if (currentProcessHistory == NULL || currentProcessHistory == ImageHistoryFilter::ProcessHistoryHead) { DBGPRINT("ImageHistoryFilter!LoadImageNotifyRoutine: Failed to find PID 0x%X in history.", ProcessId); status = STATUS_NOT_FOUND; goto Exit; } // // Allocate space for the new image history entry. // newImageLoadHistory = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(IMAGE_LOAD_HISTORY_ENTRY), IMAGE_HISTORY_TAG)); if (newImageLoadHistory == NULL) { DBGPRINT("ImageHistoryFilter!LoadImageNotifyRoutine: Failed to allocate space for the image history entry."); status = STATUS_NO_MEMORY; goto Exit; } memset(newImageLoadHistory, 0, sizeof(IMAGE_LOAD_HISTORY_ENTRY)); newImageLoadHistory->CallerProcessId = PsGetCurrentProcessId(); if (PsGetCurrentProcessId() != ProcessId) { newImageLoadHistory->RemoteImage = TRUE; ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &newImageLoadHistory->CallerImageFileName); } // // Copy the image file name if it is provided. // if (FullImageName) { // // Allocate the copy buffer. FullImageName will not be valid forever. // newImageLoadHistory->ImageFileName.Buffer = RCAST(ExAllocatePoolWithTag(PagedPool, SCAST(FullImageName->Length) + 2, IMAGE_NAME_TAG)); if (newImageLoadHistory->ImageFileName.Buffer == NULL) { DBGPRINT("ImageHistoryFilter!LoadImageNotifyRoutine: Failed to allocate space for the image file name."); status = STATUS_NO_MEMORY; goto Exit; } newImageLoadHistory->ImageFileName.Length = SCAST(FullImageName->Length) + 2; newImageLoadHistory->ImageFileName.MaximumLength = SCAST(FullImageName->Length) + 2; // // Copy the image name. // status = RtlStringCbCopyUnicodeString(newImageLoadHistory->ImageFileName.Buffer, SCAST(FullImageName->Length) + 2, FullImageName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!LoadImageNotifyRoutine: Failed to copy the image file name with status 0x%X. Destination size = 0x%X, Source Size = 0x%X.", status, SCAST(FullImageName->Length) + 2, SCAST(FullImageName->Length)); goto Exit; } } // // Grab the user-mode stack. // newImageLoadHistory->CallerStackHistorySize = MAX_STACK_RETURN_HISTORY; // Will be updated in the resolve function. walker.WalkAndResolveStack(&newImageLoadHistory->CallerStackHistory, &newImageLoadHistory->CallerStackHistorySize, STACK_HISTORY_TAG); if (newImageLoadHistory->CallerStackHistory == NULL) { DBGPRINT("ImageHistoryFilter!LoadImageNotifyRoutine: Failed to allocate space for the stack history."); status = STATUS_NO_MEMORY; goto Exit; } FltAcquirePushLockExclusive(¤tProcessHistory->ImageLoadHistoryLock); InsertHeadList(RCAST(currentProcessHistory->ImageLoadHistory), RCAST(newImageLoadHistory)); currentProcessHistory->ImageLoadHistorySize++; FltReleasePushLock(¤tProcessHistory->ImageLoadHistoryLock); // // Audit the stack. // ImageHistoryFilter::detector->AuditUserStackWalk(ImageLoad, PsGetCurrentProcessId(), currentProcessHistory->ProcessImageFileName, &newImageLoadHistory->ImageFileName, newImageLoadHistory->CallerStackHistory, newImageLoadHistory->CallerStackHistorySize); Exit: // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); // // Clean up on failure. // if (newImageLoadHistory && NT_SUCCESS(status) == FALSE) { if (newImageLoadHistory->ImageFileName.Buffer) { ExFreePoolWithTag(newImageLoadHistory->ImageFileName.Buffer, IMAGE_NAME_TAG); DBGPRINT("Free'd 'PmIn' at 0x%llx.", newImageLoadHistory->ImageFileName.Buffer); } if (newImageLoadHistory->CallerStackHistory) { ExFreePoolWithTag(newImageLoadHistory->CallerStackHistory, STACK_HISTORY_TAG); DBGPRINT("Free'd 'PmSh' at 0x%llx.", newImageLoadHistory->CallerStackHistory); } ExFreePoolWithTag(newImageLoadHistory, IMAGE_HISTORY_TAG); DBGPRINT("Free'd 'PmIh' at 0x%llx.", newImageLoadHistory); } } /** Get the summary for MaxProcessSummaries processes starting from the top of list + SkipCount. @param SkipCount - How many processes to skip in the list. @param ProcessSummaries - Caller-supplied array of process summaries that this function fills. @param MaxProcessSumaries - Maximum number of process summaries that the array allows for. @return The actual number of summaries returned. */ ULONG ImageHistoryFilter::GetProcessHistorySummary ( _In_ ULONG SkipCount, _Inout_ PPROCESS_SUMMARY_ENTRY ProcessSummaries, _In_ ULONG MaxProcessSummaries ) { PPROCESS_HISTORY_ENTRY currentProcessHistory; ULONG currentProcessIndex; ULONG actualFilledSummaries; NTSTATUS status; currentProcessIndex = 0; actualFilledSummaries = 0; if (ImageHistoryFilter::destroying) { return 0; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); // // Iterate histories for the MaxProcessSummaries processes after SkipCount processes. // if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = RCAST(ImageHistoryFilter::ProcessHistoryHead->ListEntry.Flink); while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead && actualFilledSummaries < MaxProcessSummaries) { if (currentProcessIndex >= SkipCount) { // // Fill out the summary. // ProcessSummaries[actualFilledSummaries].EpochExecutionTime = currentProcessHistory->EpochExecutionTime; ProcessSummaries[actualFilledSummaries].ProcessId = currentProcessHistory->ProcessId; ProcessSummaries[actualFilledSummaries].ProcessTerminated = currentProcessHistory->ProcessTerminated; if (currentProcessHistory->ProcessImageFileName) { // // Copy the image name. // status = RtlStringCbCopyUnicodeString(RCAST(&ProcessSummaries[actualFilledSummaries].ImageFileName), MAX_PATH * sizeof(WCHAR), currentProcessHistory->ProcessImageFileName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!GetProcessHistorySummary: Failed to copy the image file name with status 0x%X.", status); break; } } actualFilledSummaries++; } currentProcessIndex++; currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Flink); } } // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); return actualFilledSummaries; } /** Populate a request for detailed information on a process. @param ProcessDetailedRequest - The request to populate. */ VOID ImageHistoryFilter::PopulateProcessDetailedRequest ( _Inout_ PPROCESS_DETAILED_REQUEST ProcessDetailedRequest ) { NTSTATUS status; PPROCESS_HISTORY_ENTRY currentProcessHistory; PIMAGE_LOAD_HISTORY_ENTRY currentImageEntry; ULONG i; i = 0; if (ImageHistoryFilter::destroying) { return; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = RCAST(ImageHistoryFilter::ProcessHistoryHead->ListEntry.Blink); while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead) { if (ProcessDetailedRequest->ProcessId == currentProcessHistory->ProcessId && ProcessDetailedRequest->EpochExecutionTime == currentProcessHistory->EpochExecutionTime) { // // Set basic fields. // ProcessDetailedRequest->Populated = TRUE; ProcessDetailedRequest->CallerProcessId = currentProcessHistory->CallerId; ProcessDetailedRequest->ParentProcessId = currentProcessHistory->ParentId; // // Copy the stack history. // ProcessDetailedRequest->StackHistorySize = (ProcessDetailedRequest->StackHistorySize > currentProcessHistory->CallerStackHistorySize) ? currentProcessHistory->CallerStackHistorySize : ProcessDetailedRequest->StackHistorySize; memcpy(ProcessDetailedRequest->StackHistory, currentProcessHistory->CallerStackHistory, ProcessDetailedRequest->StackHistorySize * sizeof(STACK_RETURN_INFO)); // // Copy the paths. // if (currentProcessHistory->ProcessImageFileName) { status = RtlStringCbCopyUnicodeString(RCAST(ProcessDetailedRequest->ProcessPath), MAX_PATH * sizeof(WCHAR), currentProcessHistory->ProcessImageFileName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!PopulateProcessDetailedRequest: Failed to copy the image file name of the process with status 0x%X.", status); break; } } if (currentProcessHistory->CallerImageFileName) { status = RtlStringCbCopyUnicodeString(RCAST(ProcessDetailedRequest->CallerProcessPath), MAX_PATH * sizeof(WCHAR), currentProcessHistory->CallerImageFileName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!PopulateProcessDetailedRequest: Failed to copy the image file name of the caller with status 0x%X.", status); break; } } if (currentProcessHistory->ParentImageFileName) { status = RtlStringCbCopyUnicodeString(RCAST(ProcessDetailedRequest->ParentProcessPath), MAX_PATH * sizeof(WCHAR), currentProcessHistory->ParentImageFileName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!PopulateProcessDetailedRequest: Failed to copy the image file name of the parent with status 0x%X.", status); break; } } if (currentProcessHistory->ProcessCommandLine) { status = RtlStringCbCopyUnicodeString(RCAST(ProcessDetailedRequest->ProcessCommandLine), MAX_PATH * sizeof(WCHAR), currentProcessHistory->ProcessCommandLine); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!PopulateProcessDetailedRequest: Failed to copy the command line of the process with status 0x%X.", status); break; } } // // Iterate the images for basic information. // FltAcquirePushLockShared(¤tProcessHistory->ImageLoadHistoryLock); // // The head isn't an element so skip it. // currentImageEntry = RCAST(currentProcessHistory->ImageLoadHistory->ListEntry.Flink); while (currentImageEntry != currentProcessHistory->ImageLoadHistory && i < ProcessDetailedRequest->ImageSummarySize) { __try { if (currentImageEntry->ImageFileName.Buffer) { status = RtlStringCbCopyUnicodeString(RCAST(ProcessDetailedRequest->ImageSummary[i].ImagePath), MAX_PATH * sizeof(WCHAR), ¤tImageEntry->ImageFileName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!PopulateProcessDetailedRequest: Failed to copy the image file name of an image with status 0x%X and source size %i.", status, currentImageEntry->ImageFileName.Length); break; } } ProcessDetailedRequest->ImageSummary[i].StackSize = currentImageEntry->CallerStackHistorySize; } __except (1) { DBGPRINT("ImageHistoryFilter!PopulateProcessDetailedRequest: Exception while processing image summaries."); break; } i++; currentImageEntry = RCAST(currentImageEntry->ListEntry.Flink); } FltReleasePushLock(¤tProcessHistory->ImageLoadHistoryLock); ProcessDetailedRequest->ImageSummarySize = i; // Actual number of images put into the array. break; } currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Blink); } } // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); } /** Populate a process sizes request. @param ProcesSizesRequest - The request to populate. */ VOID ImageHistoryFilter::PopulateProcessSizes ( _Inout_ PPROCESS_SIZES_REQUEST ProcessSizesRequest ) { PPROCESS_HISTORY_ENTRY currentProcessHistory; if (ImageHistoryFilter::destroying) { return; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = RCAST(ImageHistoryFilter::ProcessHistoryHead->ListEntry.Blink); while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead) { if (ProcessSizesRequest->ProcessId == currentProcessHistory->ProcessId && ProcessSizesRequest->EpochExecutionTime == currentProcessHistory->EpochExecutionTime) { ProcessSizesRequest->StackSize = currentProcessHistory->CallerStackHistorySize; ProcessSizesRequest->ImageSize = currentProcessHistory->ImageLoadHistorySize; break; } currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Blink); } } // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); } /** Increment process thread count by one and retrieve the latest value. @param ProcessId - The process ID of the target process. @param ThreadCount - The resulting thread count. @return Whether or not the process was found. */ BOOLEAN ImageHistoryFilter::AddProcessThreadCount ( _In_ HANDLE ProcessId, _Inout_ ULONG* ThreadCount ) { PPROCESS_HISTORY_ENTRY currentProcessHistory; BOOLEAN foundProcess; foundProcess = FALSE; if (ImageHistoryFilter::destroying) { return foundProcess; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = RCAST(ImageHistoryFilter::ProcessHistoryHead->ListEntry.Blink); while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead) { if (ProcessId == currentProcessHistory->ProcessId && currentProcessHistory->ProcessTerminated == FALSE) { currentProcessHistory->ProcessThreadCount++; *ThreadCount = currentProcessHistory->ProcessThreadCount; foundProcess = TRUE; break; } currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Blink); } } // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); return foundProcess; } VOID ImageHistoryFilter::PopulateImageDetailedRequest( _Inout_ PIMAGE_DETAILED_REQUEST ImageDetailedRequest ) { NTSTATUS status; PPROCESS_HISTORY_ENTRY currentProcessHistory; PIMAGE_LOAD_HISTORY_ENTRY currentImageEntry; ULONG i; i = 0; if (ImageHistoryFilter::destroying) { return; } // // Acquire a shared lock to iterate processes. // FltAcquirePushLockShared(&ImageHistoryFilter::ProcessHistoryLock); if (ImageHistoryFilter::ProcessHistoryHead) { currentProcessHistory = RCAST(ImageHistoryFilter::ProcessHistoryHead->ListEntry.Blink); while (currentProcessHistory && currentProcessHistory != ImageHistoryFilter::ProcessHistoryHead) { if (ImageDetailedRequest->ProcessId == currentProcessHistory->ProcessId && ImageDetailedRequest->EpochExecutionTime == currentProcessHistory->EpochExecutionTime) { // // Iterate the images for basic information. // FltAcquirePushLockShared(¤tProcessHistory->ImageLoadHistoryLock); // // The head isn't an element so skip it. // currentImageEntry = RCAST(currentProcessHistory->ImageLoadHistory->ListEntry.Flink); while (currentImageEntry != currentProcessHistory->ImageLoadHistory) { if (i == ImageDetailedRequest->ImageIndex) { if (currentImageEntry->ImageFileName.Buffer) { status = RtlStringCbCopyUnicodeString(RCAST(ImageDetailedRequest->ImagePath), MAX_PATH * sizeof(WCHAR), ¤tImageEntry->ImageFileName); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ImageHistoryFilter!PopulateImageDetailedRequest: Failed to copy the image file name of an image with status 0x%X and source size %i.", status, currentImageEntry->ImageFileName.Length); break; } } // // Copy the stack history. // ImageDetailedRequest->StackHistorySize = (ImageDetailedRequest->StackHistorySize > currentImageEntry->CallerStackHistorySize) ? currentImageEntry->CallerStackHistorySize : ImageDetailedRequest->StackHistorySize; memcpy(ImageDetailedRequest->StackHistory, currentImageEntry->CallerStackHistory, ImageDetailedRequest->StackHistorySize * sizeof(STACK_RETURN_INFO)); ImageDetailedRequest->Populated = TRUE; } i++; currentImageEntry = RCAST(currentImageEntry->ListEntry.Flink); } FltReleasePushLock(¤tProcessHistory->ImageLoadHistoryLock); break; } currentProcessHistory = RCAST(currentProcessHistory->ListEntry.Blink); } } // // Release the lock. // FltReleasePushLock(&ImageHistoryFilter::ProcessHistoryLock); } ================================================ FILE: PeaceMaker Kernel/ImageHistoryFilter.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "StackWalker.h" #include "shared.h" #include "DetectionLogic.h" #define IMAGE_NAME_TAG 'nImP' #define IMAGE_COMMMAND_TAG 'cImP' #define PROCESS_HISTORY_TAG 'hPmP' #define STACK_HISTORY_TAG 'hSmP' #define IMAGE_HISTORY_TAG 'hImP' typedef struct ImageLoadHistoryEntry { LIST_ENTRY ListEntry; // The list entry to iterate multiple images in a process. UNICODE_STRING ImageFileName; // The full image file name of loaded image. HANDLE CallerProcessId; // The real caller of the load image routine. BOOLEAN RemoteImage; // Whether or not the image was loaded remotely. PUNICODE_STRING CallerImageFileName; // The full image file name of the caller. Only specified if RemoteImage == TRUE. PSTACK_RETURN_INFO CallerStackHistory; // A variable-length array of the stack that loaded the image. ULONG CallerStackHistorySize; // The size of the variable-length stack history array. } IMAGE_LOAD_HISTORY_ENTRY, *PIMAGE_LOAD_HISTORY_ENTRY; typedef struct ProcessHistoryEntry { LIST_ENTRY ListEntry; // The list entry to iterate multiple process histories. HANDLE CallerId; // The process id of the caller process. PUNICODE_STRING CallerImageFileName; // OPTIONAL: The image file name of the caller process. HANDLE ParentId; // The process id of the alleged parent process. PUNICODE_STRING ParentImageFileName; // OPTIONAL: The image file name of the alleged parent process. HANDLE ProcessId; // The process id of the executed process. PUNICODE_STRING ProcessImageFileName; // The image file name of the executed process. PUNICODE_STRING ProcessCommandLine; // The command-line string for the executed process. ULONG ProcessThreadCount; // The number of threads the process has. ULONGLONG EpochExecutionTime; // Process execution time in seconds since 1970. BOOLEAN ProcessTerminated; // Whether or not the process has terminated. PSTACK_RETURN_INFO CallerStackHistory; // A variable-length array of the stack that started the process. ULONG CallerStackHistorySize; // The size of the variable-length stack history array. PIMAGE_LOAD_HISTORY_ENTRY ImageLoadHistory; // A linked-list of loaded images and their respective stack histories. EX_PUSH_LOCK ImageLoadHistoryLock; // The lock protecting the linked-list of loaded images. ULONG ImageLoadHistorySize; // The size of the image load history linked-list. } PROCESS_HISTORY_ENTRY, *PPROCESS_HISTORY_ENTRY; typedef class ImageHistoryFilter { static VOID CreateProcessNotifyRoutine ( _In_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_ PPS_CREATE_NOTIFY_INFO CreateInfo ); static VOID LoadImageNotifyRoutine ( _In_ PUNICODE_STRING FullImageName, _In_ HANDLE ProcessId, _In_ PIMAGE_INFO ImageInfo ); static StackWalker walker; // Stack walking utility. static PPROCESS_HISTORY_ENTRY ProcessHistoryHead; // Linked-list of process history objects. static EX_PUSH_LOCK ProcessHistoryLock; // Lock protecting the ProcessHistory linked-list. static BOOLEAN destroying; // This boolean indicates to functions that a lock should not be held as we are in the process of destruction. static PDETECTION_LOGIC detector; static VOID AddProcessToHistory( _In_ HANDLE ProcessId, _In_ PPS_CREATE_NOTIFY_INFO CreateInfo ); static VOID TerminateProcessInHistory( _In_ HANDLE ProcessId ); public: ImageHistoryFilter( _In_ PDETECTION_LOGIC Detector, _Out_ NTSTATUS* InitializeStatus ); ~ImageHistoryFilter(VOID); static BOOLEAN GetProcessImageFileName( _In_ HANDLE ProcessId, _Inout_ PUNICODE_STRING* ImageFileName ); ULONG GetProcessHistorySummary( _In_ ULONG SkipCount, _Inout_ PPROCESS_SUMMARY_ENTRY ProcessSummaries, _In_ ULONG MaxProcessSummaries ); VOID PopulateProcessDetailedRequest( _Inout_ PPROCESS_DETAILED_REQUEST ProcessDetailedRequest ); VOID PopulateProcessSizes( _Inout_ PPROCESS_SIZES_REQUEST ProcessSizesRequest ); VOID PopulateImageDetailedRequest( _Inout_ PIMAGE_DETAILED_REQUEST ImageDetailedRequest ); static BOOLEAN AddProcessThreadCount( _In_ HANDLE ProcessId, _Inout_ ULONG* ThreadCount ); static ULONG64 ProcessHistorySize; // Number of entries in the ProcessHistory linked-list. } IMAGE_HISTORY_FILTER, *PIMAGE_HISTORY_FILTER; ================================================ FILE: PeaceMaker Kernel/PeaceMaker Kernel.inf ================================================ ;;; ;;; FilterTesting ;;; [Version] Signature = "$Windows NT$" ; TODO - Change the Class and ClassGuid to match the Load Order Group value, see https://msdn.microsoft.com/en-us/windows/hardware/gg462963 ; Class = "ActivityMonitor" ;This is determined by the work this filter driver does ; ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;This value is determined by the Load Order Group value Class = "ActivityMonitor" ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} Provider = %ManufacturerName% DriverVer = 12/17/2019,1.0.0.0 CatalogFile = PeaceMakerKernel.cat [DestinationDirs] DefaultDestDir = 12 MiniFilter.DriverFiles = 12 ;%windir%\system32\drivers ;; ;; Default install sections ;; [DefaultInstall] OptionDesc = %ServiceDescription% CopyFiles = MiniFilter.DriverFiles [DefaultInstall.Services] AddService = %ServiceName%,,MiniFilter.Service ;; ;; Default uninstall sections ;; [DefaultUninstall] DelFiles = MiniFilter.DriverFiles [DefaultUninstall.Services] DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting ; ; Services Section ; [MiniFilter.Service] DisplayName = %ServiceName% Description = %ServiceDescription% ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\ Dependencies = "FltMgr" ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER StartType = 2 ;SERVICE_AUTO_START ErrorControl = 1 ;SERVICE_ERROR_NORMAL ; TODO - Change the Load Order Group value ; LoadOrderGroup = "FSFilter Activity Monitor" LoadOrderGroup = "FSFilter Activity Monitor" AddReg = MiniFilter.AddRegistry ; ; Registry Modifications ; [MiniFilter.AddRegistry] HKR,,"DebugFlags",0x00010001 ,0x0 HKR,,"SupportedFeatures",0x00010001,0x3 HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance% HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% ; ; Copy Files ; [MiniFilter.DriverFiles] %DriverName%.sys [SourceDisksFiles] PeaceMakerKernel.sys = 1,, [SourceDisksNames] 1 = %DiskId1%,,, ;; ;; String Section ;; [Strings] ; TODO - Add your manufacturer ManufacturerName = "Bill Demirkapi" ServiceDescription = "Peacemaker Kernel Mini-Filter Driver" ServiceName = "Peacemaker Kernel" DriverName = "PeaceMakerKernel" DiskId1 = "Peacemaker Kernel Device Installation Disk" ;Instances specific information. DefaultInstance = "FilterTesting Instance" Instance1.Name = "FilterTesting Instance" ; TODO - Change the altitude value, see https://msdn.microsoft.com/en-us/windows/hardware/drivers/ifs/load-order-groups-and-altitudes-for-minifilter-drivers Instance1.Altitude = "321410" Instance1.Flags = 0x0 ; Allow all attachments ================================================ FILE: PeaceMaker Kernel/PeaceMaker Kernel.rc ================================================ #include #include #define VER_FILETYPE VFT_DRV #define VER_FILESUBTYPE VFT2_DRV_SYSTEM #define VER_COMPANYNAME_STR "Demirkapi Security Group" #define VER_FILEVERSION_STR "1.0.0.0" #define VER_LEGALCOPYRIGHT_STR "Demirkapi Security Group 2019-2020" #define VER_PRODUCTNAME_STR "PeaceMaker Threat Detection" #define VER_FILEDESCRIPTION_STR "The PeaceMaker TD Kernel Component." #define VER_INTERNALNAME_STR "peacemaker.sys" #include "common.ver" ================================================ FILE: PeaceMaker Kernel/PeaceMaker Kernel.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 Debug ARM Release ARM Debug ARM64 Release ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F} {f2f62967-0815-4fd7-9b86-6eedcac766eb} v4.5 12.0 Debug Win32 PeaceMaker Kernel PeaceMaker Kernel Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows7 false WindowsKernelModeDriver10.0 Driver WDM Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger false DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) /INTEGRITYCHECK %(AdditionalOptions) "$(OutDir)signbinary.bat" "$(TargetPath)" fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) ================================================ FILE: PeaceMaker Kernel/PeaceMaker Kernel.vcxproj.filters ================================================  {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 {8E41214B-6785-4CFE-B992-037D68949A14} inf;inv;inx;mof;mc; {e51791fd-6d37-4d03-af6a-4cf1d58ff83a} {07baa70a-638f-458b-a11e-cc54d633a0f5} {d2416d0a-1b1f-400a-a990-1af1d2a15b4b} {b653583e-a15b-446e-85e9-7f8cceecae8c} {67667525-d9bd-4041-8ca5-f90fe95a4741} {bbb90d1c-ab67-44c2-a036-5847843b131f} Driver Files Resource Files Source Files Source Files Source Files\Filters Source Files\Filters Source Files\Utilities Source Files\Filters Source Files\Utilities Source Files\Core Source Files\Core Source Files\Utilities Source Files\Filters Source Files\Filters Header Files Header Files\Filters Header Files\Filters Header Files\Utilities Header Files\Utilities Header Files\Filters Header Files\Utilities Header Files\Utilities Header Files Header Files\Core Header Files\Core Header Files\Filters Header Files\Filters ================================================ FILE: PeaceMaker Kernel/RegistryFilter.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "RegistryFilter.h" LARGE_INTEGER RegistryBlockingFilter::RegistryFilterCookie; PDETECTION_LOGIC RegistryBlockingFilter::detector; STACK_WALKER RegistryBlockingFilter::walker; PSTRING_FILTERS RegistryBlockingFilter::RegistryStringFilters; /** Initializes the necessary components of the registry filter. @param DriverObject - The object of the driver necessary for mini-filter initialization. @param RegistryPath - The registry path of the driver. @param Detector - Detection instance used to analyze untrusted operations. @param InitializeStatus - Status of initialization. */ RegistryBlockingFilter::RegistryBlockingFilter( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath, _In_ PDETECTION_LOGIC Detector, _Out_ NTSTATUS* InitializeStatus ) { UNICODE_STRING filterAltitude; RegistryBlockingFilter::RegistryStringFilters = new (NonPagedPool, STRING_REGISTRY_FILTERS_TAG) StringFilters(RegistryFilter, RegistryPath, L"RegistryFilterStore"); if (RegistryBlockingFilter::RegistryStringFilters == NULL) { DBGPRINT("RegistryBlockingFilter!RegistryBlockingFilter: Failed to allocate memory for string filters."); *InitializeStatus = STATUS_NO_MEMORY; return; } // // Restore existing filters. // RegistryBlockingFilter::RegistryStringFilters->RestoreFilters(); // // Put our altitude into a UNICODE_STRING. // RtlInitUnicodeString(&filterAltitude, FILTER_ALTITUDE); // // Register our registry callback. // *InitializeStatus = CmRegisterCallbackEx(RCAST(RegistryBlockingFilter::RegistryCallback), &filterAltitude, DriverObject, NULL, &RegistryFilterCookie, NULL); // // Set the detector. // RegistryBlockingFilter::detector = Detector; } /** Free data members that were dynamically allocated. */ RegistryBlockingFilter::~RegistryBlockingFilter() { // // Remove the registry callback. // CmUnRegisterCallback(this->RegistryFilterCookie); // // Make sure to deconstruct the class. // RegistryBlockingFilter::RegistryStringFilters->~StringFilters(); ExFreePoolWithTag(RegistryBlockingFilter::RegistryStringFilters, STRING_REGISTRY_FILTERS_TAG); } /** Return the string filters used in the registry filter. @return String filters for registry operations. */ PSTRING_FILTERS RegistryBlockingFilter::GetStringFilters() { return RegistryBlockingFilter::RegistryStringFilters; } /** Function that decides whether or not to block a registry operation. @param KeyObject - The registry key of the operation. @param ValueName - The name of the registry value specified by the operation. @param OperationFlag - The flags of the operation (i.e WRITE/DELETE). @return Whether or not to block the operation. */ BOOLEAN RegistryBlockingFilter::BlockRegistryOperation ( _In_ PVOID KeyObject, _In_ PUNICODE_STRING ValueName, _In_ ULONG OperationFlag ) { BOOLEAN blockOperation; NTSTATUS internalStatus; HANDLE keyHandle; PKEY_NAME_INFORMATION pKeyNameInformation; ULONG returnLength; ULONG fullKeyValueLength; PWCHAR tempValueName; PWCHAR fullKeyValueName; UNICODE_STRING registryOperationPath; PUNICODE_STRING callerProcessPath; PSTACK_RETURN_INFO registryOperationStack; ULONG registryOperationStackSize; blockOperation = FALSE; registryOperationStackSize = MAX_STACK_RETURN_HISTORY; keyHandle = NULL; returnLength = NULL; pKeyNameInformation = NULL; tempValueName = NULL; fullKeyValueName = NULL; if (ValueName == NULL || ValueName->Length == 0 || ValueName->Buffer == NULL || ValueName->Length > (NTSTRSAFE_UNICODE_STRING_MAX_CCH * sizeof(WCHAR))) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: ValueName is NULL."); goto Exit; } tempValueName = RCAST(ExAllocatePoolWithTag(NonPagedPoolNx, ValueName->Length, REGISTRY_KEY_NAME_TAG)); if (tempValueName == NULL) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to allocate memory for value name with size 0x%X.", ValueName->Length); goto Exit; } // // There can be some wonky exceptions with weird input, // just in case we don't handle something is a simple // catch all. // __try { // // Open the registry key. // internalStatus = ObOpenObjectByPointer(KeyObject, OBJ_KERNEL_HANDLE, NULL, GENERIC_ALL, *CmKeyObjectType, KernelMode, &keyHandle); if (NT_SUCCESS(internalStatus) == FALSE) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to open a handle to a key object with status 0x%X.", internalStatus); goto Exit; } ZwQueryKey(keyHandle, KeyNameInformation, NULL, 0, &returnLength); if (returnLength == 0) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to determine size of key name."); goto Exit; } returnLength += 1; // For null terminator. pKeyNameInformation = RCAST(ExAllocatePoolWithTag(PagedPool, returnLength, REGISTRY_KEY_NAME_TAG)); if (pKeyNameInformation == NULL) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to allocate memory for key name with size 0x%X.", returnLength); goto Exit; } // // Query the name information of the key to retrieve its name. // internalStatus = ZwQueryKey(keyHandle, KeyNameInformation, pKeyNameInformation, returnLength, &returnLength); if (NT_SUCCESS(internalStatus) == FALSE) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to query name of key object with status 0x%X.", internalStatus); goto Exit; } // // Allocate space for key name, a backslash, the value name, and the null-terminator. // fullKeyValueLength = pKeyNameInformation->NameLength + 2 + ValueName->Length + 1000; fullKeyValueName = RCAST(ExAllocatePoolWithTag(NonPagedPoolNx, fullKeyValueLength, REGISTRY_KEY_NAME_TAG)); if (fullKeyValueName == NULL) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to allocate memory for full key/value name with size 0x%X.", fullKeyValueLength); goto Exit; } // // Copy the key name. // internalStatus = RtlStringCbCopyNW(fullKeyValueName, fullKeyValueLength, RCAST(&pKeyNameInformation->Name), pKeyNameInformation->NameLength); if (NT_SUCCESS(internalStatus) == FALSE) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to copy key name with status 0x%X.", internalStatus); goto Exit; } // // Concatenate the backslash. // internalStatus = RtlStringCbCatW(fullKeyValueName, fullKeyValueLength, L"\\"); if (NT_SUCCESS(internalStatus) == FALSE) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to concatenate backslash with status 0x%X.", internalStatus); goto Exit; } // // Concatenate the value name. // internalStatus = RtlStringCbCatNW(fullKeyValueName, fullKeyValueLength, ValueName->Buffer, ValueName->Length); if (NT_SUCCESS(internalStatus) == FALSE) { DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Failed to concatenate value name with status 0x%X.", internalStatus); goto Exit; } blockOperation = RegistryBlockingFilter::RegistryStringFilters->MatchesFilter(fullKeyValueName, OperationFlag); //DBGPRINT("RegistryBlockingFilter!BlockRegistryOperation: Full name: %S.", fullKeyValueName); } __except (EXCEPTION_EXECUTE_HANDLER) {} if (blockOperation) { // // Grab the caller's path. // ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &callerProcessPath); // // Walk the stack. // RegistryBlockingFilter::walker.WalkAndResolveStack(®istryOperationStack, ®istryOperationStackSize, STACK_HISTORY_TAG); NT_ASSERT(registryOperationStack); // // Only if we successfully walked the stack, report the violation. // if (registryOperationStack != NULL && registryOperationStackSize != 0) { // // Convert the registry path to a unicode string. // RtlInitUnicodeString(®istryOperationPath, fullKeyValueName); // // Report the violation. // RegistryBlockingFilter::detector->ReportFilterViolation(RegistryFilterMatch, PsGetCurrentProcessId(), callerProcessPath, ®istryOperationPath, registryOperationStack, registryOperationStackSize); // // Clean up. // ExFreePoolWithTag(registryOperationStack, STACK_HISTORY_TAG); } ExFreePoolWithTag(callerProcessPath, IMAGE_NAME_TAG); } Exit: if (tempValueName) { ExFreePoolWithTag(tempValueName, REGISTRY_KEY_NAME_TAG); } if (fullKeyValueName) { ExFreePoolWithTag(fullKeyValueName, REGISTRY_KEY_NAME_TAG); } if (pKeyNameInformation) { ExFreePoolWithTag(pKeyNameInformation, REGISTRY_KEY_NAME_TAG); } if (keyHandle) { ZwClose(keyHandle); } return blockOperation; } /** The callback for registry operations. If necessary, blocks certain operations on protected keys/values. @param CallbackContext - Unreferenced parameter. @param OperationClass - The type of registry operation. @param Argument2 - A pointer to the structure associated with the operation. @return The status of the registry operation. */ NTSTATUS RegistryBlockingFilter::RegistryCallback ( _In_ PVOID CallbackContext, _In_ REG_NOTIFY_CLASS OperationClass, _In_ PVOID Argument2 ) { UNREFERENCED_PARAMETER(CallbackContext); NTSTATUS returnStatus; PREG_SET_VALUE_KEY_INFORMATION setValueInformation; PREG_DELETE_VALUE_KEY_INFORMATION deleteValueInformation; returnStatus = STATUS_SUCCESS; // // PeaceMaker is not designed to block kernel operations. // if (ExGetPreviousMode() != KernelMode) { switch (OperationClass) { case RegNtPreSetValueKey: setValueInformation = RCAST(Argument2); if (BlockRegistryOperation(setValueInformation->Object, setValueInformation->ValueName, FILTER_FLAG_WRITE)) { DBGPRINT("RegistryBlockingFilter!RegistryCallback: Detected RegNtPreSetValueKey of %wZ. Prevented set!", setValueInformation->ValueName); returnStatus = STATUS_ACCESS_DENIED; } break; case RegNtPreDeleteValueKey: deleteValueInformation = RCAST(Argument2); if (BlockRegistryOperation(deleteValueInformation->Object, deleteValueInformation->ValueName, FILTER_FLAG_DELETE)) { DBGPRINT("RegistryBlockingFilter!RegistryCallback: Detected RegNtPreDeleteValueKey of %wZ. Prevented rewrite!", deleteValueInformation->ValueName); returnStatus = STATUS_ACCESS_DENIED; } break; } } return returnStatus; } ================================================ FILE: PeaceMaker Kernel/RegistryFilter.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "StringFilters.h" #include "StackWalker.h" #include "DetectionLogic.h" #include "ImageHistoryFilter.h" typedef class RegistryBlockingFilter { static BOOLEAN BlockRegistryOperation(_In_ PVOID KeyObject, _In_ PUNICODE_STRING ValueName, _In_ ULONG OperationFlag ); static NTSTATUS RegistryCallback(_In_ PVOID CallbackContext, _In_ REG_NOTIFY_CLASS OperationClass, _In_ PVOID Argument2 ); // // Contains strings to block various registry operations. // static PSTRING_FILTERS RegistryStringFilters; // // Cookie used to remove registry callback. // static LARGE_INTEGER RegistryFilterCookie; static STACK_WALKER walker; static PDETECTION_LOGIC detector; public: RegistryBlockingFilter ( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath, _In_ PDETECTION_LOGIC Detector, _Out_ NTSTATUS* Initialized ); ~RegistryBlockingFilter(); static PSTRING_FILTERS GetStringFilters(); } REGISTRY_BLOCKING_FILTER, *PREGISTRY_BLOCKING_FILTER; #define STRING_REGISTRY_FILTERS_TAG 'rFmP' #define REGISTRY_KEY_NAME_TAG 'nKmP' ================================================ FILE: PeaceMaker Kernel/StackWalker.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "StackWalker.h" /** Search the current process to see if any modules contain the address. @param Address - The address to search for. @param StackReturnInfo - The structure to be populated. */ VOID StackWalker::ResolveAddressModule ( _In_ PVOID Address, _Inout_ PSTACK_RETURN_INFO StackReturnInfo ) { NTSTATUS status; MEMORY_BASIC_INFORMATION meminfo; SIZE_T returnLength; SIZE_T mappedFilenameLength; PUNICODE_STRING mappedFilename; mappedFilenameLength = sizeof(UNICODE_STRING) + MAX_PATH * 2; // // Query the virtual memory to see if it's part of an image. // status = ZwQueryVirtualMemory(NtCurrentProcess(), Address, MemoryBasicInformation, &meminfo, sizeof(meminfo), &returnLength); if (NT_SUCCESS(status) && meminfo.Type == MEM_IMAGE) { StackReturnInfo->MemoryInModule = TRUE; StackReturnInfo->BinaryOffset = RCAST(Address) - RCAST(meminfo.AllocationBase); // // Allocate the filename. // mappedFilename = RCAST(ExAllocatePoolWithTag(PagedPool, mappedFilenameLength, STACK_WALK_MAPPED_NAME)); if (mappedFilename == NULL) { DBGPRINT("StackWalker!ResolveAddressModule: Failed to allocate module name."); return; } // // Query the filename. // status = ZwQueryVirtualMemory(NtCurrentProcess(), Address, SCAST(MemoryMappedFilenameInformation), mappedFilename, mappedFilenameLength, &mappedFilenameLength); if (status == STATUS_BUFFER_OVERFLOW) { // // If we don't have a large enough buffer, allocate one! // ExFreePoolWithTag(mappedFilename, STACK_WALK_MAPPED_NAME); mappedFilename = RCAST(ExAllocatePoolWithTag(PagedPool, mappedFilenameLength, STACK_WALK_MAPPED_NAME)); if (mappedFilename == NULL) { DBGPRINT("StackWalker!ResolveAddressModule: Failed to allocate module name."); return; } status = ZwQueryVirtualMemory(NtCurrentProcess(), Address, SCAST(MemoryMappedFilenameInformation), mappedFilename, mappedFilenameLength, &mappedFilenameLength); } if (NT_SUCCESS(status) == FALSE) { DBGPRINT("StackWalker!ResolveAddressModule: Failed to query memory module name with status 0x%X.", status); return; } // // Copy the mapped name. // RtlStringCbCopyUnicodeString(RCAST(&StackReturnInfo->BinaryPath), sizeof(StackReturnInfo->BinaryPath), mappedFilename); ExFreePoolWithTag(mappedFilename, STACK_WALK_MAPPED_NAME); } } /** Check if the memory pointed by address is executable. @param Address - The address to check. @return Whether or not the memory is executable. */ BOOLEAN StackWalker::IsAddressExecutable ( _In_ PVOID Address ) { NTSTATUS status; MEMORY_BASIC_INFORMATION memoryBasicInformation; BOOLEAN executable; executable = FALSE; memset(&memoryBasicInformation, 0, sizeof(memoryBasicInformation)); // // Query the basic information about the memory. // status = ZwQueryVirtualMemory(NtCurrentProcess(), Address, MemoryBasicInformation, &memoryBasicInformation, sizeof(memoryBasicInformation), NULL); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("StackWalker!IsAddressExecutable: Failed to query virtual memory for address 0x%llx with status 0x%X.", RCAST(Address), status); goto Exit; } // // Check if the protection flags specifies executable. // executable = FlagOn(memoryBasicInformation.AllocationProtect, PAGE_EXECUTE) || FlagOn(memoryBasicInformation.AllocationProtect, PAGE_EXECUTE_READ) || FlagOn(memoryBasicInformation.AllocationProtect, PAGE_EXECUTE_READWRITE) || FlagOn(memoryBasicInformation.AllocationProtect, PAGE_EXECUTE_WRITECOPY); Exit: return NT_SUCCESS(status) && executable; } /** Walk the stack of the current thread and resolve the module associated with the return addresses. @param ResolvedStack - Caller-supplied array of return address information that this function populates. @param ResolvedStackSize - The number of return addresses to resolve. @param ResolvedStackTag - The tag to allocate ResolvedStack with. */ VOID StackWalker::WalkAndResolveStack ( _Inout_ PSTACK_RETURN_INFO* ResolvedStack, _Inout_ ULONG* ResolvedStackSize, _In_ ULONG ResolvedStackTag ) { PVOID* stackReturnPtrs; ULONG capturedReturnPtrs; ULONG i; capturedReturnPtrs = 0; *ResolvedStack = NULL; // // Allocate space for the return addresses. // stackReturnPtrs = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(PVOID) * *ResolvedStackSize, STACK_WALK_ARRAY_TAG)); if (stackReturnPtrs == NULL) { DBGPRINT("StackWalker!WalkAndResolveStack: Failed to allocate space for temporary stack array."); goto Exit; } memset(stackReturnPtrs, 0, sizeof(PVOID) * *ResolvedStackSize); // // Get the return addresses leading up to this call. // capturedReturnPtrs = RtlWalkFrameChain(stackReturnPtrs, *ResolvedStackSize, 1); if (capturedReturnPtrs == 0) { DBGPRINT("StackWalker!WalkAndResolveStack: Failed to walk the stack."); goto Exit; } NT_ASSERT(capturedReturnPtrs < ResolvedStackSize); *ResolvedStackSize = capturedReturnPtrs; // // Allocate space for the stack return info array. // *ResolvedStack = RCAST(ExAllocatePoolWithTag(PagedPool, sizeof(STACK_RETURN_INFO) * *ResolvedStackSize, ResolvedStackTag)); if (*ResolvedStack == NULL) { DBGPRINT("StackWalker!WalkAndResolveStack: Failed to allocate space for stack info array."); goto Exit; } memset(*ResolvedStack, 0, sizeof(STACK_RETURN_INFO) * *ResolvedStackSize); // // Iterate each return address and fill out the struct. // for (i = 0; i < capturedReturnPtrs; i++) { (*ResolvedStack)[i].RawAddress = stackReturnPtrs[i]; // // If the memory isn't executable or is in kernel, it's not worth our time. // if (RCAST(stackReturnPtrs[i]) < MmUserProbeAddress && this->IsAddressExecutable(stackReturnPtrs[i])) { (*ResolvedStack)[i].ExecutableMemory = TRUE; this->ResolveAddressModule(stackReturnPtrs[i], &(*ResolvedStack)[i]); } } Exit: if (stackReturnPtrs) { ExFreePoolWithTag(stackReturnPtrs, STACK_WALK_ARRAY_TAG); } } ================================================ FILE: PeaceMaker Kernel/StackWalker.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "shared.h" typedef class StackWalker { BOOLEAN IsAddressExecutable ( _In_ PVOID Address ); public: StackWalker() {}; VOID WalkAndResolveStack ( _Inout_ PSTACK_RETURN_INFO* ResolvedStack, _Inout_ ULONG* ResolvedStackSize, _In_ ULONG ResolvedStackTag ); VOID ResolveAddressModule ( _In_ PVOID Address, _Inout_ PSTACK_RETURN_INFO StackReturnInfo ); } STACK_WALKER, *PSTACK_WALKER; #define STACK_WALK_ARRAY_TAG 'aSmP' #define STACK_WALK_MAPPED_NAME 'nMmP' ================================================ FILE: PeaceMaker Kernel/StringFilters.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "StringFilters.h" /** Initialize the CUSTOM_FILTERS class by initializing the linked list's lock. @param Type - The type of filter to add (filesystem or registry). @param RegistryPath - The registry path of the driver. @param FilterStoreName - Name of the filter store. */ StringFilters::StringFilters ( _In_ STRING_FILTER_TYPE FilterType, _In_ PUNICODE_STRING RegistryPath, _In_ CONST WCHAR* FilterStoreName ) { // // Initialize the lock for the filters. // FltInitializePushLock(&this->filtersLock); this->filtersHead = RCAST(ExAllocatePoolWithTag(NonPagedPool, sizeof(FILTER_INFO_LINKED), FILTER_INFO_TAG)); InitializeListHead(RCAST(this->filtersHead)); this->destroying = FALSE; this->filtersCount = 0; this->filterType = FilterType; // // Initialize space for the driver registry key. // this->driverRegistryPath.Buffer = RCAST(ExAllocatePoolWithTag(NonPagedPool, RegistryPath->MaximumLength, FILTER_INFO_TAG)); this->driverRegistryPath.MaximumLength = RegistryPath->MaximumLength; RtlCopyUnicodeString(&this->driverRegistryPath, RegistryPath); RtlInitUnicodeString(&this->filterStoreValueName, FilterStoreName); } /** Destroy the CustomFilters class by clearing the filters linked list and deleting the associated lock. */ StringFilters::~StringFilters() { PLIST_ENTRY currentFilter; // // Set destroying to TRUE so that no other threads can get a lock. // this->destroying = TRUE; // // Acquire an exclusive lock to push out other threads. // FltAcquirePushLockExclusive(&this->filtersLock); // // Release the lock. // FltReleasePushLock(&this->filtersLock); // // Delete the lock for the filters. // FltDeletePushLock(&this->filtersLock); // // Go through each filter and free it. // if (this->filtersHead) { while (IsListEmpty(RCAST(this->filtersHead)) == FALSE) { currentFilter = RemoveHeadList(RCAST(this->filtersHead)); // // Free the filter. // ExFreePoolWithTag(SCAST(currentFilter), FILTER_INFO_TAG); } // // Finally, free the list head. // ExFreePoolWithTag(SCAST(this->filtersHead), FILTER_INFO_TAG); } // // Free the driver registy path. // ExFreePoolWithTag(this->driverRegistryPath.Buffer, FILTER_INFO_TAG); } /** Add a filter to the linked list of filters. @param MatchString - The string to filter with. @param OperationFlag - Specifies what operations this filter should be used for. @param SaveFilters - Whether or not to save filters. @return A random identifier required for future operations with the new filter. */ ULONG StringFilters::AddFilter ( _In_ WCHAR* MatchString, _In_ ULONG OperationFlag, _In_ BOOLEAN SaveFilters ) { PFILTER_INFO_LINKED newFilter; LARGE_INTEGER currentTime; ULONG epochSeconds; if (this == NULL || this->destroying) { return NULL; } // // Get an exclusive lock because we're modifying the filters linked list. // FltAcquirePushLockExclusive(&this->filtersLock); // // Allocate space for the new filter. // newFilter = RCAST(ExAllocatePoolWithTag(NonPagedPool, sizeof(FILTER_INFO_LINKED), FILTER_INFO_TAG)); if (newFilter == NULL) { DBGPRINT("Failed to allocate space for filter info."); goto Exit; } memset(RCAST(newFilter), 0, sizeof(FILTER_INFO_LINKED)); InsertTailList(RCAST(this->filtersHead), RCAST(newFilter)); this->filtersCount++; // // Generate a pseudo-random ID for the filter using the system time. // KeQuerySystemTime(¤tTime); RtlTimeToSecondsSince1970(¤tTime, &epochSeconds); newFilter->Filter.Id = RtlRandomEx(&epochSeconds); newFilter->Filter.Type = this->filterType; // // Copy the filter string to the new filter. // wcsncpy_s(newFilter->Filter.MatchString, MatchString, MAX_PATH); // // Set the operation flags for this filter. // newFilter->Filter.Flags = OperationFlag; Exit: // // New filter has been initialized, release the lock. // FltReleasePushLock(&this->filtersLock); if (SaveFilters) { this->SaveFilters(); } if (newFilter) { return newFilter->Filter.Id; } // // The ID cannot be 0 because we add 1. // return NULL; } /** Remove a filter from the linked list of filters. @param FilterId - The unique filter ID of the filter to delete. @return Whether or not deletion was successful. */ BOOLEAN StringFilters::RemoveFilter ( _In_ ULONG FilterId ) { BOOLEAN filterDeleted; PFILTER_INFO_LINKED currentFilter; currentFilter = NULL; filterDeleted = FALSE; if (this == NULL || this->destroying) { return FALSE; } // // Get an exclusive lock because we're modifying the filters linked list. // FltAcquirePushLockExclusive(&this->filtersLock); // // Check if we have a single filter already. // if (this->filtersHead) { currentFilter = RCAST(this->filtersHead->ListEntry.Flink); while (currentFilter && currentFilter != this->filtersHead) { if (currentFilter->Filter.Id == FilterId) { break; } currentFilter = RCAST(currentFilter->ListEntry.Flink); } // // Remove the entry from the list. // if (currentFilter && currentFilter != this->filtersHead) { RemoveEntryList(RCAST(currentFilter)); filterDeleted = TRUE; this->filtersCount--; // // Free the filter. // ExFreePoolWithTag(SCAST(currentFilter), FILTER_INFO_TAG); } } // // Release the lock. // FltReleasePushLock(&this->filtersLock); if (filterDeleted) { this->SaveFilters(); } return filterDeleted; } /** Check if a string contains any filtered phrases. @param StrToCmp - The string to search. @param OperationFlag - Specify FILTER_FLAG_X's to match certain filters for a variety of operations. @return Whether or not there was a filter that matched. */ BOOLEAN StringFilters::MatchesFilter ( _In_ WCHAR* StrToCmp, _In_ ULONG OperationFlag ) { BOOLEAN filterMatched; PFILTER_INFO_LINKED currentFilter; WCHAR tempStrToCmp[MAX_PATH]; INT i; if (this == NULL || this->destroying) { return FALSE; } filterMatched = FALSE; // // Copy the string to compare so we don't modify the original string. // wcsncpy_s(tempStrToCmp, StrToCmp, MAX_PATH); // // Make the input string lowercase. // i = 0; while (tempStrToCmp[i]) { tempStrToCmp[i] = towlower(tempStrToCmp[i]); i++; } // // Acquire a shared lock to iterate filters. // FltAcquirePushLockShared(&this->filtersLock); // // Iterate filters for a match. // if (this->filtersHead) { currentFilter = RCAST(this->filtersHead->ListEntry.Flink); while (currentFilter && currentFilter != this->filtersHead) { // // Check if the string to compare contains the filter. // if ((currentFilter->Filter.Flags & OperationFlag) && (wcsstr(RCAST(&tempStrToCmp), RCAST(¤tFilter->Filter.MatchString)) != NULL)) { filterMatched = TRUE; goto Exit; } currentFilter = RCAST(currentFilter->ListEntry.Flink); } } Exit: FltReleasePushLock(&this->filtersLock); return filterMatched; } /** Get the filters present in the linked-list. @param SkipFilters - The number of filters to skip. @param Filters - The output array. @param FilterSize - Maximum number of filters. @return The number of filters copied. */ ULONG StringFilters::GetFilters ( _In_ ULONG SkipFilters, _Inout_ PFILTER_INFO Filters, _In_ ULONG FiltersSize ) { PFILTER_INFO_LINKED currentFilter; ULONG skipCount; ULONG copyCount; skipCount = 0; copyCount = 0; // // Acquire a shared lock to iterate filters. // FltAcquirePushLockShared(&this->filtersLock); // // Iterate filters for a match. // if (this->filtersHead) { currentFilter = RCAST(this->filtersHead->ListEntry.Flink); while (currentFilter && currentFilter != this->filtersHead && copyCount < FiltersSize) { if (skipCount >= SkipFilters) { memcpy_s(&Filters[copyCount], sizeof(FILTER_INFO), ¤tFilter->Filter, sizeof(FILTER_INFO)); DBGPRINT("StringFilters!GetFilters: Copying filter ID 0x%X.", currentFilter->Filter.Id); copyCount++; } skipCount++; currentFilter = RCAST(currentFilter->ListEntry.Flink); } } FltReleasePushLock(&this->filtersLock); return copyCount; } /** Save the current filters to the registry for persistence. @return Whether or not the save was successful. */ BOOLEAN StringFilters::SaveFilters ( VOID ) { PFILTER_STORE filterStore; PFILTER_INFO_LINKED currentFilter; ULONG i; OBJECT_ATTRIBUTES driverRegistryAttributes; NTSTATUS status; HANDLE driverRegistryKey; BOOLEAN result; result = FALSE; driverRegistryKey = NULL; i = 0; // // Allocate space for the filter store. // filterStore = RCAST(ExAllocatePoolWithTag(NonPagedPool, FILTER_STORE_SIZE(this->filtersCount), FILTER_INFO_TAG)); if (filterStore == NULL) { DBGPRINT("StringFilters!SaveFilters: Failed to allocate space for the filter store with size %i.", this->filtersCount); goto Exit; } memset(filterStore, 0, sizeof(filterStore)); // // Initialize basic members. // filterStore->FilterCount = this->filtersCount; // // Acquire a shared lock to iterate filters. // FltAcquirePushLockShared(&this->filtersLock); // // Iterate filters for a match. // if (this->filtersHead) { currentFilter = RCAST(this->filtersHead->ListEntry.Flink); while (currentFilter && currentFilter != this->filtersHead) { memcpy_s(&filterStore->Filters[i], sizeof(FILTER_INFO), ¤tFilter->Filter, sizeof(FILTER_INFO)); i++; currentFilter = RCAST(currentFilter->ListEntry.Flink); } } FltReleasePushLock(&this->filtersLock); // // Open the driver's registry key. // InitializeObjectAttributes(&driverRegistryAttributes, &this->driverRegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwOpenKey(&driverRegistryKey, KEY_ALL_ACCESS, &driverRegistryAttributes); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("StringFilters!SaveFilters: Failed to open driver registry key with status 0x%X.", status); goto Exit; } // // Write the current filters. // status = ZwSetValueKey(driverRegistryKey, &this->filterStoreValueName, 0, REG_BINARY, filterStore, FILTER_STORE_SIZE(this->filtersCount)); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("StringFilters!SaveFilters: Failed to write filter store to driver registry key with status 0x%X.", status); goto Exit; } result = TRUE; Exit: if (filterStore) { ExFreePoolWithTag(filterStore, FILTER_INFO_TAG); } if (driverRegistryKey) { ZwClose(driverRegistryKey); } return result; } /** Restore filters from the registry. @return Whether or not the restoration was successful. */ BOOLEAN StringFilters::RestoreFilters ( VOID ) { OBJECT_ATTRIBUTES driverRegistryAttributes; NTSTATUS status; HANDLE driverRegistryKey; ULONG filterStorePartialSize; PFILTER_STORE filterStore; PKEY_VALUE_PARTIAL_INFORMATION filterStorePartial; ULONG i; BOOLEAN result; result = FALSE; i = 0; filterStorePartial = NULL; filterStore = NULL; // // Open the driver's registry key. // InitializeObjectAttributes(&driverRegistryAttributes, &this->driverRegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwOpenKey(&driverRegistryKey, KEY_ALL_ACCESS, &driverRegistryAttributes); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("StringFilters!RestoreFilters: Failed to open driver registry key with status 0x%X.", status); goto Exit; } // // Read the size of the FilterStore. // status = ZwQueryValueKey(driverRegistryKey, &this->filterStoreValueName, KeyValuePartialInformation, NULL, 0, &filterStorePartialSize); if (status != STATUS_BUFFER_TOO_SMALL) { DBGPRINT("StringFilters!RestoreFilters: Failed to query filter store size with status 0x%X.", status); goto Exit; } // // Allocate space for the FilterStore partial struct and query the actual value. // filterStorePartial = RCAST(ExAllocatePoolWithTag(NonPagedPool, filterStorePartialSize, FILTER_INFO_TAG)); status = ZwQueryValueKey(driverRegistryKey, &this->filterStoreValueName, KeyValuePartialInformation, filterStorePartial, filterStorePartialSize, &filterStorePartialSize); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("StringFilters!RestoreFilters: Failed to query filter store with status 0x%X.", status); goto Exit; } // // Grab the filter store from the data member of the partial struct. // filterStore = RCAST(filterStorePartial->Data); // // Add the filters. // for (i = 0; i < filterStore->FilterCount; i++) { this->AddFilter(filterStore->Filters[i].MatchString, filterStore->Filters[i].Flags, FALSE); } result = TRUE; Exit: if (driverRegistryKey) { ZwClose(driverRegistryKey); } if (filterStorePartial) { ExFreePoolWithTag(filterStorePartial, FILTER_INFO_TAG); } return result; } ================================================ FILE: PeaceMaker Kernel/StringFilters.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "shared.h" typedef struct FilterInfoLinked { LIST_ENTRY ListEntry; // The list entry used to iterate multiple filters. FILTER_INFO Filter; // The filter itself. } FILTER_INFO_LINKED, *PFILTER_INFO_LINKED; typedef struct FilterStore { ULONG FilterCount; // Number of filters in the following array. FILTER_INFO Filters[1]; // Dynamically-sized array based on FilterCount member. } FILTER_STORE, *PFILTER_STORE; #define FILTER_STORE_SIZE(filterCount) sizeof(FILTER_STORE) + (sizeof(FILTER_INFO) * (filterCount - 1)) typedef class StringFilters { PFILTER_INFO_LINKED filtersHead; // The linked list of filters. EX_PUSH_LOCK filtersLock; // The lock protecting the linked list of filters. BOOLEAN destroying; // This boolean indicates to functions that a lock should not be held as we are in the process of destruction. UNICODE_STRING driverRegistryPath; // Used for filter persistence across reboots. UNICODE_STRING filterStoreValueName; // Used for filter persistence across reboots. STRING_FILTER_TYPE filterType; // What type of filter this class stores. public: StringFilters( _In_ STRING_FILTER_TYPE FilterType, _In_ PUNICODE_STRING RegistryPath, _In_ CONST WCHAR* FilterStoreName ); ~StringFilters(); ULONG AddFilter( _In_ WCHAR* MatchString, _In_ ULONG OperationFlag, _In_ BOOLEAN SaveFilters = TRUE ); BOOLEAN RemoveFilter( _In_ ULONG FilterId ); ULONG GetFilters( _In_ ULONG SkipFilters, _Inout_ PFILTER_INFO Filters, _In_ ULONG FiltersSize ); BOOLEAN MatchesFilter( _In_ WCHAR* StrToCmp, _In_ ULONG OperationFlag ); BOOLEAN SaveFilters( VOID ); BOOLEAN RestoreFilters( VOID ); ULONG filtersCount; // Count of filters in the linked-list. } STRING_FILTERS, *PSTRING_FILTERS; #define FILTER_INFO_TAG 'iFmP' ================================================ FILE: PeaceMaker Kernel/TamperGuard.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "TamperGuard.h" HANDLE TamperGuard::ProtectedProcessId; /** Initialize tamper protection. This constructor will create appropriate callbacks. @param InitializeStatus - Status of initialization. */ TamperGuard::TamperGuard ( _Out_ NTSTATUS* InitializeStatus ) { UNICODE_STRING CallbackAltitude; RtlInitUnicodeString(&CallbackAltitude, FILTER_ALTITUDE); ObRegistrationInformation.Version = OB_FLT_REGISTRATION_VERSION; ObRegistrationInformation.OperationRegistrationCount = 2; ObRegistrationInformation.Altitude = CallbackAltitude; ObRegistrationInformation.RegistrationContext = NULL; ObRegistrationInformation.OperationRegistration = ObOperationRegistration; // // We want to protect both the process and the threads of the protected process. // ObOperationRegistration[0].ObjectType = PsProcessType; ObOperationRegistration[0].Operations |= OB_OPERATION_HANDLE_CREATE; ObOperationRegistration[0].Operations |= OB_OPERATION_HANDLE_DUPLICATE; ObOperationRegistration[0].PreOperation = PreOperationCallback; ObOperationRegistration[1].ObjectType = PsThreadType; ObOperationRegistration[1].Operations |= OB_OPERATION_HANDLE_CREATE; ObOperationRegistration[1].Operations |= OB_OPERATION_HANDLE_DUPLICATE; ObOperationRegistration[1].PreOperation = PreOperationCallback; // // Actually register the callbacks. // *InitializeStatus = ObRegisterCallbacks(&ObRegistrationInformation, &RegistrationHandle); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("TamperGuard!TamperGuard: Failed to register object callbacks with status 0x%X.", *InitializeStatus); } } /** Destruct tamper guard members. */ TamperGuard::~TamperGuard ( VOID ) { ObUnRegisterCallbacks(RegistrationHandle); } /** Update the process to protect. @param NewProcessId - The new process to protect from tampering. */ VOID TamperGuard::UpdateProtectedProcess ( _In_ HANDLE NewProcessId ) { TamperGuard::ProtectedProcessId = NewProcessId; } /** Filter for certain operations on a protected process. @param RegistrationContext - Always NULL. @param OperationInformation - Information about the current operation. */ OB_PREOP_CALLBACK_STATUS TamperGuard::PreOperationCallback ( _In_ PVOID RegistrationContext, _In_ POB_PRE_OPERATION_INFORMATION OperationInformation ) { HANDLE callerProcessId; HANDLE targetProcessId; ACCESS_MASK targetAccessMask; UNREFERENCED_PARAMETER(RegistrationContext); callerProcessId = NULL; targetProcessId = NULL; targetAccessMask = NULL; callerProcessId = PsGetCurrentProcessId(); // // Grab the appropriate process IDs based on the operation object type. // if (OperationInformation->ObjectType == *PsProcessType) { targetProcessId = PsGetProcessId(RCAST(OperationInformation->Object)); targetAccessMask = PROCESS_TERMINATE; } else if (OperationInformation->ObjectType == *PsThreadType) { targetProcessId = PsGetThreadProcessId(RCAST(OperationInformation->Object)); targetAccessMask = THREAD_TERMINATE; } // // If this is an operation on your own process, ignore it. // if (callerProcessId == targetProcessId) { return OB_PREOP_SUCCESS; } // // If the target process isn't the process we're protecting, no issue. // if (targetProcessId != TamperGuard::ProtectedProcessId) { return OB_PREOP_SUCCESS; } // // Strip the proper desired access ACCESS_MASK. // switch (OperationInformation->Operation) { case OB_OPERATION_HANDLE_CREATE: OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~targetAccessMask; break; case OB_OPERATION_HANDLE_DUPLICATE: OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess &= ~targetAccessMask; break; } DBGPRINT("TamperGuard!PreOperationCallback: Stripped process 0x%X terminate handle on protected process.", callerProcessId); return OB_PREOP_SUCCESS; } ================================================ FILE: PeaceMaker Kernel/TamperGuard.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "shared.h" typedef class TamperGuard { static OB_PREOP_CALLBACK_STATUS PreOperationCallback(_In_ PVOID RegistrationContext, _In_ POB_PRE_OPERATION_INFORMATION OperationInformation ); OB_CALLBACK_REGISTRATION ObRegistrationInformation; OB_OPERATION_REGISTRATION ObOperationRegistration[2]; PVOID RegistrationHandle; static HANDLE ProtectedProcessId; public: TamperGuard(_Out_ NTSTATUS* InitializeStatus ); ~TamperGuard(VOID); VOID UpdateProtectedProcess(_In_ HANDLE NewProcessId ); } TAMPER_GUARD, *PTAMPER_GUARD; #define PROCESS_TERMINATE (0x0001) #define THREAD_TERMINATE (0x0001) ================================================ FILE: PeaceMaker Kernel/ThreadFilter.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "ThreadFilter.h" PDETECTION_LOGIC ThreadFilter::Detector; // Detection utility. STACK_WALKER ThreadFilter::Walker; // Stack walking utility. /** Initialize thread notify filters to detect manually mapped threads. @param DetectionLogic - Detection instance used to analyze untrusted operations. @param InitializeStatus - Status of initialization. */ ThreadFilter::ThreadFilter ( _In_ PDETECTION_LOGIC DetectionLogic, _Inout_ NTSTATUS* InitializeStatus ) { ThreadFilter::Detector = DetectionLogic; // // Create a thread notify routine. // *InitializeStatus = PsSetCreateThreadNotifyRoutine(ThreadFilter::ThreadNotifyRoutine); if (NT_SUCCESS(*InitializeStatus) == FALSE) { DBGPRINT("ThreadFilter!ThreadFilter: Failed to create thread notify routine with status 0x%X.", *InitializeStatus); } } /** Teardown dynamic components of the thread filter. */ ThreadFilter::~ThreadFilter ( VOID ) { PsRemoveCreateThreadNotifyRoutine(ThreadFilter::ThreadNotifyRoutine); } /** Query a thread's start address for validation. @param ThreadId - The target thread's ID. @return The start address of the target thread. */ PVOID ThreadFilter::GetThreadStartAddress ( _In_ HANDLE ThreadId ) { NTSTATUS status; PVOID startAddress; PETHREAD threadObject; HANDLE threadHandle; ULONG returnLength; startAddress = NULL; threadHandle = NULL; // // First look up the PETHREAD of the thread. // status = PsLookupThreadByThreadId(ThreadId, &threadObject); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ThreadFilter!GetThreadStartAddress: Failed to lookup thread 0x%X by its ID.", ThreadId); goto Exit; } // // Open a handle to the thread. // status = ObOpenObjectByPointer(threadObject, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, GENERIC_ALL, *PsThreadType, KernelMode, &threadHandle); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ThreadFilter!GetThreadStartAddress: Failed to open handle to process with status 0x%X.", status); goto Exit; } // // Query the thread's start address. // status = NtQueryInformationThread(threadHandle, ThreadQuerySetWin32StartAddress, &startAddress, sizeof(startAddress), &returnLength); if (NT_SUCCESS(status) == FALSE) { DBGPRINT("ThreadFilter!GetThreadStartAddress: Failed to query thread start address with status 0x%X.", status); goto Exit; } Exit: if (threadHandle != NULL) { ZwClose(threadHandle); } return startAddress; } /** Called when a new thread is created. Ensure the thread is legit. @param ProcessId - The process ID of the process receiving the new thread. @param ThreadId - The thread ID of the new thread. @param Create - Whether or not this is termination of a thread or creation. */ VOID ThreadFilter::ThreadNotifyRoutine ( _In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _In_ BOOLEAN Create ) { ULONG processThreadCount; PVOID threadStartAddress; PSTACK_RETURN_INFO threadCreateStack; ULONG threadCreateStackSize; PUNICODE_STRING threadCallerName; PUNICODE_STRING threadTargetName; threadCreateStack = NULL; threadCreateStackSize = 20; threadCallerName = NULL; threadTargetName = NULL; // // We don't really care about thread termination or if the thread is kernel-mode. // if (Create == FALSE || ExGetPreviousMode() == KernelMode) { return; } // // If we can't find the process or it's the first thread of the process, skip it. // if (ImageHistoryFilter::AddProcessThreadCount(ProcessId, &processThreadCount) == FALSE || processThreadCount <= 1) { return; } // // Walk the stack. // ThreadFilter::Walker.WalkAndResolveStack(&threadCreateStack, &threadCreateStackSize, STACK_HISTORY_TAG); // // Grab the name of the caller. // if (ImageHistoryFilter::GetProcessImageFileName(PsGetCurrentProcessId(), &threadCallerName) == FALSE) { goto Exit; } threadTargetName = threadCallerName; // // We only need to resolve again if the target process is a different than the caller. // if (PsGetCurrentProcessId() != ProcessId) { // // Grab the name of the target. // if (ImageHistoryFilter::GetProcessImageFileName(ProcessId, &threadTargetName) == FALSE) { goto Exit; } } // // Grab the start address of the thread. // threadStartAddress = ThreadFilter::GetThreadStartAddress(ThreadId); // // Audit the target's start address. // ThreadFilter::Detector->AuditUserPointer(ThreadCreate, threadStartAddress, PsGetCurrentProcessId(), threadCallerName, threadTargetName, threadCreateStack, threadCreateStackSize); // // Audit the caller's stack. // ThreadFilter::Detector->AuditUserStackWalk(ThreadCreate, PsGetCurrentProcessId(), threadCallerName, threadTargetName, threadCreateStack, threadCreateStackSize); // // Check if this is a remote operation. // ThreadFilter::Detector->AuditCallerProcessId(ThreadCreate, PsGetCurrentProcessId(), ProcessId, threadCallerName, threadTargetName, threadCreateStack, threadCreateStackSize); Exit: if (threadCreateStack != NULL) { ExFreePoolWithTag(threadCreateStack, STACK_HISTORY_TAG); } if (threadCallerName != NULL) { ExFreePoolWithTag(threadCallerName, IMAGE_NAME_TAG); } if (threadCallerName != threadTargetName && threadTargetName != NULL) { ExFreePoolWithTag(threadTargetName, IMAGE_NAME_TAG); } } ================================================ FILE: PeaceMaker Kernel/ThreadFilter.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "common.h" #include "DetectionLogic.h" #include "ImageHistoryFilter.h" #include "StackWalker.h" typedef class ThreadFilter { static PDETECTION_LOGIC Detector; // Detection utility. static STACK_WALKER Walker; // Stack walking utility. static PVOID GetThreadStartAddress( _In_ HANDLE ThreadId ); static VOID ThreadNotifyRoutine( HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create ); public: ThreadFilter( _In_ PDETECTION_LOGIC DetectionLogic, _Inout_ NTSTATUS* InitializeStatus ); ~ThreadFilter(VOID); } THREAD_FILTER, *PTHREAD_FILTER; ================================================ FILE: PeaceMaker Kernel/common.cpp ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #include "common.h" #include "shared.h" void* __cdecl operator new(size_t size, POOL_TYPE pool, ULONG tag) { PVOID newAddress = ExAllocatePoolWithTag(pool, size, tag); // // Remove remenants from previous use. // if (newAddress) { memset(newAddress, 0, size); } return newAddress; } void __cdecl operator delete(void* p, unsigned __int64) { ExFreePool(p); } PPEB PsGetProcessPeb(IN PEPROCESS Process) { UNICODE_STRING funcName; typedef PPEB(NTAPI* PsGetProcessPeb_t)(PEPROCESS Process); static PsGetProcessPeb_t fPsGetProcessPeb = NULL; if (fPsGetProcessPeb == NULL) { RtlInitUnicodeString(&funcName, L"PsGetProcessPeb"); fPsGetProcessPeb = RCAST(MmGetSystemRoutineAddress(&funcName)); } return fPsGetProcessPeb(Process); } NTSTATUS NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength) { UNICODE_STRING funcName; typedef NTSTATUS(NTAPI* NtQueryInformationProcess_t)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); static NtQueryInformationProcess_t fNtQueryInformationProcess = NULL; if (fNtQueryInformationProcess == NULL) { RtlInitUnicodeString(&funcName, L"ZwQueryInformationProcess"); fNtQueryInformationProcess = RCAST(MmGetSystemRoutineAddress(&funcName)); } return fNtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength); } NTSTATUS NtQueryInformationThread(_In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass, _Out_ PVOID ThreadInformation, _In_ ULONG ThreadInformationLength, _Out_ PULONG ReturnLength) { UNICODE_STRING funcName; typedef NTSTATUS(NTAPI * NtQueryInformationThread_t)(_In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass, _Out_ PVOID ThreadInformation, _In_ ULONG ThreadInformationLength, _Out_ PULONG ReturnLength); static NtQueryInformationThread_t fNtQueryInformationThread = NULL; if (fNtQueryInformationThread == NULL) { RtlInitUnicodeString(&funcName, L"ZwQueryInformationThread"); fNtQueryInformationThread = RCAST(MmGetSystemRoutineAddress(&funcName)); } return fNtQueryInformationThread(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength, ReturnLength); } ================================================ FILE: PeaceMaker Kernel/common.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include "ntdef.h" #define MAX_PATH 260 #define DBGPRINT(msg, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, msg"\n", __VA_ARGS__) void* __cdecl operator new(size_t size, POOL_TYPE pool, ULONG tag = 0); void __cdecl operator delete(void* p, unsigned __int64); #define FILTER_ALTITUDE L"321410" #define TICKSPERSEC 10000000 #define SECSPERDAY 86400 #define SECS_1601_TO_1970 ((369 * 365 + 89) * (ULONGLONG)SECSPERDAY) PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process); NTSTATUS NTAPI NtQueryInformationProcess(_In_ HANDLE ProcessHandle, _In_ PROCESSINFOCLASS ProcessInformationClass, _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength); NTSTATUS NTAPI NtQueryInformationThread(_In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass, _Out_ PVOID ThreadInformation, _In_ ULONG ThreadInformationLength, _Out_ PULONG ReturnLength); ================================================ FILE: PeaceMaker Kernel/ntdef.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #include #include #include typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, * PLDR_MODULE; typedef struct _PEB_LDR_DATA { ULONG Length; UCHAR Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; } PEB_LDR_DATA, * PPEB_LDR_DATA; typedef struct _PEB { UCHAR InheritedAddressSpace; UCHAR ReadImageFileExecOptions; UCHAR BeingDebugged; UCHAR BitField; ULONG ImageUsesLargePages : 1; ULONG IsProtectedProcess : 1; ULONG IsLegacyProcess : 1; ULONG IsImageDynamicallyRelocated : 1; ULONG SpareBits : 4; PVOID Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA Ldr; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID AtlThunkSListPtr; PVOID IFEOKey; ULONG CrossProcessFlags; ULONG ProcessInJob : 1; ULONG ProcessInitializing : 1; ULONG ReservedBits0 : 30; union { PVOID KernelCallbackTable; PVOID UserSharedInfoPtr; }; ULONG SystemReserved[1]; ULONG SpareUlong; PVOID FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[2]; PVOID ReadOnlySharedMemoryBase; PVOID HotpatchInformation; VOID** ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; VOID** ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; ULONG GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; SHORT OSBuildNumber; SHORT OSCSDVersion; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; ULONG ImageProcessAffinityMask; ULONG GdiHandleBuffer[34]; PVOID PostProcessInitRoutine; PVOID TlsExpansionBitmap; ULONG TlsExpansionBitmapBits[32]; ULONG SessionId; ULARGE_INTEGER AppCompatFlags; ULARGE_INTEGER AppCompatFlagsUser; PVOID pShimData; PVOID AppCompatInfo; UNICODE_STRING CSDVersion; PVOID ActivationContextData; PVOID ProcessAssemblyStorageMap; PVOID SystemDefaultActivationContextData; PVOID SystemAssemblyStorageMap; ULONG MinimumStackCommit; PVOID FlsCallback; LIST_ENTRY FlsListHead; PVOID FlsBitmap; ULONG FlsBitmapBits[4]; ULONG FlsHighIndex; PVOID WerRegistrationData; PVOID WerShipAssertPtr; } PEB, * PPEB; #pragma warning(disable : 4201) typedef struct _KNONVOLATILE_CONTEXT_POINTERS { union { PM128A FloatingContext[16]; struct { PM128A Xmm0; PM128A Xmm1; PM128A Xmm2; PM128A Xmm3; PM128A Xmm4; PM128A Xmm5; PM128A Xmm6; PM128A Xmm7; PM128A Xmm8; PM128A Xmm9; PM128A Xmm10; PM128A Xmm11; PM128A Xmm12; PM128A Xmm13; PM128A Xmm14; PM128A Xmm15; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; union { PDWORD64 IntegerContext[16]; struct { PDWORD64 Rax; PDWORD64 Rcx; PDWORD64 Rdx; PDWORD64 Rbx; PDWORD64 Rsp; PDWORD64 Rbp; PDWORD64 Rsi; PDWORD64 Rdi; PDWORD64 R8; PDWORD64 R9; PDWORD64 R10; PDWORD64 R11; PDWORD64 R12; PDWORD64 R13; PDWORD64 R14; PDWORD64 R15; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME2; } KNONVOLATILE_CONTEXT_POINTERS, * PKNONVOLATILE_CONTEXT_POINTERS; #pragma warning(default : 4201) typedef struct _SCOPE_TABLE_AMD64 { DWORD Count; struct { DWORD BeginAddress; DWORD EndAddress; DWORD HandlerAddress; DWORD JumpTarget; } ScopeRecord[1]; } SCOPE_TABLE_AMD64, * PSCOPE_TABLE_AMD64; typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY RUNTIME_FUNCTION, * PRUNTIME_FUNCTION; typedef SCOPE_TABLE_AMD64 SCOPE_TABLE, * PSCOPE_TABLE; #define UNWIND_HISTORY_TABLE_SIZE 12 typedef struct _UNWIND_HISTORY_TABLE_ENTRY { DWORD64 ImageBase; PRUNTIME_FUNCTION FunctionEntry; } UNWIND_HISTORY_TABLE_ENTRY, * PUNWIND_HISTORY_TABLE_ENTRY; typedef struct _UNWIND_HISTORY_TABLE { DWORD Count; UCHAR LocalHint; UCHAR GlobalHint; UCHAR Search; UCHAR Once; DWORD64 LowAddress; DWORD64 HighAddress; UNWIND_HISTORY_TABLE_ENTRY Entry[UNWIND_HISTORY_TABLE_SIZE]; } UNWIND_HISTORY_TABLE, * PUNWIND_HISTORY_TABLE; #define UNW_FLAG_NHANDLER 0x0 #define UNW_FLAG_EHANDLER 0x1 #define UNW_FLAG_UHANDLER 0x2 #define UNW_FLAG_CHAININFO 0x4 #define MEM_IMAGE 0x1000000 #define MemoryWorkingSetInformation 0x1 #define MemoryMappedFilenameInformation 0x2 #define MemoryRegionInformation 0x3 #define MemoryWorkingSetExInformation 0x4 #define MemorySharedCommitInformation 0x5 #define MemoryImageInformation 0x6 #define MemoryRegionInformationEx 0x7 #define MemoryPrivilegedBasicInformation 0x8 #define MemoryEnclaveImageInformation 0x9 #define MemoryBasicInformationCapped 0xA ================================================ FILE: PeaceMaker Kernel/shared.h ================================================ /* * This file is subject to the terms and conditions defined in * file 'LICENSE', which is part of this source code package. * * COPYRIGHT Bill Demirkapi 2020 */ #pragma once #if _KERNEL_MODE == 1 #include #else #include #endif #define GLOBAL_NAME L"\\\\.\\PeaceMaker" #define NT_DEVICE_NAME L"\\Device\\PeaceMaker" #define DOS_DEVICE_NAME L"\\DosDevices\\PeaceMaker" #define IOCTL_ALERTS_QUEUED CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x1, METHOD_BUFFERED, FILE_WRITE_DATA) #define IOCTL_POP_ALERT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x2, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_GET_PROCESSES CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x3, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_GET_PROCESS_DETAILED CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x4, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_ADD_FILTER CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x5, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_LIST_FILTERS CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x6, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_GET_IMAGE_DETAILED CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x7, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_GET_PROCESS_SIZES CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x8, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_GET_GLOBAL_SIZES CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x9, METHOD_BUFFERED, FILE_WRITE_DATA) #define IOCTL_DELETE_FILTER CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0x10, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define RCAST reinterpret_cast #define SCAST static_cast #define CCAST const_cast #define CONFIG_FILE_NAME "peacemaker.cfg" // // Maximum amount of STACK_RETURN_INFO to have in the process execution stack return history. // #define MAX_STACK_RETURN_HISTORY 30 typedef struct ProcessSummaryEntry { HANDLE ProcessId; // The process id of the executed process. WCHAR ImageFileName[MAX_PATH]; // The image file name of the executed process. ULONGLONG EpochExecutionTime; // Process execution time in seconds since 1970. BOOLEAN ProcessTerminated; // Whether or not the process has terminated. } PROCESS_SUMMARY_ENTRY, * PPROCESS_SUMMARY_ENTRY; typedef struct StackReturnInfo { PVOID RawAddress; // The raw return address. BOOLEAN MemoryInModule; // Whether or not the address is in a loaded module. BOOLEAN ExecutableMemory; // Whether or not the address is in executable memory. WCHAR BinaryPath[MAX_PATH]; // The path of the binary this return address specifies. ULONG64 BinaryOffset; // The offset in the binary that the return address refers to. } STACK_RETURN_INFO, * PSTACK_RETURN_INFO; typedef struct ProcessSummaryRequest { ULONG SkipCount; // How many processes to skip. ULONG ProcessHistorySize; // Size of the variable-length ProcessHistory array. PROCESS_SUMMARY_ENTRY ProcessHistory[1]; // Variable-length array of process history summaries. } PROCESS_SUMMARY_REQUEST, *PPROCESS_SUMMARY_REQUEST; #define MAX_PROCESS_SUMMARY_REQUEST_SIZE_RAW(size) sizeof(PROCESS_SUMMARY_REQUEST) + (size - 1) * sizeof(PROCESS_SUMMARY_ENTRY) #define MAX_PROCESS_SUMMARY_REQUEST_SIZE(summaryRequest) MAX_PROCESS_SUMMARY_REQUEST_SIZE_RAW(summaryRequest->ProcessHistorySize) typedef struct ImageSummary { WCHAR ImagePath[MAX_PATH]; // The path to the image. Populated by the driver. ULONG StackSize; // The size of the stack history. } IMAGE_SUMMARY, *PIMAGE_SUMMARY; typedef struct ProcessDetailedRequest { HANDLE ProcessId; // The process id of the executed process. ULONGLONG EpochExecutionTime; // Process execution time in seconds since 1970. BOOLEAN Populated; // Whether not this structure was populated (the process was found). WCHAR ProcessPath[MAX_PATH]; // The image file name of the executed process. HANDLE CallerProcessId; // The process id of the caller process. WCHAR CallerProcessPath[MAX_PATH]; // OPTIONAL: The image file name of the caller process. HANDLE ParentProcessId; // The process id of the alleged parent process. WCHAR ParentProcessPath[MAX_PATH]; // OPTIONAL: The image file name of the alleged parent process. WCHAR ProcessCommandLine[MAX_PATH]; // The process command line. ULONG ImageSummarySize; // The length of the ImageSummary array. PIMAGE_SUMMARY ImageSummary; // Variable-length array of image summaries. ULONG StackHistorySize; // The length of the StackHistory array. PSTACK_RETURN_INFO StackHistory; // Variable-length array of stack history. } PROCESS_DETAILED_REQUEST, *PPROCESS_DETAILED_REQUEST; typedef struct ImageDetailedRequest { HANDLE ProcessId; // The process id of the executed process. ULONGLONG EpochExecutionTime; // Process execution time in seconds since 1970. BOOLEAN Populated; // Whether not this structure was populated (the image was found). ULONG ImageIndex; // The index of the target image. Must not be larger than the process images list size. WCHAR ImagePath[MAX_PATH]; // The path to the image. Populated by the driver. ULONG StackHistorySize; // The length of the StackHistory array. STACK_RETURN_INFO StackHistory[1]; // Variable-length array of stack history. Populated by the driver. } IMAGE_DETAILED_REQUEST, *PIMAGE_DETAILED_REQUEST; #define MAX_IMAGE_DETAILED_REQUEST_SIZE_RAW(size) sizeof(IMAGE_DETAILED_REQUEST) + ((size - 1) * sizeof(STACK_RETURN_INFO)) #define MAX_IMAGE_DETAILED_REQUEST_SIZE(detailedRequest) MAX_IMAGE_DETAILED_REQUEST_SIZE_RAW(detailedRequest->StackHistorySize) typedef struct ProcessSizesRequest { HANDLE ProcessId; // The process id of the executed process. ULONGLONG EpochExecutionTime; // Process execution time in seconds since 1970. ULONG ProcessSize; // The number of loaded processes. ULONG ImageSize; // The number of loaded images in the process. ULONG StackSize; // The number of stack return entries in the stack history for the process. } PROCESS_SIZES_REQUEST, *PPROCESS_SIZES_REQUEST; typedef enum FilterType { FilesystemFilter, RegistryFilter } STRING_FILTER_TYPE, *PSTRING_FILTER_TYPE; // // Bitwise flags used for filtering for specific filters. // #define FILTER_FLAG_DELETE 0x1 #define FILTER_FLAG_WRITE 0x2 #define FILTER_FLAG_EXECUTE 0x4 #define FlagOn(_F,_SF) ((_F) & (_SF)) #define FILTER_FLAG_ALL (FILTER_FLAG_DELETE | FILTER_FLAG_WRITE | FILTER_FLAG_EXECUTE) typedef struct FilterInfo { ULONG Id; // Unique ID of the filter used to remove entries. STRING_FILTER_TYPE Type; // The general target of the filter (Filesystem/Registry). WCHAR MatchString[MAX_PATH]; // The string to match against. Always lowercase. ULONG MatchStringSize; // The length of the match string. ULONG Flags; // Used by MatchesFilter to determine if should use filter. Caller specifies the filters they want via flag. } FILTER_INFO, *PFILTER_INFO; typedef struct StringFilterRequest { STRING_FILTER_TYPE FilterType; // The general target of the filter (Filesystem/Registry). FILTER_INFO Filter; } STRING_FILTER_REQUEST, * PSTRING_FILTER_REQUEST; typedef struct ListFiltersRequest { STRING_FILTER_TYPE FilterType; // The general target of the filter (Filesystem/Registry). ULONG TotalFilters; // Populated by the IOCTL request. The number of total filters there really are. ULONG SkipFilters; // Number of filters to skip. ULONG CopiedFilters; // Populated by the IOCTL request. Number of filters actually copied. FILTER_INFO Filters[10]; // You can request more than 10 filters via multiple requests. } LIST_FILTERS_REQUEST, *PLIST_FILTERS_REQUEST; typedef enum AlertType { StackViolation, FilterViolation, ParentProcessIdSpoofing, RemoteThreadCreation } ALERT_TYPE; typedef enum DetectionSource { ProcessCreate, ImageLoad, RegistryFilterMatch, FileFilterMatch, ThreadCreate } DETECTION_SOURCE; typedef struct BaseAlertInfo { LIST_ENTRY Entry; // The LIST_ENTRY details. ULONG AlertSize; // The size (in bytes) of the structure. DETECTION_SOURCE AlertSource; // The source of the alert. ALERT_TYPE AlertType; // The type of alert. HANDLE SourceId; // The process id of the source of the alert. WCHAR SourcePath[MAX_PATH]; // The path to the source. WCHAR TargetPath[MAX_PATH]; // The path to the target. } BASE_ALERT_INFO, * PBASE_ALERT_INFO; typedef struct StackViolationAlert { BASE_ALERT_INFO AlertInformation; // Basic alert information. PVOID ViolatingAddress; // The specific address that was detected out-of-bounds. ULONG StackHistorySize; // The length of the StackHistory array. STACK_RETURN_INFO StackHistory[1]; // Variable-length array of stack history. } STACK_VIOLATION_ALERT, * PSTACK_VIOLATION_ALERT; // // How many bytes the user-mode caller must supply as its output buffer. // #define MAX_STACK_VIOLATION_ALERT_SIZE sizeof(STACK_VIOLATION_ALERT) + (MAX_STACK_RETURN_HISTORY-1) * sizeof(STACK_RETURN_INFO) typedef struct FilterViolationAlert { BASE_ALERT_INFO AlertInformation; // Basic alert information. ULONG StackHistorySize; // The length of the StackHistory array. STACK_RETURN_INFO StackHistory[1]; // Variable-length array of stack history. } FILTER_VIOLATION_ALERT, * PFILTER_VIOLATION_ALERT; // // How many bytes the user-mode caller must supply as its output buffer. // #define MAX_FILTER_VIOLATION_ALERT_SIZE sizeof(FILTER_VIOLATION_ALERT) + (MAX_STACK_RETURN_HISTORY-1) * sizeof(STACK_RETURN_INFO) typedef struct RemoteOperationAlert { BASE_ALERT_INFO AlertInformation; // Basic alert information. HANDLE RemoteTargetId; // Process ID of the target process. ULONG StackHistorySize; // The length of the StackHistory array. STACK_RETURN_INFO StackHistory[1]; // Variable-length array of stack history. } REMOTE_OPERATION_ALERT, *PREMOTE_OPERATION_ALERT; // // How many bytes the user-mode caller must supply as its output buffer. // #define MAX_REMOTE_OPERATION_ALERT_SIZE sizeof(REMOTE_OPERATION_ALERT) + (MAX_STACK_RETURN_HISTORY-1) * sizeof(STACK_RETURN_INFO) typedef struct GlobalSizes { ULONG64 ProcessHistorySize; ULONG64 FilesystemFilterSize; ULONG64 RegistryFilterSize; } GLOBAL_SIZES, *PGLOBAL_SIZES; typedef struct DeleteFilterRequest { ULONG FilterId; // Unique ID of the filter used to remove entries. STRING_FILTER_TYPE FilterType; // The general target of the filter (Filesystem/Registry). BOOLEAN Deleted; // Whether or not the filter was deleted. } DELETE_FILTER_REQUEST, *PDELETE_FILTER_REQUEST; ================================================ FILE: PeaceMaker Kernel.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 Debug ARM Release ARM Debug ARM64 Release ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F} {f2f62967-0815-4fd7-9b86-6eedcac766eb} v4.5 12.0 Debug Win32 PeaceMaker Kernel PeaceMaker Kernel Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM Windows10 true WindowsKernelModeDriver10.0 Driver WDM Windows10 false WindowsKernelModeDriver10.0 Driver WDM DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger false DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) "$(OutDir)signbinary.bat" "$(TargetPath)" fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) fltmgr.lib;%(AdditionalDependencies) ================================================ FILE: PeaceMaker.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29215.179 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PeaceMaker Kernel", "PeaceMaker Kernel\PeaceMaker Kernel.vcxproj", "{5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PeaceMaker CLI", "PeaceMaker CLI\PeaceMaker CLI.vcxproj", "{A287D40E-AB7B-4FE9-AA84-44114766C79D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|ARM = Release|ARM Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|ARM.ActiveCfg = Debug|ARM {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|ARM.Build.0 = Debug|ARM {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|ARM.Deploy.0 = Debug|ARM {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|ARM64.ActiveCfg = Debug|ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|ARM64.Build.0 = Debug|ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|ARM64.Deploy.0 = Debug|ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|x64.ActiveCfg = Debug|x64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|x64.Build.0 = Debug|x64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|x64.Deploy.0 = Debug|x64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|x86.ActiveCfg = Debug|Win32 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|x86.Build.0 = Debug|Win32 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Debug|x86.Deploy.0 = Debug|Win32 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|ARM.ActiveCfg = Release|ARM {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|ARM.Build.0 = Release|ARM {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|ARM.Deploy.0 = Release|ARM {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|ARM64.ActiveCfg = Release|ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|ARM64.Build.0 = Release|ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|ARM64.Deploy.0 = Release|ARM64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|x64.ActiveCfg = Release|x64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|x64.Build.0 = Release|x64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|x64.Deploy.0 = Release|x64 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|x86.ActiveCfg = Release|Win32 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|x86.Build.0 = Release|Win32 {5A9C319B-EDBD-4E53-BFEE-2FBD7BAE767F}.Release|x86.Deploy.0 = Release|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Debug|ARM.ActiveCfg = Debug|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Debug|ARM64.ActiveCfg = Debug|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Debug|x64.ActiveCfg = Debug|x64 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Debug|x64.Build.0 = Debug|x64 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Debug|x86.ActiveCfg = Debug|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Debug|x86.Build.0 = Debug|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Release|ARM.ActiveCfg = Release|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Release|ARM64.ActiveCfg = Release|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Release|x64.ActiveCfg = Release|x64 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Release|x64.Build.0 = Release|x64 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Release|x86.ActiveCfg = Release|Win32 {A287D40E-AB7B-4FE9-AA84-44114766C79D}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {479F6A46-1341-4A08-A813-8E0B2C36CFC9} EndGlobalSection EndGlobal ================================================ FILE: PeaceMakerGUI/AssetResources.qrc ================================================ assets/PeaceMakerLogo.png assets/AlertsTabActive.png assets/AlertsTabInactive.png assets/ConfigTabActive.png assets/ConfigTabInactive.png assets/FiltersTabActive.png assets/FiltersTabInactive.png assets/ProcessesTabActive.png assets/ProcessesTabInactive.png assets/Copyright.png assets/PeaceMakerIcon.ico assets/BasicProcessInformation.png assets/CreatorStackHistory.png assets/Images.png assets/ImageStackHistory.png assets/Search.png assets/AlertDetails.png assets/AlertStackHistory.png assets/FilterContent.png assets/FilterFlags.png assets/FilterType.png assets/PendingAlertsTab.png ================================================ FILE: PeaceMakerGUI/ClickableTab.cpp ================================================ #include "ClickableTab.h" #include "mainwindow.h" ClickableTab::ClickableTab(QWidget* parent, Qt::WindowFlags f) : QLabel(parent) { std::string parentName; QWidget* currentParent; // // Find the MainWindow. // currentParent = parent; do { currentParent = currentParent->parentWidget(); parentName = currentParent->objectName().toStdString(); } while(parentName != "MainWindow"); this->mainWindow = currentParent; this->tabActive = false; } ClickableTab::~ClickableTab() {} /** * @brief ClickableTab::SwapActiveState - Swap the "state" of the tab. If was clicked, remove underline, otherwise add underline. */ void ClickableTab::SwapActiveState() { int statusPosition; std::string currentTabContent; // // These values are what we look for in the tab HTML to replace. // const std::string activeName = "Active"; const std::string inactiveName = "Inactive"; // // Get the current tab content. // currentTabContent = this->text().toStdString(); if(customText) { currentTabContent = oldText.toStdString(); customText = false; } // // Replace the active state depending on whether it was already active. // if(this->tabActive) { statusPosition = currentTabContent.find(activeName); currentTabContent.replace(statusPosition, activeName.length(), inactiveName); this->tabActive = false; for(QWidget* widget : this->associatedElements) { widget->setVisible(false); } } else { statusPosition = currentTabContent.find(inactiveName); currentTabContent.replace(statusPosition, inactiveName.length(), activeName); this->tabActive = true; for(QWidget* widget : this->associatedElements) { widget->setVisible(true); } } // // Set this new text. // this->setText(QString::fromStdString(currentTabContent)); } /** * @brief ClickableTab::AddAssociatedElement - Track associated widgets to hide/show on state changes. * @param widget - The widget associated with this tab. */ void ClickableTab::AddAssociatedElement(QWidget *widget) { this->associatedElements.push_back(widget); // // By default, widgets should be hidden. // widget->setVisible(false); } /** * @brief ClickableTab::SetCustomText - Set custom text for the tab, but store the previous text. * @param newText - The new custom text to set. */ void ClickableTab::SetCustomText(QString newText) { if(customText == false) { oldText = this->text(); this->setText(newText); customText = true; } } /** * @brief ClickableTab::mousePressEvent notifies the main window of a tab being clicked. * @param event - Details about the click event. */ void ClickableTab::mousePressEvent(QMouseEvent* event) { ((MainWindow*)this->mainWindow)->NotifyTabClick(this); } ================================================ FILE: PeaceMakerGUI/ClickableTab.h ================================================ #ifndef CLICKABLETAB_H #define CLICKABLETAB_H #include #include #include #include // // These tab types allow the MainWindow to know what was clicked. // enum TabType { AlertsTab, ProcessesTab, FiltersTab, ConfigTab }; class ClickableTab : public QLabel { Q_OBJECT QWidget* mainWindow; bool tabActive; std::vector associatedElements; QString oldText; bool customText = false; public: explicit ClickableTab(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); ~ClickableTab(); void SwapActiveState(); void AddAssociatedElement(QWidget* widget); void SetCustomText(QString newText); signals: void clicked(); protected: void mousePressEvent(QMouseEvent* event); }; #endif // CLICKABLETAB_H ================================================ FILE: PeaceMakerGUI/InvestigateProcessWindow.cpp ================================================ #include "InvestigateProcessWindow.h" #include "ui_InvestigateProcessWindow.h" /** * @brief InvestigateProcessWindow::InitializeCommonTable - Common initialization across all tables. * @param table */ void InvestigateProcessWindow::InitializeCommonTable(QTableWidget *table) { // // Set properties that are common across tables. // table->horizontalHeader()->setStretchLastSection(true); table->horizontalHeader()->setHighlightSections(false); table->verticalHeader()->setVisible(false); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->setSelectionMode(QAbstractItemView::SingleSelection); table->verticalScrollBar()->setStyleSheet("color: white;"); table->setSortingEnabled(false); table->setStyleSheet("background: white;"); table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); table->verticalHeader()->setDefaultSectionSize(10); } /** * @brief InvestigateProcessWindow::InitializeProcessInfoTable - Initialize properties for the process info table. */ void InvestigateProcessWindow::InitializeProcessInfoTable() { this->ui->ProcessInformationTable->setColumnCount(2); this->ProcessInfoTableSize = 0; AddProcessInfoItem("Process Id"); AddProcessInfoItem("Path"); AddProcessInfoItem("Command Line"); AddProcessInfoItem("Execution Time"); AddProcessInfoItem("Caller Process Id"); //AddProcessInfoItem("Caller Path"); AddProcessInfoItem("Parent Process Id"); AddProcessInfoItem("Parent Path"); this->ui->ProcessInformationTable->horizontalHeader()->hide(); this->ui->ProcessInformationTable->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); InitializeCommonTable(this->ui->ProcessInformationTable); } /** * @brief InvestigateProcessWindow::InitializeStackHistoryTable - Initialize properties for the stack history table. * @param historyTable - The stack history table to modify. */ void InvestigateProcessWindow::InitializeStackHistoryTable(QTableWidget* historyTable) { QStringList headers; historyTable->setColumnCount(1); InitializeCommonTable(historyTable); headers << "Stack Return Address"; historyTable->setHorizontalHeaderLabels(headers); historyTable->setColumnWidth(0, 300); historyTable->setWordWrap(true); } /** * @brief InvestigateProcessWindow::InitializeProcessImagesTable - Initialize properties for the process images table. */ void InvestigateProcessWindow::InitializeProcessImagesTable() { InitializeCommonTable(this->ui->ImagesTable); this->ui->ImagesTable->setColumnCount(1); this->ui->ImagesTable->setColumnWidth(0, 400); this->ui->ImagesTable->horizontalHeader()->hide(); this->ui->ImagesTable->setWordWrap(true); } /** * @brief InvestigateProcessWindow::AddProcessInfoItem - Add a header item to the process info table. * @param HeaderName - Header name. */ void InvestigateProcessWindow::AddProcessInfoItem(std::string HeaderName) { this->ui->ProcessInformationTable->setRowCount(this->ProcessInfoTableSize + 1); this->ui->ProcessInformationTable->setItem(this->ProcessInfoTableSize, 0, new QTableWidgetItem(QString::fromStdString(HeaderName))); this->ProcessInfoTableSize++; } /** * @brief InvestigateProcessWindow::UpdateWidget - Refresh stylesheet (Qt bug). * @param widget - Widget to update. */ void InvestigateProcessWindow::UpdateWidget(QWidget *widget) { widget->style()->unpolish(widget); widget->style()->polish(widget); QEvent event(QEvent::StyleChange); QApplication::sendEvent(widget, &event); widget->update(); widget->updateGeometry(); } InvestigateProcessWindow::InvestigateProcessWindow(QWidget *parent) : QWidget(parent), ui(new Ui::InvestigateProcessWindow) { ui->setupUi(this); this->setFixedSize(QSize(930, 400)); InitializeProcessInfoTable(); InitializeStackHistoryTable(this->ui->ProcessStackHistoryTable); InitializeProcessImagesTable(); InitializeStackHistoryTable(this->ui->ImageStackHistoryTable); } InvestigateProcessWindow::~InvestigateProcessWindow() { delete ui; } /** * @brief InvestigateProcessWindow::UpdateNewProcess - Set the investigator to display another process. * @param detailedProcessInformation - The new process to display. */ void InvestigateProcessWindow::UpdateNewProcess(PROCESS_DETAILED_REQUEST detailedProcessInformation) { std::time_t processExecutionDate; std::string dateString; ULONG i; CHAR tempBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; PSYMBOL_INFO currentSymbolInformation; ULONG64 offset; QString stackHistoryString; QString tooltip; QTableWidgetItem* newWidget; bool stackHistoryViolation; memset(tempBuffer, 0, sizeof(tempBuffer)); currentSymbolInformation = RCAST(tempBuffer); currentSymbolInformation->SizeOfStruct = sizeof(SYMBOL_INFO); currentSymbolInformation->MaxNameLen = MAX_SYM_NAME; // // First, we need to convert the epoch time to a date. // processExecutionDate = detailedProcessInformation.EpochExecutionTime; dateString = std::ctime(&processExecutionDate); dateString[dateString.length()-1] = '\0'; // Remove the newline. this->ProcessId = detailedProcessInformation.ProcessId; this->EpochExecutionTime = detailedProcessInformation.EpochExecutionTime; // // Copy basic process information. // this->ui->ProcessInformationTable->setItem(0, 1, new QTableWidgetItem(QString::number(RCAST(detailedProcessInformation.ProcessId)))); newWidget = new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.ProcessPath)); newWidget->setToolTip(QString::fromWCharArray(detailedProcessInformation.ProcessPath)); this->ui->ProcessInformationTable->setItem(1, 1, newWidget); newWidget = new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.ProcessCommandLine)); newWidget->setToolTip(QString::fromWCharArray(detailedProcessInformation.ProcessCommandLine)); this->ui->ProcessInformationTable->setItem(2, 1, newWidget); this->ui->ProcessInformationTable->setItem(3, 1, new QTableWidgetItem(QString::fromStdString(dateString))); this->ui->ProcessInformationTable->resizeRowsToContents(); this->ui->ProcessInformationTable->setItem(4, 1, new QTableWidgetItem(QString::number(RCAST(detailedProcessInformation.CallerProcessId)))); //newWidget = new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.CallerProcessPath)); //newWidget->setToolTip(QString::fromWCharArray(detailedProcessInformation.CallerProcessPath)); //this->ui->ProcessInformationTable->setItem(5, 1, new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.CallerProcessPath))); this->ui->ProcessInformationTable->setItem(5, 1, new QTableWidgetItem(QString::number(RCAST(detailedProcessInformation.ParentProcessId)))); newWidget = new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.ParentProcessPath)); newWidget->setToolTip(QString::fromWCharArray(detailedProcessInformation.ParentProcessPath)); this->ui->ProcessInformationTable->setItem(6, 1, new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.ParentProcessPath))); this->ui->ProcessInformationTable->resizeRowsToContents(); // // Fix sizing. // this->ui->ProcessInformationTable->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); this->ui->ProcessInformationTable->verticalHeader()->setDefaultSectionSize(10); // // Copy the stack history. // this->ui->ProcessStackHistoryTable->setRowCount(detailedProcessInformation.StackHistorySize); for(i = 0; i < detailedProcessInformation.StackHistorySize; i++) { stackHistoryString = ""; stackHistoryViolation = false; // // First, try a symbol lookup. // if(SymFromAddr(GetCurrentProcess(), RCAST(detailedProcessInformation.StackHistory[i].RawAddress), &offset, currentSymbolInformation)) { stackHistoryString = stackHistoryString.sprintf("%s+0x%llx", currentSymbolInformation->Name, offset); tooltip = tooltip.sprintf("%ls+0x%llx", detailedProcessInformation.StackHistory[i].BinaryPath, detailedProcessInformation.StackHistory[i].BinaryOffset); } else { printf("Failed SymFromAddr %i.\n", GetLastError()); if(wcslen(detailedProcessInformation.StackHistory[i].BinaryPath) == 0) { stackHistoryString = stackHistoryString.sprintf("0x%llx", detailedProcessInformation.StackHistory[i].RawAddress); stackHistoryViolation = true; } else { stackHistoryString = stackHistoryString.sprintf("%ls+0x%llx", detailedProcessInformation.StackHistory[i].BinaryPath, detailedProcessInformation.StackHistory[i].BinaryOffset); } tooltip = stackHistoryString; } this->ui->ProcessStackHistoryTable->setRowCount(i + 1); newWidget = new QTableWidgetItem(stackHistoryString); newWidget->setToolTip(tooltip); if(stackHistoryViolation) { newWidget->setBackground(Qt::red); } this->ui->ProcessStackHistoryTable->setItem(i, 0, newWidget); this->ui->ProcessStackHistoryTable->resizeRowsToContents(); } // // Copy the images. // this->images.clear(); for(i = 0; i < detailedProcessInformation.ImageSummarySize; i++) { this->images.push_back(detailedProcessInformation.ImageSummary[i]); this->ui->ImagesTable->setRowCount(i + 1); newWidget = new QTableWidgetItem(QString::fromWCharArray(detailedProcessInformation.ImageSummary[i].ImagePath)); newWidget->setToolTip(QString::fromWCharArray(detailedProcessInformation.ImageSummary[i].ImagePath)); this->ui->ImagesTable->setItem(i, 0, newWidget); this->ui->ImagesTable->resizeRowsToContents(); } this->ui->ImagesTable->selectRow(0); // // By default, first image is picked. // this->on_ImagesTable_cellClicked(0, 0); } /** * @brief InvestigateProcessWindow::RefreshWidgets - Update the standard tables. */ void InvestigateProcessWindow::RefreshWidgets() { UpdateWidget(this->ui->ProcessInformationTable); UpdateWidget(this->ui->ProcessStackHistoryTable); UpdateWidget(this->ui->ImagesTable); UpdateWidget(this->ui->ImageStackHistoryTable); UpdateWidget(this); this->setStyleSheet("background-color: #404040;"); } /** * @brief InvestigateProcessWindow::on_ImagesTable_cellClicked - Grab the stack for the new image. * @param row - Row of the image selected. * @param column - Column of the image selected. */ void InvestigateProcessWindow::on_ImagesTable_cellClicked(int row, int column) { IMAGE_SUMMARY image; PIMAGE_DETAILED_REQUEST imageDetailed; ULONG i; QString stackHistoryString; QString tooltip; CHAR tempBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_SYM_NAME]; PIMAGEHLP_SYMBOL64 currentSymbolInformation; ULONG64 offset; QTableWidgetItem* newWidget; bool stackHistoryViolation; memset(tempBuffer, 0, sizeof(tempBuffer)); currentSymbolInformation = RCAST(tempBuffer); currentSymbolInformation->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); currentSymbolInformation->MaxNameLength = MAX_SYM_NAME; image = images[row]; imageDetailed = communicator->RequestDetailedImage(this->ProcessId, this->EpochExecutionTime, row, image.StackSize); if (imageDetailed == NULL || imageDetailed->Populated == FALSE) { return; } // // Copy the stack history. // this->ui->ImageStackHistoryTable->setRowCount(imageDetailed->StackHistorySize); for(i = 0; i < imageDetailed->StackHistorySize; i++) { stackHistoryViolation = false; // // First, try a symbol lookup. // if(SymGetSymFromAddr64(GetCurrentProcess(), RCAST(imageDetailed->StackHistory[i].RawAddress), &offset, currentSymbolInformation)) { stackHistoryString = stackHistoryString.sprintf("%s+0x%llx", currentSymbolInformation->Name, offset); tooltip = tooltip.sprintf("%ls+0x%llx", imageDetailed->StackHistory[i].BinaryPath, imageDetailed->StackHistory[i].BinaryOffset); } else { if(wcslen(imageDetailed->StackHistory[i].BinaryPath) == 0) { stackHistoryString = stackHistoryString.sprintf("0x%llx", imageDetailed->StackHistory[i].RawAddress); stackHistoryViolation = true; } else { stackHistoryString = stackHistoryString.sprintf("%ls+0x%llx", imageDetailed->StackHistory[i].BinaryPath, imageDetailed->StackHistory[i].BinaryOffset); } tooltip = stackHistoryString; } this->ui->ImageStackHistoryTable->setRowCount(i + 1); newWidget = new QTableWidgetItem(stackHistoryString); // // Always set the tooltip to the expanded version. // newWidget->setToolTip(tooltip); if(stackHistoryViolation) { newWidget->setBackground(Qt::red); } this->ui->ImageStackHistoryTable->setItem(i, 0, newWidget); this->ui->ImageStackHistoryTable->resizeRowsToContents(); } free(imageDetailed); } ================================================ FILE: PeaceMakerGUI/InvestigateProcessWindow.h ================================================ #ifndef INVESTIGATEPROCESSWINDOW_H #define INVESTIGATEPROCESSWINDOW_H #include #include #include #include #include #include #include #include #include "shared.h" #include "IOCTLCommunicationUser.h" namespace Ui { class InvestigateProcessWindow; } class InvestigateProcessWindow : public QWidget { Q_OBJECT void InitializeCommonTable(QTableWidget* table); void InitializeProcessInfoTable(); void InitializeStackHistoryTable(QTableWidget* historyTable); void InitializeProcessImagesTable(); void AddProcessInfoItem(std::string HeaderName); HANDLE ProcessId; ULONG64 EpochExecutionTime; int ProcessInfoTableSize; std::vector images; void UpdateWidget(QWidget* widget); public: explicit InvestigateProcessWindow(QWidget *parent = nullptr); ~InvestigateProcessWindow(); void UpdateNewProcess(PROCESS_DETAILED_REQUEST detailedProcessInformation); void RefreshWidgets(); IOCTLCommunication* communicator; private slots: void on_ImagesTable_cellClicked(int row, int column); private: Ui::InvestigateProcessWindow *ui; }; #endif // INVESTIGATEPROCESSWINDOW_H ================================================ FILE: PeaceMakerGUI/InvestigateProcessWindow.ui ================================================ InvestigateProcessWindow 0 0 930 400 Process Investigator :/assets/PeaceMakerIcon.ico:/assets/PeaceMakerIcon.ico background-color: #404040; 20 30 440 160 0 20 background: white; 20 220 440 160 background: white; 150 10 179 17 12 75 true color:white; <img src=":/assets/BasicProcessInformation.png"/> 160 200 148 21 12 75 true color:white; <img src=":/assets/CreatorStackHistory.png"/> 660 10 50 21 12 75 true color:white; <img src=":/assets/Images.png"/> 620 200 138 21 12 75 true color:white; <img src=":/assets/ImageStackHistory.png"/> 470 30 440 160 background: white; 470 220 440 160 background: white; ================================================ FILE: PeaceMakerGUI/PeaceMakerGUI.pro ================================================ QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ ClickableTab.cpp \ InvestigateProcessWindow.cpp \ addfilterwindow.cpp \ configparser.cpp \ detailedalertwindow.cpp \ main.cpp \ mainwindow.cpp HEADERS += \ ClickableTab.h \ InvestigateProcessWindow.h \ addfilterwindow.h \ configparser.h \ detailedalertwindow.h \ mainwindow.h FORMS += \ InvestigateProcessWindow.ui \ addfilterwindow.ui \ detailedalertwindow.ui \ mainwindow.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target RESOURCES += \ AssetResources.qrc INCLUDEPATH += "../PeaceMaker Kernel" \ "../PeaceMaker CLI" SOURCES += "../PeaceMaker CLI/IOCTLCommunicationUser.cpp" LIBS += -limagehlp ================================================ FILE: PeaceMakerGUI/addfilterwindow.cpp ================================================ #include "addfilterwindow.h" #include "ui_addfilterwindow.h" AddFilterWindow::AddFilterWindow(QWidget *parent) : QWidget(parent), ui(new Ui::AddFilterWindow) { ui->setupUi(this); this->setFixedSize(QSize(300, 240)); // // Add the different types of filter. // this->ui->FilterTypeBox->addItem("Filesystem Filter"); this->ui->FilterTypeBox->addItem("Registry Filter"); } AddFilterWindow::~AddFilterWindow() { delete ui; } /** * @brief AddFilterWindow::ClearStates - Clear previously entered input. */ void AddFilterWindow::ClearStates() { this->ui->FilterTypeBox->setCurrentIndex(0); this->ui->FilterContent->setText(""); this->ui->DeleteFlag->setChecked(false); this->ui->WriteFlag->setChecked(false); this->ui->ExecuteFlag->setChecked(false); } /** * @brief AddFilterWindow::on_pushButton_clicked - Handle the "Add Filter" button and actually add the filter. */ void AddFilterWindow::on_pushButton_clicked() { STRING_FILTER_TYPE filterType; std::wstring filterContent; ULONG filterFlags; filterFlags = 0; switch(this->ui->FilterTypeBox->currentIndex()) { case 0: filterType = FilesystemFilter; break; case 1: filterType = RegistryFilter; break; } filterContent = this->ui->FilterContent->text().toStdWString(); if(this->ui->DeleteFlag->isChecked()) { filterFlags |= FILTER_FLAG_DELETE; } if(this->ui->WriteFlag->isChecked()) { filterFlags |= FILTER_FLAG_WRITE; } if(this->ui->ExecuteFlag->isChecked()) { filterFlags |= FILTER_FLAG_EXECUTE; } communicator->AddFilter(filterType, filterFlags, CCAST(filterContent.c_str()), filterContent.length() + 1); this->hide(); } ================================================ FILE: PeaceMakerGUI/addfilterwindow.h ================================================ #ifndef ADDFILTERWINDOW_H #define ADDFILTERWINDOW_H #include #include #include "shared.h" #include "IOCTLCommunicationUser.h" namespace Ui { class AddFilterWindow; } class AddFilterWindow : public QWidget { Q_OBJECT public: explicit AddFilterWindow(QWidget *parent = nullptr); ~AddFilterWindow(); void ClearStates(); IOCTLCommunication* communicator; private slots: void on_pushButton_clicked(); private: Ui::AddFilterWindow *ui; }; #endif // ADDFILTERWINDOW_H ================================================ FILE: PeaceMakerGUI/addfilterwindow.ui ================================================ AddFilterWindow 0 0 300 240 Add Filter :/assets/PeaceMakerIcon.ico:/assets/PeaceMakerIcon.ico background-color: #404040; 30 100 241 20 background-color: white; 30 40 241 22 background-color: white; 50 160 65 18 10 75 true color: white; Delete 120 160 61 18 10 75 true color: white; Write 180 160 70 18 10 75 true color: white; Execute 30 200 241 23 background-color: white; Add Filter 115 15 71 21 <img src=":/assets/FilterType.png"/> 105 80 94 17 <img src=":/assets/FilterContent.png"/> 110 140 78 21 <img src=":/assets/FilterFlags.png"/> ================================================ FILE: PeaceMakerGUI/configparser.cpp ================================================ #include "configparser.h" /** * @brief ConfigParser::ConfigParser - Read the config file. * @param ConfigFile - The config file to parse. */ ConfigParser::ConfigParser(std::string ConfigFile) { std::ifstream configFile(ConfigFile); std::string currentLine; size_t findIndex; std::string currentPropertyName; std::string currentPropertyValue; if(configFile.is_open() == FALSE) { printf("Failed to open config file."); return; } while(std::getline(configFile, currentLine)) { // // Check if the current line is a comment. // if(currentLine[0] == '#') { continue; } // // Check if there is anything on the current line. // if(currentLine.length() == 0) { continue; } findIndex = currentLine.find("="); currentPropertyName = currentLine.substr(0, findIndex); currentPropertyValue = currentLine.substr(findIndex + 1); configMap[currentPropertyName] = currentPropertyValue; } } /** * @brief ConfigParser::GetConfigFalsePositives - Parse false positive strings from the config. * @return False positives. */ FALSE_POSITIVES ConfigParser::GetConfigFalsePositives() { FALSE_POSITIVES falsePositives; std::string currentCommaItem; std::stringstream falsePositiveStream; std::wstring_convert> converter; // // Check if there is anything for us to parse. // if(configMap.count("false_positive_sourcepath") == 0) { printf("ConfigParser!GetConfigFalsePositives: No source path false positives to parse.\n"); } if(configMap.count("false_positive_targetpath") == 0) { printf("ConfigParser!GetConfigFalsePositives: No target path false positives to parse.\n"); } if(configMap.count("false_positive_stackhistory") == 0) { printf("ConfigParser!GetConfigFalsePositives: No stack history false positives to parse.\n"); } if(configMap.count("false_positive_sourcepath") != 0) { falsePositiveStream = std::stringstream(configMap["false_positive_sourcepath"]); // // Enumerate source path false positives. // while(falsePositiveStream.good()) { std::getline(falsePositiveStream, currentCommaItem, ','); if(configMap.count(currentCommaItem) == 0) { printf("ConfigParser!GetConfigFalsePositives: false_positive_sourcepath had invalid false positive named %s.\n", currentCommaItem.c_str()); continue; } falsePositives.SourcePathFilter.push_back(converter.from_bytes(configMap[currentCommaItem])); } } if(configMap.count("false_positive_targetpath") != 0) { falsePositiveStream = std::stringstream(configMap["false_positive_targetpath"]); // // Enumerate target path false positives. // while(falsePositiveStream.good()) { std::getline(falsePositiveStream, currentCommaItem, ','); if(configMap.count(currentCommaItem) == 0) { printf("ConfigParser!GetConfigFalsePositives: false_positive_targetpath had invalid false positive named %s.\n", currentCommaItem.c_str()); continue; } falsePositives.TargetPathFilter.push_back(converter.from_bytes(configMap[currentCommaItem])); } } if(configMap.count("false_positive_stackhistory") != 0) { falsePositiveStream = std::stringstream(configMap["false_positive_stackhistory"]); // // Enumerate source path false positives. // while(falsePositiveStream.good()) { std::getline(falsePositiveStream, currentCommaItem, ','); if(configMap.count(currentCommaItem) == 0) { printf("ConfigParser!GetConfigFalsePositives: false_positive_stackhistory had invalid false positive named %s.\n", currentCommaItem.c_str()); continue; } falsePositives.StackHistoryFilter.push_back(converter.from_bytes(configMap[currentCommaItem])); } } return falsePositives; } /** * @brief ConfigParser::GetConfigFilters - Parse filters from the config. * @return Parsed filter info. */ std::vector ConfigParser::GetConfigFilters() { std::vector filterNames; std::vector filters; FILTER_INFO currentFilter; std::string currentFilterName; std::string currentFilterType; std::wstring currentFilterContent; ULONG currentFilterFlags; std::stringstream filterStream; std::wstring_convert> converter; // // Check if there is anything for us to parse. // if(configMap.count("filters") == 0) { printf("ConfigParser!GetConfigFilters: No filters to parse.\n"); return filters; } filterStream = std::stringstream(configMap["filters"]); // // Enumerate the filters. // while(filterStream.good()) { std::getline(filterStream, currentFilterName, ','); filterNames.push_back(currentFilterName); } for(std::string filterName : filterNames) { // // Check for the content of the filter. // if(configMap.count(filterName + ".content") == 0) { printf("ConfigParser!GetConfigFilters: Failed to find content of filter %s.\n", filterName.c_str()); continue; } // // Check for the type of the filter. // if(configMap.count(filterName + ".type") == 0) { printf("ConfigParser!GetConfigFilters: Failed to find type of filter %s.\n", filterName.c_str()); continue; } // // Check for the flags of the filter. // if(configMap.count(filterName + ".flags") == 0) { printf("ConfigParser!GetConfigFilters: Failed to find flags of filter %s.\n", filterName.c_str()); continue; } currentFilterContent = converter.from_bytes(configMap[filterName + ".content"]); currentFilterType = configMap[filterName + ".type"]; // f or r currentFilterFlags = 0; // // Parse the flags config. // if(configMap[filterName + ".flags"].find("e") != std::string::npos) { currentFilterFlags |= FILTER_FLAG_EXECUTE; } if(configMap[filterName + ".flags"].find("d") != std::string::npos) { currentFilterFlags |= FILTER_FLAG_DELETE; } if(configMap[filterName + ".flags"].find("w") != std::string::npos) { currentFilterFlags |= FILTER_FLAG_WRITE; } // // Parse the filter type. // if(currentFilterType[0] == 'f') { currentFilter.Type = FilesystemFilter; } else if(currentFilterType[0] == 'r') { currentFilter.Type = RegistryFilter; } currentFilter.Flags = currentFilterFlags; wcscpy_s(currentFilter.MatchString, currentFilterContent.c_str()); currentFilter.MatchStringSize = currentFilterContent.size() + 2; filters.push_back(currentFilter); } } ================================================ FILE: PeaceMakerGUI/configparser.h ================================================ #ifndef CONFIGPARSER_H #define CONFIGPARSER_H #include "shared.h" #include #include #include #include #include #include #include typedef struct FalsePositives { std::vector SourcePathFilter; std::vector TargetPathFilter; std::vector StackHistoryFilter; } FALSE_POSITIVES; class ConfigParser { std::map configMap; public: ConfigParser(std::string ConfigFile); FALSE_POSITIVES GetConfigFalsePositives(); std::vector GetConfigFilters(); }; #endif // CONFIGPARSER_H ================================================ FILE: PeaceMakerGUI/detailedalertwindow.cpp ================================================ #include "detailedalertwindow.h" #include "ui_detailedalertwindow.h" /** * @brief DetailedAlertWindow::InitializeCommonTable - Common initialization across all tables. * @param table */ void DetailedAlertWindow::InitializeCommonTable(QTableWidget *table) { // // Set properties that are common across tables. // table->horizontalHeader()->setStretchLastSection(true); table->horizontalHeader()->setHighlightSections(false); table->verticalHeader()->setVisible(false); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->setSelectionMode(QAbstractItemView::SingleSelection); table->verticalScrollBar()->setStyleSheet("color: white;"); table->setSortingEnabled(false); table->setStyleSheet("background: white;"); table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); table->verticalHeader()->setDefaultSectionSize(10); table->horizontalHeader()->setVisible(false); } DetailedAlertWindow::DetailedAlertWindow(QWidget *parent) : QWidget(parent), ui(new Ui::DetailedAlertWindow) { ui->setupUi(this); this->setFixedSize(QSize(770, 550)); } DetailedAlertWindow::~DetailedAlertWindow() { delete ui; } /** * @brief DetailedAlertWindow::UpdateDisplayAlert - Update the window to display alert details based on AlertInfo. * @param AlertInfo - The new alert to display. */ void DetailedAlertWindow::UpdateDisplayAlert(PBASE_ALERT_INFO AlertInfo) { QString alertName; QString alertSource; PSTACK_VIOLATION_ALERT stackViolationAlert; PFILTER_VIOLATION_ALERT filterViolationAlert; PREMOTE_OPERATION_ALERT remoteOperationAlert; ULONG i; QString violatingAddress; STACK_RETURN_INFO* stackHistory; ULONG stackHistorySize; CHAR tempBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; PSYMBOL_INFO currentSymbolInformation; ULONG64 offset; QString stackHistoryString; QString tooltip; QTableWidgetItem* newWidget; bool stackHistoryViolation; memset(tempBuffer, 0, sizeof(tempBuffer)); currentSymbolInformation = RCAST(tempBuffer); currentSymbolInformation->SizeOfStruct = sizeof(SYMBOL_INFO); currentSymbolInformation->MaxNameLen = MAX_SYM_NAME; this->ui->AlertDetailsTable->setColumnCount(2); this->ui->AlertDetailsTable->setRowCount(6); InitializeCommonTable(this->ui->AlertDetailsTable); // // Fill out basic fields. // this->ui->AlertDetailsTable->setItem(0, 0, new QTableWidgetItem("Name")); this->ui->AlertDetailsTable->setItem(1, 0, new QTableWidgetItem("Source")); this->ui->AlertDetailsTable->setItem(2, 0, new QTableWidgetItem("Source Id")); this->ui->AlertDetailsTable->setItem(2, 1, new QTableWidgetItem(QString::number(RCAST(AlertInfo->SourceId)))); this->ui->AlertDetailsTable->setItem(3, 0, new QTableWidgetItem("Source Path")); newWidget = new QTableWidgetItem(QString::fromWCharArray(AlertInfo->SourcePath)); newWidget->setToolTip(QString::fromWCharArray(AlertInfo->SourcePath)); this->ui->AlertDetailsTable->setItem(3, 1, newWidget); this->ui->AlertDetailsTable->setItem(4, 0, new QTableWidgetItem("Target Path")); newWidget = new QTableWidgetItem(QString::fromWCharArray(AlertInfo->TargetPath)); newWidget->setToolTip(QString::fromWCharArray(AlertInfo->TargetPath)); this->ui->AlertDetailsTable->setItem(4, 1, newWidget); // // Get the name of the source. // switch(AlertInfo->AlertSource) { case ProcessCreate: alertSource = "Process Creation"; break; case ImageLoad: alertSource = "Image Load"; break; case RegistryFilterMatch: alertSource = "Registry Filter Match"; break; case FileFilterMatch: alertSource = "File Filter Match"; break; case ThreadCreate: alertSource = "Thread Create"; break; } // // Grab alert-type specific stuff. // switch(AlertInfo->AlertType) { case StackViolation: stackViolationAlert = RCAST(AlertInfo); this->ui->AlertDetailsTable->setRowCount(6); this->ui->AlertDetailsTable->setItem(5, 0, new QTableWidgetItem("Violating Address")); violatingAddress = violatingAddress.sprintf("0x%llx", stackViolationAlert->ViolatingAddress); this->ui->AlertDetailsTable->setItem(5, 1, new QTableWidgetItem(violatingAddress)); // // Set the stack history info for this alert. // stackHistory = stackViolationAlert->StackHistory; stackHistorySize = stackViolationAlert->StackHistorySize; alertName = "Stack Violation Alert"; break; case FilterViolation: filterViolationAlert = RCAST(AlertInfo); this->ui->AlertDetailsTable->setRowCount(6); this->ui->AlertDetailsTable->setItem(5, 0, new QTableWidgetItem("Filter Type")); switch(filterViolationAlert->AlertInformation.AlertSource) { case FileFilterMatch: this->ui->AlertDetailsTable->setItem(5, 1, new QTableWidgetItem("Filesystem Filter")); break; case RegistryFilterMatch: this->ui->AlertDetailsTable->setItem(5, 1, new QTableWidgetItem("Registry Filter")); break; } //this->ui->AlertDetailsTable->setItem(6, 0, new QTableWidgetItem("Filter Content")); // // Set the stack history info for this alert. // stackHistory = filterViolationAlert->StackHistory; stackHistorySize = filterViolationAlert->StackHistorySize; alertName = "Filter Violation Alert"; break; case ParentProcessIdSpoofing: remoteOperationAlert = RCAST(AlertInfo); this->ui->AlertDetailsTable->setRowCount(6); this->ui->AlertDetailsTable->setItem(5, 0, new QTableWidgetItem("Target Id")); this->ui->AlertDetailsTable->setItem(5, 1, new QTableWidgetItem(QString::number(RCAST(remoteOperationAlert->RemoteTargetId)))); // // Set the stack history info for this alert. // stackHistory = remoteOperationAlert->StackHistory; stackHistorySize = remoteOperationAlert->StackHistorySize; alertName = "Parent Process ID Spoofing"; break; case RemoteThreadCreation: remoteOperationAlert = RCAST(AlertInfo); this->ui->AlertDetailsTable->setRowCount(6); this->ui->AlertDetailsTable->setItem(5, 0, new QTableWidgetItem("Target Id")); this->ui->AlertDetailsTable->setItem(5, 1, new QTableWidgetItem(QString::number(RCAST(remoteOperationAlert->RemoteTargetId)))); // // Set the stack history info for this alert. // stackHistory = remoteOperationAlert->StackHistory; stackHistorySize = remoteOperationAlert->StackHistorySize; alertName = "Remote Thread Creation"; break; } this->ui->AlertDetailsTable->setItem(0, 1, new QTableWidgetItem(alertName)); this->ui->AlertDetailsTable->setItem(1, 1, new QTableWidgetItem(alertSource)); this->ui->AlertDetailsTable->resizeRowsToContents(); // // Copy the stack history. // InitializeCommonTable(this->ui->AlertStackHistoryTable); this->ui->AlertStackHistoryTable->setRowCount(stackHistorySize); this->ui->AlertStackHistoryTable->setColumnCount(1); for(i = 0; i < stackHistorySize; i++) { stackHistoryString = ""; stackHistoryViolation = false; // // First, try a symbol lookup. // if(SymFromAddr(GetCurrentProcess(), RCAST(stackHistory[i].RawAddress), &offset, currentSymbolInformation)) { stackHistoryString = stackHistoryString.sprintf("%s+0x%llx", currentSymbolInformation->Name, offset); tooltip = tooltip.sprintf("%ls+0x%llx", stackHistory[i].BinaryPath, stackHistory[i].BinaryOffset); } else { if(wcslen(stackHistory[i].BinaryPath) == 0) { stackHistoryString = stackHistoryString.sprintf("0x%llx", stackHistory[i].RawAddress); stackHistoryViolation = true; } else { stackHistoryString = stackHistoryString.sprintf("%ls+0x%llx", stackHistory[i].BinaryPath, stackHistory[i].BinaryOffset); } tooltip = stackHistoryString; } this->ui->AlertStackHistoryTable->setRowCount(i + 1); newWidget = new QTableWidgetItem(stackHistoryString); newWidget->setToolTip(tooltip); if(stackHistoryViolation) { newWidget->setBackground(Qt::red); } this->ui->AlertStackHistoryTable->setItem(i, 0, newWidget); this->ui->AlertStackHistoryTable->resizeRowsToContents(); } } ================================================ FILE: PeaceMakerGUI/detailedalertwindow.h ================================================ #ifndef DETAILEDALERTWINDOW_H #define DETAILEDALERTWINDOW_H #include #include #include #include #include #include "shared.h" namespace Ui { class DetailedAlertWindow; } class DetailedAlertWindow : public QWidget { Q_OBJECT void InitializeCommonTable(QTableWidget* table); public: explicit DetailedAlertWindow(QWidget *parent = nullptr); ~DetailedAlertWindow(); void UpdateDisplayAlert(PBASE_ALERT_INFO AlertInfo); private: Ui::DetailedAlertWindow *ui; }; #endif // DETAILEDALERTWINDOW_H ================================================ FILE: PeaceMakerGUI/detailedalertwindow.ui ================================================ DetailedAlertWindow 0 0 770 550 0 0 Alert Details :/assets/PeaceMakerIcon.ico:/assets/PeaceMakerIcon.ico background-color: #404040; 20 30 731 231 background-color: white; 20 290 731 231 background-color: white; 320 270 131 21 <img src=":/assets/AlertStackHistory.png"/> 345 10 87 17 <img src=":/assets/AlertDetails.png"/> ================================================ FILE: PeaceMakerGUI/main.cpp ================================================ #include "mainwindow.h" #include #include int main(int argc, char *argv[]) { AllocConsole(); freopen("CONOUT$", "w", stdout); QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ================================================ FILE: PeaceMakerGUI/mainwindow.cpp ================================================ #include "mainwindow.h" #include "ui_mainwindow.h" VOID pmlog ( const char* format, ... ) { va_list vargs; va_start(vargs, format); printf("[PeaceMaker] "); vprintf(format, vargs); printf("\n"); va_end(vargs); } /** * @brief MainWindow::InitializeCommonTable - Common initialization across all tables. * @param table */ void MainWindow::InitializeCommonTable(QTableWidget *table) { // // Set properties that are common across tables. // table->horizontalHeader()->setStretchLastSection(true); table->horizontalHeader()->setHighlightSections(false); table->verticalHeader()->setVisible(false); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->setSelectionMode(QAbstractItemView::SingleSelection); table->verticalScrollBar()->setStyleSheet("color: white;"); table->setSortingEnabled(false); table->setWordWrap(true); } /** * @brief MainWindow::InitializeAlertsTable - Initialize the table used for alerts. */ void MainWindow::InitializeAlertsTable() { QStringList headers; this->AlertsTableSize = 0; this->ui->AlertsTable->setColumnCount(2); this->ui->AlertsTable->setRowCount(100); headers << "Date" << "Alert Name"; this->ui->AlertsTable->setHorizontalHeaderLabels(headers); this->ui->AlertsTable->setColumnWidth(0, 200); InitializeCommonTable(this->ui->AlertsTable); // // Add the table as an "associated element". // this->ui->AlertsLabel->AddAssociatedElement(RCAST(this->ui->AlertsTable)); this->ui->AlertsLabel->AddAssociatedElement(RCAST(this->ui->OpenAlertButton)); this->ui->AlertsLabel->AddAssociatedElement(RCAST(this->ui->DeleteAlertButton)); } /** * @brief MainWindow::InitializeProcessesTable - Initialize the processes table. */ void MainWindow::InitializeProcessesTable() { QStringList headers; this->ProcessesTableSize = 0; this->ui->ProcessesTable->setColumnCount(3); this->ui->ProcessesTable->setRowCount(100); headers << "Process Id" << "Execution Date" << "Path"; this->ui->ProcessesTable->setHorizontalHeaderLabels(headers); InitializeCommonTable(this->ui->ProcessesTable); this->ui->ProcessesTable->setColumnWidth(1, 200); this->ui->ProcessesTable->setColumnWidth(2, 400); // // Add the table as an "associated element". // this->ui->ProcessesLabel->AddAssociatedElement(RCAST(this->ui->ProcessesTable)); this->ui->ProcessesLabel->AddAssociatedElement(RCAST(this->ui->InvestigateProcessButton)); this->ui->ProcessesLabel->AddAssociatedElement(RCAST(this->ui->ProcessSearch)); this->ui->ProcessesLabel->AddAssociatedElement(RCAST(this->ui->ProcessSearchLabel)); } /** * @brief MainWindow::InitializeFiltersTable - Initialize the filters table. */ void MainWindow::InitializeFiltersTable() { QStringList headers; this->FiltersTableSize = 0; this->FilesystemFiltersCount = 0; this->RegistryFiltersCount = 0; this->ui->FiltersTable->setColumnCount(3); this->ui->FiltersTable->setRowCount(0); headers << "Filter Type" << "Filter Flags" << "Filter Content"; this->ui->FiltersTable->setHorizontalHeaderLabels(headers); this->ui->FiltersTable->setColumnWidth(2, 100); InitializeCommonTable(this->ui->FiltersTable); // // Add the table as an "associated element". // this->ui->FiltersLabel->AddAssociatedElement(RCAST(this->ui->FiltersTable)); this->ui->FiltersLabel->AddAssociatedElement(RCAST(this->ui->AddFilterButton)); this->ui->FiltersLabel->AddAssociatedElement(RCAST(this->ui->DeleteFilterButton)); } /** * @brief MainWindow::ImportConfigFilters - One-time config filter import. */ void MainWindow::ImportConfigFilters() { std::vector configFilters; BOOLEAN filterExists; configFilters = config.GetConfigFilters(); for(FILTER_INFO configFilter : configFilters) { filterExists = FALSE; // // Make sure we don't add an existing filter. // for(FILTER_INFO existingFilter : filters) { if(existingFilter.Type == configFilter.Type && wcscmp(existingFilter.MatchString, configFilter.MatchString) == 0) { filterExists = TRUE; break; } } if(filterExists) { printf("MainWindow!ImportConfigFilters: Ignoring filter with content %ls. Already exists.\n", configFilter.MatchString); continue; } communicator.AddFilter(configFilter.Type, configFilter.Flags, configFilter.MatchString, configFilter.MatchStringSize); printf("MainWindow!ImportConfigFilters: Added config filter with content %ls.\n", configFilter.MatchString); } } /** * @brief MainWindow::ThreadUpdateTables - Thread callback that updates all tables. * @param This - This pointer for the MainWindow instance. */ void MainWindow::ThreadUpdateTables(MainWindow *This) { BOOLEAN connected; ULONG i; ULONG c; PBASE_ALERT_INFO currentAlert; GLOBAL_SIZES globalSizes; ULONG newProcessCount; PPROCESS_SUMMARY_REQUEST processSummaries; ULONG totalFilterSize; ULONG filesystemFilterIterations; ULONG registryFilterIterations; LIST_FILTERS_REQUEST filterRequest; connected = FALSE; while(connected == FALSE) { if(This->communicator.ConnectDevice() == FALSE) { pmlog("Failed to connect to device. Retrying in 2 seconds."); Sleep(2000); continue; } connected = TRUE; } pmlog("Established connection."); // // Loop update tables. // while(true) { // // Check for alerts. // while(This->communicator.QueuedAlerts()) { currentAlert = This->communicator.PopAlert(); if(currentAlert == NULL) { break; } This->AddAlertSummary(currentAlert); } // // Get global sizes. // globalSizes = This->communicator.GetGlobalSizes(); // // Check for processes. // if(globalSizes.ProcessHistorySize != This->ProcessesTableSize) { newProcessCount = globalSizes.ProcessHistorySize - This->ProcessesTableSize; if(newProcessCount > 0) { processSummaries = This->communicator.RequestProcessSummary(This->ProcessesTableSize, newProcessCount); if(processSummaries) { for(i = 0; i < processSummaries->ProcessHistorySize; i++) { This->AddProcessSummary(processSummaries->ProcessHistory[i]); } free(processSummaries); This->ui->ProcessesTable->resizeRowsToContents(); } else { pmlog("processSummaries is NULL."); } } } totalFilterSize = globalSizes.FilesystemFilterSize + globalSizes.RegistryFilterSize; printf("FS Size: %i, RY Size: %i, This Size: %i\n", globalSizes.FilesystemFilterSize, globalSizes.RegistryFilterSize, This->FiltersTableSize); // // Look for new filters. // if(totalFilterSize != This->FiltersTableSize) { filesystemFilterIterations = (globalSizes.FilesystemFilterSize - This->FilesystemFiltersCount) % 10; registryFilterIterations = (globalSizes.RegistryFilterSize - This->RegistryFiltersCount) % 10; for(i = 0; i < filesystemFilterIterations; i++) { filterRequest = This->communicator.RequestFilters(FilesystemFilter, This->FilesystemFiltersCount); for(c = 0; c < filterRequest.CopiedFilters; c++) { This->AddFilterSummary(filterRequest.Filters[c], FilesystemFilter); } } for(i = 0; i < registryFilterIterations; i++) { filterRequest = This->communicator.RequestFilters(RegistryFilter, This->RegistryFiltersCount); for(c = 0; c < filterRequest.CopiedFilters; c++) { This->AddFilterSummary(filterRequest.Filters[c], RegistryFilter); } } } // // Import filters defined in our config if not done before. // if(This->importedConfigFilters == FALSE) { This->ImportConfigFilters(); This->importedConfigFilters = TRUE; } Sleep(3000); } } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , config(CONFIG_FILE_NAME) { ui->setupUi(this); this->setFixedSize(QSize(990, 610)); InitializeAlertsTable(); InitializeProcessesTable(); InitializeFiltersTable(); // // By default set the "activeTab" to the alerts tab. // ui->AlertsLabel->SwapActiveState(); this->activeTab = ui->AlertsLabel; this->investigatorWindow = new InvestigateProcessWindow(); this->investigatorWindow->communicator = &this->communicator; this->alertWindow = new DetailedAlertWindow(); this->addFilterWindow = new AddFilterWindow(); this->addFilterWindow->communicator = &this->communicator; this->alertFalsePositives = config.GetConfigFalsePositives(); CreateThread(NULL, 0, RCAST(this->ThreadUpdateTables), this, 0, NULL); SymInitialize(GetCurrentProcess(), NULL, TRUE); } MainWindow::~MainWindow() { delete ui; delete investigatorWindow; delete alertWindow; } /** * @brief MainWindow::NotifyTabClick - Swap the active tab when a click is detected. * @param tab - The tab to swap to. */ void MainWindow::NotifyTabClick(ClickableTab *tab) { std::string tabName; // // The tab name is just its object name. // tabName = tab->objectName().toStdString(); // // Swap the active/inactive state because of the click. // activeTab->SwapActiveState(); activeTab = tab; activeTab->SwapActiveState(); } /** * @brief MainWindow::AddAlertSummary - Add an alert to the table of alerts. * @param Alert - The alert to add. */ void MainWindow::AddAlertSummary(PBASE_ALERT_INFO Alert) { std::time_t currentTime; std::string date; std::string alertName; PSTACK_VIOLATION_ALERT stackViolationAlert; PFILTER_VIOLATION_ALERT filterViolationAlert; PREMOTE_OPERATION_ALERT remoteOperationAlert; STACK_RETURN_INFO* stackHistory; ULONG stackHistorySize; ULONG i; // // Filter source path. // for(std::wstring filteredSourcePath : alertFalsePositives.SourcePathFilter) { if(wcsstr(Alert->SourcePath, filteredSourcePath.c_str()) != NULL) { printf("MainWindow!AddAlertSummary: Ignoring alert, matched source path filter %ls.\n", filteredSourcePath.c_str()); return; } } // // Filter target path. // for(std::wstring filteredTargetPath : alertFalsePositives.TargetPathFilter) { if(wcsstr(Alert->TargetPath, filteredTargetPath.c_str()) != NULL) { printf("MainWindow!AddAlertSummary: Ignoring alert, matched target path filter %ls.\n", filteredTargetPath.c_str()); return; } } // // Get the current time. // currentTime = std::time(nullptr); date = std::ctime(¤tTime); date[date.length()-1] = '\0'; // Remove the newline. // // Determine the alert type. // switch(Alert->AlertType) { case StackViolation: alertName = "Stack Violation"; stackViolationAlert = RCAST(Alert); // // Set the stack history info for this alert. // stackHistory = stackViolationAlert->StackHistory; stackHistorySize = stackViolationAlert->StackHistorySize; break; case FilterViolation: alertName = "Filter Violation"; filterViolationAlert = RCAST(Alert); // // Set the stack history info for this alert. // stackHistory = filterViolationAlert->StackHistory; stackHistorySize = filterViolationAlert->StackHistorySize; break; case ParentProcessIdSpoofing: alertName = "Parent Process ID Spoofing"; remoteOperationAlert = RCAST(Alert); // // Set the stack history info for this alert. // stackHistory = remoteOperationAlert->StackHistory; stackHistorySize = remoteOperationAlert->StackHistorySize; break; case RemoteThreadCreation: alertName = "Remote Thread Creation"; remoteOperationAlert = RCAST(Alert); // // Set the stack history info for this alert. // stackHistory = remoteOperationAlert->StackHistory; stackHistorySize = remoteOperationAlert->StackHistorySize; break; } // // Filter stack history. // for(i = 0; i < stackHistorySize; i++) { for(std::wstring filteredStackHistory : alertFalsePositives.StackHistoryFilter) { if(wcsstr(stackHistory[i].BinaryPath, filteredStackHistory.c_str()) != NULL) { printf("MainWindow!AddAlertSummary: Ignoring alert, matched stack history filter %ls.\n", filteredStackHistory.c_str()); return; } } } this->ui->AlertsTable->setRowCount(this->AlertsTableSize + 1); this->ui->AlertsTable->insertRow(0); this->ui->AlertsTable->setItem(0, 0, new QTableWidgetItem(QString::fromStdString(date))); this->ui->AlertsTable->setItem(0, 1, new QTableWidgetItem(QString::fromStdString(alertName))); this->AlertsTableSize++; alerts.push_back(Alert); this->ui->AlertsLabel->SetCustomText(""); } /** * @brief MainWindow::AddProcessSummary - Add a process to the processes table. * @param ProcessSummary - The process to add. */ void MainWindow::AddProcessSummary(PROCESS_SUMMARY_ENTRY ProcessSummary) { std::time_t processExecutionDate; std::string dateString; QTableWidgetItem* pathItem; // // First, we need to convert the epoch time to a date. // processExecutionDate = ProcessSummary.EpochExecutionTime; dateString = std::ctime(&processExecutionDate); dateString[dateString.length()-1] = '\0'; // Remove the newline. // // Next, add the appropriate information to the table. // this->ui->ProcessesTable->setRowCount(this->ProcessesTableSize + 1); this->ui->ProcessesTable->insertRow(0); this->ui->ProcessesTable->setItem(0, 0, new QTableWidgetItem(QString::number(RCAST(ProcessSummary.ProcessId)))); this->ui->ProcessesTable->setItem(0, 1, new QTableWidgetItem(QString::fromStdString(dateString))); pathItem = new QTableWidgetItem(QString::fromWCharArray(ProcessSummary.ImageFileName)); pathItem->setToolTip(QString::fromWCharArray(ProcessSummary.ImageFileName)); this->ui->ProcessesTable->setItem(0, 2, pathItem); //this->ui->ProcessesTable->resizeRowsToContents(); this->ProcessesTableSize++; processes.push_back(ProcessSummary); } /** * @brief MainWindow::AddFilterSummary - Add a filter to the filters table. * @param FilterInfo - The filter to add. * @param FilterType - The type of filter to add. */ void MainWindow::AddFilterSummary(FILTER_INFO FilterInfo, STRING_FILTER_TYPE FilterType) { std::string filterType; std::string filterFlags; // // Depending on the filter type, make the enum value a string. // switch(FilterType) { case FilesystemFilter: filterType = "Filesystem Filter"; this->FilesystemFiltersCount++; break; case RegistryFilter: filterType = "Registry Filter"; this->RegistryFiltersCount++; break; } // // Now convert the flags. // if(FlagOn(FilterInfo.Flags, FILTER_FLAG_DELETE)) { filterFlags = "Delete"; } if(FlagOn(FilterInfo.Flags, FILTER_FLAG_WRITE)) { // // Add a comma if this is the second flag. // if(FlagOn(FilterInfo.Flags, FILTER_FLAG_DELETE)) { filterFlags += ", "; } filterFlags += "Write"; } if(FlagOn(FilterInfo.Flags, FILTER_FLAG_EXECUTE)) { // // Add a comma if this is the third flag. // if(FlagOn(FilterInfo.Flags, FILTER_FLAG_DELETE) || FlagOn(FilterInfo.Flags, FILTER_FLAG_WRITE)) { filterFlags += ", "; } filterFlags += "Execute"; } this->ui->FiltersTable->setRowCount(this->FiltersTableSize + 1); this->ui->FiltersTable->setItem(this->FiltersTableSize, 0, new QTableWidgetItem(QString::fromStdString(filterType))); this->ui->FiltersTable->setItem(this->FiltersTableSize, 1, new QTableWidgetItem(QString::fromStdString(filterFlags))); this->ui->FiltersTable->setItem(this->FiltersTableSize, 2, new QTableWidgetItem(QString::fromWCharArray(FilterInfo.MatchString))); this->FiltersTableSize++; filters.push_back(FilterInfo); } /** * @brief MainWindow::on_InvestigateProcessButton_clicked - Open the process investigation window for the selected process. */ void MainWindow::on_InvestigateProcessButton_clicked() { PPROCESS_DETAILED_REQUEST processDetailed; PROCESS_SIZES_REQUEST processSizes; int selectedRow; if(this->ui->ProcessesTable->selectedItems().size() == 0) { return; } // // Since rows start from 0, we need to subtract // the row count from the table size to get the // right index. // selectedRow = processes.size() - 1 - this->ui->ProcessesTable->selectedItems()[0]->row(); processSizes = communicator.GetProcessSizes(processes[selectedRow].ProcessId, processes[selectedRow].EpochExecutionTime); processDetailed = communicator.RequestDetailedProcess(processes[selectedRow].ProcessId, processes[selectedRow].EpochExecutionTime, processSizes.ImageSize, processSizes.StackSize); if (processDetailed == NULL || processDetailed->Populated == FALSE) { pmlog("main: Failed to retrieve a detailed process report."); return; } this->investigatorWindow->UpdateNewProcess(*processDetailed); free(processDetailed); this->investigatorWindow->show(); this->investigatorWindow->RefreshWidgets(); } /** * @brief MainWindow::on_ProcessSearch_editingFinished - Search for the specified process. */ void MainWindow::on_ProcessSearch_editingFinished() { QList searchResults; QModelIndexList indexList; int firstSelectedRow; QTableWidgetItem* nextWidgetItem; nextWidgetItem = NULL; indexList = this->ui->ProcessesTable->selectionModel()->selectedIndexes(); if(indexList.count() >= 1) { firstSelectedRow = indexList[0].row(); } else { firstSelectedRow = 0; } // // Search for the first widget after whatever row I have selected. // Allows for searching of multiple items, not just one. // searchResults = this->ui->ProcessesTable->findItems(this->ui->ProcessSearch->text(), Qt::MatchContains); for(QTableWidgetItem* result : searchResults) { if(result->row() > firstSelectedRow) { nextWidgetItem = result; break; } } if(nextWidgetItem) { this->ui->ProcessesTable->selectRow(nextWidgetItem->row()); } } /** * @brief MainWindow::on_OpenAlertButton_clicked - Open the selected alert. */ void MainWindow::on_OpenAlertButton_clicked() { int selectedRow; if(this->ui->AlertsTable->selectedItems().size() == 0) { return; } // // Since rows start from 0, we need to subtract // the row count from the table size to get the // right index. // selectedRow = alerts.size() - 1 - this->ui->AlertsTable->selectedItems()[0]->row(); alertWindow->UpdateDisplayAlert(alerts[selectedRow]); alertWindow->show(); } /** * @brief MainWindow::on_DeleteAlertButton_clicked - Delete the selected alert. */ void MainWindow::on_DeleteAlertButton_clicked() { int selectedRow; if(this->ui->AlertsTable->selectedItems().size() == 0) { return; } selectedRow = this->ui->AlertsTable->selectedItems()[0]->row(); this->ui->AlertsTable->removeRow(selectedRow); } /** * @brief MainWindow::on_AddFilterButton_clicked - Add a filter. */ void MainWindow::on_AddFilterButton_clicked() { this->addFilterWindow->ClearStates(); this->addFilterWindow->show(); } /** * @brief MainWindow::on_DeleteFilterButton_clicked - Make a request to delete the selected filter. */ void MainWindow::on_DeleteFilterButton_clicked() { int selectedRow; if(this->ui->FiltersTable->selectedItems().size() == 0) { return; } // // Since rows start from 0, we need to subtract // the row count from the table size to get the // right index. // selectedRow = this->ui->FiltersTable->selectedItems()[0]->row(); // // If we successfully deleted the filter, remove the row. // if(communicator.DeleteFilter(filters[selectedRow])) { this->ui->FiltersTable->removeRow(selectedRow); // // Delete the filter from our records. // switch(filters[selectedRow].Type) { case FilesystemFilter: this->FilesystemFiltersCount--; break; case RegistryFilter: this->RegistryFiltersCount--; break; } filters.erase(filters.begin() + selectedRow); this->FiltersTableSize--; } } ================================================ FILE: PeaceMakerGUI/mainwindow.h ================================================ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include "shared.h" #include "InvestigateProcessWindow.h" #include "detailedalertwindow.h" #include "addfilterwindow.h" #include "ClickableTab.h" #include "IOCTLCommunicationUser.h" #include "configparser.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE VOID pmlog ( const char* format, ... ); class MainWindow : public QMainWindow { Q_OBJECT ClickableTab* activeTab; int AlertsTableSize; ULONG64 ProcessesTableSize; ULONG FiltersTableSize; int FilesystemFiltersCount; int RegistryFiltersCount; std::vector alerts; std::vector processes; std::vector filters; ConfigParser config; FALSE_POSITIVES alertFalsePositives; IOCTLCommunication communicator; void InitializeCommonTable(QTableWidget* table); void InitializeAlertsTable(); void InitializeProcessesTable(); void InitializeFiltersTable(); void ImportConfigFilters(); BOOLEAN importedConfigFilters = FALSE; static void ThreadUpdateTables(MainWindow* This); InvestigateProcessWindow* investigatorWindow; DetailedAlertWindow* alertWindow; AddFilterWindow* addFilterWindow; public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); void NotifyTabClick(ClickableTab* tab); void AddAlertSummary(PBASE_ALERT_INFO Alert); void AddProcessSummary(PROCESS_SUMMARY_ENTRY ProcessSummary); void AddFilterSummary(FILTER_INFO FilterInfo, STRING_FILTER_TYPE FilterType); void ActivateAlertsWindow(); void ActivateProcessesWindow(); void ActivateFiltersWindow(); void ActivateConfigWindow(); private slots: void on_InvestigateProcessButton_clicked(); void on_ProcessSearch_editingFinished(); void on_OpenAlertButton_clicked(); void on_DeleteAlertButton_clicked(); void on_AddFilterButton_clicked(); void on_DeleteFilterButton_clicked(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H ================================================ FILE: PeaceMakerGUI/mainwindow.ui ================================================ MainWindow 0 0 990 610 PeaceMaker :/assets/PeaceMakerIcon.ico:/assets/PeaceMakerIcon.ico background-color: #404040; 20 20 225 36 <html><head/><body><p><img src=":/assets/PeaceMakerLogo.png"/></p></body></html> 350 30 95 28 <img src=":/assets/AlertsTabInactive.png"/> 500 30 147 28 <img src=":/assets/ProcessesTabInactive.png"/> 700 30 96 28 <img src=":/assets/FiltersTabInactive.png"/> 850 30 89 28 <img src=":/assets/ConfigTabInactive.png"/> 10 70 971 481 background: white; 10 70 971 481 background: white; 10 70 971 481 background: white; 700 580 280 18 <img src=":/assets/Copyright.png"/> 210 570 151 31 false background: white; Investigate Process 10 580 181 21 false background: white; 75 560 49 17 false <img src=":/assets/Search.png"/> 10 570 151 31 false background-color: white; Open Alert 170 570 151 31 false background-color: white; Delete Alert 10 570 151 31 background-color: white; Add Filter 170 570 151 31 background-color: white; Delete Filter FiltersTable LogoLabel AlertsLabel ProcessesLabel FiltersLabel ConfigLabel ProcessesTable AlertsTable CopyrightLabel InvestigateProcessButton ProcessSearch ProcessSearchLabel OpenAlertButton DeleteAlertButton AddFilterButton DeleteFilterButton ClickableTab QWidget
clickabletab.h
================================================ FILE: README.md ================================================ # PeaceMaker Threat Detection PeaceMaker Threat Detection is a kernel-mode utility designed to detect a variety of methods commonly used in advanced forms of malware. Compared to a stereotypical anti-virus that may detect via hashes or patterns, PeaceMaker targets the techniques malware commonly uses in order to catch them in the act. Furthermore, PeaceMaker is designed to provide an incredible amount of detail when a malicious technique is detected, allowing for effective containment and response. ## Motivation PeaceMaker was designed primarily as a weapon to detect custom malware in virtualized environments. Specifically, this project was started in pursuit of preparing for the [Information Security Talent Search](https://www.ists.io/) blue/red team competition hosted by RIT's Security Club, [RITSEC](https://www.ritsec.club/). The competition's red team is primarily industry security professionals, which is why I decided my own defense platform would be useful. In a project like this, I can make sacrifices to factors such as performance that widely-employed AV/EDR companies can't make, allowing me to make decisions I couldn't get away with in a real product. ## Features - View what code started a process (stack trace). - View what code loaded an image into a process (stack trace). - Detect unmapped (hidden) code via Stack Walking common operations such as: - Process Creation - Image Load - Thread Creation - Detect remote thread creation. - Detect parent process ID spoofing. - Detect threat creation on unmapped (hidden) code. - Block basic tamper operations on the GUI Client. - Block filesystem/registry write, delete, or execute operations that violate a user-specified filter. - Detect filesystem/registry write, delete, or execute operations that violate a user-specified filter. - Logs the source process and stack of the operation. - Filter for known false positives. ## Notable properties - Heavily commented code. - All detection routines are in the kernel driver. - Designed to detect user-mode malware. - Tested using Driver Verifier standard configuration. - Tested by putting it on my "daily driver" laptop and monitoring for issues (none occurred). ## Shortcomings - Inefficient time and space complexity. - Performs useful, but expensive forensics that slow down common operations. - Often allocates more memory than needed, doesn't utilize space optimization techniques such as compression. - Weak operation filtering mechanism. - For example, filters that prevent deletion of a file or registry key can be bypassed. - You can only filter on the target of an operation (i.e file/key name). - Weak tamper protection. - Only protects against process termination of the GUI, nothing else. - Incomplete GUI. ## Screenshots ![Alerts Tab](https://i.imgur.com/5L7fcsu.png) ![Processes Tab](https://i.imgur.com/v6NtDXH.png) ![Filters Tab](https://i.imgur.com/PL2clda.png) ![Process Information](https://i.imgur.com/Jefp8ac.png)