diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e457f656..bf6cf06b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: BLUESPAWN client build +name: BLUESPAWN-win-client build on: push: @@ -20,27 +20,25 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - - name: Setup NuGet.exe - uses: warrenbuckley/Setup-Nuget@v1 + - uses: actions/checkout@main - name: Update submodules run: git submodule update --init --recursive - - name: Install vcpkg + - name: Restore from cache, and install vcpkg and project dependencies + uses: lukka/run-vcpkg@v3 + with: + vcpkgArguments: '@../vcpkg_response_file.txt' + vcpkgDirectory: 'vcpkg' + appendedCacheKey: ${{ hashFiles(env.vcpkgResponseFile) }} + + - name: Integrate vcpkg packages shell: powershell run: | cd vcpkg - .\bootstrap-vcpkg.bat - .\vcpkg.exe install yara:${{ matrix.buildarch }}-windows-static - .\vcpkg.exe install libzip:${{ matrix.buildarch }}-windows-static .\vcpkg.exe integrate install cd .. - - name: Download NuGet packages - run: nuget restore BLUESPAWN.sln - - name: Build BLUESPAWN-client run: | "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" BLUESPAWN.sln /p:Configuration=${{ matrix.buildtype }} /p:Platform=${{ matrix.buildarch }} @@ -59,7 +57,7 @@ jobs: shell: powershell - name: Run BLUESPAWN Hunt - run: artifacts\${{ matrix.buildarch }}\${{ matrix.buildtype }}\BLUESPAWN-client.exe --hunt -l Normal --log=xml --reaction=log + run: artifacts\${{ matrix.buildarch }}\${{ matrix.buildtype }}\BLUESPAWN-client.exe --hunt -a Normal --log=xml shell: cmd - name: Rename BLUESPAWN XML output file @@ -70,17 +68,17 @@ jobs: run: testing\run-hunt-results-comparison.ps1 shell: powershell - - uses: actions/upload-artifact@master + - uses: actions/upload-artifact@main with: name: BLUESPAWN-client-${{ matrix.buildarch }}-${{ matrix.buildtype }} path: artifacts\${{ matrix.buildarch }}\${{ matrix.buildtype }}\BLUESPAWN-client.exe - - uses: actions/upload-artifact@master + - uses: actions/upload-artifact@main with: name: AtomicTestsResults-${{ matrix.buildarch }}-${{ matrix.buildtype }}.csv path: AtomicTestsResults.csv - - uses: actions/upload-artifact@master + - uses: actions/upload-artifact@main with: name: BLUESPAWNHuntResults-${{ matrix.buildarch }}-${{ matrix.buildtype }}.xml path: BLUESPAWNHuntResults.xml diff --git a/.gitignore b/.gitignore index 3cfaa9c1..bb4eba2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,9 @@ +# General .vs/ build/ artifacts/ packages/ vcpkg/ -BLUESPAWN-client/external/ -BLUESPAWN-client/resources/severe -BLUESPAWN-client/resources/severe2 -BLUESPAWN-client/resources/indicators *.user *.filters *.cache @@ -14,6 +11,12 @@ BLUESPAWN-client/resources/indicators *.lib *.aps +# BLUESPAWN-win-client +BLUESPAWN-win-client/external/ +BLUESPAWN-win-client/resources/severe +BLUESPAWN-win-client/resources/severe2 +BLUESPAWN-win-client/resources/indicators + # BLUESPAWN Project Site - docs/ *.lock docs/db.sqlite3 diff --git a/.gitmodules b/.gitmodules index d64346a8..4048fd7d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,24 +1,15 @@ -[submodule "BLUESPAWN-client/external/cxxopts"] - path = BLUESPAWN-client/external/cxxopts - url = https://github.com/jarro2783/cxxopts -[submodule "BLUESPAWN-client/external/krabsetw"] - path = BLUESPAWN-client/external/krabsetw - url = https://github.com/microsoft/krabsetw -[submodule "BLUESPAWN-client/external/pe-sieve"] - path = BLUESPAWN-client/external/pe-sieve - url = https://github.com/hasherezade/pe-sieve -[submodule "BLUESPAWN-client/external/yara"] - path = BLUESPAWN-client/external/yara - url = https://github.com/VirusTotal/yara -[submodule "BLUESPAWN-client/external/yara-rules"] - path = BLUESPAWN-client/external/yara-rules - url = https://github.com/Yara-Rules/rules.git -[submodule "BLUESPAWN-client/external/tinyxml2"] - path = BLUESPAWN-client/external/tinyxml2 - url = https://github.com/leethomason/tinyxml2 [submodule "vcpkg"] path = vcpkg url = https://github.com/Microsoft/vcpkg.git -[submodule "BLUESPAWN-client/external/signature-base"] - path = BLUESPAWN-client/external/signature-base - url = https://github.com/Neo23x0/signature-base +[submodule "BLUESPAWN-win-client/external/pe-sieve"] + path = BLUESPAWN-win-client/external/pe-sieve + url = https://github.com/Jack-McDowell/pe-sieve +[submodule "BLUESPAWN-win-client/external/yara-rules"] + path = BLUESPAWN-win-client/external/yara-rules + url = https://github.com/Jack-McDowell/rules +[submodule "BLUESPAWN-win-client/external/signature-base"] + path = BLUESPAWN-win-client/external/signature-base + url = https://github.com/Jack-McDowell/signature-base +[submodule "BLUESPAWN-win-client/external/tinyxml2"] + path = BLUESPAWN-win-client/external/tinyxml2 + url = https://github.com/Jack-McDowell/tinyxml2 diff --git a/BLUESPAWN-client/external/cxxopts b/BLUESPAWN-client/external/cxxopts deleted file mode 160000 index b0f67a06..00000000 --- a/BLUESPAWN-client/external/cxxopts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b0f67a06de3446aa97a4943ad0ad6086460b2b61 diff --git a/BLUESPAWN-client/external/pe-sieve b/BLUESPAWN-client/external/pe-sieve deleted file mode 160000 index cd486abb..00000000 --- a/BLUESPAWN-client/external/pe-sieve +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd486abb9b61680a94fb56296d1ef6289a4e1f0a diff --git a/BLUESPAWN-client/external/signature-base b/BLUESPAWN-client/external/signature-base deleted file mode 160000 index be0caf47..00000000 --- a/BLUESPAWN-client/external/signature-base +++ /dev/null @@ -1 +0,0 @@ -Subproject commit be0caf471dc9c513314ef77c9325b9becb501b85 diff --git a/BLUESPAWN-client/external/tinyxml2 b/BLUESPAWN-client/external/tinyxml2 deleted file mode 160000 index bd5950bb..00000000 --- a/BLUESPAWN-client/external/tinyxml2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd5950bb026675c795f4acfdfe60fa59f097435e diff --git a/BLUESPAWN-client/external/yara b/BLUESPAWN-client/external/yara deleted file mode 160000 index 26615bd0..00000000 --- a/BLUESPAWN-client/external/yara +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 26615bd08a7ed2fbd3400df30f7095a74703ebfe diff --git a/BLUESPAWN-client/external/yara-rules b/BLUESPAWN-client/external/yara-rules deleted file mode 160000 index 2bb79cb6..00000000 --- a/BLUESPAWN-client/external/yara-rules +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2bb79cb6127732f21d00892ffb468f6a3018ed99 diff --git a/BLUESPAWN-client/headers/hunt/Hunt.h b/BLUESPAWN-client/headers/hunt/Hunt.h deleted file mode 100644 index 18f31099..00000000 --- a/BLUESPAWN-client/headers/hunt/Hunt.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include - -#include -#include - -#include "Scope.h" -#include "HuntInfo.h" - -#include "reaction/Reaction.h" -#include "monitor/Event.h" - -class HuntRegister; - -#define GET_INFO() \ - HuntInfo{ this->name, __func__ == std::string{"ScanCursory"} ? Aggressiveness::Cursory : \ - __func__ == std::string{"ScanNormal"} ? Aggressiveness::Normal : Aggressiveness::Intensive, \ - this->dwTacticsUsed, this->dwCategoriesAffected, this->dwSourcesInvolved } - -class Hunt { -protected: - DWORD dwTacticsUsed; - DWORD dwSourcesInvolved; - DWORD dwCategoriesAffected; - DWORD dwSupportedScans; - - std::wstring name; - -public: - Hunt(const std::wstring& name); - - std::wstring GetName(); - - bool UsesTactics(DWORD tactics); - bool UsesSources(DWORD sources); - bool AffectsCategory(DWORD category); - bool SupportsScan(Aggressiveness scan); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual int ScanNormal(const Scope& scope, Reaction reaction); - virtual int ScanIntensive(const Scope& scope, Reaction reaction); - - virtual std::vector> GetMonitoringEvents(); -}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/HuntRegister.h b/BLUESPAWN-client/headers/hunt/HuntRegister.h deleted file mode 100644 index 7efd3a21..00000000 --- a/BLUESPAWN-client/headers/hunt/HuntRegister.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include - -#include -#include -#include -#include - -#include "Hunt.h" -#include "Scope.h" -#include "user/CLI.h" - -using namespace std; - -class HuntRegister { -private: - vector> vRegisteredHunts{}; - const IOBase& io; - - map>> mTactics{}; - map>> mDataSources{}; - map>> mAffectedThings{}; - - Aggressiveness getLevelForHunt(Hunt& hunt, Aggressiveness aggressiveness); - -public: - HuntRegister(const IOBase& oIo); - - void RunHunts(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction, vector vExcludedHunts, vector vIncludedHunts); - void RunHunt(Hunt& hunt, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction); - - bool HuntRegister::HuntShouldRun(Hunt& hunt, vector vExcludedHunts, vector vIncludedHunts); - void SetupMonitoring(Aggressiveness aggressiveness, const Reaction& reaction); - void RegisterHunt(std::shared_ptr hunt); -}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/Scope.h b/BLUESPAWN-client/headers/hunt/Scope.h deleted file mode 100644 index 7fc16315..00000000 --- a/BLUESPAWN-client/headers/hunt/Scope.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include -#include - -/** - * Used to define the scope of a hunt. Currently, this operates by requiring the programmer to - * define a new class for each new scope. This is less than ideal, as scopes should eventually - * be defined by the end user. Future implementation will allow the programmer to pass in lambdas - * which will be handled by the functions built in to the class, removing the need for new scopes. - */ -class Scope { -public: - virtual bool FileIsInScope(LPCSTR sFileName) const; - virtual bool FileIsInScope(HANDLE hFile) const; - virtual std::vector GetScopedFileHandles() const; - virtual std::vector GetScopedFileNames() const; - - virtual bool RegistryKeyIsInScope(LPCSTR sKeyPath) const; - virtual bool RegistryKeyIsInScope(HKEY key) const; - virtual std::vector GetScopedKHEYs() const; - virtual std::vector GetScopedRegKeyNames() const; - - virtual bool ProcessIsInScope(DWORD pid) const; - virtual bool ProcessIsInScope(HANDLE hProcess) const; - virtual std::vector GetScopedProcessHandles() const; - virtual std::vector GetScopedProcessPIDs() const; - - virtual bool ServiceIsInScope(LPCSTR sServiceName) const; - virtual bool ServiceIsInScope(SC_HANDLE hService) const; - virtual std::vector GetScopedServiceHandles() const; - virtual std::vector GetScopedServiceNames() const; -}; - diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1004.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1004.h deleted file mode 100644 index ee65ed63..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1004.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1004 examines Winlogon related registry keys that can be used for - * persistence. - * - * @scans Cursory checks the values of the associated Winlogon keys that can be abused. - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1004 : public Hunt { - public: - HuntT1004(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1013.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1013.h deleted file mode 100644 index ce94948d..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1013.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1013 examines the registry for bad port monitors - * - * @scans Cursory checks for bad DLLs configured as port monitors - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1013 : public Hunt { - public: - HuntT1013(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1015.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1015.h deleted file mode 100644 index 5981ce3b..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1015.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include "../Hunt.h" - -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1015 looks for Windows Accessibility Features to be messed with in some way - * - * @scans Cursory checks for any Debugger keys and if they are signed - * @scans Normal checks for any Debugger keys, checks if signed, scans with YARA - * @scans Intensive Scan not supported. - */ - class HuntT1015 : public Hunt { - private: - std::vector vAccessibilityBinaries = { L"sethc.exe", L"utilman.exe", L"osk.exe", L"Magnify.exe", - L"Narrator.exe", L"DisplaySwitch.exe", L"AtBroker.exe" }; - std::wstring wsIFEO = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"; - std::wstring wsIFEOWow64 = L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"; - - int HuntT1015::EvaluateRegistry(Reaction& reaction); - int HuntT1015::EvaluateFiles(Reaction& reaction, bool bScanYara); - public: - HuntT1015(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual int ScanNormal(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1031.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1031.h deleted file mode 100644 index 8f11adc6..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1031.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1031 examines the registry for additions/changes to Services configured - * in the registry such as an extra Dll it launches based on a specific value - * - * @scans Cursory checks for a ServerLevelPluginDll to be configured on the DNS Service - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1031 : public Hunt { - public: - HuntT1031(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1035.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1035.h deleted file mode 100644 index 0164ed32..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1035.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include - -#include - -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1035 examines the system for malicious services - * - * @scans Cursory scans the services that are installed and their binaries - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1035 : public Hunt { - private: - - int EvaluateService(Registry::RegistryKey key, Reaction& reaction); - - public: - HuntT1035(); - - virtual int ScanNormal(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1036.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1036.h deleted file mode 100644 index 594f7858..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1036.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1036 examines the local file system for executables in user writable - * locations in %WINDIR% - * - * @scans Cursory checks all such writable folders for executable files - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1036 : public Hunt { - private: - std::vector susExts = { L".bat", L".cmd", L".exe", L".dll", L".js", L".jse", - L".lnk", L".ps1", L".sct", L".vb", L".vbe", L".vbs", L".vbscript", L".hta" }; - - // Credit: https://twitter.com/mattifestation/status/1172520995472756737/photo/1 - std::vector writableFolders = { - L"%WINDIR%\\System32\\Microsoft\\crypto\\rsa\\machinekeys", - L"%WINDIR%\\System32\\tasks_migrated\\microsoft\\windows\\pla\\system", - L"%WINDIR%\\Syswow64\\tasks\\microsoft\\windows\\pla\\system", - L"%WINDIR%\\debug\\WIA", - L"%WINDIR%\\System32\\Tasks", - L"%WINDIR%\\Syswow64\\Tasks", - L"%WINDIR%\\Tasks", - L"%WINDIR%\\Registration\\crmlog", - L"%WINDIR%\\System32\\com\\dmp", - L"%WINDIR%\\System32\\fxstmp", - L"%WINDIR%\\System32\\spool\\drivers\\color", - L"%WINDIR%\\System32\\spool\\printers", - L"%WINDIR%\\System32\\spool\\servers", - L"%WINDIR%\\Syswow64\\com\\dmp", - L"%WINDIR%\\Syswow64\\fxstmp", - L"%WINDIR%\\Temp", - L"%WINDIR%\\tracing" - }; - public: - HuntT1036(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h deleted file mode 100644 index c0d82e4b..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include "../Hunt.h" - -#include "reaction/Reaction.h" -#include "reaction/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" - -namespace Hunts { - - /** - * HuntT1037 examines the registry and filesystem for logon scripts - * - * @scans Cursory checks the value of the UserInitMprLogonScript key for scripts, scans with YARA - * @scans Normal Scans Registry + Filesystem with YARA and suspicious extensions - * @scans Intensive Scans Registry and FileSystem and alerts on everything - */ - class HuntT1037 : public Hunt { - private: - std::vector sus_exts = { L".bat", L".cmd", L".dll", L".job", L".js", L".jse", - L".lnk", L".ps1", L".sct", L".vb", L".vbe", L".vbs", L".vbscript", L".hta" }; - - int HuntT1037::EvaluateStartupFile(FileSystem::File file, Reaction& reaction, Aggressiveness level); - public: - HuntT1037(); - - int AnalyzeRegistryStartupKey(Reaction reaction, Aggressiveness level); - int AnalayzeStartupFolders(Reaction reaction, Aggressiveness level); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual int ScanNormal(const Scope& scope, Reaction reaction); - virtual int ScanIntensive(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h deleted file mode 100644 index edfd14c7..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1050 examines Windows events for new services created - * - * @scans Cursory Scan not supported. - * @scans Normal checks System logs for event id 7045 for new events - * @scans Intensive checks System logs for event id 7045 for new events - * @monitor Triggers a hunt whenever System log event ID 7045 is generated - */ - class HuntT1050 : public Hunt { - public: - HuntT1050(); - - std::vector Get7045Events(); - - virtual int ScanNormal(const Scope& scope, Reaction reaction) override; - virtual int ScanIntensive(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1053.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1053.h deleted file mode 100644 index f4869d7a..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1053.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1053 examines Windows events for new scheduled tasks - * - * @scans Cursory Scan not supported. - * @scans Normal Scan not supported. - * @scans Intensive Security Logs for a 4698 and Task-Scheduler for a 106 - * @monitor Triggers a hunt whenever Security log event ID 4698/Task-Scheduler 106 is generated - */ - class HuntT1053 : public Hunt { - public: - HuntT1053(); - - std::vector Get4698Events(); - std::vector Get106Events(); - - virtual int ScanIntensive(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1055.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1055.h deleted file mode 100644 index f952de5e..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1055.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" -#include "common/DynamicLinker.h" - -namespace Hunts { - - /** - * HuntT1055 examines all processes for shellcode injections, injected PE images, - * function hooks, and doppelganging. This individual hunt will eventually be broken - * into separate hunts - * - * @scans Cursory Scan not supported. - * @scans Normal Scans all processes running on the system for evidence of process injection - * @scans Intensive Scan not supported. - */ - class HuntT1055 : public Hunt { - - public: - HuntT1055(); - - virtual int ScanNormal(const Scope& scope, Reaction reaction) override; - }; -} diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1060.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1060.h deleted file mode 100644 index 9a42b7c4..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1060.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1060 examines associated Registry Run Keys - * - * @scans Cursory checks the values of the associated Registry Run Keys - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1060 : public Hunt { - private: - std::vector RunKeys; - int EvaluateFile(const std::wstring& wLaunchString, Reaction& reaction); - - public: - HuntT1060(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1068.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1068.h deleted file mode 100644 index 2a347f38..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1068.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1068 examines the registry and file system for evidence of CVE-2020-1048. - * - * @scans Cursory checks for registry ports that write to files (CVE-2020-1048). - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1068 : public Hunt { - private: - int HuntCVE20201048(Reaction reaction); - public: - HuntT1068(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1089.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1089.h deleted file mode 100644 index 9136cc3c..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1089.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1089 examines the registry for firewall settings that allow - * applications to override the existing firewall rules. - * - * @scans Cursory checks for potentially malicious firewall configurations - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1089 : public Hunt { - public: - HuntT1089(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1099.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1099.h deleted file mode 100644 index b2502628..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1099.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1099 examines Sysmon logs looking for timestomp events - * - * @scans Cursory Scan not supported. - * @scans Normal checks Sysmon logs for event id 2 (file MAC time change), - * scans Timestomp file with YARA. - * @scans Intensive checks Sysmon logs for event id 2 (file MAC time change). - * @monitor Triggers a hunt whenever Sysmon log event ID 2 is generated - */ - class HuntT1099 : public Hunt { - public: - HuntT1099(); - - virtual int ScanNormal(const Scope& scope, Reaction reaction) override; - virtual int ScanIntensive(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h deleted file mode 100644 index 595f6220..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -#include -#include -#include - -namespace Hunts { - - /** - * HuntT1100 scans the locations of web roots, looking for files that are likely to be - * webshells. - * - * @scans Cursory Checks for obvious bad functions that indicate a webshell - * @scans Normal Adds more suspicious indicators in the regex to look for - * @scans Intensive Scan not supported. - */ - class HuntT1100 : public Hunt { - private: - std::vector web_directories = { L"C:\\inetpub\\wwwroot", L"C:\\xampp\\htdocs" }; - std::vector web_exts = { L".php", L".jsp", L".jspx", L".asp", L".aspx", L".asmx", L".ashx", L".ascx" }; - std::regex php_vuln_functions{}; - std::regex asp_indicators{}; - std::regex jsp_indicators{}; - std::smatch match_index{}; - - void SetRegexAggressivenessLevel(Aggressiveness aLevel); - - public: - HuntT1100(); - - void AddDirectoryToSearch(const std::wstring& sFileName); - void AddFileExtensionToSearch(const std::wstring& sFileExtension); - int AnalyzeDirectoryFiles(std::wstring path, Reaction reaction, Aggressiveness level); - - virtual int ScanCursory(const Scope& scope, Reaction reaction = Reactions::LogReaction()); - virtual int ScanNormal(const Scope& scope, Reaction reaction = Reactions::LogReaction()); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h deleted file mode 100644 index 90dc5ba6..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include - -#include - -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1101 examines Security Support Providers (SSPs) on the system - * - * @scans Cursory scans the SSPs installed on the system and their DLLs. - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1101 : public Hunt { - public: - HuntT1101(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h deleted file mode 100644 index 160be849..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include - -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1103 examines AppInit DLLs related registry keys that can be used for - * persistence and privilege escalation. - * - * @scans Cursory checks the values of the associated AppInit DLLs keys that can be abused. - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1103 : public Hunt { - public: - HuntT1103(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1122.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1122.h deleted file mode 100644 index bc5eaf4e..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1122.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts{ - - /** - * HuntT1122 examines CLSID registry values to detect COM hijacking, used in - * persistence. - * - * @scans Cursory Scan not supported. - * @scans Normal Scan not supported. - * @scans Intensive Enumerates all CLSID values in the registry to detect COM hijacking. - */ - class HuntT1122 : public Hunt { - public: - HuntT1122(); - - virtual int ScanIntensive(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1128.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1128.h deleted file mode 100644 index d24ee5d4..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1128.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1128 examines the registry for bad Netsh Helper DLLs - * - * @scans Cursory checks the registry and associated files of netsh helper dlls - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1128 : public Hunt { - public: - HuntT1128(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h deleted file mode 100644 index 6adcd963..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "../Hunt.h" - -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1131 examines the Authentication packages listed in the registry to - * hunt for persistence. - * - * @scans Cursory checks the values of the associated Authentication packages - * keys that can be abused. - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1131 : public Hunt { - public: - HuntT1131(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1136.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1136.h deleted file mode 100644 index 442b317f..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1136.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1136 examines Windows events for new accounts created - * - * @scans Cursory checks Security logs for event id 4720 for new accounts - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - * @monitor Triggers a hunt whenever Security log event ID 4720 is generated - */ - class HuntT1136 : public Hunt { - public: - HuntT1136(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} -#pragma once diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h deleted file mode 100644 index 16411d6c..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1138 examines the system for the presence of Application Shimming that can - * be used for persistence and privilege escalation. - * - * @scans Cursory checks the values of the associated Application Shimming registry - * keys that can be abused. - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1138 : public Hunt { - public: - HuntT1138(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h deleted file mode 100644 index 61198afd..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1182 examines AppCert DLLs related registry keys that can be used for - * persistence and privilege escalation. - * - * @scans Cursory checks the values of the associated AppCert DLLs keys that - * can be abused. - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1182 : public Hunt { - public: - HuntT1182(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1183.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1183.h deleted file mode 100644 index 9a275896..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1183.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts{ - - /** - * HuntT1183 examines the Image File Execution Options for debuggers and silent - * process exit hooks - * - * @scans Cursory checks the values of the debugger and global flags for each process - * with an Image File Execution Options key - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1183 : public Hunt { - public: - HuntT1183(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1198.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1198.h deleted file mode 100644 index 440e6caa..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1198.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts{ - - /** - * HuntT1198 examines Subject Interface Providers and Trust Providers, which can - * be used by malicious actors to cause malicious payloads to appear signed, and - * establish persistence - * - * @scans Cursory Scan not supported. - * @scans Normal Scan not supported. - * @scans Intensive Scans SIPs and trust providers and ensures they are valid.. - */ - class HuntT1198 : public Hunt { - public: - HuntT1198(); - - virtual int ScanIntensive(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1484.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1484.h deleted file mode 100644 index 7fa4b972..00000000 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1484.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "../Hunt.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" - -namespace Hunts { - - /** - * HuntT1484 examines the local file system for presence of ntuser.man files which - * can be used to override GPO settings - * - * @scans Cursory checks for ntuser.man in all user folders - * @scans Normal Scan not supported. - * @scans Intensive Scan not supported. - */ - class HuntT1484 : public Hunt { - public: - HuntT1484(); - - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; - virtual std::vector> GetMonitoringEvents() override; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/monitor/Event.h b/BLUESPAWN-client/headers/monitor/Event.h deleted file mode 100644 index a80650f8..00000000 --- a/BLUESPAWN-client/headers/monitor/Event.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -#include -#include -#include -#include "reaction/Reaction.h" -#include "hunt/Scope.h" -#include "util/eventlogs/EventSubscription.h" -#include "util/configurations/Registry.h" -#include "util/configurations/RegistryValue.h" -#include "util/eventlogs/XpathQuery.h" -#include "util/filesystem/FileSystem.h" - -enum class EventType { - EventLog, - Registry, - FileSystem -}; - -class Event { -public: - EventType type; - - void AddCallback(const std::function& callback); - - virtual void RunCallbacks() const; - - virtual bool Subscribe() = 0; - - virtual bool operator==(const Event& e) const = 0; - -protected: - Event(EventType type); - - std::vector> callbacks; - Reaction reaction; - std::optional scope; - -}; - -class EventLogEvent : public Event { -public: - EventLogEvent(const std::wstring & channel, int eventID, const std::vector& queries = {}); - - std::function eventLogTrigger; - - std::wstring GetChannel() const; - int GetEventID() const; - std::vector GetQueries() const; - - virtual bool Subscribe(); - - virtual bool operator==(const Event& e) const; - -private: - std::optional eventSub; - std::wstring channel; - int eventID; - std::vector queries; -}; - -class RegistryEvent : public Event { - - // Event that is triggered when the key changes - HandleWrapper hEvent; - - // True if this event watches subkeys. Note that this will be unable to determine - // which value (or subkey) was changed. - bool WatchSubkeys; - - // The registry key being watched - Registry::RegistryKey key; - -public: - - RegistryEvent(const Registry::RegistryKey& key, bool WatchSubkeys = false); - - const HandleWrapper& GetEvent() const; - - const Registry::RegistryKey& GetKey() const; - - virtual bool Subscribe(); - - virtual bool operator==(const Event& e) const; -}; - -class FileEvent : public Event { - - /// Directory to be watched - FileSystem::Folder directory; - - /// Event that is triggered when the key changes - GenericWrapper hEvent; - -public: - FileEvent(const FileSystem::Folder& file); - - const GenericWrapper& GetEvent() const; - - const FileSystem::Folder& GetFolder() const; - - virtual bool Subscribe(); - - virtual bool operator==(const Event& e) const; -}; - -namespace Registry { - std::vector> GetRegistryEvents(HKEY hkHive, const std::wstring& path, bool WatchWow64 = true, bool WatchUsers = true, bool WatchSubkeys = false); -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/reaction/CarveMemory.h b/BLUESPAWN-client/headers/reaction/CarveMemory.h deleted file mode 100644 index c92aaab7..00000000 --- a/BLUESPAWN-client/headers/reaction/CarveMemory.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "Reaction.h" - -#include "hunt/HuntInfo.h" -#include "user/iobase.h" - -namespace Reactions{ - - class CarveProcessReaction : public Reaction { - private: - const IOBase& io; - - /// Handlers for detections that log the detection - void CarveProcessIdentified(std::shared_ptr detection); - - public: - CarveProcessReaction(const IOBase& io); - }; -} - diff --git a/BLUESPAWN-client/headers/reaction/DeleteFile.h b/BLUESPAWN-client/headers/reaction/DeleteFile.h deleted file mode 100644 index 57976709..00000000 --- a/BLUESPAWN-client/headers/reaction/DeleteFile.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Reaction.h" - -#include "hunt/HuntInfo.h" -#include "user/iobase.h" -#include "common/DynamicLinker.h" - -#include - -namespace Reactions { - - class DeleteFileReaction : public Reaction { - private: - const IOBase& io; - - /// Handlers for detections that log the detection - void DeleteFileIdentified(std::shared_ptr detection); - - public: - DeleteFileReaction(const IOBase& io); - }; -} - diff --git a/BLUESPAWN-client/headers/reaction/Detections.h b/BLUESPAWN-client/headers/reaction/Detections.h deleted file mode 100644 index 11a4c691..00000000 --- a/BLUESPAWN-client/headers/reaction/Detections.h +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include "hunt/HuntInfo.h" -#include "util/configurations/RegistryValue.h" -#include "util/filesystem/FileSystem.h" -#include "common/StringUtils.h" -#include "common/Utils.h" - -enum class DetectionType { - File, - Registry, - Service, - Process, - Event -}; - -struct DETECTION { - DetectionType Type; - DETECTION(DetectionType Type) : Type{ Type }{} -}; - -/// A struct containing information about a file identified in a hunt -struct FILE_DETECTION : public DETECTION { - std::wstring wsFileName; - std::wstring wsFilePath; - std::wstring md5; - std::wstring sha1; - std::wstring sha256; - std::wstring created; - std::wstring modified; - std::wstring accessed; - FileSystem::File fFile; - FILE_DETECTION(const FileSystem::File f) : - DETECTION{ DetectionType::File }, - wsFilePath{ f.GetFilePath() }, - fFile{ f } { - wsFileName = ToLowerCaseW(wsFilePath.substr(wsFilePath.find_last_of(L"\\/") + 1)); - if (f.GetMD5Hash()) { - md5 = f.GetMD5Hash().value(); - } - if (f.GetSHA1Hash()) { - sha1 = f.GetSHA1Hash().value(); - } - if (f.GetSHA256Hash()) { - sha256 = f.GetSHA256Hash().value(); - } - if (f.GetCreationTime()) { - created = FormatWindowsTime(f.GetCreationTime().value()); - } - if (f.GetModifiedTime()) { - modified = FormatWindowsTime(f.GetModifiedTime().value()); - } - if (f.GetAccessTime()) { - accessed = FormatWindowsTime(f.GetAccessTime().value()); - } - } -}; -typedef std::function)> DetectFile; - -/// A struct containing information about a registry key value identified in a hunt -struct REGISTRY_DETECTION : public DETECTION { - Registry::RegistryValue value; - REGISTRY_DETECTION(const Registry::RegistryValue& value) : - DETECTION{ DetectionType::Registry }, - value{ value }{} -}; -typedef std::function)> DetectRegistry; - -/// A struct containing information about a service identified in a hunt -struct SERVICE_DETECTION : public DETECTION { - std::wstring wsServiceName; - std::wstring wsServiceExecutablePath; - std::wstring wsServiceDll; - int ServicePID; - SERVICE_DETECTION(const std::wstring& wsServiceName, const std::wstring& wsServiceExecutablePath, - const std::wstring& wsServiceDll, const int& ServicePID) : - DETECTION{ DetectionType::Service }, - wsServiceName{ wsServiceName }, - wsServiceExecutablePath{ wsServiceExecutablePath }, - wsServiceDll{ wsServiceDll }, - ServicePID{ ServicePID }{} -}; -typedef std::function)> DetectService; - -enum class ProcessDetectionMethod { - Replaced = 1, - HeaderModified = 2, - Detached = 4, - Hooked = 8, - Implanted = 16, - Other = 32 -}; - -struct PROCESS_DETECTION : public DETECTION { - std::wstring wsImagePath; - std::wstring wsCmdline; - int PID; - DWORD method; - LPVOID lpAllocationBase; - DWORD dwAllocationSize; - BYTE AllocationStart[512]; // This member is intended to be used for signaturing purposes - PROCESS_DETECTION(const std::wstring& wsImagePath, const std::wstring& wsCmdLine, const int& PID, - const LPVOID& lpAllocationBase, const DWORD& dwAllocationSize, const DWORD& method) : - DETECTION{ DetectionType::Process }, - wsImagePath{ wsImagePath }, - wsCmdline{ wsCmdLine }, - PID{ PID }, - method{ method }, - lpAllocationBase{ lpAllocationBase }, - dwAllocationSize{ dwAllocationSize }, - AllocationStart{}{} -}; - -typedef std::function)> DetectProcess; - -enum class ServiceType { - kernelModeDriver, - userModeService -}; - -enum class ServiceStartType { - systemStart, - autoStart, - demandStart -}; - -/// A struct containing information about a event identified in a hunt -struct EVENT_DETECTION : public DETECTION { - unsigned int eventID; - unsigned int eventRecordID; - std::wstring timeCreated; - std::wstring channel; - std::wstring rawXML; - std::unordered_map params; - - EVENT_DETECTION(unsigned int eventID, unsigned int eventRecordID, std::wstring timeCreated, std::wstring channel, std::wstring rawXML) : - DETECTION{ DetectionType::Event }, - eventID{ eventID }, - eventRecordID{ eventRecordID }, - timeCreated{ timeCreated }, - channel{ channel }, - rawXML{ rawXML }{} -}; -typedef std::function)> DetectEvent; - -typedef std::function HuntStart; -typedef std::function HuntEnd; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/reaction/Log.h b/BLUESPAWN-client/headers/reaction/Log.h deleted file mode 100644 index 2065fc10..00000000 --- a/BLUESPAWN-client/headers/reaction/Log.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include "Reaction.h" - -#include "hunt/HuntInfo.h" -#include "util/log/huntlogmessage.h" - -#include - -namespace Reactions { - - class LogReaction : public Reaction { - private: - std::optional _HuntLogMessage; - bool HuntBegun = false; - - void LogBeginHunt(const HuntInfo& info); - void LogEndHunt(); - - /// Handlers for detections that log the detection - void LogFileIdentified(std::shared_ptr detection); - void LogRegistryKeyIdentified(std::shared_ptr detection); - void LogProcessIdentified(std::shared_ptr detection); - void LogServiceIdentified(std::shared_ptr detection); - void LogEventIdentified(std::shared_ptr detection); - - public: - LogReaction(); - }; -} - diff --git a/BLUESPAWN-client/headers/reaction/QuarantineFile.h b/BLUESPAWN-client/headers/reaction/QuarantineFile.h deleted file mode 100644 index 57109c22..00000000 --- a/BLUESPAWN-client/headers/reaction/QuarantineFile.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Reaction.h" - -#include "hunt/HuntInfo.h" -#include "user/iobase.h" -#include "common/DynamicLinker.h" - -#include - -namespace Reactions { - - class QuarantineFileReaction : public Reaction { - private: - const IOBase& io; - - /// Handlers for detections that log the detection - void QuarantineFileIdentified(std::shared_ptr detection); - - public: - QuarantineFileReaction(const IOBase& io); - }; -} - diff --git a/BLUESPAWN-client/headers/reaction/Reaction.h b/BLUESPAWN-client/headers/reaction/Reaction.h deleted file mode 100644 index a732165d..00000000 --- a/BLUESPAWN-client/headers/reaction/Reaction.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once -#include - -#include -#include - -#include "hunt/HuntInfo.h" - -#include "Detections.h" - -/** - * A container class for handling reactions to various types of detections. - * This class will usually be used by instantiating one of more subclass of Reaction and - * combining them to create the desired reaction. Addition reactions for certain types of - * detections can be added with the AddXXXXXReaction functions. - */ -class Reaction { -protected: - /// Handlers for detections - std::vector vFileReactions; - std::vector vRegistryReactions; - std::vector vServiceReactions; - std::vector vProcessReactions; - std::vector vEventReactions; - - /// Handlers for startting and beginning hunts - std::vector vStartHuntProcs; - std::vector vEndHuntProcs; - -public: - /// These functions handle the beginning and end of hunts - void BeginHunt(const HuntInfo& info); - void EndHunt(); - - /// These functions handle the identification of a detection by calling all of the associated handlers - void FileIdentified(std::shared_ptr); - void RegistryKeyIdentified(std::shared_ptr); - void ProcessIdentified(std::shared_ptr); - void ServiceIdentified(std::shared_ptr); - void EventIdentified(std::shared_ptr); - - /// These functions add handlers for beginning and ending hunts - void AddHuntBegin(HuntStart handler); - void AddHuntEnd(HuntEnd handler); - - /// These functions add handlers for detections - void AddFileReaction(DetectFile handler); - void AddRegistryReaction(DetectRegistry handler); - void AddProcessReaction(DetectProcess handler); - void AddServiceReaction(DetectService handler); - void AddEventReaction(DetectEvent handler); - - /// Merges the given reaction into the current reaction - Reaction& Combine(const Reaction& reaction); - Reaction& Combine(Reaction&& reaction); -}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/reaction/RemoveValue.h b/BLUESPAWN-client/headers/reaction/RemoveValue.h deleted file mode 100644 index ccbe4cf6..00000000 --- a/BLUESPAWN-client/headers/reaction/RemoveValue.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "Reaction.h" - -#include "hunt/HuntInfo.h" -#include "user/iobase.h" -#include "common/DynamicLinker.h" - -#include - -namespace Reactions{ - - class RemoveValueReaction : public Reaction { - private: - const IOBase& io; - - /// Handlers for detections that log the detection - void RemoveRegistryIdentified(std::shared_ptr detection); - - public: - RemoveValueReaction(const IOBase& io); - }; -} - diff --git a/BLUESPAWN-client/headers/reaction/SuspendProcess.h b/BLUESPAWN-client/headers/reaction/SuspendProcess.h deleted file mode 100644 index ecf83431..00000000 --- a/BLUESPAWN-client/headers/reaction/SuspendProcess.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "Reaction.h" - -#include "hunt/HuntInfo.h" -#include "user/iobase.h" -#include "common/DynamicLinker.h" - -#include - -DEFINE_FUNCTION(NTSTATUS, NtSuspendProcess, NTAPI, IN HANDLE ProcessHandle); - -namespace Reactions{ - - class SuspendProcessReaction : public Reaction { - private: - const IOBase& io; - - bool CheckModules(const HandleWrapper& process, const std::wstring& file) const; - - /// Handlers for detections that log the detection - void SuspendFileIdentified(std::shared_ptr detection); - void SuspendProcessIdentified(std::shared_ptr detection); - void SuspendServiceIdentified(std::shared_ptr detection); - - public: - SuspendProcessReaction(const IOBase& io); - }; -} - diff --git a/BLUESPAWN-client/headers/user/bluespawn.h b/BLUESPAWN-client/headers/user/bluespawn.h deleted file mode 100644 index 43d5e2f4..00000000 --- a/BLUESPAWN-client/headers/user/bluespawn.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include - -#include "user/banners.h" - -#include "util/log/Log.h" -#include "util/log/CLISink.h" -#include "util/configurations/Registry.h" - -#include "hunt/Hunt.h" -#include "hunt/HuntRegister.h" -#include "reaction/Reaction.h" - -#include "mitigation/Mitigation.h" -#include "mitigation/MitigationRegister.h" - -class Bluespawn { - Reaction reaction; - - public: - Bluespawn(); - - void SetReaction(const Reaction& reaction); - - void dispatch_hunt(Aggressiveness aHuntLevel, vector vExcludedHunts, vector vIncludedHunts); - void dispatch_mitigations_analysis(MitigationMode mode, bool bForceEnforce); - void monitor_system(Aggressiveness aHuntLevel); - void check_correct_arch(); - - static HuntRegister huntRecord; - static MitigationRegister mitigationRecord; - static const IOBase& io; -}; diff --git a/BLUESPAWN-client/headers/util/filesystem/YaraScanner.h b/BLUESPAWN-client/headers/util/filesystem/YaraScanner.h deleted file mode 100644 index 70c48b13..00000000 --- a/BLUESPAWN-client/headers/util/filesystem/YaraScanner.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "yara.h" -#include "util/filesystem/FileSystem.h" -#include "common/wrappers.hpp" - -#include -#include - -enum class YaraStatus { - Success, - RulesMissing, - RulesInvalid, - Failure, -}; - -struct YaraScanResult { - std::vector vKnownBadRules; - std::vector vIndicatorRules; - - YaraStatus status; - - operator bool(); - bool operator!(); - - void AddBadRule(const char* identifier); - void AddIndicatorRule(const char* identifier); -}; - -class YaraScanner { -private: - static const YaraScanner instance; - - YaraScanner(); - - YR_RULES* KnownBad = nullptr; - YR_RULES* KnownBad2 = nullptr; - YR_RULES* Indicators = nullptr; - - YaraStatus status; - -public: - - static const YaraScanner& GetInstance(); - - ~YaraScanner(); - - YaraScanResult ScanFile(const FileSystem::File& file) const; - YaraScanResult ScanMemory(LPVOID location, DWORD size) const; - YaraScanResult ScanMemory(const AllocationWrapper& allocation) const; - YaraScanResult ScanMemory(const MemoryWrapper<>& memory) const; - - YaraScanner(const YaraScanner&) = delete; - YaraScanner operator=(const YaraScanner&) = delete; - YaraScanner(YaraScanner&&) = delete; - YaraScanner operator=(YaraScanner&&) = delete; -}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/log/CLISink.h b/BLUESPAWN-client/headers/util/log/CLISink.h deleted file mode 100644 index c1ebd76e..00000000 --- a/BLUESPAWN-client/headers/util/log/CLISink.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include - -#include "LogSink.h" -#include "LogLevel.h" - -namespace Log { - - /** - * CLISink provides a sink for the logger that directs output to the console. - * - * Each log message is prepended with the severity of the log, as defined in - * MessagePrepends. This prepended text is colored with the color indicated in - * PrependColors. - */ - class CLISink : public LogSink { - private: - enum class MessageColor { - BLACK = 0x0, - DARKBLUE = 0x1, - DARKGREEN = 0x2, - CYAN = 0x3, - DARKRED = 0x4, - DARKPINK = 0x5, - GOLD = 0x6, - LIGHTGREY = 0x7, - DARKGREY = 0x8, - BLUE = 0x9, - GREEN = 0xA, - LIGHTBLUE = 0xB, - RED = 0xC, - PINK = 0xD, - YELLOW = 0xE, - WHITE = 0xF, - }; - std::string MessagePrepends[4] = { "[ERROR]", "[WARNING]", "[INFO]", "[OTHER]" }; - MessageColor PrependColors[5] = { MessageColor::RED, MessageColor::YELLOW, MessageColor::BLUE, MessageColor::GREEN, MessageColor::GOLD }; - HandleWrapper hMutex; - - /** - * Sets the color of text written to the console. The low order nibble is the color - * of the text, and the high order nibble is the color of the background. Colors are - * defined in the MessageColor enum. Note that this function is for internal use, and - * any external calls to it will be overridden by the next log message. - * - * @param color The color to set the console - */ - void SetConsoleColor(MessageColor color); - - public: - - CLISink(); - - /** - * Outputs a message to the console if its logging level is enabled. The log message - * is prepended with its severity level. - * - * @param level The level at which the message is being logged - * @param message The message to log - */ - virtual void LogMessage(const LogLevel& level, const std::string& message, const std::optional info = std::nullopt, - const std::vector>& detections = {}) override; - - /** - * Compares this CLISink to another LogSink. Currently, as only one console is supported, - * any other CLISink is considered to be equal. This is subject to change in the event that - * support for more consoles is added. - * - * @param sink The LogSink to compare - * - * @return Whether or not the argument and this sink are considered equal. - */ - virtual bool operator==(const LogSink& sink) const; - }; -} diff --git a/BLUESPAWN-client/headers/util/log/DebugSink.h b/BLUESPAWN-client/headers/util/log/DebugSink.h deleted file mode 100644 index 269c4b67..00000000 --- a/BLUESPAWN-client/headers/util/log/DebugSink.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include "LogSink.h" - -namespace Log { - - /** - * DebugSink provides a sink for the logger that directs output to the debug console. - * - * Each log message is prepended with the severity of the log, as defined in - * MessagePrepends. - */ - class DebugSink : public LogSink { - private: - std::string MessagePrepends[5] = { "[ERROR]", "[WARNING]", "[INFO]", "[OTHER]", "[HUNT]" }; - - public: - - /** - * Outputs a message to the debug console if its logging level is enabled. The log message - * is prepended with its severity level. - * - * @param level The level at which the message is being logged - * @param message The message to log - */ - virtual void LogMessage(const LogLevel& level, const std::string& message, const std::optional info = std::nullopt, - const std::vector>& detections = {}); - - /** - * Compares this DebugSink to another LogSink. Currently, as only one debug console is supported, - * any other DebugSink is considered to be equal. This is subject to change in the event that - * support for more consoles is added. - * - * @param sink The LogSink to compare - * - * @return Whether or not the argument and this sink are considered equal. - */ - virtual bool operator==(const LogSink& sink) const; - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/log/HuntLogMessage.h b/BLUESPAWN-client/headers/util/log/HuntLogMessage.h deleted file mode 100644 index ff5fac9e..00000000 --- a/BLUESPAWN-client/headers/util/log/HuntLogMessage.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once -#include "Log.h" -#include "LogLevel.h" -#include "LogSink.h" - -#include "reaction/detections.h" -#include "hunt/huntinfo.h" - -#include -#include -#include - -// Creates a Hunt log message named _HuntLogMessage. This macro is only to be called inside -// ScanCursory, ScanNormal, or ScanIntensive. -#define LOG_HUNT_BEGIN() \ - auto _HuntLogMessage = Log::HuntLogMessage(GET_INFO(), Log::_LogHuntSinks) - -// Logs a detection to the log message for the current hunt. LOG_HUNT_BEGIN should be called first. -#define LOG_HUNT_DETECTION(detection) _HuntLogMessage.AddDetection(std::static_pointer_cast(detection)) - -// Adds a message to the log for this hunt. LOG_HUNT_BEGIN should be called first. -#define LOG_HUNT_MESSAGE(...) _HuntLogMessage << __VA_ARGS__ - -// Terminates the current hunt's log message. LOG_HUNT_BEGIN should be called first. -// Note that this must be called in order for the message to actually be logged. -#define LOG_HUNT_END() _HuntLogMessage << Log::endlog - -namespace Log { - - // A vector containing the set of sinks to be used when LOG_HUNT is used. - // This vector is updated by the AddHuntSink and RemoveHuntSink functions. - extern std::vector> _LogHuntSinks; - - /** - * This class is a specialization of the LogMessage class designed to handle detections - * from a hunt. When a hunt is started, it should create a new HuntLogMessage. With each - * detection, it should add the detection to the HuntLogMessage. When the hunt is finished, - * it should stream a LogTerminator to the HuntLogMessage. - */ - class HuntLogMessage : public LogMessage { - protected: - std::vector> Detections; - HuntInfo HuntName; - - public: - - /** - * Creates a log message at a given level and with a vector of sinks. - * - * @param Hunt A HuntInfo struct containing information about the hunt. - * @param sinks The sinks that this message will log itself to. - */ - HuntLogMessage(const HuntInfo& Hunt, const std::vector>& sinks); - - /** - * Creates a log message at a given level and with a sink. - * - * @param Hunt A HuntInfo struct containing information about the hunt. - * @param sink The sink that this message will log itself to. - */ - HuntLogMessage(const HuntInfo& Hunt, const std::shared_ptr& sink); - - /** - * Records a detection to the hunt. - * - * @param detection The detection to record. This should be an instance of - * FILE_DETECTION, REGISTRY_DETECTION, SERIVCE_DETECTION, or PROCESS_DETECTION. - */ - void AddDetection(std::shared_ptr detection); - - /** - * When the LogTerminator is supplied to the stream, the stream is terminated and forwarded to - * the sinks for recording. After this happens, the log message is emptied and able to be used - * again. - * - * @param terminator An instance of the LogTerminator class used to denote the termination of a - * message - * - * @return a reference to this log message. - */ - virtual LogMessage& operator<<(const LogTerminator& termiantor); - - using LogMessage::operator<<; - - /** - * Copy overload for =. Copies hunt information, sinks, the message, and any detections. - * - * @param message The message to copy from. - * - * @return The new value of *this; - */ - HuntLogMessage operator =(const HuntLogMessage& message); - HuntLogMessage(const HuntLogMessage& message); - }; - - /** - * Adds a sink to the vector of default sinks to be used in LOG_HUNT_*. - * If the provided sink is equal to any sink in the vector already, this will return false - * and the sink will not be added. - * - * @param sink The sink to be added - * - * @return A boolean indicating whether or not the sink was added - */ - bool AddHuntSink(const std::shared_ptr& sink); - - /** - * Removes a sink from the vector of default sinks to be used in LOG_ERROR, LOG_WARNING, etc. - * If the provided sink is not equal to any sink in the vector already, this will return false - * and nothing will happen. - * - * @param sink The sink to be removed - * - * @return A boolean indicating whether or not the sink was removed - */ - bool RemoveHuntSink(const std::shared_ptr& sink); -} diff --git a/BLUESPAWN-client/headers/util/log/XMLSink.h b/BLUESPAWN-client/headers/util/log/XMLSink.h deleted file mode 100644 index 2855179b..00000000 --- a/BLUESPAWN-client/headers/util/log/XMLSink.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include "LogSink.h" -#include "../external/tinyxml2/tinyxml2.h" - -namespace Log { - - /** - * XMLSink provides a sink for the logger that saves log messages to an XML file. - */ - class XMLSink : public LogSink { - HandleWrapper hMutex; - - tinyxml2::XMLDocument XMLDoc; - tinyxml2::XMLElement* Root; - - std::wstring wFileName; - - std::string MessageTags[5] = { "error", "warning", "info", "other", "hunt" }; - - HandleWrapper thread; - - public: - - /** - * Default constructor for XMLSink. By default, the log will be saved to a file - * named bluespawn-MM-DD-YYYY-HHMM-SS.xml - */ - XMLSink(); - - /** - * Constructor for XMLSink. The log will be saved with the name passed as the argument - * - * @param wFileName The name of the file to save the log as. - */ - XMLSink(const std::wstring& wFileName); - - XMLSink operator=(const XMLSink&) = delete; - XMLSink operator=(XMLSink&&) = delete; - XMLSink(const XMLSink&) = delete; - XMLSink(XMLSink&&) = delete; - - ~XMLSink(); - - /** - * Outputs a message to the debug console if its logging level is enabled. The log message - * is prepended with its severity level. - * - * @param level The level at which the message is being logged - * @param message The message to log - */ - virtual void LogMessage(const LogLevel& level, const std::string& message, const std::optional info = std::nullopt, - const std::vector>& detections = {}); - - /** - * Compares this DebugSink to another LogSink. Currently, as only one debug console is supported, - * any other DebugSink is considered to be equal. This is subject to change in the event that - * support for more consoles is added. - * - * @param sink The LogSink to compare - * - * @return Whether or not the argument and this sink are considered equal. - */ - virtual bool operator==(const LogSink& sink) const; - - /** - * Flushes the log to the file. - */ - void Flush(); - }; -} \ No newline at end of file diff --git a/BLUESPAWN-client/libpeconv.vcxproj b/BLUESPAWN-client/libpeconv.vcxproj deleted file mode 100644 index 716d258c..00000000 --- a/BLUESPAWN-client/libpeconv.vcxproj +++ /dev/null @@ -1,119 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - Debug - Win32 - - - Release - Win32 - - - - {C9D09618-1DE6-3323-AED8-9B885AC8D9F3} - libpeconv - - - - $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log - - - $(SolutionDir)BLUESPAWN-client\external\pe-sieve\include;$(SolutionDir)BLUESPAWN-client\external\pe-sieve\libpeconv\libpeconv\include;%(AdditionalIncludeDirectories) - UNICODE - - - Secur32.lib;DbgHelp.lib;Wintrust.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - StaticLibrary - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/BLUESPAWN-client/pe-sieve.vcxproj b/BLUESPAWN-client/pe-sieve.vcxproj deleted file mode 100644 index 1ca0c65c..00000000 --- a/BLUESPAWN-client/pe-sieve.vcxproj +++ /dev/null @@ -1,102 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - Debug - Win32 - - - Release - Win32 - - - - {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9} - pe-sieve - - - - $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log - - - $(SolutionDir)BLUESPAWN-client\external\pe-sieve\include;$(SolutionDir)BLUESPAWN-client\external\pe-sieve\libpeconv\libpeconv\include;%(AdditionalIncludeDirectories) - UNICODE - - - Secur32.lib;DbgHelp.lib;Wintrust.lib;%(AdditionalDependencies) - - - - - StaticLibrary - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {C9D09618-1DE6-3323-AED8-9B885AC8D9F3} - libpeconv - - - \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/Hunt.cpp b/BLUESPAWN-client/src/hunt/Hunt.cpp deleted file mode 100644 index bec9d21d..00000000 --- a/BLUESPAWN-client/src/hunt/Hunt.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "hunt/Hunt.h" -#include "hunt/HuntRegister.h" -#include "reaction/Reaction.h" - -HuntInfo::HuntInfo(const std::wstring& HuntName, Aggressiveness HuntAggressiveness, DWORD HuntTactics, DWORD HuntCategories, DWORD HuntDatasources) : - HuntName{ HuntName }, - HuntAggressiveness{ HuntAggressiveness }, - HuntTactics{ HuntTactics }, - HuntCategories{ HuntCategories }, - HuntDatasources{ HuntDatasources }{ - GetSystemTime(&HuntStartTime); -} - -Hunt::Hunt(const std::wstring& name) : - name{ name }{ - dwTacticsUsed = 0; - dwSourcesInvolved = 0; - dwCategoriesAffected = 0; - dwSupportedScans = 0; -} - -std::wstring Hunt::GetName() { - return name; -} - -int Hunt::ScanCursory(const Scope& scope, Reaction reaction){ - if(!(dwSupportedScans & (DWORD) Aggressiveness::Cursory)){ - return -1; - } - return 0; -} - -int Hunt::ScanNormal(const Scope& scope, Reaction reaction){ - if(!(dwSupportedScans & (DWORD) Aggressiveness::Normal)){ - return -1; - } - return 0; -} - -int Hunt::ScanIntensive(const Scope& scope, Reaction reaction){ - if(!(dwSupportedScans & (DWORD) Aggressiveness::Intensive)){ - return -1; - } - return 0; -} - -std::vector> Hunt::GetMonitoringEvents() { - return std::vector>(); -} - -bool Hunt::AffectsCategory(DWORD dwStuff){ - return (dwStuff && dwCategoriesAffected) == dwStuff; -} - -bool Hunt::UsesTactics(DWORD dwTactics){ - return (dwTactics && dwTacticsUsed) == dwTactics; -} - -bool Hunt::UsesSources(DWORD dwSources){ - return (dwSources && dwSourcesInvolved) == dwSources; -} - -bool Hunt::SupportsScan(Aggressiveness aggressiveness){ - return ((DWORD) aggressiveness & dwSupportedScans) == (DWORD) aggressiveness; -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/HuntRegister.cpp b/BLUESPAWN-client/src/hunt/HuntRegister.cpp deleted file mode 100644 index 33005db7..00000000 --- a/BLUESPAWN-client/src/hunt/HuntRegister.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "hunt/HuntRegister.h" -#include -#include -#include "monitor/EventManager.h" -#include "util/log/Log.h" -#include "common/StringUtils.h" -#include "user/bluespawn.h" - -HuntRegister::HuntRegister(const IOBase& io) : io(io) {} - -void HuntRegister::RegisterHunt(std::shared_ptr hunt) { - // The actual hunt itself is stored in the vector here! - // Make sure that all internal references to it are referencing - // the copy in vRegisteredHunts and not the argument to this function. - vRegisteredHunts.emplace_back(hunt); - - /*for(DWORD i = 1; i != 0; i <<= 1){ - if(hunt->UsesTactics(i)){ - mTactics[(Tactic::Tactic) i].emplace_back(hunt); - } - - if(hunt->UsesSources(i)){ - mDataSources[(DataSource::DataSource) i].emplace_back(hunt); - } - - if(hunt->AffectsStuff(i)){ - mAffectedThings[(AffectedThing::AffectedThing) i].emplace_back(hunt); - } - }*/ -} - -bool HuntRegister::HuntShouldRun(Hunt& hunt, vector vExcludedHunts, vector vIncludedHunts) { - if (vExcludedHunts.size() != 0) { - for (auto name : vExcludedHunts) { - if (hunt.GetName().find(StringToWidestring(name)) != wstring::npos) { - return false; - } - } - return true; - } - if(vIncludedHunts.size() != 0) { - for (auto name : vIncludedHunts) { - if (hunt.GetName().find(StringToWidestring(name)) != wstring::npos) { - return true; - } - } - return false; - } - return true; -} - -bool CallFunctionSafe(const std::function& func){ - __try{ - func(); - return true; - } __except(EXCEPTION_EXECUTE_HANDLER){ - return false; - } -} - -void HuntRegister::RunHunts(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction, vector vExcludedHunts, vectorvIncludedHunts){ - io.InformUser(L"Starting a hunt for " + std::to_wstring(vRegisteredHunts.size()) + L" techniques."); - DWORD huntsRan = 0; - - for (auto name : vRegisteredHunts) { - if (HuntShouldRun(*name, vExcludedHunts, vIncludedHunts)) { - int huntRunStatus = 0; - auto level = getLevelForHunt(*name, aggressiveness); - bool status{ false }; - status |= level == Aggressiveness::Cursory && CallFunctionSafe([&](){ huntRunStatus = name->ScanCursory(scope, reaction); }); - status |= level == Aggressiveness::Normal && CallFunctionSafe([&](){ huntRunStatus = name->ScanNormal(scope, reaction); }); - status |= level == Aggressiveness::Intensive && CallFunctionSafe([&](){ huntRunStatus = name->ScanIntensive(scope, reaction); }); - if(!status){ - Bluespawn::io.InformUser(L"An issue occured in hunt " + name->GetName() + L", preventing it from being run", ImportanceLevel::HIGH); - } else if(huntRunStatus != -1){ - huntsRan++; - } - } - } - if (huntsRan != vRegisteredHunts.size()) { - io.InformUser(L"Successfully ran " + std::to_wstring(huntsRan) + L" hunts. There were no scans available for " + std::to_wstring(vRegisteredHunts.size() - huntsRan) + L" of the techniques."); - } - else { - io.InformUser(L"Successfully ran " + std::to_wstring(huntsRan) + L" hunts."); - } -} - -void HuntRegister::RunHunt(Hunt& hunt, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction){ - io.InformUser(L"Starting scan for " + hunt.GetName()); - int huntRunStatus = 0; - - auto level = getLevelForHunt(hunt, aggressiveness); - switch (level) { - case Aggressiveness::Intensive: - huntRunStatus = hunt.ScanIntensive(scope, reaction); - break; - case Aggressiveness::Normal: - huntRunStatus = hunt.ScanNormal(scope, reaction); - break; - case Aggressiveness::Cursory: - huntRunStatus = hunt.ScanCursory(scope, reaction); - break; - } - - if (huntRunStatus == -1) { - io.InformUser(L"No scans for this level available for " + hunt.GetName()); - } - else { - io.InformUser(L"Successfully scanned for " + hunt.GetName()); - } -} - -void HuntRegister::SetupMonitoring(Aggressiveness aggressiveness, const Reaction& reaction) { - auto& EvtManager = EventManager::GetInstance(); - for (auto name : vRegisteredHunts) { - auto level = getLevelForHunt(*name, aggressiveness); - if(name->SupportsScan(level)) { - io.InformUser(L"Setting up monitoring for " + name->GetName()); - for(auto event : name->GetMonitoringEvents()) { - - std::function callback; - - switch(level) { - case Aggressiveness::Intensive: - callback = std::bind(&Hunt::ScanIntensive, name.get(), Scope{}, reaction); - break; - case Aggressiveness::Normal: - callback = std::bind(&Hunt::ScanNormal, name.get(), Scope{}, reaction); - break; - case Aggressiveness::Cursory: - callback = std::bind(&Hunt::ScanCursory, name.get(), Scope{}, reaction); - break; - } - - if(name->SupportsScan(level)) { - DWORD status = EvtManager.SubscribeToEvent(event, callback); - if(status != ERROR_SUCCESS){ - LOG_ERROR(L"Monitoring for " << name->GetName() << L" failed with error code " << status); - } - } - } - } - } -} - -Aggressiveness HuntRegister::getLevelForHunt(Hunt& hunt, Aggressiveness aggressiveness) { - if (aggressiveness == Aggressiveness::Intensive) { - if (hunt.SupportsScan(Aggressiveness::Intensive)) - return Aggressiveness::Intensive; - else if (hunt.SupportsScan(Aggressiveness::Normal)) - return Aggressiveness::Normal; - } - else if (aggressiveness == Aggressiveness::Normal) - if (hunt.SupportsScan(Aggressiveness::Normal)) - return Aggressiveness::Normal; - - return Aggressiveness::Cursory; -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/Scope.cpp b/BLUESPAWN-client/src/hunt/Scope.cpp deleted file mode 100644 index 68eda6ba..00000000 --- a/BLUESPAWN-client/src/hunt/Scope.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "hunt/Scope.h" - -bool Scope::FileIsInScope(LPCSTR sFileName) const { - return true; -} -bool Scope::FileIsInScope(HANDLE hFile) const { - return true; -} -std::vector Scope::GetScopedFileHandles() const { - return std::vector(); -} -std::vector Scope::GetScopedFileNames() const { - return std::vector(); -} - -bool Scope::RegistryKeyIsInScope(LPCSTR pid) const { - return true; -} -bool Scope::RegistryKeyIsInScope(HKEY key) const { - return true; -} -std::vector Scope::GetScopedKHEYs() const { - return std::vector(); -} -std::vector Scope::GetScopedRegKeyNames() const { - return std::vector(); -} - -bool Scope::ProcessIsInScope(DWORD sProcessName) const { - return true; -} -bool Scope::ProcessIsInScope(HANDLE hProcess) const { - return true; -} -std::vector Scope::GetScopedProcessHandles() const { - return std::vector(); -} -std::vector Scope::GetScopedProcessPIDs() const { - return std::vector(); -} - -bool Scope::ServiceIsInScope(LPCSTR sServiceName) const { - return true; -} -bool Scope::ServiceIsInScope(SC_HANDLE hService) const { - return true; -} -std::vector Scope::GetScopedServiceHandles() const { - return std::vector(); -} -std::vector Scope::GetScopedServiceNames() const { - return std::vector(); -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1004.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1004.cpp deleted file mode 100644 index 3d7e279e..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1004.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "hunt/hunts/HuntT1004.h" -#include "hunt/RegistryHunt.h" - -#include "util/configurations/Registry.h" -#include "util/log/Log.h" -#include "util/eventlogs/EventLogs.h" - -#include "common/Utils.h" - -#include - -using namespace Registry; - -namespace Hunts { - - HuntT1004::HuntT1004() : Hunt(L"T1004 - Winlogon Helper DLL") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - int HuntT1004::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - std::vector winlogons{ CheckValues(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", { - { L"Shell", L"explorer\\.exe,?", false, CheckSzRegexMatch }, - { L"UserInit", L"(C:\\\\(Windows|WINDOWS|windows)\\\\(System32|SYSTEM32|system32)\\\\)?(U|u)(SERINIT|serinit)\\.(exe|EXE),?", false, CheckSzRegexMatch } - }, true, true) }; - for(auto& detection : winlogons){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - - std::vector notifies{ CheckKeyValues(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify", true, true) }; - for(auto& notify : CheckSubkeys(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify", true, true)){ - if(notify.ValueExists(L"DllName")){ - notifies.emplace_back(RegistryValue{ notify, L"DllName", *notify.GetValue(L"DllName") }); - } - } - for(auto& detection : notifies){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - - auto filename{ std::get(detection.data) }; - auto filepath{ FileSystem::SearchPathExecutable(filename) }; - if(filepath){ - reaction.FileIdentified(std::make_shared(FileSystem::File{ *filepath })); - detections++; - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1004::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon")); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Notify", true, true, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1013.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1013.cpp deleted file mode 100644 index 4c64c095..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1013.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "hunt/hunts/HuntT1013.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" - -using namespace Registry; - -namespace Hunts { - - HuntT1013::HuntT1013() : Hunt(L"T1013 - Port Monitors") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1013::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - auto monitors = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors" }; - - for (auto monitor : monitors.EnumerateSubkeys()) { - if (monitor.ValueExists(L"Driver")) { - auto filepath = FileSystem::SearchPathExecutable(monitor.GetValue(L"Driver").value()); - - if (filepath && FileSystem::CheckFileExists(*filepath)) { - FileSystem::File monitordll = FileSystem::File(*filepath); - - if (!monitordll.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ monitor, L"Driver", monitor.GetValue(L"Driver").value() })); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(monitordll); - - reaction.FileIdentified(std::make_shared(monitordll)); - - detections += 2; - } - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1013::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors", false, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1015.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1015.cpp deleted file mode 100644 index 8b067718..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1015.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "hunt/hunts/HuntT1015.h" -#include "hunt/RegistryHunt.h" - -#include "util/filesystem/FileSystem.h" -#include "util/log/Log.h" -#include "util/filesystem/YaraScanner.h" - -#include "common/Utils.h" - -using namespace Registry; - -namespace Hunts { - HuntT1015::HuntT1015() : Hunt(L"T1015 - Accessibility Features") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory | (DWORD) Aggressiveness::Normal; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1015::EvaluateRegistry(Reaction& reaction) { - int detections = 0; - - auto& yara = YaraScanner::GetInstance(); - - for (auto key : vAccessibilityBinaries) { - std::vector debugger{ CheckValues(HKEY_LOCAL_MACHINE, wsIFEO + key, { - { L"Debugger", L"", false, CheckSzEmpty }, - }, true, false) }; - for(auto& detection : debugger){ - detections++; - reaction.RegistryKeyIdentified(std::make_shared(detection)); - LOG_INFO(detection.key.GetName() << L" is configured with a Debugger value of " << detection); - - FileSystem::File file = FileSystem::File(detection.ToString()); - bool bFileSigned = file.GetFileSigned(); - - if(!bFileSigned) { - YaraScanResult result = yara.ScanFile(file); - detections++; - reaction.FileIdentified(std::make_shared(file)); - } - } - } - - return detections; - } - - int HuntT1015::EvaluateFiles(Reaction& reaction, bool bScanYara) { - int detections = 0; - - for (auto key : vAccessibilityBinaries) { - FileSystem::File file = FileSystem::File(L"C:\\Windows\\System32\\" + key); - - if (!file.GetFileSigned()) { - if (bScanYara) { - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(file); - } - - LOG_INFO(file.GetFilePath() << L" is not signed!"); - reaction.FileIdentified(std::make_shared(file)); - detections++; - } - } - - return detections; - } - - int HuntT1015::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int results = EvaluateRegistry(reaction); - results += EvaluateFiles(reaction, false); - - reaction.EndHunt(); - return results; - } - - int HuntT1015::ScanNormal(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Normal"); - reaction.BeginHunt(GET_INFO()); - - - int results = EvaluateRegistry(reaction); - results += EvaluateFiles(reaction, true); - - reaction.EndHunt(); - return results; - } - - std::vector> HuntT1015::GetMonitoringEvents() { - std::vector> events; - - for (auto key : vAccessibilityBinaries) { - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, wsIFEO + key, true, false, false)); - } - - return events; - } -} diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1031.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1031.cpp deleted file mode 100644 index e9b8c859..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1031.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "hunt/hunts/HuntT1031.h" - -#include "util/filesystem/FileSystem.h" -#include "util/configurations/Registry.h" -#include "hunt/RegistryHunt.h" -#include "util/filesystem/YaraScanner.h" -#include "util/processes/ProcessUtils.h" -#include "util/processes/CheckLolbin.h" - -#include "util/log/Log.h" - -using namespace Registry; - -namespace Hunts { - - HuntT1031::HuntT1031() : Hunt(L"T1031 - Modify Existing Service") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::Services; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - int HuntT1031::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - // DNS Service Audit - - if (RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\DNS\\Parameters" }.Exists()) { - std::vector dnsServerPlugins{ CheckValues(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\DNS\\Parameters", { - { L"ServerLevelPluginDll", L"", false, CheckSzEmpty }, - }, false, false) }; - - for (auto& detection : dnsServerPlugins) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - reaction.FileIdentified(std::make_shared(FileSystem::File(detection.ToString()))); - detections += 2; - } - } - - - // NTDS Service Audit - if (RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NTDS" }.Exists()) { - std::vector lsassDlls{ CheckValues(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NTDS", { - { L"LsaDbExtPt", L"", false, CheckSzEmpty }, - { L"DirectoryServiceExtPt", L"", false, CheckSzEmpty }, - }, false, false) }; - - for (auto& detection : lsassDlls) { - auto file = FileSystem::File{ detection.ToString() }; - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - } - } - } - - - // Winsock2 Service Audit - - auto winsock2 = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters" }; - - for (auto paramdll : { L"AutodialDLL", L"NameSpace_Callout" }) { - auto filepath = winsock2.GetValue(paramdll); - if (filepath) { - auto file = FileSystem::File{ filepath.value() }; - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ winsock2, paramdll, winsock2.GetValue(paramdll).value() })); - reaction.FileIdentified(std::make_shared(FileSystem::File(file))); - detections += 2; - } - } - } - - auto appids = RegistryKey{ winsock2, L"AppId_Catalog" }; - - if (appids.Exists()) { - for (auto subkey : appids.EnumerateSubkeys()) { - auto filepath = subkey.GetValue(L"AppFullPath"); - if (filepath) { - auto file = FileSystem::File{ filepath.value() }; - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ subkey, L"AppFullPath", subkey.GetValue(L"AppFullPath").value() })); - reaction.FileIdentified(std::make_shared(FileSystem::File(file))); - detections += 2; - } - } - } - } - - auto currentCallout = winsock2.GetValue(L"Current_NameSpace_Catalog"); - if (currentCallout) { - auto namespaceCatalog = RegistryKey{ winsock2, currentCallout.value() + L"\\Catalog_Entries" }; - auto namespaceCatalog64 = RegistryKey{ winsock2, currentCallout.value() + L"\\Catalog_Entries64" }; - for (auto subkey : { namespaceCatalog, namespaceCatalog64 }) { - for (auto entry : subkey.EnumerateSubkeys()) { - auto filepath = entry.GetValue(L"LibraryPath"); - if (filepath) { - auto file = FileSystem::File{ filepath.value() }; - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ entry, L"LibraryPath", entry.GetValue(L"LibraryPath").value() })); - reaction.FileIdentified(std::make_shared(FileSystem::File(file))); - detections += 2; - } - } - } - } - } - - - // Service Failure Audit - - auto services = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services" }; - - for (auto service : services.EnumerateSubkeys()) { - if (!service.ValueExists(L"FailureCommand")) { - continue; - } - - auto cmd{ *service.GetValue(L"FailureCommand") }; - - if(IsLolbinMalicious(cmd)){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ service, L"FailureCommand", service.GetValue(L"FailureCommand").value() })); - detections++; - } else { - auto filepath = GetImagePathFromCommand(cmd); - - FileSystem::File image = FileSystem::File(filepath); - if(image.GetFileExists() && !image.GetFileSigned()){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ service, L"FailureCommand", service.GetValue(L"FailureCommand").value() })); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(image); - - reaction.FileIdentified(std::make_shared(image)); - - detections += 2; - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1031::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services", false, false)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\DNS\\Parameters", false, false, false)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", false, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1035.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1035.cpp deleted file mode 100644 index b8ee9ee7..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1035.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "hunt/hunts/HuntT1035.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" -#include "util/processes/ProcessUtils.h" -#include "util/processes/CheckLolbin.h" -#include "common/Utils.h" - -using namespace Registry; - -namespace Hunts { - HuntT1035::HuntT1035() : Hunt(L"T1035 - Service Execution") { - dwSupportedScans = (DWORD) Aggressiveness::Normal; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files | (DWORD) Category::Processes; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Execution; - } - - int HuntT1035::EvaluateService(Registry::RegistryKey key, Reaction& reaction) { - int detections = 0; - - auto cmd{ *key.GetValue(L"ImagePath") }; - - if(IsLolbinMalicious(cmd)){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ key, L"ImagePath", key.GetValue(L"ImagePath").value() })); - detections++; - } else{ - auto filepath = GetImagePathFromCommand(cmd); - - FileSystem::File image = FileSystem::File(filepath); - if(image.GetFileExists() && !image.GetFileSigned()){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ key, L"ImagePath", key.GetValue(L"ImagePath").value() })); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(image); - - reaction.FileIdentified(std::make_shared(image)); - - detections += 2; - } - } - - RegistryKey subkey = RegistryKey{ key, L"Parameters" }; - - for (auto regkey : { key, subkey }) { - if (!regkey.Exists()) { - continue; - } - - std::wstring value = L"ServiceDll"; - if (regkey.ValueExists(value)) { - auto filepath2 = FileSystem::SearchPathExecutable(regkey.GetValue(value).value()); - if (filepath2 && FileSystem::CheckFileExists(*filepath2)) { - FileSystem::File servicedll = FileSystem::File(*filepath2); - - if (!servicedll.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ regkey, value, regkey.GetValue(value).value() })); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(servicedll); - - reaction.FileIdentified(std::make_shared(servicedll)); - - detections += 2; - } - } - } - } - - return detections; - } - - int HuntT1035::ScanNormal(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << " at level Normal"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - auto services = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services" }; - - for (auto service : services.EnumerateSubkeys()) { - if (service.GetValue(L"Type") >= 0x10u) { - detections += EvaluateService(service, reaction); - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1035::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services", false, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1036.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1036.cpp deleted file mode 100644 index 62225fb8..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1036.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "hunt/hunts/HuntT1036.h" - -#include "util/log/Log.h" -#include "util/filesystem/FileSystem.h" - -namespace Hunts { - - HuntT1036::HuntT1036() : Hunt(L"T1036 - Masquerading") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; - } - - int HuntT1036::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - FileSystem::FileSearchAttribs searchFilters; - searchFilters.extensions = susExts; - - for (auto folder : writableFolders) { - auto f = FileSystem::Folder(folder); - if (f.GetFolderExists()) { - LOG_VERBOSE(1, L"Scanning " << f.GetFolderPath()); - for (auto value : f.GetFiles(searchFilters, -1)) { - if (value.GetFileAttribs().extension == L".exe" || value.GetFileAttribs().extension == L".dll") { - if (!value.GetFileSigned()) { - reaction.FileIdentified(std::make_shared(value)); - detections++; - } - } else { - reaction.FileIdentified(std::make_shared(value)); - detections++; - } - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1036::GetMonitoringEvents() { - std::vector> events; - - for (auto folder : writableFolders) { - auto f = FileSystem::Folder(folder); - if (f.GetFolderExists()) { - events.push_back(std::make_shared(f)); - for (auto subdir : f.GetSubdirectories(-1)) { - events.push_back(std::make_shared(subdir)); - } - } - } - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp deleted file mode 100644 index e4d7f02c..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "hunt/hunts/HuntT1037.h" -#include "hunt/RegistryHunt.h" - -#include "util/filesystem/FileSystem.h" -#include "util/log/Log.h" -#include "util/filesystem/YaraScanner.h" - -using namespace Registry; - -namespace Hunts{ - HuntT1037::HuntT1037() : Hunt(L"T1037 - Logon Scripts") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory | (DWORD) Aggressiveness::Normal | (DWORD) Aggressiveness::Intensive; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::LateralMovement; - } - - int HuntT1037::EvaluateStartupFile(FileSystem::File file, Reaction& reaction, Aggressiveness level) { - //Scan with YARA at all levels - LOG_VERBOSE(1, L"Examining " << file.GetFilePath()); - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(file); - bool bFileSigned = file.GetFileSigned(); - - if(level == Aggressiveness::Cursory || level == Aggressiveness::Normal) { - if (file.GetFileAttribs().extension == L".exe" && !bFileSigned) { - reaction.FileIdentified(std::make_shared(file)); - return 1; - } - if(!result && result.vKnownBadRules.size() > 0) { - reaction.FileIdentified(std::make_shared(file)); - return 1; - } - } - if(level == Aggressiveness::Normal) { - if((std::find(sus_exts.begin(), sus_exts.end(), file.GetFileAttribs().extension) != sus_exts.end())) { - LOG_INFO(L"Startup with suspicious extension identified."); - reaction.FileIdentified(std::make_shared(file)); - return 1; - } - } else if(level == Aggressiveness::Intensive) { - reaction.FileIdentified(std::make_shared(file)); - return 1; - } - - return 0; - } - - int Hunts::HuntT1037::AnalyzeRegistryStartupKey(Reaction reaction, Aggressiveness level) { - std::map> keys; - - int detections = 0; - for(auto& detection : CheckValues(HKEY_CURRENT_USER, L"Environment", { - { L"UserInitMprLogonScript", L"", false, CheckSzEmpty } - }, true, true)){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - - FileSystem::File file = FileSystem::File(detection.ToString()); - detections += EvaluateStartupFile(file, reaction, level); - } - - return detections; - } - - int Hunts::HuntT1037::AnalayzeStartupFolders(Reaction reaction, Aggressiveness level) { - int detections = 0; - - std::vector startup_directories = { FileSystem::Folder(L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp") }; - auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); - for (auto userFolder : userFolders) { - auto folder = FileSystem::Folder(userFolder.GetFolderPath() + L"\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp"); - if (folder.GetFolderExists()) { - startup_directories.emplace_back(folder); - } - } - for (auto folder : startup_directories) { - LOG_VERBOSE(1, L"Scanning " << folder.GetFolderPath()); - for (auto value : folder.GetFiles(std::nullopt, -1)) { - detections += EvaluateStartupFile(value, reaction, level); - } - } - - return detections; - } - - int HuntT1037::ScanCursory(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = AnalyzeRegistryStartupKey(reaction, Aggressiveness::Cursory); - detections += AnalayzeStartupFolders(reaction, Aggressiveness::Cursory); - - reaction.EndHunt(); - return detections; - } - - int HuntT1037::ScanNormal(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Normal"); - reaction.BeginHunt(GET_INFO()); - - int detections = AnalyzeRegistryStartupKey(reaction, Aggressiveness::Normal); - detections += AnalayzeStartupFolders(reaction, Aggressiveness::Normal); - - reaction.EndHunt(); - return detections; - } - - int HuntT1037::ScanIntensive(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Intensive"); - reaction.BeginHunt(GET_INFO()); - - int detections = AnalyzeRegistryStartupKey(reaction, Aggressiveness::Intensive); - detections += AnalayzeStartupFolders(reaction, Aggressiveness::Intensive); - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1037::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_CURRENT_USER, L"Environment", true, true, false)); - - events.push_back(std::make_shared(FileSystem::Folder(L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp"))); - - auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); - - for (auto userFolder : userFolders) { - auto folder = FileSystem::Folder(userFolder.GetFolderPath() + L"\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp"); - if (folder.GetFolderExists()) { - events.push_back(std::make_shared(folder)); - } - } - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp deleted file mode 100644 index f635b910..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "hunt/hunts/HuntT1050.h" - -#include "util/eventlogs/EventLogs.h" -#include "util/log/Log.h" -#include "util/filesystem/YaraScanner.h" -#include "util/processes/ProcessUtils.h" -#include "util/processes/CheckLolbin.h" - -#include "common/Utils.h" - -#include - -namespace Hunts { - - HuntT1050::HuntT1050() : Hunt(L"T1050 - New Service") { - dwSupportedScans = (DWORD) Aggressiveness::Normal | (DWORD) Aggressiveness::Intensive; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - std::vector HuntT1050::Get7045Events() { - // Create existance queries so interesting data is output - std::vector queries; - auto param1 = EventLogs::ParamList(); - auto param2 = EventLogs::ParamList(); - auto param3 = EventLogs::ParamList(); - auto param4 = EventLogs::ParamList(); - param1.push_back(std::make_pair(L"Name", L"'ServiceName'")); - param2.push_back(std::make_pair(L"Name", L"'ImagePath'")); - param3.push_back(std::make_pair(L"Name", L"'ServiceType'")); - param4.push_back(std::make_pair(L"Name", L"'StartType'")); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); - - auto queryResults = EventLogs::QueryEvents(L"System", 7045, queries); - - return queryResults; - } - - int HuntT1050::ScanNormal(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Normal"); - reaction.BeginHunt(GET_INFO()); - - auto queryResults = Get7045Events(); - - auto& yara = YaraScanner::GetInstance(); - int detections = 0; - - std::map, bool> findings{}; - - for(auto result : queryResults){ - auto imageName = result.GetProperty(L"Event/EventData/Data[@Name='ServiceName']"); - - auto cmd{ result.GetProperty(L"Event/EventData/Data[@Name='ImagePath']") }; - std::pair pair{ imageName, cmd }; - - if(findings.count(pair)){ - if(findings.at(pair)){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - } - } else{ - if(IsLolbinMalicious(cmd)){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - findings.emplace(pair, true); - detections++; - } else{ - auto imagePath = GetImagePathFromCommand(cmd); - - FileSystem::File file = FileSystem::File(imagePath); - if(file.GetFileExists() && !file.GetFileSigned()){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(file); - - reaction.FileIdentified(std::make_shared(file)); - - detections += 2; - findings.emplace(pair, true); - } - - // Look for PSExec services - else if(imageName.find(L"PSEXESVC") != std::wstring::npos){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - detections++; - findings.emplace(pair, true); - } - - // Look for Mimikatz Driver loading - else if(imageName.find(L"mimikatz") != std::wstring::npos || imageName.find(L"mimidrv") != std::wstring::npos - || imagePath.find(L"mimidrv.sys") != std::wstring::npos){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - findings.emplace(pair, true); - } - - // Calculate entropy of service names to look for suspicious services like - // the ones MSF generates https://www.offensive-security.com/metasploit-unleashed/psexec-pass-hash/ - else if(false && (GetShannonEntropy(imageName) < 3.00 || GetShannonEntropy(imageName) > 5.00)){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - findings.emplace(pair, true); - } else { - findings.emplace(pair, false); - } - } - } - } - - reaction.EndHunt(); - return detections; - } - - int HuntT1050::ScanIntensive(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Intensive"); - reaction.BeginHunt(GET_INFO()); - - auto queryResults = Get7045Events(); - - auto& yara = YaraScanner::GetInstance(); - int detections = 0; - - std::map, bool> findings{}; - - for(auto result : queryResults){ - auto imageName = result.GetProperty(L"Event/EventData/Data[@Name='ServiceName']"); - - auto cmd{ result.GetProperty(L"Event/EventData/Data[@Name='ImagePath']") }; - std::pair pair{ imageName, cmd }; - - if(findings.count(pair)){ - if(findings.at(pair)){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - } - } else{ - if(IsLolbinMalicious(cmd)){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - findings.emplace(pair, true); - detections++; - } else{ - auto imagePath = GetImagePathFromCommand(cmd); - - FileSystem::File file = FileSystem::File(imagePath); - if(file.GetFileExists() && !file.GetFileSigned()){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(file); - - reaction.FileIdentified(std::make_shared(file)); - - detections += 2; - findings.emplace(pair, true); - } - - // Look for PSExec services - else if(imageName.find(L"PSEXESVC") != std::wstring::npos){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - detections++; - findings.emplace(pair, true); - } - - // Look for Mimikatz Driver loading - else if(imageName.find(L"mimikatz") != std::wstring::npos || imageName.find(L"mimidrv") != std::wstring::npos - || imagePath.find(L"mimidrv.sys") != std::wstring::npos){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - findings.emplace(pair, true); - } - - // Calculate entropy of service names to look for suspicious services like - // the ones MSF generates https://www.offensive-security.com/metasploit-unleashed/psexec-pass-hash/ - else if(!file.GetFileExists() || (GetShannonEntropy(imageName) < 3.00 || GetShannonEntropy(imageName) > 5.00)){ - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - findings.emplace(pair, true); - } else{ - findings.emplace(pair, false); - } - } - } - } - - reaction.EndHunt(); - return detections; - - } - - std::vector> HuntT1050::GetMonitoringEvents() { - std::vector> events; - - events.push_back(std::make_shared(L"System", 7045)); - - return events; - } - -} diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1053.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1053.cpp deleted file mode 100644 index 0d2b7b6c..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1053.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "hunt/hunts/HuntT1053.h" - -#include "util/eventlogs/EventLogs.h" -#include "util/log/Log.h" -#include "util/filesystem/YaraScanner.h" - -#include "common/Utils.h" - -namespace Hunts { - - HuntT1053::HuntT1053() : Hunt(L"T1053 - Scheduled Task") { - dwSupportedScans = (DWORD) Aggressiveness::Intensive; - dwCategoriesAffected = (DWORD) Category::Files | (DWORD) Category::Processes; - dwSourcesInvolved = (DWORD) DataSource::EventLogs; - dwTacticsUsed = (DWORD) Tactic::Execution | (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - std::vector HuntT1053::Get4698Events() { - // Create existance queries so interesting data is output - std::vector queries; - auto param1 = EventLogs::ParamList(); - auto param2 = EventLogs::ParamList(); - auto param3 = EventLogs::ParamList(); - auto param4 = EventLogs::ParamList(); - param1.push_back(std::make_pair(L"Name", L"'SubjectUserName'")); - param2.push_back(std::make_pair(L"Name", L"'SubjectDomainName'")); - param3.push_back(std::make_pair(L"Name", L"'TaskName'")); - param4.push_back(std::make_pair(L"Name", L"'TaskContent'")); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); - - auto queryResults = EventLogs::QueryEvents(L"Security", 4698, queries); - - return queryResults; - } - - std::vector HuntT1053::Get106Events() { - // Create existance queries so interesting data is output - std::vector queries; - auto param1 = EventLogs::ParamList(); - auto param2 = EventLogs::ParamList(); - param1.push_back(std::make_pair(L"Name", L"'TaskName'")); - param2.push_back(std::make_pair(L"Name", L"'UserContext'")); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); - - auto queryResults = EventLogs::QueryEvents(L"Microsoft-Windows-TaskScheduler/Operational", 106, queries); - - return queryResults; - } - - int HuntT1053::ScanIntensive(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Intensive"); - reaction.BeginHunt(GET_INFO()); - - auto queryResults = Get4698Events(); - auto queryResults2 = Get106Events(); - - for (auto result : queryResults) { - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - } - - for (auto result : queryResults2) { - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - } - - reaction.EndHunt(); - return queryResults.size(); - } - - std::vector> HuntT1053::GetMonitoringEvents() { - std::vector> events; - - events.push_back(std::make_shared(L"Security", 4698)); - events.push_back(std::make_shared(L"Microsoft-Windows-TaskScheduler/Operational", 106)); - - return events; - } - -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1055.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1055.cpp deleted file mode 100644 index b732c71d..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1055.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include - -#include "hunt/hunts/HuntT1055.h" -#include "util/eventlogs/EventLogs.h" -#include "util/log/Log.h" -#include "util/processes/ProcessUtils.h" -#include "util/processes/ParseCobalt.h" -#include "common/wrappers.hpp" - -#include "pe_sieve.h" -#include "pe_sieve_types.h" - -extern "C" { - void __stdcall PESieve_help(void); - DWORD __stdcall PESieve_version(void); - pesieve::t_report __stdcall PESieve_scan(pesieve::t_params args); -}; - - -namespace Hunts{ - - HuntT1055::HuntT1055() : Hunt(L"T1055 - Process Injection") { - dwSupportedScans = (DWORD) Aggressiveness::Normal; - dwCategoriesAffected = (DWORD) Category::Processes; - dwSourcesInvolved = (DWORD) DataSource::Processes; - dwTacticsUsed = (DWORD) Tactic::PrivilegeEscalation | (DWORD) Tactic::DefenseEvasion; - } - - bool ScanProcess(DWORD pid, Reaction& reaction){ - pesieve::t_params params = { - pid, - 3, - pesieve::PE_IMPREC_NONE, - true, - pesieve::OUT_NO_DIR, - true, - false, - false, - false, - pesieve::PE_DUMP_AUTO, - false, - 0 - }; - - WRAP(pesieve::ReportEx*, report, scan_and_dump(params), delete data); - - if(!report){ - LOG_WARNING("Unable to scan process " << pid << " due to an error in PE-Sieve.dll"); - return false; - } - - auto summary = report->scan_report->generateSummary(); - if(summary.skipped){ - LOG_WARNING("Skipped scanning " << summary.skipped << " modules in process " << pid << ". This is likely due to use of .NET"); - } - - if(summary.suspicious && !summary.errors){ - DWORD identifiers = 0; - if(summary.replaced) identifiers |= static_cast(ProcessDetectionMethod::Replaced); - if(summary.hdr_mod) identifiers |= static_cast(ProcessDetectionMethod::HeaderModified); - if(summary.detached) identifiers |= static_cast(ProcessDetectionMethod::Detached); - if(summary.hooked) identifiers |= static_cast(ProcessDetectionMethod::Hooked); - if(summary.implanted) identifiers |= static_cast(ProcessDetectionMethod::Implanted); - if(summary.implanted + summary.hooked + summary.detached + summary.hdr_mod + summary.replaced != summary.suspicious) - identifiers |= static_cast(ProcessDetectionMethod::Other); - - std::wstring path = StringToWidestring(report->scan_report->mainImagePath); - - for(auto module : report->scan_report->module_reports){ - if(module->status & SCAN_SUSPICIOUS){ - reaction.ProcessIdentified(std::make_shared(path, GetProcessCommandline(pid), pid, module->module, - static_cast(module->moduleSize), identifiers)); - - HandleWrapper process{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, pid) }; - if(process){ - MemoryWrapper<> wrapper{ module->module, module->moduleSize, process }; - DumpBeaconInformation(wrapper); - } else{ - LOG_ERROR("Unable to open process with PID " << pid << " to dump cobalt strike information (error " << GetLastError() << ")"); - } - - } - } - - return true; - } - - return false; - } - - int HuntT1055::ScanNormal(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Normal"); - reaction.BeginHunt(GET_INFO()); - - int identified = 0; - - DWORD processes[1024]; - DWORD ProcessCount = 0; - ZeroMemory(processes, sizeof(processes)); - auto success = EnumProcesses(processes, sizeof(processes), &ProcessCount); - if(success){ - ProcessCount /= sizeof(DWORD); - for(int i = 0; i < ProcessCount; i++){ - if(scope.ProcessIsInScope(processes[i])){ - if(ScanProcess(processes[i], reaction)){ - identified++; - } - } - } - } else { - LOG_ERROR("Unable to enumerate processes - Process related hunts will not run."); - } - - reaction.EndHunt(); - return identified; - } - -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1060.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1060.cpp deleted file mode 100644 index a3756ef9..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1060.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "hunt/hunts/HuntT1060.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" -#include "util/processes/ProcessUtils.h" -#include "util/processes/CheckLolbin.h" - -#include "common/Utils.h" - -using namespace Registry; - -namespace Hunts { - HuntT1060::HuntT1060() : Hunt(L"T1060 - Registry Run Keys / Startup Folder") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence; - - auto HKLMRun = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }; - auto HKLMRunServices = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices" }; - auto HKLMRunOnce = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }; - auto HKLMRunServicesOnce = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceServices" }; - auto HKLMRunOnceEx = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }; - auto HKLMRunServicesOnceEx = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceServicesEx" }; - auto HKLMExplorerRun = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }; - - RunKeys = { - HKLMRun, HKLMRunServices, HKLMRunOnce, HKLMRunServicesOnce, - HKLMRunOnceEx, HKLMRunServicesOnceEx, HKLMExplorerRun, - }; - } - - int HuntT1060::EvaluateFile(const std::wstring& cmd, Reaction& reaction) { - - if(IsLolbinMalicious(cmd)){ - LOG_VERBOSE(1, "When evaluating " << cmd << ", it was determined that this was a malicious use of a lolbin"); - return true; - } else{ - LOG_VERBOSE(2, "When evaluating " << cmd << ", it was determined that this was not a malicious use of a lolbin"); - auto filepath = GetImagePathFromCommand(cmd); - auto image{ FileSystem::File(filepath) }; - - if(image.GetFileExists() && !image.GetFileSigned()){ - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result{ yara.ScanFile(image) }; - - reaction.FileIdentified(std::make_shared(image)); - - return true; - } - } - - return false; - } - - int HuntT1060::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - for(auto& key : RunKeys){ - for (auto& detection : CheckKeyValues(HKEY_LOCAL_MACHINE, key)) { - if (EvaluateFile(std::get(detection.data), reaction)) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - } - for (auto& sub : CheckSubkeys(HKEY_LOCAL_MACHINE, key)) { - for(auto& value : sub.EnumerateValues()){ - auto type{ sub.GetValueType(value) }; - if(type == RegistryType::REG_SZ_T || type == RegistryType::REG_EXPAND_SZ_T){ - RegistryValue detection{ sub, value, *sub.GetValue(value) }; - if(EvaluateFile(std::get(detection.data), reaction)){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - } - } - } - } - - for (auto& detection : CheckValues(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", { - { L"load", L"", false, CheckSzEmpty }, - { L"run", L"", false, CheckSzEmpty } - })) { - detections += EvaluateFile(std::get(detection.data), reaction); - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - - for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager", { - { L"BootExecute", { L"autocheck autochk *" }, false, CheckMultiSzSubset } - })) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - - for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Command Processor", { - { L"AutoRun", L"", false, CheckSzEmpty } - })){ - if (EvaluateFile(std::get(detection.data), reaction)) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - } - - for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", { - { L"Startup", L"%USERPROFILE%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", false, CheckSzEqual } - })){ - if (EvaluateFile(std::get(detection.data), reaction)) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - } - - for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", { - { L"Common Startup", L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", false, CheckSzEqual } - })){ - if (EvaluateFile(std::get(detection.data), reaction)) { - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - } - } - - // rover.dll http://www.hexacorn.com/blog/2014/05/21/beyond-good-ol-run-key-part-12/ - RegistryKey roverkey = RegistryKey{ HKEY_CLASSES_ROOT, L"CLSID\\{16d12736-7a9e-4765-bec6-f301d679caaa}" }; - FileSystem::File rover = FileSystem::File(L"C:\\windows\\system32\\rover.dll"); - if (roverkey.Exists() && rover.GetFileExists()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ roverkey, L"", L"" })); - reaction.FileIdentified(std::make_shared(rover)); - detections += 2; - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1060::GetMonitoringEvents() { - std::vector> events; - - for(auto key : RunKeys){ - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, key, true, true, true)); - } - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", true, true, false)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager", true, false, false)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Command Processor", true, false, true)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders")); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1068.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1068.cpp deleted file mode 100644 index 430ae1ad..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1068.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "hunt/hunts/HuntT1068.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" - -#include - -using namespace Registry; - -namespace Hunts { - - HuntT1068::HuntT1068() : Hunt(L"T1068 - Exploitation for Privilege Escalation") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1068::HuntCVE20201048(Reaction reaction) { - int detections = 0; - - auto ports = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports", true }; - auto printers = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Printers", true }; - - for (auto printer : printers.EnumerateSubkeys()) { - if (printer.ValueExists(L"Port")) { - auto filepath = FileSystem::File{ printer.GetValue(L"Port").value() }; - - // Regex ensures the file is an actual drive and not, say, a COM port - if (std::regex_match(filepath.GetFilePath(), std::wregex(L"([a-zA-z]{1}:\\\\)(.*)")) && filepath.GetFileExists() && filepath.HasReadAccess()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ printer, L"Port", printer.GetValue(L"Port").value() })); - reaction.FileIdentified(std::make_shared(filepath)); - detections += 2; - } - } - } - - for (auto value : ports.EnumerateValues()) { - auto filepath = FileSystem::File{ value }; - - // Regex ensures the file is an actual drive and not, say, a COM port - if (std::regex_match(filepath.GetFilePath(), std::wregex(L"([a-zA-z]{1}:\\\\)(.*)")) && filepath.GetFileExists() && filepath.HasReadAccess()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ ports, value, ports.GetValue(value).value() })); - reaction.FileIdentified(std::make_shared(filepath)); - detections += 2; - } - } - - return detections; - } - - int HuntT1068::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - detections += HuntCVE20201048(reaction); - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1068::GetMonitoringEvents() { - std::vector> events; - - // CVE-2020-1048 - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Printers", true, false, true)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports", true, false, false)); - - return events; - } -} diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1089.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1089.cpp deleted file mode 100644 index 5b8d8323..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1089.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "hunt/hunts/HuntT1089.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" - -using namespace Registry; - -namespace Hunts { - - HuntT1089::HuntT1089() : Hunt(L"T1089 - Disabling Security Tools") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Network; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; - } - - int HuntT1089::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - auto DomainProfile = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\DomainProfile" }; - auto StandardProfile = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\StandardProfile" }; - auto PublicProfile = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\PublicProfile" }; - - for (auto key : { DomainProfile, StandardProfile, PublicProfile }) { - auto allowedapps = RegistryKey{ key, L"AuthorizedApplications\\List" }; - if (allowedapps.Exists()) { - for (auto ProgramException : allowedapps.EnumerateValues()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ allowedapps, ProgramException, allowedapps.GetValue(ProgramException).value() })); - auto program = FileSystem::File{ ProgramException }; - if (!program.GetFileSigned()) { - reaction.FileIdentified(std::make_shared(program)); - } - } - } - - auto ports = RegistryKey{ key, L"GloballyOpenPorts\\List" }; - if (ports.Exists()) { - for (auto PortsException : ports.EnumerateValues()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ ports, PortsException, ports.GetValue(PortsException).value() })); - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1089::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\DomainProfile", false, false, true)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\StandardProfile", false, false, true)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\PublicProfile", false, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1099.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1099.cpp deleted file mode 100644 index b72b484e..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1099.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "hunt/hunts/HuntT1099.h" -#include "util/eventlogs/EventLogs.h" -#include "util/log/Log.h" -#include "util/log/HuntLogMessage.h" -#include - -#include - -namespace Hunts { - - HuntT1099::HuntT1099() : Hunt(L"T1099 - Timestomp") { - dwSupportedScans = (DWORD) Aggressiveness::Normal; - dwCategoriesAffected = (DWORD) Category::Files | (DWORD) Category::Processes; - dwSourcesInvolved = (DWORD) DataSource::EventLogs; - dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; - } - - int HuntT1099::ScanNormal(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Normal"); - reaction.BeginHunt(GET_INFO()); - - // Create existance queries so interesting data is output - std::vector queries; - auto param1 = EventLogs::ParamList(); - auto param2 = EventLogs::ParamList(); - auto param3 = EventLogs::ParamList(); - auto param4 = EventLogs::ParamList(); - auto param5 = EventLogs::ParamList(); - param1.push_back(std::make_pair(L"Name", L"'Image'")); - param2.push_back(std::make_pair(L"Name", L"'ProcessId'")); - param3.push_back(std::make_pair(L"Name", L"'TargetFilename'")); - param4.push_back(std::make_pair(L"Name", L"'CreationUtcTime'")); - param5.push_back(std::make_pair(L"Name", L"'PreviousCreationUtcTime'")); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param5)); - - auto queryResults = EventLogs::QueryEvents(L"Microsoft-Windows-Sysmon/Operational", 2, queries); - - auto& yara = YaraScanner::GetInstance(); - int detections = 0; - - // Find detections with YARA rules - for (auto query : queryResults) { - //TODO: Also scan ProcessId with PE-Sieve to see if malicious - FileSystem::File file = FileSystem::File(query.GetProperty(L"Event/EventData/Data[@Name='TargetFilename']")); - YaraScanResult result = yara.ScanFile(file); - bool bFileSigned = file.GetFileSigned(); - - if (bFileSigned || (!result && result.vKnownBadRules.size() > 0)) { - detections++; - reaction.EventIdentified(EventLogs::EventLogItemToDetection(query)); - reaction.FileIdentified(std::make_shared(file)); - } - } - - reaction.EndHunt(); - return detections; - } - - int HuntT1099::ScanIntensive(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Intensive"); - reaction.BeginHunt(GET_INFO()); - - // Create existance queries so interesting data is output - std::vector queries; - auto param1 = EventLogs::ParamList(); - auto param2 = EventLogs::ParamList(); - auto param3 = EventLogs::ParamList(); - auto param4 = EventLogs::ParamList(); - auto param5 = EventLogs::ParamList(); - param1.push_back(std::make_pair(L"Name", L"'Image'")); - param2.push_back(std::make_pair(L"Name", L"'ProcessId'")); - param3.push_back(std::make_pair(L"Name", L"'TargetFilename'")); - param4.push_back(std::make_pair(L"Name", L"'CreationUtcTime'")); - param5.push_back(std::make_pair(L"Name", L"'PreviousCreationUtcTime'")); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param5)); - - auto queryResults = EventLogs::QueryEvents(L"Microsoft-Windows-Sysmon/Operational", 2, queries); - - for (auto query : queryResults) { - reaction.EventIdentified(EventLogs::EventLogItemToDetection(query)); - - FileSystem::File file = FileSystem::File(query.GetProperty(L"Event/EventData/Data[@Name='TargetFilename']")); - reaction.FileIdentified(std::make_shared(file)); - } - - reaction.EndHunt(); - return queryResults.size(); - } - - std::vector> HuntT1099::GetMonitoringEvents() { - std::vector> events; - events.push_back(std::make_shared(L"Microsoft-Windows-Sysmon/Operational", 2)); - return events; - } - -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp deleted file mode 100644 index 40339dfc..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "hunt/hunts/HuntT1100.h" - -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" -#include "util/log/Log.h" - -#include "common/StringUtils.h" - -namespace Hunts { - HuntT1100::HuntT1100() : Hunt(L"T1100 - Web Shells") { - std::smatch match_index; - - dwSupportedScans = (DWORD) Aggressiveness::Cursory | (DWORD) Aggressiveness::Normal; - dwCategoriesAffected = (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - void HuntT1100::SetRegexAggressivenessLevel(Aggressiveness aLevel) { - //PHP regex credit to: https://github.com/emposha/PHP-Shell-Detector - php_vuln_functions.assign(R"(preg_replace.*\/e|`.*?\$.*?`|\bcreate_function\b|\bpassthru\b|\bshell_exec\b|\bexec\b|\bbase64_decode\b|\bedoced_46esab\b|\beval\b|\bsystem\b|\bproc_open\b|\bpopen\b|\bcurl_exec\b|\bcurl_multi_exec\b|\bparse_ini_file\b|\bshow_source\b)"); - - if (aLevel == Aggressiveness::Cursory) { - asp_indicators.assign(R"(\bcmd.exe\b|\bpowershell.exe\b|\bwscript.shell\b|\bprocessstartinfo\b|createobject\("scripting.filesystemobject"\))"); - jsp_indicators.assign(R"(\bcmd.exe\b|\bpowershell.exe\b)"); - } - else if (aLevel == Aggressiveness::Normal) { - asp_indicators.assign(R"(\bcmd.exe\b|\bpowershell.exe\b|\bwscript.shell\b|\bprocessstartinfo\b|\bcreatenowindow\b|\bcmd\b|\beval request\b|\bexecute request\b|\boscriptnet\b|createobject\("scripting.filesystemobject"\))"); - jsp_indicators.assign(R"(\bcmd.exe\b|\bpowershell.exe\b|\bgetruntime\(\)\.exec\b)"); - } - } - - void HuntT1100::AddDirectoryToSearch(const std::wstring& sFileName){ - web_directories.emplace_back(sFileName); - } - - void HuntT1100::AddFileExtensionToSearch(const std::wstring& sFileExtension) { - web_exts.emplace_back(sFileExtension); - } - - int HuntT1100::AnalyzeDirectoryFiles(std::wstring path, Reaction reaction, Aggressiveness level) { - int identified = 0; - - auto f = FileSystem::Folder(path); - FileSystem::FileSearchAttribs attribs; - attribs.extensions = web_exts; - std::vector files = f.GetFiles(attribs, -1); - - auto& yara = YaraScanner::GetInstance(); - - for (const auto& entry : files) { - int k = identified; - - long offset = 0; - unsigned long targetAmount = 1000000; - DWORD amountRead = 0; - auto file_ext = ToLowerCaseW(entry.GetFileAttribs().extension); - - do { - auto read = entry.Read(targetAmount, offset, &amountRead); - read.SetByte(amountRead, '\0'); - std::string sus_file = ToLowerCaseA(*read.ReadString()); - if (file_ext.compare(L".php") == 0) { - if (regex_search(sus_file, match_index, php_vuln_functions)) { - identified++; - reaction.FileIdentified(std::make_shared(entry)); - LOG_INFO(L"Located likely web shell in file " << entry.GetFilePath() << L" in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - else if (file_ext.substr(0, 4).compare(L".jsp") == 0) { - if (regex_search(sus_file, match_index, jsp_indicators)) { - identified++; - reaction.FileIdentified(std::make_shared(entry)); - LOG_INFO(L"Located likely web shell in file " << entry.GetFilePath() << L" in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - else if (file_ext.substr(0, 3).compare(L".as") == 0) { - if (regex_search(sus_file, match_index, asp_indicators)) { - identified++; - reaction.FileIdentified(std::make_shared(entry)); - LOG_INFO(L"Located likely web shell in file " << entry.GetFilePath() << L" in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - offset += amountRead - 1000; - } while (targetAmount <= amountRead); - - // Use YARA to also scan the files if our regex didn't detect anything suspicious - if (k == identified) { - YaraScanResult result = yara.ScanFile(entry); - if (!result && result.vKnownBadRules.size() > 0) { - identified++; - reaction.FileIdentified(std::make_shared(entry)); - } - } - } - - return identified; - } - - int HuntT1100::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - SetRegexAggressivenessLevel(Aggressiveness::Cursory); - - int identified = 0; - - for (std::wstring path : web_directories) { - identified += AnalyzeDirectoryFiles(path, reaction, Aggressiveness::Cursory); - } - reaction.EndHunt(); - return identified; - } - - int HuntT1100::ScanNormal(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Normal"); - reaction.BeginHunt(GET_INFO()); - SetRegexAggressivenessLevel(Aggressiveness::Normal); - - int identified = 0; - - for (std::wstring path : web_directories) { - identified += AnalyzeDirectoryFiles(path, reaction, Aggressiveness::Normal); - } - reaction.EndHunt(); - return identified; - } - - std::vector> HuntT1100::GetMonitoringEvents() { - std::vector> events; - - for (auto dir : web_directories) { - auto folder = FileSystem::Folder{ dir }; - if (folder.GetFolderExists()) { - events.push_back(std::make_shared(folder)); - for (auto subdir : folder.GetSubdirectories()) { - events.push_back(std::make_shared(subdir)); - } - } - } - - return events; - } -} diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp deleted file mode 100644 index 0e5d00f0..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "hunt/hunts/HuntT1101.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" - -using namespace Registry; - -namespace Hunts { - HuntT1101::HuntT1101() : Hunt(L"T1101 - Security Support Provider") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - int HuntT1101::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - auto lsa = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; - auto lsa2 = RegistryKey(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa\\OSConfig"); - - for (auto key : { lsa, lsa2 }) { - auto Packages = key.GetValue>(L"Security Packages"); - if (Packages) { - for (auto Package : Packages.value()) { - if (Package != L"\"\"") { - auto filepath = FileSystem::SearchPathExecutable(Package + L".dll"); - - if (filepath) { - FileSystem::File file = FileSystem::File(filepath.value()); - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ key, L"Security Packages", key.GetValue>(L"Security Packages").value() })); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - } - } - } - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1101::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", false, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp deleted file mode 100644 index 7a87e4ad..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "hunt/hunts/HuntT1103.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" - -using namespace Registry; - -namespace Hunts { - HuntT1103::HuntT1103() : Hunt(L"T1103 - AppInit DLLs") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1103::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L"at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", { - { L"AppInit_Dlls", L"", false, CheckSzEmpty }, - { L"LoadAppInit_Dlls", 0, false, CheckDwordEqual }, - { L"RequireSignedAppInit_DLLs", 1, false, CheckDwordEqual }, - }, true, false)){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - if (detection.wValueName == L"AppInit_Dlls") { - auto filepath = FileSystem::SearchPathExecutable(detection.ToString()); - if (filepath) { - reaction.FileIdentified(std::make_shared(FileSystem::File{ filepath.value() })); - } - } - detections++; - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1103::GetMonitoringEvents(){ - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", true, false, false)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1122.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1122.cpp deleted file mode 100644 index 13ec24fc..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1122.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "hunt/hunts/HuntT1122.h" -#include "hunt/RegistryHunt.h" - -#include "util/configurations/Registry.h" -#include "util/filesystem/Filesystem.h" -#include "util/log/Log.h" -#include "util/log/HuntLogMessage.h" -#include "util/processes/ProcessUtils.h" -#include "util/processes/CheckLolbin.h" - -#include "common/Utils.h" - -#include - -using namespace Registry; - -namespace Hunts{ - - HuntT1122::HuntT1122() : Hunt(L"T1122 - COM Hijacking"){ - dwSupportedScans = (DWORD) Aggressiveness::Intensive; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::DefenseEvasion; - } - -#define ADD_FILE(file, ...) \ - if(files.find(file) != files.end()){ \ - files.at(file).emplace_back(__VA_ARGS__); \ - } else { \ - files.emplace(file, std::vector{ __VA_ARGS__ }); \ - } - - int HuntT1122::ScanIntensive(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Intensive"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - std::map> files{}; - - for(auto key : CheckSubkeys(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID", true, true)){ - RegistryKey subkey{ key, L"InprocServer32" }; - if(subkey.Exists() && subkey.ValueExists(L"")){ - auto filename{ *subkey.GetValue(L"") }; - auto path{ FileSystem::SearchPathExecutable(filename) }; - if(path){ - ADD_FILE(*path, RegistryValue{ subkey, L"", std::move(filename) }); - } - } - subkey = { key, L"InprocServer" }; - if(subkey.Exists() && subkey.ValueExists(L"")){ - auto filename{ *subkey.GetValue(L"") }; - auto path{ FileSystem::SearchPathExecutable(filename) }; - if(path){ - ADD_FILE(*path, RegistryValue{ subkey, L"", std::move(filename) }); - } - } - if(key.ValueExists(L"InprocHandler32")){ - auto filename{ *key.GetValue(L"InprocHandler32") }; - auto path{ FileSystem::SearchPathExecutable(filename) }; - if(path){ - ADD_FILE(*path, RegistryValue{ key, L"InprocHandler32", std::move(filename) }); - } - } - if(key.ValueExists(L"InprocHandler")){ - auto filename{ *key.GetValue(L"InprocHandler") }; - auto path{ FileSystem::SearchPathExecutable(filename) }; - if(path){ - ADD_FILE(*path, RegistryValue{ key, L"InprocHandler", std::move(filename) }); - } - } - if(key.ValueExists(L"LocalServer")){ - auto filename{ *key.GetValue(L"LocalServer") }; - ADD_FILE(filename, RegistryValue{ key, L"LocalServer", *subkey.GetValue(L"") }); - } - subkey = { key, L"LocalServer32" }; - if(subkey.Exists() && subkey.ValueExists(L"")){ - auto filename{ *subkey.GetValue(L"") }; - ADD_FILE(filename, RegistryValue{ subkey, L"", *subkey.GetValue(L"") }); - } - if(subkey.Exists() && subkey.ValueExists(L"ServerExecutable")){ - auto filename{ *subkey.GetValue(L"ServerExecutable") }; - ADD_FILE(filename, RegistryValue{ subkey, L"ServerExecutable", *subkey.GetValue(L"") }); - } - } - - for(auto pair : files){ - auto path{ pair.first }; - if(!FileSystem::CheckFileExists(path)){ - path = GetImagePathFromCommand(path); - if(!FileSystem::CheckFileExists(path)){ - continue; - } - } - - FileSystem::File file{ path }; - if((!CompareIgnoreCaseW(file.GetFileAttribs().extension, L".dll") && IsLolbinMalicious(pair.first)) || !file.GetFileSigned()){ - for(auto& value : pair.second){ - reaction.RegistryKeyIdentified(std::make_shared(value)); - detections++; - } - reaction.FileIdentified(std::make_shared(file)); - detections++; - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1122::GetMonitoringEvents(){ - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID", true, true, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1128.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1128.cpp deleted file mode 100644 index bd99781f..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1128.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "hunt/hunts/HuntT1128.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" -#include "util/processes/ProcessUtils.h" - -using namespace Registry; - -namespace Hunts { - - HuntT1128::HuntT1128() : Hunt(L"T1128 - Netsh Helper DLL") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; - dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - int HuntT1128::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - auto netshKey = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Netsh", true }; - - for (auto& helperDllValue : CheckKeyValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Netsh", true, false)) { - auto filepath = FileSystem::SearchPathExecutable(std::get(helperDllValue.data)); - if (filepath) { - FileSystem::File helperDll{ *filepath }; - if (!helperDll.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(helperDllValue)); - - auto& yara = YaraScanner::GetInstance(); - YaraScanResult result = yara.ScanFile(helperDll); - - reaction.FileIdentified(std::make_shared(helperDll)); - - detections += 2; - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1128::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Netsh", true, false, false)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp deleted file mode 100644 index e278dc7e..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "hunt/hunts/HuntT1131.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" -#include "util/filesystem/YaraScanner.h" -#include "common/StringUtils.h" - -using namespace Registry; - -namespace Hunts { - HuntT1131::HuntT1131() : Hunt(L"T1131 - Authentication Package") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - int HuntT1131::ScanCursory(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - // LSA Configuration - auto lsa = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; - - for (auto PackageGroup : { L"Authentication Packages", L"Notification Packages" }) { - auto Packages = lsa.GetValue>(PackageGroup); - if (Packages) { - for (auto Package : Packages.value()) { - if (Package != L"\"\"") { - auto filepath = FileSystem::SearchPathExecutable(Package + L".dll"); - - if (filepath) { - FileSystem::File file = FileSystem::File(filepath.value()); - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ lsa, PackageGroup, lsa.GetValue>(PackageGroup).value() })); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - } - } - } - } - } - } - - // LSA Extensions Configuration - auto lsaext = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\LsaExtensionConfig" }; - - for (auto subkeyName : lsaext.EnumerateSubkeyNames()) { - if (subkeyName == L"Interfaces") { - for (auto subkey : RegistryKey{ lsaext, L"Interfaces" }.EnumerateSubkeys()) { - auto ext = subkey.GetValue(L"Extension"); - if (ext) { - auto filepath = FileSystem::SearchPathExecutable(ext.value()); - if (filepath) { - FileSystem::File file = FileSystem::File(filepath.value()); - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ subkey, L"Extension", subkey.GetValue(L"Extension").value() })); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - } - } - } - } - } - else { - auto subkey = RegistryKey{ lsaext, subkeyName }; - auto exts = subkey.GetValue>(L"Extensions"); - if (exts) { - for (auto ext : exts.value()) { - auto filepath = FileSystem::SearchPathExecutable(ext); - if (filepath) { - FileSystem::File file = FileSystem::File(filepath.value()); - if (file.GetFileExists() && !file.GetFileSigned()) { - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ subkey, L"Extensions", subkey.GetValue(L"Extensions").value() })); - reaction.FileIdentified(std::make_shared(file)); - detections += 2; - } - } - } - } - } - } - - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1131::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", false, false, true)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\LsaExtensionConfig", false, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1136.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1136.cpp deleted file mode 100644 index 5afa54a7..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1136.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "hunt/hunts/HuntT1136.h" -#include "util/eventlogs/EventLogs.h" -#include "util/log/Log.h" -#include "util/log/HuntLogMessage.h" - -namespace Hunts { - - HuntT1136::HuntT1136() : Hunt(L"T1136 - Account Created") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::EventLogs; - dwTacticsUsed = (DWORD) Tactic::Persistence; - } - - int HuntT1136::ScanCursory(const Scope& scope, Reaction reaction) { - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - // Create existance queries so interesting data is output - std::vector queries; - auto param1 = EventLogs::ParamList(); - auto param2 = EventLogs::ParamList(); - param1.push_back(std::make_pair(L"Name", L"'TargetUserName'")); - param2.push_back(std::make_pair(L"Name", L"'SubjectUserName'")); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); - queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); - - auto results = EventLogs::QueryEvents(L"Security", 4720, queries); - for (auto result : results) - reaction.EventIdentified(EventLogs::EventLogItemToDetection(result)); - - reaction.EndHunt(); - return results.size(); - } - - std::vector> HuntT1136::GetMonitoringEvents() { - std::vector> events; - events.push_back(std::make_shared(L"Security", 4720)); - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp deleted file mode 100644 index 962b68cf..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "hunt/hunts/HuntT1138.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" - -#include "common/Utils.h" - -using namespace Registry; - -namespace Hunts { - HuntT1138::HuntT1138() : Hunt(L"T1138 - Application Shimming") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1138::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - auto& detections = CheckKeyValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB", true, false); - ADD_ALL_VECTOR(detections, CheckKeyValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom", true, false)); - - int detectionCount = 0; - for(const auto& detection : detections){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detectionCount++; - } - - reaction.EndHunt(); - return detectionCount; - } - - std::vector> HuntT1138::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB")); - ADD_ALL_VECTOR(events, GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom")); - events.push_back(std::make_shared(FileSystem::Folder{ L"C:\\Windows\\AppPatch\\Custom" })); - events.push_back(std::make_shared(FileSystem::Folder{ L"C:\\Windows\\AppPatch\\Custom\\Custom64" })); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp deleted file mode 100644 index d68dcfc6..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "hunt/hunts/HuntT1182.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" -#include "util/filesystem/FileSystem.h" - -using namespace Registry; - -namespace Hunts { - HuntT1182::HuntT1182() : Hunt(L"T1182 - AppCert DLLs") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1182::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - for(auto& detection : CheckKeyValues(HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Session Manager\\AppCertDlls", false, false)){ - reaction.RegistryKeyIdentified(std::make_shared(detection)); - detections++; - - auto filepath = FileSystem::SearchPathExecutable(detection.ToString()); - if (filepath) { - reaction.FileIdentified(std::make_shared(FileSystem::File{ filepath.value() })); - detections++; - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1182::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Session Manager\\AppCertDlls", false, false, false)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1183.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1183.cpp deleted file mode 100644 index 7d8faeda..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1183.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "hunt/hunts/HuntT1183.h" -#include "hunt/RegistryHunt.h" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" - -#include "common/Utils.h" - -using namespace Registry; - -namespace Hunts{ - HuntT1183::HuntT1183() : Hunt(L"T1183 - Image File Execution Options") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; - } - - int HuntT1183::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO(L"Hunting for " << name << L" at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - std::vector values; - - auto IFEO = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options" }; - for(auto subkey : IFEO.EnumerateSubkeys()){ - auto name = subkey.GetName(); - name = name.substr(name.find_last_of(L"\\") + 1); - ADD_ALL_VECTOR(values, CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\" + name, { - { L"Debugger", L"", false, CheckSzEmpty }, - { L"GlobalFlag", 0, false, [](DWORD d1, DWORD d2){ return !(d1 & 0x200); } }, - })); - - auto GFlags = subkey.GetValue(L"GlobalFlag"); - if(GFlags && *GFlags & 0x200){ - ADD_ALL_VECTOR(values, CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\" + name, { - { L"ReportingMode", 0, false, CheckDwordEqual }, - { L"MonitorProcess", L"", false, CheckSzEmpty }, - })); - } - } - - int detections = 0; - for(const auto& value : values){ - reaction.RegistryKeyIdentified(std::make_shared(value)); - detections++; - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1183::GetMonitoringEvents() { - std::vector> events; - - ADD_ALL_VECTOR(events, GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", true, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1198.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1198.cpp deleted file mode 100644 index 4f9e1c8c..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1198.cpp +++ /dev/null @@ -1,268 +0,0 @@ -#include "hunt/hunts/HuntT1198.h" -#include "hunt/RegistryHunt.h" - -#include "util/configurations/Registry.h" -#include "util/filesystem/Filesystem.h" -#include "util/log/Log.h" -#include "util/log/HuntLogMessage.h" -#include "util/processes/ProcessUtils.h" - -#include "../resources/resource.h" - -#include "common/Utils.h" - -#include -#include -#include - -using namespace Registry; - -namespace Hunts{ - - HuntT1198::HuntT1198() : Hunt(L"T1198 - SIP and Trust Provider Hijacking"){ - dwSupportedScans = (DWORD) Aggressiveness::Intensive; - dwCategoriesAffected = (DWORD) Category::Configurations; - dwSourcesInvolved = (DWORD) DataSource::Registry; - dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::DefenseEvasion; - } - - std::wstring GetResource(DWORD identifier){ - auto hRsrcInfo = FindResourceW(nullptr, MAKEINTRESOURCE(identifier), L"textfile"); - if(!hRsrcInfo){ - return { nullptr, 0 }; - } - - auto hRsrc = LoadResource(nullptr, hRsrcInfo); - if(!hRsrc){ - return { nullptr, 0 }; - } - - return StringToWidestring({ reinterpret_cast(LockResource(hRsrc)), SizeofResource(nullptr, hRsrcInfo) }); - } - - std::map>> ParseResource(DWORD dwResourceID){ - auto resource{ GetResource(dwResourceID) }; - - std::map>> map{}; - - auto lines{ SplitStringW(resource, L"\n") }; - for(auto& line : lines){ - std::map> values; - auto type{ line.substr(0, line.find(L":")) }; - auto entries{ SplitStringW(line.substr(line.find(L":") + 1), L" ") }; - for(auto& entry : entries){ - auto parts{ SplitStringW(entry, L",") }; - auto path{ FileSystem::SearchPathExecutable(parts[1]) }; - if(path){ - values.emplace(parts[0], std::pair{ ToLowerCaseW(*path), parts[2] }); - } else{ - values.emplace(parts[0], std::pair{ ToLowerCaseW(parts[1]), parts[2] }); - } - } - map.emplace(type, std::move(values)); - } - - return map; - } - - int HuntT1198::ScanIntensive(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Intensive"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - std::map>> files{}; - - // Verify that the installed SIPs are good - auto goodSIP{ ParseResource(GoodSIP) }; - for(auto keypath : { L"SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0", L"SOFTWARE\\WoW6432Node\\Microsoft\\Cryptography\\OID\\EncodingType 0" }){ - RegistryKey key{ HKEY_LOCAL_MACHINE, keypath }; - for(auto subkey : key.EnumerateSubkeyNames()){ - if(goodSIP.find(subkey) != goodSIP.end()){ - auto& entry{ goodSIP.at(subkey) }; - RegistryKey SIPType{ key, subkey }; - - for(auto GUID : SIPType.EnumerateSubkeyNames()){ - RegistryKey GUIDInfo{ SIPType, GUID }; - auto dll{ GUIDInfo.GetValue(L"Dll") }; - auto func{ GUIDInfo.GetValue(L"FuncName") }; - GUID = GUID.substr(1, GUID.length() - 2); - - if(entry.find(GUID) != entry.end()){ - auto& pair{ entry.at(GUID) }; - if(func && func != pair.second){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ GUIDInfo, L"FuncName", std::move(*func) })); - detections++; - } - - if(dll){ - if(files.find(*dll) == files.end()){ - files.emplace(*dll, std::vector>{ std::pair{ - RegistryValue{ GUIDInfo, L"Dll", *GUIDInfo.GetValue(L"Dll") }, - pair.first - }}); - } else{ - files.at(*dll).emplace_back(std::pair{ - RegistryValue{ GUIDInfo, L"Dll", *GUIDInfo.GetValue(L"Dll") }, - pair.first - }); - } - } - } else { - LOG_INFO("Nonstandard subject interface provider GUID " << GUID << " (DLL: " << *dll << ", Function: " << *func << ")"); - - if(func){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ GUIDInfo, L"FuncName", std::move(*func) })); - detections++; - } - - if(dll){ - auto path{ FileSystem::SearchPathExecutable(*dll) }; - if(path){ - reaction.FileIdentified(std::make_shared(FileSystem::File{ *path })); - detections++; - } - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ GUIDInfo, L"Dll", std::move(*dll) })); - detections++; - } - } - } - } - } - } - - // Verify that the installed Trust Providers are good - auto goodTrustProviders{ ParseResource(GoodTrustProviders) }; - for(auto keypath : { L"SOFTWARE\\Microsoft\\Cryptography\\Providers\\Trust", L"SOFTWARE\\WoW6432Node\\Microsoft\\Cryptography\\Providers\\Trust" }){ - RegistryKey key{ HKEY_LOCAL_MACHINE, keypath }; - for(auto& subkey : key.EnumerateSubkeyNames()){ - if(goodTrustProviders.find(subkey) != goodTrustProviders.end()){ - auto& entry{ goodTrustProviders.at(subkey) }; - RegistryKey ProviderType{ key, subkey }; - - for(auto& GUID : ProviderType.EnumerateSubkeyNames()){ - RegistryKey GUIDInfo{ ProviderType, GUID }; - auto dll{ GUIDInfo.GetValue(L"$DLL") }; - auto func{ GUIDInfo.GetValue(L"$Function") }; - GUID = GUID.substr(1, GUID.length() - 2); - - if(entry.find(GUID) != entry.end()){ - auto& pair{ entry.at(GUID) }; - if(func && func != pair.second){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ GUIDInfo, L"$Function", std::move(*func) })); - detections++; - } - - if(files.find(*dll) == files.end()){ - files.emplace(*dll, std::vector>{ std::pair{ - RegistryValue{ GUIDInfo, L"$DLL", *GUIDInfo.GetValue(L"$DLL") }, - pair.first - }}); - } else{ - files.at(*dll).emplace_back(std::pair{ - RegistryValue{ GUIDInfo, L"$DLL", *GUIDInfo.GetValue(L"$DLL") }, - pair.first - }); - } - } else{ - LOG_INFO("Nonstandard trust provider GUID " << GUID << " for " << subkey << " (DLL: " << *dll << ", Function: " << *func << ")"); - - if(func){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ GUIDInfo, L"$Function", std::move(*func) })); - detections++; - } - - if(dll){ - auto path{ FileSystem::SearchPathExecutable(*dll) }; - if(path){ - reaction.FileIdentified(std::make_shared(FileSystem::File{ *path })); - detections++; - } - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ GUIDInfo, L"$DLL", std::move(*dll) })); - detections++; - } - } - } - } - } - } - - // Verify the collection of DLLs - for(auto& pair : files){ - auto dllpath{ FileSystem::SearchPathExecutable(pair.first) }; - if(!dllpath){ - // Assume the worst - if the DLL path isn't found, it's because there's a target process that WILL find it - for(auto& value : pair.second){ - LOG_INFO("DLL " << pair.first << " not found and may be a target for hijacking"); - reaction.RegistryKeyIdentified(std::make_shared(value.first)); - detections++; - } - } else{ - dllpath = ToLowerCaseW(*dllpath); - auto location{ dllpath->find(L"syswow64") }; - if(location != std::wstring::npos){ - dllpath->replace(dllpath->begin() + location, dllpath->begin() + location + 8, L"system32"); - } - for(auto& value : pair.second){ - if(dllpath != value.second && (dllpath->length() >= value.second.length() && - dllpath->substr(dllpath->length() - value.second.length()) != value.second)){ - LOG_INFO("Path for dll " << *dllpath << " does not match " << value.second << " and may have been hijacked"); - reaction.FileIdentified(std::make_shared(FileSystem::File{ *dllpath })); - reaction.RegistryKeyIdentified(std::make_shared(value.first)); - detections += 2; - } - } - } - } - - // Ensure only Microsoft signed DLLs are used here - std::vector keypaths{ - L"SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0", - L"SOFTWARE\\Microsoft\\Cryptography\\Providers\\Trust" - }; - for(auto keypath : keypaths){ - for(auto key : CheckSubkeys(HKEY_LOCAL_MACHINE, keypath, true, false)){ - std::queue keys{}; - keys.emplace(key); - - while(keys.size()){ - auto check{ keys.front() }; - keys.pop(); - - for(auto val : check.EnumerateValues()){ - auto type{ check.GetValueType(val) }; - if(type == RegistryType::REG_SZ_T || type == RegistryType::REG_EXPAND_SZ_T){ - auto path{ FileSystem::SearchPathExecutable(*check.GetValue(val)) }; - if(path){ - auto file{ FileSystem::File(*path) }; - if(!file.IsMicrosoftSigned()){ - reaction.FileIdentified(std::make_shared(file)); - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ check, val, *check.GetValue(val) })); - detections += 2; - } - } else if(ToLowerCaseW(val).find(L"dll") != std::wstring::npos){ - reaction.RegistryKeyIdentified(std::make_shared(RegistryValue{ check, val, *check.GetValue(val) })); - detections++; - } - } - } - for(auto subkey : check.EnumerateSubkeys()){ - keys.emplace(subkey); - } - } - } - } - - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1198::GetMonitoringEvents(){ - std::vector> events; - - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0", true, false, true)); - ADD_ALL_VECTOR(events, Registry::GetRegistryEvents(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography\\Providers\\Trust", true, false, true)); - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1484.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1484.cpp deleted file mode 100644 index 4e47b0e5..00000000 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1484.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "hunt/hunts/HuntT1484.h" - -#include "util/log/Log.h" -#include "util/filesystem/FileSystem.h" - -namespace Hunts { - - HuntT1484::HuntT1484() : Hunt(L"T1484 - Group Policy Modification") { - dwSupportedScans = (DWORD) Aggressiveness::Cursory; - dwCategoriesAffected = (DWORD)Category::Files; - dwSourcesInvolved = (DWORD)DataSource::FileSystem | (DWORD)DataSource::GPO; - dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; - } - - int HuntT1484::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for " << name << " at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int detections = 0; - - auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); - for (auto userFolder : userFolders) { - auto ntuserman = FileSystem::File(userFolder.GetFolderPath() + L"\\ntuser.man"); - if (ntuserman.GetFileExists()) { - detections++; - reaction.FileIdentified(std::make_shared(ntuserman)); - } - } - reaction.EndHunt(); - return detections; - } - - std::vector> HuntT1484::GetMonitoringEvents() { - std::vector> events; - - events.push_back(std::make_shared(FileSystem::Folder(L"C:\\Users"))); - auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); - for (auto userFolder : userFolders) { - events.push_back(std::make_shared(userFolder)); - } - - return events; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/CarveMemory.cpp b/BLUESPAWN-client/src/reaction/CarveMemory.cpp deleted file mode 100644 index d057acea..00000000 --- a/BLUESPAWN-client/src/reaction/CarveMemory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include - -#include "reaction/CarveMemory.h" -#include "util/log/Log.h" -#include "util/processes/PERemover.h" - -namespace Reactions { - - void CarveProcessReaction::CarveProcessIdentified(std::shared_ptr detection){ - if(io.GetUserConfirm(detection->wsImagePath + L" (PID " + std::to_wstring(detection->PID) + L") appears to be infected. Carve out and terminate malicious memory section?") == 1){ - auto remover = PERemover{ static_cast(detection->PID), detection->lpAllocationBase, detection->dwAllocationSize }; - remover.RemoveImage(); - } - } - - CarveProcessReaction::CarveProcessReaction(const IOBase& io) : io{ io }{ - vProcessReactions.emplace_back(std::bind(&CarveProcessReaction::CarveProcessIdentified, this, std::placeholders::_1)); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/DeleteFile.cpp b/BLUESPAWN-client/src/reaction/DeleteFile.cpp deleted file mode 100644 index 0a119ba3..00000000 --- a/BLUESPAWN-client/src/reaction/DeleteFile.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include - -#include "reaction/DeleteFile.h" -#include "common/wrappers.hpp" - -#include "util/log/Log.h" - -namespace Reactions { - - void DeleteFileReaction::DeleteFileIdentified(std::shared_ptr detection) { - if (io.GetUserConfirm(L"File " + detection->wsFilePath + L" appears to be malicious. Delete file?") == 1) { - if (!detection->fFile.TakeOwnership()) { - LOG_ERROR("Unable to take ownership of file, still attempting to delete. (Error: " << GetLastError() << ")"); - } - ACCESS_MASK amDelete{ 0 }; - Permissions::AccessAddDelete(amDelete); - std::optional BluespawnOwner = Permissions::GetProcessOwner(); - if (BluespawnOwner == std::nullopt) { - LOG_ERROR("Unable to get process owner, still attempting to delete. (Error: " << GetLastError() << ")"); - } - else { - if (!detection->fFile.GrantPermissions(*BluespawnOwner, amDelete)) { - LOG_ERROR("Unable to grant delete permission, still attempting to delete. (Error: " << GetLastError() << ")"); - } - } - if (!detection->fFile.Delete()) { - LOG_ERROR("Unable to delete file " << detection->wsFilePath << ". (Error " << GetLastError() << ")"); - } - } - } - - DeleteFileReaction::DeleteFileReaction(const IOBase& io) : io{ io } { - vFileReactions.emplace_back(std::bind(&DeleteFileReaction::DeleteFileIdentified, this, std::placeholders::_1)); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/QuarantineFile.cpp b/BLUESPAWN-client/src/reaction/QuarantineFile.cpp deleted file mode 100644 index afa73681..00000000 --- a/BLUESPAWN-client/src/reaction/QuarantineFile.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include - -#include "reaction/QuarantineFile.h" -#include "common/wrappers.hpp" - -#include "util/log/Log.h" - -namespace Reactions { - - void QuarantineFileReaction::QuarantineFileIdentified(std::shared_ptr detection) { - if (io.GetUserConfirm(L"File " + detection->wsFilePath + L" appears to be malicious. Quarantine file?") == 1) { - if (!detection->fFile.Quarantine()) { - LOG_ERROR("Unable to quarantine file " << detection->wsFilePath << ". (Error " << GetLastError() << ")"); - } - } - } - - QuarantineFileReaction::QuarantineFileReaction(const IOBase& io) : io{ io } { - vFileReactions.emplace_back(std::bind(&QuarantineFileReaction::QuarantineFileIdentified, this, std::placeholders::_1)); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/ReactLog.cpp b/BLUESPAWN-client/src/reaction/ReactLog.cpp deleted file mode 100644 index c26c4d82..00000000 --- a/BLUESPAWN-client/src/reaction/ReactLog.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include - -#include "reaction/Log.h" - -#include "util/log/HuntLogMessage.h" - -namespace Reactions { - void LogReaction::LogBeginHunt(const HuntInfo& info){ - _HuntLogMessage = std::optional{ Log::HuntLogMessage{ info, Log::_LogHuntSinks } }; - HuntBegun = true; - } - void LogReaction::LogEndHunt(){ - *_HuntLogMessage << Log::endlog; - _HuntLogMessage = std::nullopt; - HuntBegun = false; - } - void LogReaction::LogFileIdentified(std::shared_ptr detection){ - if(HuntBegun){ - _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); - } else { - LOG_ERROR("Potentially malicious file " << detection->wsFilePath << " detected outside of a hunt!"); - } - } - void LogReaction::LogRegistryKeyIdentified(std::shared_ptr detection){ - if(HuntBegun){ - _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); - } else { - LOG_ERROR(L"\tPotentially malicious registry key detected outside of a hunt - " << detection->value.key - << L": " << detection->value.GetPrintableName() << L" with data " << detection->value); - } - } - void LogReaction::LogProcessIdentified(std::shared_ptr detection){ - if(HuntBegun){ - _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); - } else { - LOG_ERROR("Potentially malicious process " << detection->wsImagePath << " (PID " << detection->PID - << ") detected outside of a hunt!"); - } - } - void LogReaction::LogServiceIdentified(std::shared_ptr detection){ - if(HuntBegun){ - _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); - } else { - LOG_ERROR("Potentially malicious service " << detection->wsServiceName << " detected outside of a hunt!"); - } - } - void LogReaction::LogEventIdentified(std::shared_ptr detection) { - if (HuntBegun) { - _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); - } - else { - //LOG_ERROR("Potentially malicious service " << detection->wsServiceName << " detected outside of a hunt!"); - } - } - - LogReaction::LogReaction() : - _HuntLogMessage{ std::nullopt }{ - vStartHuntProcs.emplace_back( std::bind(&LogReaction::LogBeginHunt, this, std::placeholders::_1)); - vEndHuntProcs.emplace_back( std::bind(&LogReaction::LogEndHunt, this )); - vRegistryReactions.emplace_back(std::bind(&LogReaction::LogRegistryKeyIdentified, this, std::placeholders::_1)); - vFileReactions.emplace_back( std::bind(&LogReaction::LogFileIdentified, this, std::placeholders::_1)); - vProcessReactions.emplace_back( std::bind(&LogReaction::LogProcessIdentified, this, std::placeholders::_1)); - vServiceReactions.emplace_back( std::bind(&LogReaction::LogServiceIdentified, this, std::placeholders::_1)); - vEventReactions.emplace_back( std::bind(&LogReaction::LogEventIdentified, this, std::placeholders::_1)); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/Reaction.cpp b/BLUESPAWN-client/src/reaction/Reaction.cpp deleted file mode 100644 index 7cceaa7d..00000000 --- a/BLUESPAWN-client/src/reaction/Reaction.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "reaction/Reaction.h" - -void Reaction::BeginHunt(const HuntInfo& info){ - for(auto BeginProc : vStartHuntProcs){ - BeginProc(info); - } -} -void Reaction::EndHunt(){ - for(auto EndProc : vEndHuntProcs){ - EndProc(); - } -} - -void Reaction::FileIdentified(std::shared_ptr info){ - for(auto reaction : vFileReactions){ - reaction(info); - } -} -void Reaction::RegistryKeyIdentified(std::shared_ptr info){ - for(auto reaction : vRegistryReactions){ - reaction(info); - } -} -void Reaction::ProcessIdentified(std::shared_ptr info){ - for(auto reaction : vProcessReactions){ - reaction(info); - } -} -void Reaction::ServiceIdentified(std::shared_ptr info){ - for(auto reaction : vServiceReactions){ - reaction(info); - } -} -void Reaction::EventIdentified(std::shared_ptr info) { - for (auto reaction : vEventReactions) { - reaction(info); - } -} - -void Reaction::AddHuntBegin(HuntStart start){ - vStartHuntProcs.emplace_back(start); -} -void Reaction::AddHuntEnd(HuntEnd end){ - vEndHuntProcs.emplace_back(end); -} - -void Reaction::AddFileReaction(DetectFile handler){ - vFileReactions.emplace_back(handler); -} -void Reaction::AddRegistryReaction(DetectRegistry handler){ - vRegistryReactions.emplace_back(handler); -} -void Reaction::AddProcessReaction(DetectProcess handler){ - vProcessReactions.emplace_back(handler); -} -void Reaction::AddServiceReaction(DetectService handler){ - vServiceReactions.emplace_back(handler); -} -void Reaction::AddEventReaction(DetectEvent handler) { - vEventReactions.emplace_back(handler); -} - -Reaction& Reaction::Combine(const Reaction& reaction){ - - for(auto function : reaction.vStartHuntProcs) - vStartHuntProcs.emplace_back(function); - for(auto function : reaction.vEndHuntProcs) - vEndHuntProcs.emplace_back(function); - for(auto function : reaction.vFileReactions) - vFileReactions.emplace_back(function); - for(auto function : reaction.vRegistryReactions) - vRegistryReactions.emplace_back(function); - for(auto function : reaction.vProcessReactions) - vProcessReactions.emplace_back(function); - for(auto function : reaction.vServiceReactions) - vServiceReactions.emplace_back(function); - for (auto function : reaction.vEventReactions) - vEventReactions.emplace_back(function); - - return *this; -} - -Reaction& Reaction::Combine(Reaction&& reaction){ - - for(auto function : reaction.vStartHuntProcs) - vStartHuntProcs.emplace_back(function); - for(auto function : reaction.vEndHuntProcs) - vEndHuntProcs.emplace_back(function); - for(auto function : reaction.vFileReactions) - vFileReactions.emplace_back(function); - for(auto function : reaction.vRegistryReactions) - vRegistryReactions.emplace_back(function); - for(auto function : reaction.vProcessReactions) - vProcessReactions.emplace_back(function); - for(auto function : reaction.vServiceReactions) - vServiceReactions.emplace_back(function); - for (auto function : reaction.vEventReactions) - vEventReactions.emplace_back(function); - - return *this; -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/RemoveValue.cpp b/BLUESPAWN-client/src/reaction/RemoveValue.cpp deleted file mode 100644 index d24f105d..00000000 --- a/BLUESPAWN-client/src/reaction/RemoveValue.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include - -#include "reaction/RemoveValue.h" -#include "util/configurations/Registry.h" -#include "common/wrappers.hpp" - -#include "util/log/Log.h" - -namespace Reactions{ - - void RemoveValueReaction::RemoveRegistryIdentified(std::shared_ptr detection){ - if(io.GetUserConfirm(L"Registry key " + detection->value.key.ToString() + L" contains potentially malicious value " - + detection->value.GetPrintableName() + L" with data " + detection->value.ToString() + L". Remove value?") == 1){ - if(!detection->value.key.RemoveValue(detection->value.wValueName)){ - LOG_ERROR("Unable to remove registry value " << detection->value.key.ToString() << ": " << detection->value.GetPrintableName() << " (Error " << GetLastError() << ")"); - } - } - } - - RemoveValueReaction::RemoveValueReaction(const IOBase& io) : io{ io }{ - vRegistryReactions.emplace_back(std::bind(&RemoveValueReaction::RemoveRegistryIdentified, this, std::placeholders::_1)); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/reaction/SuspendProcess.cpp b/BLUESPAWN-client/src/reaction/SuspendProcess.cpp deleted file mode 100644 index e23efbfe..00000000 --- a/BLUESPAWN-client/src/reaction/SuspendProcess.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include - -#include "reaction/SuspendProcess.h" -#include "common/wrappers.hpp" -#include "util/log/Log.h" - -#include - -LINK_FUNCTION(NtSuspendProcess, NTDLL.DLL) - -namespace Reactions{ - bool SuspendProcessReaction::CheckModules(const HandleWrapper& process, const std::wstring& file) const { - HMODULE hMods[1024]; - DWORD cbNeeded = 0; - if(EnumProcessModules(process, hMods, sizeof(hMods), &cbNeeded)){ - for(DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++){ - WCHAR szModName[MAX_PATH]; - if(GetModuleFileNameExW(process, hMods[i], szModName, sizeof(szModName) / sizeof(WCHAR))){ - if(file == szModName){ - return true; - } - } - } - } - return false; - } - - void SuspendProcessReaction::SuspendFileIdentified(std::shared_ptr detection){ - auto ext = detection->wsFileName.substr(detection->wsFileName.size() - 4); - if(ext != L".exe" && ext != L".dll"){ - return; - } - - if(io.GetUserConfirm(detection->wsFileName + L" appears to be a malicious file. Suspend related processes?") == 1){ - DWORD processes[1024]; - DWORD ProcessCount = 0; - ZeroMemory(processes, sizeof(processes)); - auto success = EnumProcesses(processes, sizeof(processes), &ProcessCount); - if(success){ - ProcessCount /= sizeof(DWORD); - for(int i = 0; i < ProcessCount; i++){ - HandleWrapper process = OpenProcess(PROCESS_SUSPEND_RESUME | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processes[i]); - if(process){ - if(CheckModules(process, detection->wsFilePath)){ - Linker::NtSuspendProcess(process); - io.InformUser(L"Process with PID " + std::to_wstring(processes[i]) + L" was suspended."); - } - } else { - LOG_WARNING("Unable to open process " << processes[i] << "."); - } - } - } else { - LOG_ERROR("Unable to enumerate processes - Unable to detect if malicious file is loaded."); - } - } - } - - void SuspendProcessReaction::SuspendProcessIdentified(std::shared_ptr detection){ - HandleWrapper process = OpenProcess(PROCESS_SUSPEND_RESUME, false, detection->PID); - if(process){ - if(io.GetUserConfirm(detection->wsCmdline + L" appears to be infected. Suspend process?") == 1){ - Linker::NtSuspendProcess(process); - } - } else { - LOG_ERROR("Unable to open potentially infected process " << detection->PID); - } - } - - void SuspendProcessReaction::SuspendServiceIdentified(std::shared_ptr detection){ - HandleWrapper process = OpenProcess(PROCESS_SUSPEND_RESUME, false, detection->ServicePID); - if(process){ - if(io.GetUserConfirm(L"Service " + detection->wsServiceName + L" appears to be infected. Suspend process?") == 1){ - Linker::NtSuspendProcess(process); - } - } else { - LOG_ERROR("Unable to open potentially infected process " << detection->ServicePID); - } - } - - SuspendProcessReaction::SuspendProcessReaction(const IOBase& io) : io{ io }{ - vFileReactions.emplace_back(std::bind(&SuspendProcessReaction::SuspendFileIdentified, this, std::placeholders::_1)); - vProcessReactions.emplace_back(std::bind(&SuspendProcessReaction::SuspendProcessIdentified, this, std::placeholders::_1)); - vServiceReactions.emplace_back(std::bind(&SuspendProcessReaction::SuspendServiceIdentified, this, std::placeholders::_1)); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/user/BLUESPAWN.cpp b/BLUESPAWN-client/src/user/BLUESPAWN.cpp deleted file mode 100644 index 21f4ec56..00000000 --- a/BLUESPAWN-client/src/user/BLUESPAWN.cpp +++ /dev/null @@ -1,394 +0,0 @@ -#include "user/bluespawn.h" -#include "user/CLI.h" -#include "util/log/HuntLogMessage.h" -#include "util/log/DebugSink.h" -#include "util/log/XMLSink.h" -#include "common/DynamicLinker.h" -#include "common/StringUtils.h" -#include "util/eventlogs/EventLogs.h" -#include "reaction/SuspendProcess.h" -#include "reaction/RemoveValue.h" -#include "reaction/CarveMemory.h" -#include "reaction/DeleteFile.h" -#include "reaction/QuarantineFile.h" -#include "util/permissions/permissions.h" - -#include "hunt/hunts/HuntT1004.h" -#include "hunt/hunts/HuntT1013.h" -#include "hunt/hunts/HuntT1015.h" -#include "hunt/hunts/HuntT1031.h" -#include "hunt/hunts/HuntT1035.h" -#include "hunt/hunts/HuntT1036.h" -#include "hunt/hunts/HuntT1037.h" -#include "hunt/hunts/HuntT1050.h" -#include "hunt/hunts/HuntT1053.h" -#include "hunt/hunts/HuntT1055.h" -#include "hunt/hunts/HuntT1060.h" -#include "hunt/hunts/HuntT1068.h" -#include "hunt/hunts/HuntT1089.h" -#include "hunt/hunts/HuntT1099.h" -#include "hunt/hunts/HuntT1100.h" -#include "hunt/hunts/HuntT1101.h" -#include "hunt/hunts/HuntT1103.h" -#include "hunt/hunts/HuntT1122.h" -#include "hunt/hunts/HuntT1128.h" -#include "hunt/hunts/HuntT1131.h" -#include "hunt/hunts/HuntT1136.h" -#include "hunt/hunts/HuntT1138.h" -#include "hunt/hunts/HuntT1182.h" -#include "hunt/hunts/HuntT1183.h" -#include "hunt/hunts/HuntT1198.h" -#include "hunt/hunts/HuntT1484.h" - -#include "monitor/ETW_Wrapper.h" - -#include "mitigation/mitigations/MitigateM1025.h" -#include "mitigation/mitigations/MitigateM1028-WFW.h" -#include "mitigation/mitigations/MitigateM1035-RDP.h" -#include "mitigation/mitigations/MitigateM1042-LLMNR.h" -#include "mitigation/mitigations/MitigateM1042-NBT.h" -#include "mitigation/mitigations/MitigateM1042-WSH.h" -#include "mitigation/mitigations/MitigateM1047.h" -#include "mitigation/mitigations/MitigateM1054-RDP.h" -#include "mitigation/mitigations/MitigateM1054-WSC.h" -#include "mitigation/mitigations/MitigateV1093.h" -#include "mitigation/mitigations/MitigateV1153.h" -#include "mitigation/mitigations/MitigateV3338.h" -#include "mitigation/mitigations/MitigateV3340.h" -#include "mitigation/mitigations/MitigateV3344.h" -#include "mitigation/mitigations/MitigateV3379.h" -#include "mitigation/mitigations/MitigateV3479.h" -#include "mitigation/mitigations/MitigateV63597.h" -#include "mitigation/mitigations/MitigateV63687.h" -#include "mitigation/mitigations/MitigateV63753.h" -#include "mitigation/mitigations/MitigateV63817.h" -#include "mitigation/mitigations/MitigateV63825.h" -#include "mitigation/mitigations/MitigateV63829.h" -#include "mitigation/mitigations/MitigateV71769.h" -#include "mitigation/mitigations/MitigateV72753.h" -#include "mitigation/mitigations/MitigateV73511.h" -#include "mitigation/mitigations/MitigateV73519.h" -#include "mitigation/mitigations/MitigateV73585.h" - -#pragma warning(push) - -#pragma warning(disable : 26451) -#pragma warning(disable : 26444) - -#include "cxxopts.hpp" - -#pragma warning(pop) - -#include -#include - -DEFINE_FUNCTION(BOOL, IsWow64Process2, NTAPI, HANDLE hProcess, USHORT* pProcessMachine, USHORT* pNativeMachine); -LINK_FUNCTION(IsWow64Process2, KERNEL32.DLL); - -const IOBase& Bluespawn::io = CLI::GetInstance(); -HuntRegister Bluespawn::huntRecord{ io }; -MitigationRegister Bluespawn::mitigationRecord{ io }; - -Bluespawn::Bluespawn(){ - - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - huntRecord.RegisterHunt(std::make_shared()); - - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); - mitigationRecord.RegisterMitigation(std::make_shared()); -} - -void Bluespawn::dispatch_hunt(Aggressiveness aHuntLevel, vector vExcludedHunts, vector vIncludedHunts) { - Bluespawn::io.InformUser(L"Starting a Hunt"); - DWORD tactics = UINT_MAX; - DWORD dataSources = UINT_MAX; - DWORD affectedThings = UINT_MAX; - Scope scope{}; - - huntRecord.RunHunts(tactics, dataSources, affectedThings, scope, aHuntLevel, reaction, vExcludedHunts, vIncludedHunts); -} - -void Bluespawn::dispatch_mitigations_analysis(MitigationMode mode, bool bForceEnforce) { - if (mode == MitigationMode::Enforce) { - Bluespawn::io.InformUser(L"Enforcing Mitigations"); - mitigationRecord.EnforceMitigations(SecurityLevel::High, bForceEnforce); - } - else { - Bluespawn::io.InformUser(L"Auditing Mitigations"); - mitigationRecord.AuditMitigations(SecurityLevel::High); - } -} - -void Bluespawn::monitor_system(Aggressiveness aHuntLevel) { - DWORD tactics = UINT_MAX; - DWORD dataSources = UINT_MAX; - DWORD affectedThings = UINT_MAX; - Scope scope{}; - - Bluespawn::io.InformUser(L"Monitoring the system"); - huntRecord.SetupMonitoring(aHuntLevel, reaction); - - HandleWrapper hRecordEvent{ CreateEventW(nullptr, false, false, L"Local\\FlushLogs") }; - while (true) { - SetEvent(hRecordEvent); - Sleep(5000); - } -} - -void Bluespawn::SetReaction(const Reaction& reaction){ - this->reaction = reaction; -} - -void print_help(cxxopts::ParseResult result, cxxopts::Options options) { - std::string help_category = result["help"].as < std::string >(); - - std::transform(help_category.begin(), help_category.end(), - help_category.begin(), [](unsigned char c) { return std::tolower(c); }); - - if(help_category.compare("hunt") == 0) { - std::cout << (options.help({ "hunt" })) << std::endl; - } else if(help_category.compare("general") == 0) { - std::cout << (options.help()) << std::endl; - } else { - std::cerr << ("Unknown help category") << std::endl; - } -} - -void Bluespawn::check_correct_arch() { - BOOL bIsWow64 = FALSE; - if (IsWindows10OrGreater() && Linker::IsWow64Process2) { - USHORT ProcessMachine; - USHORT NativeMachine; - - Linker::IsWow64Process2(GetCurrentProcess(), &ProcessMachine, &NativeMachine); - if (ProcessMachine != IMAGE_FILE_MACHINE_UNKNOWN) { - bIsWow64 = TRUE; - } - } - else { - IsWow64Process(GetCurrentProcess(), &bIsWow64); - } - if (bIsWow64) { - Bluespawn::io.AlertUser(L"Running the x86 version of BLUESPAWN on an x64 system! This configuration is not fully supported, so we recommend downloading the x64 version.", 5000, ImportanceLevel::MEDIUM); - LOG_WARNING("Running the x86 version of BLUESPAWN on an x64 system! This configuration is not fully supported, so we recommend downloading the x64 version."); - } -} - -int main(int argc, char* argv[]){ - Bluespawn bluespawn{}; - - print_banner(); - - bluespawn.check_correct_arch(); - - cxxopts::Options options("BLUESPAWN.exe", "BLUESPAWN: A Windows based Active Defense Tool to empower Blue Teams"); - - options.add_options() - ("h,hunt", "Perform a Hunt Operation", cxxopts::value()) - ("n,monitor", "Monitor the System for Malicious Activity. Available options are Cursory, Normal, or Intensive.", cxxopts::value()->implicit_value("Normal")) - ("m,mitigate", "Mitigates vulnerabilities by applying security settings. Available options are audit and enforce.", cxxopts::value()->implicit_value("audit")) - ("help", "Help Information. You can also specify a category for help on a specific module such as hunt.", cxxopts::value()->implicit_value("general")) - ("log", "Specify how Bluespawn should log events. Options are console (default), xml, and debug.", cxxopts::value()->default_value("console")) - ("reaction", "Specifies how bluespawn should react to potential threats dicovered during hunts.", cxxopts::value()->default_value("log")) - ("v,verbose", "Verbosity", cxxopts::value()->default_value("0")) - ("debug", "Enable Debug Output", cxxopts::value()) - ; - - options.add_options("hunt") - ("l,level", "Aggressiveness of Hunt. Either Cursory, Normal, or Intensive", cxxopts::value()) - ("hunts", "List of hunts to run by Mitre ATT&CK name. Will only run these hunts.", cxxopts::value>()) - ("exclude-hunts", "List of hunts to avoid running by Mitre ATT&CK name. Will run all hunts but these.", cxxopts::value>()) - ; - - options.add_options("mitigate") - ("force", "Use this option to forcibly apply mitigations with no prompt", cxxopts::value()) - ; - - options.parse_positional({ "level" }); - try { - auto result = options.parse(argc, argv); - - if (result.count("verbose")) { - if(result["verbose"].as() >= 1) { - Log::LogLevel::LogVerbose1.Enable(); - } - if(result["verbose"].as() >= 2) { - Log::LogLevel::LogVerbose2.Enable(); - } - if(result["verbose"].as() >= 3) { - Log::LogLevel::LogVerbose3.Enable(); - } - } - - auto sinks = result["log"].as(); - std::set sink_set; - for(unsigned startIdx = 0; startIdx < sinks.size();){ - auto endIdx = min(sinks.find(',', startIdx), sinks.size()); - auto sink = sinks.substr(startIdx, endIdx - startIdx); - sink_set.emplace(sink); - startIdx = endIdx + 1; - } - for(auto sink : sink_set){ - if(sink == "console"){ - auto Console = std::make_shared(); - Log::AddHuntSink(Console); - if(result.count("debug")) Log::AddSink(Console); - } else if(sink == "xml"){ - auto XMLSink = std::make_shared(); - Log::AddHuntSink(XMLSink); - if(result.count("debug")) Log::AddSink(XMLSink); - } else if(sink == "debug"){ - auto DbgSink = std::make_shared(); - Log::AddHuntSink(DbgSink); - if(result.count("debug")) Log::AddSink(DbgSink); - } else { - bluespawn.io.AlertUser(L"Unknown log sink \"" + StringToWidestring(sink) + L"\"", INFINITY, ImportanceLevel::MEDIUM); - } - } - - if (result.count("help")) { - print_help(result, options); - } - - else if (result.count("hunt") || result.count("monitor")) { - std::map reactions = { - {"log", Reactions::LogReaction{}}, - {"remove-value", Reactions::RemoveValueReaction{ bluespawn.io }}, - {"suspend", Reactions::SuspendProcessReaction{ bluespawn.io }}, - {"carve-memory", Reactions::CarveProcessReaction{ bluespawn.io }}, - {"delete-file", Reactions::DeleteFileReaction{ bluespawn.io }}, - {"quarantine-file", Reactions::QuarantineFileReaction{ bluespawn.io}}, - }; - - auto UserReactions = result["reaction"].as(); - std::set reaction_set; - for(unsigned startIdx = 0; startIdx < UserReactions.size();){ - auto endIdx = min(UserReactions.find(',', startIdx), UserReactions.size()); - auto sink = UserReactions.substr(startIdx, endIdx - startIdx); - reaction_set.emplace(sink); - startIdx = endIdx + 1; - } - - Reaction combined = {}; - for(auto reaction : reaction_set){ - if(reactions.find(reaction) != reactions.end()){ - combined.Combine(reactions[reaction]); - } else { - bluespawn.io.AlertUser(L"Unknown reaction \"" + StringToWidestring(reaction) + L"\"", INFINITY, ImportanceLevel::MEDIUM); - } - } - - bluespawn.SetReaction(combined); - - // Parse the hunt level - std::string sHuntLevelFlag = "Normal"; - Aggressiveness aHuntLevel; - try { - sHuntLevelFlag = result["level"].as < std::string >(); - } - catch (int e) {} - - if (CompareIgnoreCase(sHuntLevelFlag, "Cursory")) { - aHuntLevel = Aggressiveness::Cursory; - } - else if (CompareIgnoreCase(sHuntLevelFlag, "Normal")) { - aHuntLevel = Aggressiveness::Normal; - } - else if (CompareIgnoreCase(sHuntLevelFlag, "Intensive")) { - aHuntLevel = Aggressiveness::Intensive; - } - else { - LOG_ERROR("Error " << sHuntLevelFlag << " - Unknown level. Please specify either Cursory, Normal, or Intensive"); - LOG_ERROR("Will default to Cursory for this run."); - Bluespawn::io.InformUser(L"Error " + StringToWidestring(sHuntLevelFlag) + L" - Unknown level. Please specify either Cursory, Normal, or Intensive"); - Bluespawn::io.InformUser(L"Will default to Cursory."); - aHuntLevel = Aggressiveness::Cursory; - } - - //Parse included and excluded hunts - std::vector vIncludedHunts; - std::vector vExcludedHunts; - - if (result.count("hunts")) { - vIncludedHunts = result["hunts"].as>(); - } - else if (result.count("exclude-hunts")) { - vExcludedHunts = result["exclude-hunts"].as>(); - } - - if (result.count("hunt")) - bluespawn.dispatch_hunt(aHuntLevel, vExcludedHunts, vIncludedHunts); - else if (result.count("monitor")) - bluespawn.monitor_system(aHuntLevel); - - } - else if (result.count("mitigate")) { - bool bForceEnforce = false; - if (result.count("force")) - bForceEnforce = true; - - MitigationMode mode = MitigationMode::Audit; - if (result["mitigate"].as() == "e" || result["mitigate"].as() == "enforce") - mode = MitigationMode::Enforce; - - bluespawn.dispatch_mitigations_analysis(mode, bForceEnforce); - } - else { - LOG_ERROR("Nothing to do. Use the -h or --hunt flags to launch a hunt"); - } - } - catch (cxxopts::OptionParseException e1) { - LOG_ERROR(e1.what()); - } - - return 0; -} diff --git a/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp b/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp deleted file mode 100644 index b9411d42..00000000 --- a/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "util/eventlogs/EventLogs.h" -#include "common/StringUtils.h" -#include "reaction/Detections.h" -#include "util/log/Log.h" -#include "common/Utils.h" - -const int SIZE_DATA = 4096; -const int ARRAY_SIZE = 10; - -namespace EventLogs { - - /** - * The callback function directly called by event subscriptions. - * In turn it calls the EventSubscription::SubscriptionCallback of a specific class instance. - */ - DWORD WINAPI CallbackWrapper(EVT_SUBSCRIBE_NOTIFY_ACTION Action, PVOID UserContext, EVT_HANDLE Event) { - return reinterpret_cast(UserContext)->SubscriptionCallback(Action, Event); - } - - std::optional EventLogs::GetEventParam(const EventWrapper& hEvent, const std::wstring& param) { - auto queryParam = param.c_str(); - EventWrapper hContext = EvtCreateRenderContext(1, &queryParam, EvtRenderContextValues); - if (!hContext){ - LOG_ERROR("EventLogs::GetEventParam: EvtCreateRenderContext failed with " + std::to_string(GetLastError())); - return std::nullopt; - } - - DWORD dwBufferSize{}; - if(!EvtRender(hContext, hEvent, EvtRenderEventValues, dwBufferSize, nullptr, &dwBufferSize, nullptr)){ - if(ERROR_INSUFFICIENT_BUFFER == GetLastError()){ - auto pRenderedValues = AllocationWrapper{ HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize), dwBufferSize, AllocationWrapper::HEAP_ALLOC }; - if(pRenderedValues){ - if(EvtRender(hContext, hEvent, EvtRenderEventValues, dwBufferSize, pRenderedValues, &dwBufferSize, nullptr)){ - /* - Table of variant members found here: https://docs.microsoft.com/en-us/windows/win32/api/winevt/ns-winevt-evt_variant - Table of type values found here: https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_variant_type - */ - PEVT_VARIANT result = reinterpret_cast((LPVOID) pRenderedValues); - if(result->Type == EvtVarTypeString) - return std::wstring(result->StringVal); - else if(result->Type == EvtVarTypeFileTime) { - wchar_t ar[30]; - _ui64tow(result->FileTimeVal, ar, 10); - return ar; - } else if(result->Type == EvtVarTypeUInt16) { - return std::to_wstring(result->UInt16Val); - } else if (result->Type == EvtVarTypeUInt32) { - return std::to_wstring(result->UInt32Val); - } else if(result->Type == EvtVarTypeUInt64) { - return std::to_wstring(result->UInt64Val); - } else if(result->Type == EvtVarTypeNull) - return L"NULL"; - else { - return L"Unknown VARIANT: " + std::to_wstring(result->Type); - } - } - } - } - } - return std::nullopt; - } - - std::optional EventLogs::GetEventXML(const EventWrapper& hEvent){ - DWORD dwBufferSize = 0; - if(!EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, nullptr, &dwBufferSize, nullptr)){ - if (ERROR_INSUFFICIENT_BUFFER == GetLastError()){ - auto pRenderedContent = AllocationWrapper{ HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize), dwBufferSize, AllocationWrapper::HEAP_ALLOC }; - if (pRenderedContent){ - if(EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, pRenderedContent, &dwBufferSize, nullptr)){ - return reinterpret_cast((LPVOID)pRenderedContent); - } - } - } - } - - return std::nullopt; - } - - // Enumerate all the events in the result set. - std::vector EventLogs::ProcessResults(const EventWrapper& hResults, const std::vector& filters) { - EVT_HANDLE hEvents[ARRAY_SIZE]; - - std::vector results; - std::vector params; - for(auto query : filters){ - if(!query.SearchesByValue()){ - params.push_back(query.ToString()); - } - } - - DWORD dwReturned{}; - while(EvtNext(hResults, ARRAY_SIZE, hEvents, INFINITE, 0, &dwReturned)){ - for(DWORD i = 0; i < dwReturned; i++) { - - auto item = EventToEventLogItem(hEvents[i], params); - if(item){ - results.push_back(*item); - } - - EvtClose(hEvents[i]); - hEvents[i] = NULL; - } - } - - for(unsigned i = 0; i < ARRAY_SIZE; i++){ - if(hEvents[i]){ - EvtClose(hEvents[i]); - } - } - - if(GetLastError() != ERROR_NO_MORE_ITEMS){ - LOG_ERROR("EventLogs::ProcessResults: EvtNext failed with " << GetLastError()); - } - - return results; - } - - std::optional EventToEventLogItem(const EventWrapper& hEvent, const std::vector& params){ - DWORD status = ERROR_SUCCESS; - - std::optional eventIDStr, eventRecordIDStr, timeCreated, channel, rawXML; - - if (std::nullopt == (eventIDStr = GetEventParam(hEvent, L"Event/System/EventID"))) - return std::nullopt; - if (std::nullopt == (eventRecordIDStr = GetEventParam(hEvent, L"Event/System/EventRecordID"))) - return std::nullopt; - if (std::nullopt == (timeCreated = GetEventParam(hEvent, L"Event/System/TimeCreated/@SystemTime"))) - return std::nullopt; - if (std::nullopt == (channel = GetEventParam(hEvent, L"Event/System/Channel"))) - return std::nullopt; - if (std::nullopt == (rawXML = GetEventXML(hEvent))) - return std::nullopt; - - EventLogItem pItem{}; - - // Provide values for filtered parameters - for (std::wstring key : params) { - std::optional val = GetEventParam(hEvent, key); - if (!val) { - return std::nullopt; - } - - pItem.SetProperty(key, *val); - } - - pItem.SetEventID(std::stoul(*eventIDStr)); - pItem.SetEventRecordID(std::stoul(*eventRecordIDStr)); - pItem.SetTimeCreated(FormatWindowsTime(*timeCreated)); - pItem.SetChannel(*channel); - pItem.SetXML(*rawXML); - - return pItem; - } - - std::vector EventLogs::QueryEvents(const std::wstring& channel, unsigned int id, const std::vector& filters) { - - std::vector items; - - auto query = std::wstring(L"Event/System[EventID=") + std::to_wstring(id) + std::wstring(L"]"); - for (auto param : filters) - query += L" and " + param.ToString(); - - EventWrapper hResults = EvtQuery(NULL, channel.c_str(), query.c_str(), EvtQueryChannelPath | EvtQueryReverseDirection); - if (NULL == hResults) { - if (ERROR_EVT_CHANNEL_NOT_FOUND == GetLastError()) - LOG_ERROR("EventLogs::QueryEvents: The channel was not found."); - else if (ERROR_EVT_INVALID_QUERY == GetLastError()) - LOG_ERROR(L"EventLogs::QueryEvents: The query " << query << L" is not valid."); - else - LOG_ERROR("EventLogs::QueryEvents: EvtQuery failed with " << GetLastError()); - } - else { - items = ProcessResults(hResults, filters); - } - - return items; - } - - std::vector subscriptions = {}; - - std::optional> EventLogs::SubscribeToEvent(const std::wstring& pwsPath, - unsigned int id, const std::function& callback, const std::vector& filters){ - auto query = std::wstring(L"Event/System[EventID=") + std::to_wstring(id) + std::wstring(L"]"); - for (auto param : filters) - query += L" and " + param.ToString(); - - subscriptions.emplace_back(EventSubscription{ callback }); - auto& eventSub = subscriptions[subscriptions.size() - 1]; - - EventWrapper hSubscription = EvtSubscribe(NULL, NULL, pwsPath.c_str(), query.c_str(), NULL, &subscriptions[subscriptions.size() - 1], - CallbackWrapper, EvtSubscribeToFutureEvents); - eventSub.setSubHandle(hSubscription); - - if(!hSubscription){ - if (ERROR_EVT_CHANNEL_NOT_FOUND == GetLastError()) - LOG_ERROR("EventLogs::SubscribeToEvent: Channel was not found."); - else if (ERROR_EVT_INVALID_QUERY == GetLastError()) - LOG_ERROR(L"EventLogs::SubscribeToEvent: query " << query << L" is not valid."); - else - LOG_ERROR("EventLogs::SubscribeToEvent: EvtSubscribe failed with " << GetLastError()); - - return std::nullopt; - } - - return eventSub; - } - - std::shared_ptr EventLogs::EventLogItemToDetection(const EventLogItem& pItem) { - auto detect = std::make_shared(0, 0, L"", L"", L""); - - detect->eventID = pItem.GetEventID(); - detect->channel = pItem.GetChannel(); - detect->eventRecordID = pItem.GetEventRecordID(); - detect->timeCreated = pItem.GetTimeCreated(); - detect->rawXML = pItem.GetXML(); - detect->params = pItem.GetProperties(); - - return detect; - } - - bool IsChannelOpen(const std::wstring& channel) { - EVT_HANDLE hChannel = NULL; - DWORD status = ERROR_SUCCESS; - PEVT_VARIANT pProperty = NULL; - PEVT_VARIANT pTemp = NULL; - DWORD dwBufferSize = 0; - DWORD dwBufferUsed = 0; - - // Open the channel config - hChannel = EvtOpenChannelConfig(NULL, channel.c_str(), 0); - if (NULL == hChannel) - { - LOG_ERROR(L"EventLogs::IsChannelOpen: EvtOpenChannelConfig failed with " + std::to_wstring(GetLastError()) + L" for channel " + channel); - return false; - } - - // Attempt to get the channel property - if (!EvtGetChannelConfigProperty(hChannel, EvtChannelConfigEnabled, 0, dwBufferSize, pProperty, &dwBufferUsed)) - { - status = GetLastError(); - if (ERROR_INSUFFICIENT_BUFFER == status) { - dwBufferSize = dwBufferUsed; - pTemp = (PEVT_VARIANT)realloc(pProperty, dwBufferSize); - - if (pTemp) { - pProperty = pTemp; - pTemp = NULL; - EvtGetChannelConfigProperty(hChannel, EvtChannelConfigEnabled, 0, dwBufferSize, pProperty, &dwBufferUsed); - } - else { - if (pProperty) - free(pProperty); - - LOG_ERROR(L"EventLogs::IsChannelOpen: realloc failed for channel " + channel); - return false; - } - } - - if (ERROR_SUCCESS != (status = GetLastError())) { - LOG_ERROR(L"EventLogs::IsChannelOpen: EvtGetChannelConfigProperty failed with " + std::to_wstring(GetLastError()) + L" for channel " + channel); - return false; - } - - } - if (pProperty) - free(pProperty); - - return pProperty->BooleanVal; - } - - bool OpenChannel(const std::wstring& channel) { - EVT_HANDLE hChannel = NULL; - DWORD status = ERROR_SUCCESS; - EVT_VARIANT ChannelProperty; - DWORD dwBufferSize = sizeof(EVT_VARIANT); - DWORD dwBufferUsed = 0; - hChannel = EvtOpenChannelConfig(NULL, channel.c_str(), 0); - if (NULL == hChannel) - { - LOG_ERROR(L"EventLogs::OpenChannel: EvtOpenChannelConfig failed with " + std::to_wstring(GetLastError()) + L" for channel " + channel); - return false; - } - RtlZeroMemory(&ChannelProperty, dwBufferSize); - - ChannelProperty.Type = EvtVarTypeBoolean; - ChannelProperty.BooleanVal = TRUE; - - if (!EvtSetChannelConfigProperty(hChannel, EvtChannelConfigEnabled, 0, &ChannelProperty)) - { - LOG_ERROR(L"EventLogs::OpenChannel: EvtSetChannelConfigProperty failed with " + std::to_wstring(GetLastError()) + L" for channel " + channel); - return false; - } - if (!EvtSaveChannelConfig(hChannel, 0)) - { - LOG_ERROR(L"EventLogs::OpenChannel: EvtSaveChannelConfig failed with " + std::to_wstring(GetLastError()) + L" for channel " + channel); - return false; - } - - return true; - } - -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp b/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp deleted file mode 100644 index 6df645da..00000000 --- a/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp +++ /dev/null @@ -1,1041 +0,0 @@ -#include "util/filesystem/FileSystem.h" -#include "util/log/Log.h" -#include "common/StringUtils.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/wrappers.hpp" -#include "common/StringUtils.h" -#include "aclapi.h" - -LINK_FUNCTION(NtCreateFile, ntdll.dll) - -namespace FileSystem{ - bool CheckFileExists(const std::wstring& path) { - auto attribs = GetFileAttributesW(path.c_str()); - if(INVALID_FILE_ATTRIBUTES == attribs && GetLastError() == ERROR_FILE_NOT_FOUND){ - LOG_VERBOSE(3, "File " << path << " does not exist."); - return false; - } - - if(attribs & FILE_ATTRIBUTE_DIRECTORY){ - LOG_VERBOSE(3, "File " << path << " is a directory."); - return false; - } - LOG_VERBOSE(3, "File " << path << " exists"); - return true; - } - - std::optional SearchPathExecutable(const std::wstring& name){ - std::wstring fullname = ExpandEnvStringsW(name); - - auto size = SearchPathW(nullptr, fullname.c_str(), L".exe", 0, nullptr, nullptr); - if(!size){ - return std::nullopt; - } - - std::vector buffer(static_cast(size) + 1); - WCHAR* filename{}; - if(!SearchPathW(nullptr, fullname.c_str(), L".exe", size + 1, buffer.data(), &filename)){ - return std::nullopt; - } - - return buffer.data(); - } - - DWORD File::SetFilePointer(DWORD64 val) const { - //Calculate the offset into format needed for SetFilePointer - long lowerMask = 0xFFFFFFFF; - DWORD64 upperMask = static_cast(0xFFFFFFFF) << 32; - auto lowerVal = static_cast(val & lowerMask); - auto upperVal = static_cast((val & upperMask) >> 32); - return ::SetFilePointer(hFile, lowerVal, reinterpret_cast(&upperVal), 0); - } - - std::optional GetCatalog(const HandleWrapper& hFile){ - std::optional catalogfile; //Whether the file was found in system catalogs - HCATADMIN hCatAdmin = NULL; //Context for enumerating system catalogs - HCATINFO hCatInfo = NULL; - GUID gAction = DRIVER_ACTION_VERIFY; - //Hash info - DWORD dwHashLength = 0; //Length of the hash - PBYTE pbHash = NULL; //Hash of the file - if (!CryptCATAdminAcquireContext(&hCatAdmin, &gAction, 0)) { - LOG_ERROR("Error acquiring catalog admin context " << GetLastError()); - goto end; - } - - //Get hash length - if (!CryptCATAdminCalcHashFromFileHandle(hFile, &dwHashLength, NULL, 0)) { - LOG_ERROR("Error getting hash size " << GetLastError()); - goto end; - } - - //Get the hash of the file - pbHash = (PBYTE) HeapAlloc(GetProcessHeap(), 0, dwHashLength); - if (pbHash == NULL) { - LOG_ERROR("Error allocating " << dwHashLength << " bytes, out of memory."); - goto end; - } - if (!CryptCATAdminCalcHashFromFileHandle(hFile, &dwHashLength, pbHash, 0)) { - LOG_ERROR("Error getting file hash " << GetLastError()); - goto end; - } - - //Search catalogs for hash - hCatInfo = CryptCATAdminEnumCatalogFromHash(hCatAdmin, pbHash, dwHashLength, 0, &hCatInfo); - while (hCatInfo != NULL) { - CATALOG_INFO ciCatalogInfo = {}; - ciCatalogInfo.cbStruct = sizeof(ciCatalogInfo); - - if (!CryptCATCatalogInfoFromContext(hCatInfo, &ciCatalogInfo, 0)) { - LOG_ERROR("Couldn't get catalog info for catalog containing hash of file"); - break; - } - - LOG_VERBOSE(3, "Hash for file found in catalog " << ciCatalogInfo.wszCatalogFile); - catalogfile = ciCatalogInfo.wszCatalogFile; - hCatInfo = CryptCATAdminEnumCatalogFromHash(hCatAdmin, pbHash, dwHashLength, 0, &hCatInfo); - } - end: - if (hCatInfo != NULL) CryptCATAdminReleaseCatalogContext(&hCatAdmin, &hCatInfo, 0); - if (hCatAdmin != NULL) CryptCATAdminReleaseContext(&hCatAdmin, 0); - if (pbHash != NULL) HeapFree(GetProcessHeap(), 0, pbHash); - return catalogfile; - } - - bool File::GetFileInSystemCatalogs() const { - return GetCatalog(hFile) != std::nullopt; - } - - std::optional File::CalculateHashType(HashType sHashType) const { - if (!bFileExists) { - LOG_ERROR("Can't get hash of " << FilePath << ". File doesn't exist"); - SetLastError(ERROR_FILE_NOT_FOUND); - return std::nullopt; - } - if (!bReadAccess) { - LOG_ERROR("Can't get hash of " << FilePath << ". Insufficient permissions."); - SetLastError(ERROR_ACCESS_DENIED); - return std::nullopt; - } - //Function from Microsoft - //https://docs.microsoft.com/en-us/windows/desktop/SecCrypto/example-c-program--creating-an-md-5-hash-from-file-content - - // Get handle to the crypto provider - HCRYPTPROV hProv{}; - if (!CryptAcquireContext(&hProv, - nullptr, - nullptr, - PROV_RSA_AES, - CRYPT_VERIFYCONTEXT)) { - LOG_ERROR("CryptAcquireContext failed: " << GetLastError() << " while getting hash of " << FilePath); - return std::nullopt; - } - auto provider{ GenericWrapper(hProv, [hProv](auto v) { CryptReleaseContext(hProv, 0); }) }; - - HCRYPTHASH hHash = 0; - auto rgbHash = AllocationWrapper{ nullptr, 0 }; - DWORD cbHash = 0; - if (sHashType == HashType::SHA1_HASH) { - rgbHash = AllocationWrapper{ new BYTE[SHA1LEN], SHA1LEN, AllocationWrapper::CPP_ARRAY_ALLOC }; - cbHash = SHA1LEN; - if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { - LOG_ERROR("CryptCreateHash failed: " << GetLastError() << " while getting hash of " << FilePath); - return std::nullopt; - } - } - else if (sHashType == HashType::SHA256_HASH) { - rgbHash = AllocationWrapper{ new BYTE[SHA256LEN], SHA256LEN, AllocationWrapper::CPP_ARRAY_ALLOC }; - cbHash = SHA256LEN; - if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) { - LOG_ERROR("CryptCreateHash failed: " << GetLastError() << " while getting hash of " << FilePath); - LOG_SYSTEM_ERROR(GetLastError()); - return std::nullopt; - } - } - else { - rgbHash = AllocationWrapper{ new BYTE[MD5LEN], MD5LEN, AllocationWrapper::CPP_ARRAY_ALLOC }; - cbHash = MD5LEN; - if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { - LOG_ERROR("CryptCreateHash failed: " << GetLastError() << " while getting hash of " << FilePath); - return std::nullopt; - } - } - auto HashData{ GenericWrapper(hHash, CryptDestroyHash) }; - - DWORD cbRead{}; - std::wstring digits{ L"0123456789abcdef" }; - std::vector file(BUFSIZE); - bool bResult{ false }; - while((bResult = ReadFile(hFile, file.data(), file.size(), &cbRead, nullptr)) && cbRead){ - if (!CryptHashData(hHash, file.data(), cbRead, 0)) { - LOG_ERROR("CryptHashData failed: " << GetLastError() << " while getting hash of " << FilePath); - return std::nullopt; - } - } - SetFilePointer(0); - - if (!bResult) { - LOG_ERROR("ReadFile failed: " << GetLastError() << " while getting hash of " << FilePath); - return std::nullopt; - } - - std::wstring buffer{}; - std::wstring rgbDigits{ L"0123456789abcdef" }; - if (CryptGetHashParam(hHash, HP_HASHVAL, reinterpret_cast(LPVOID(rgbHash)), &cbHash, 0)) { - for (DWORD i = 0; i < cbHash; i++) { - buffer += rgbDigits[(rgbHash[i] >> 4) & 0xf]; - buffer += rgbDigits[rgbHash[i] & 0xf]; - } - LOG_VERBOSE(3, "Successfully got hash of " << FilePath); - return buffer; - } - else { - LOG_ERROR("CryptGetHashParam failed: " << GetLastError() << " while getting hash of " << FilePath); - } - - return std::nullopt; - } - - - File::File(IN const std::wstring& path) : hFile{ nullptr }{ - if(!path.length()){ - bFileExists = false; - bWriteAccess = false; - bReadAccess = false; - return; - } - FilePath = ExpandEnvStringsW(path); - LOG_VERBOSE(2, "Attempting to open file: " << FilePath << "."); - if(FilePath.at(0) == L'\\'){ - HANDLE hFile{}; - UNICODE_STRING UnicodeName{ - static_cast(FilePath.length() * 2), - static_cast(FilePath.length() * 2), - const_cast(FilePath.c_str()) - }; - OBJECT_ATTRIBUTES attributes{}; - IO_STATUS_BLOCK IoStatus{}; - InitializeObjectAttributes(&attributes, &UnicodeName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr, nullptr); - NTSTATUS Status{ Linker::NtCreateFile(&hFile, GENERIC_READ | GENERIC_WRITE, &attributes, &IoStatus, nullptr, FILE_ATTRIBUTE_NORMAL, - FILE_SHARE_READ, FILE_OPEN, FILE_SEQUENTIAL_ONLY, nullptr, 0) }; - if(NT_SUCCESS(Status)){ - this->hFile = hFile; - bFileExists = true; - bWriteAccess = true; - bReadAccess = true; - } else if(Status == 0xC0000022 || Status == 0xC0000043){ // STATUS_ACCESS_DENIED or STATUS_SHARING_VIOLATION - Status = Linker::NtCreateFile(&hFile, GENERIC_READ, &attributes, &IoStatus, nullptr, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, - FILE_OPEN, FILE_SEQUENTIAL_ONLY, nullptr, 0); - if(NT_SUCCESS(Status)){ - this->hFile = hFile; - bFileExists = true; - bWriteAccess = true; - bReadAccess = false; - } else{ - LOG_ERROR("Unable to create a file handle for file " << FilePath << " (NTSTATUS " << Status << ")"); - bFileExists = true; - bWriteAccess = false; - bReadAccess = false; - } - } else{ - LOG_VERBOSE(2, "Couldn't open file since file doesn't exist (" << FilePath << ")."); - bFileExists = false; - bWriteAccess = false; - bReadAccess = false; - } - } else{ - hFile = CreateFileW(FilePath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, - FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, nullptr); - if(!hFile && GetLastError() == ERROR_FILE_NOT_FOUND){ - LOG_VERBOSE(2, "Couldn't open file, file doesn't exist " << FilePath << "."); - bFileExists = false; - bWriteAccess = false; - bReadAccess = false; - } else if(!hFile && (GetLastError() == ERROR_ACCESS_DENIED || GetLastError() == ERROR_SHARING_VIOLATION)){ - bWriteAccess = false; - hFile = CreateFileW(FilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, - FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, nullptr); - if(!hFile && GetLastError() == ERROR_SHARING_VIOLATION){ - LOG_VERBOSE(2, "Couldn't open file, sharing violation " << FilePath << "."); - bFileExists = true; - bReadAccess = false; - } else if(!hFile && GetLastError() == ERROR_ACCESS_DENIED){ - LOG_VERBOSE(2, "Couldn't open file, Access Denied" << FilePath << "."); - bFileExists = true; - bReadAccess = false; - } else if(GetLastError() != ERROR_SUCCESS){ - LOG_VERBOSE(2, "Couldn't open file " << FilePath << ". (Error " << GetLastError() << ")"); - bFileExists = true; - bReadAccess = false; - } else { - LOG_VERBOSE(2, "File " << FilePath << " opened."); - bFileExists = true; - bReadAccess = true; - } - } else if(ERROR_SUCCESS == GetLastError()){ - LOG_VERBOSE(2, "File " << FilePath << " opened."); - bFileExists = true; - bWriteAccess = true; - bReadAccess = true; - } else{ - LOG_VERBOSE(2, "File " << FilePath << " failed to open with error " << GetLastError()); - bFileExists = false; - bWriteAccess = false; - bReadAccess = false; - } - Attribs.extension = PathFindExtensionW(FilePath.c_str()); - } - } - - - std::wstring File::GetFilePath() const { - return FilePath; - } - - FileAttribs File::GetFileAttribs() const{ - return Attribs; - } - - bool File::GetFileExists() const { - return bFileExists; - } - - bool File::HasWriteAccess() const { - return bWriteAccess; - } - - bool File::HasReadAccess() const { - return bReadAccess; - } - - bool File::Write(IN const LPVOID value, IN const long offset, IN const unsigned long length, __in_opt const bool truncate, __in_opt const bool insert) const { - SCOPE_LOCK(SetFilePointer(0), ResetFilePointer); - LOG_VERBOSE(2, "Writing to file " << FilePath << " at " << offset << ". Insert = " << insert); - - DWORD dwBytesIO{}; - - if(!bFileExists) { - LOG_ERROR("Can't write to file " << FilePath << ". File doesn't exist"); - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - - if (!bWriteAccess) { - LOG_ERROR("Can't write to file " << FilePath << ". Insufficient permissions."); - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - //Insert value into file at specified offset - if(insert && !truncate) { - DWORD64 dwFileSize = GetFileSize(); - for(DWORD64 dwCopyOffset = 0; dwCopyOffset < length; dwCopyOffset += min(1 << 20, length - dwCopyOffset)){ - DWORD dwCopySize = min(1 << 20, length - dwCopyOffset); - AllocationWrapper buffer = { new char[dwCopySize], dwCopySize, AllocationWrapper::CPP_ARRAY_ALLOC }; - if(SetFilePointer(dwFileSize - dwCopyOffset - dwCopySize) == INVALID_SET_FILE_POINTER){ - LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); - return false; - } - if(!ReadFile(hFile, buffer, dwCopySize, &dwBytesIO, nullptr)){ - LOG_ERROR("Unable to read " << FilePath << " at offset " << dwFileSize - dwCopyOffset - dwCopySize << " (Error " << GetLastError() << ")"); - return false; - } - if(SetFilePointer(dwFileSize - dwCopyOffset) == INVALID_SET_FILE_POINTER){ - LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); - return false; - } - if(!WriteFile(hFile, buffer, dwCopySize, &dwBytesIO, nullptr)){ - LOG_ERROR("Unable to write to " << FilePath << " at offset " << dwFileSize - dwCopyOffset << " (Error " << GetLastError() << ")"); - return false; - } - } - } - //Write value over file at specified offset - if(SetFilePointer(offset) == INVALID_SET_FILE_POINTER) { - LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); - return false; - } - - if(!WriteFile(hFile, value, length, &dwBytesIO, nullptr)) { - LOG_ERROR("Failed to write to " << FilePath << " at offset " << offset << " with error " << GetLastError()); - return false; - } - - if(truncate) { - if(!SetEndOfFile(hFile)) { - LOG_ERROR("Couldn't truncate file " << FilePath); - return false; - } - } - LOG_VERBOSE(1, "Successfule wrote to " << FilePath << "at offset" << offset); - return true; - } - - bool File::Read(OUT LPVOID buffer, __in_opt const unsigned long amount, __in_opt const long offset, __out_opt PDWORD amountRead) const { - SCOPE_LOCK(SetFilePointer(0), ResetFilePointer); - LOG_VERBOSE(2, "Attempting to read " << amount << " bytes from " << FilePath << " at offset " << offset); - if(!bFileExists) { - LOG_ERROR("Can't read from " << FilePath << ". File doesn't exist."); - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - - if (!bReadAccess) { - LOG_ERROR("Can't read from " << FilePath << ". Insufficient permissions."); - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - if(SetFilePointer(offset) == INVALID_SET_FILE_POINTER) { - LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); - return false; - } - - DWORD dwBytesRead{}; - if(!amountRead){ - amountRead = &dwBytesRead; - } - - if(!ReadFile(hFile, buffer, amount, amountRead, NULL)) { - LOG_ERROR("Failed to read from " << FilePath << " at offset " << offset << " with error " << GetLastError()); - return false; - } - LOG_VERBOSE(1, "Successfully wrote " << amount << " bytes to " << FilePath); - return true; - } - - AllocationWrapper File::Read(__in_opt unsigned long amount, __in_opt long offset, __out_opt PDWORD amountRead) const { - if(amount == -1){ - amount = GetFileSize(); - } - AllocationWrapper memory = { HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, amount + 1), amount + 1, AllocationWrapper::HEAP_ALLOC }; - bool success = Read(memory, amount, offset, amountRead); - return success ? memory : AllocationWrapper{ nullptr, 0 }; - } - - bool File::MatchesAttributes(IN const FileSearchAttribs& searchAttribs) const { - if (searchAttribs.extensions.size() > 0) { - std::wstring ext = ToLowerCaseW(GetFileAttribs().extension); - if (std::count(searchAttribs.extensions.begin(), searchAttribs.extensions.end(), ext) == 0) { - return false; - } - } - - return true; - } - - bool File::GetFileSigned() const { - if (!bFileExists) { - LOG_ERROR("Can't check file signature for " << FilePath << ". File doesn't exist."); - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - if (!bReadAccess) { - LOG_ERROR("Can't check file signature for " << FilePath << ". Insufficient permissions."); - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - WINTRUST_FILE_INFO FileData{}; - FileData.cbStruct = sizeof(WINTRUST_FILE_INFO); - FileData.pcwszFilePath = FilePath.c_str(); - FileData.hFile = hFile; - FileData.pgKnownSubject = NULL; - - GUID verification = WINTRUST_ACTION_GENERIC_VERIFY_V2; - - WINTRUST_DATA WinTrustData{}; - - WinTrustData.cbStruct = sizeof(WinTrustData); - WinTrustData.pPolicyCallbackData = NULL; - WinTrustData.pSIPClientData = NULL; - WinTrustData.dwUIChoice = WTD_UI_NONE; - WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; - WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; - WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; - WinTrustData.hWVTStateData = NULL; - WinTrustData.pwszURLReference = NULL; - WinTrustData.dwUIContext = 0; - WinTrustData.pFile = &FileData; - - LONG result = WinVerifyTrust((HWND) INVALID_HANDLE_VALUE, &verification, &WinTrustData); - WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE; - WinVerifyTrust(NULL, &verification, &WinTrustData); - if(result == ERROR_SUCCESS){ - LOG_VERBOSE(1, FilePath << " is signed."); - return true; - } - else { - //Verify signature in system catalog - bool bInCatalog = File::GetFileInSystemCatalogs(); - if (bInCatalog) { - LOG_VERBOSE(1, FilePath << " signed in system catalogs."); - return true; - } - } - LOG_VERBOSE(1, FilePath << " not signed or located in system catalogs."); - return false; - } - - std::optional GetCertificateIssuer(const std::wstring& wsFilePath){ - DWORD dwEncoding{}; - DWORD dwContentType{}; - DWORD dwFormatType{}; - GenericWrapper hStore{ nullptr, [](HCERTSTORE store){ CertCloseStore(store, 0); }, INVALID_HANDLE_VALUE }; - GenericWrapper hMsg{ nullptr, CryptMsgClose, INVALID_HANDLE_VALUE }; - auto status{ CryptQueryObject(CERT_QUERY_OBJECT_FILE, wsFilePath.c_str(), CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, - CERT_QUERY_FORMAT_FLAG_BINARY, 0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, nullptr) }; - if(!status){ - LOG_ERROR("Failed to query signature for " << wsFilePath << ": " << SYSTEM_ERROR); - return std::nullopt; - } - - DWORD dwSignerInfoSize{}; - status = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr, &dwSignerInfoSize); - if(!status){ - LOG_ERROR("Failed to query signer information size for " << wsFilePath << ": " << SYSTEM_ERROR); - return std::nullopt; - } - - std::vector info(dwSignerInfoSize); - status = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, info.data(), &dwSignerInfoSize); - if(!status){ - LOG_ERROR("Failed to query signer information for " << wsFilePath << ": " << SYSTEM_ERROR); - return std::nullopt; - } - - auto signer{ reinterpret_cast(info.data())->Issuer }; - DWORD dwSize = CertNameToStrW(X509_ASN_ENCODING, &signer, CERT_SIMPLE_NAME_STR, nullptr, 0); - - std::vector buffer(dwSize); - CertNameToStrW(X509_ASN_ENCODING, &signer, CERT_SIMPLE_NAME_STR, buffer.data(), dwSize); - - return std::wstring{ buffer.data(), dwSize }; - } - - bool File::IsMicrosoftSigned() const{ - if(!bFileExists){ - LOG_ERROR("Can't check file signature for " << FilePath << ". File doesn't exist."); - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - if(!bReadAccess){ - LOG_ERROR("Can't check file signature for " << FilePath << ". Insufficient permissions."); - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - if(!GetFileSigned()){ - return false; - } - - if(File::GetFileInSystemCatalogs()){ - auto catalog{ GetCatalog(hFile) }; - if(catalog){ - auto signer{ GetCertificateIssuer(*catalog) }; - if(signer){ - return ToLowerCaseW(*signer).find(L"microsoft") != std::wstring::npos; - } else{ - LOG_ERROR("Unable to get the certificate issuer for " << *catalog << ": " << SYSTEM_ERROR); - } - } else{ - LOG_ERROR("Unable to get the catalog for " << FilePath << ": " << SYSTEM_ERROR); - } - } else{ - auto signer{ GetCertificateIssuer(FilePath) }; - if(signer){ - return ToLowerCaseW(*signer).find(L"microsoft") != std::wstring::npos; - } else{ - LOG_ERROR("Unable to get the certificate issuer for " << FilePath << ": " << SYSTEM_ERROR); - } - } - return false; - } - - std::optional File::GetMD5Hash() const { - LOG_VERBOSE(3, "Attempting to get MD5 hash of " << FilePath); - return CalculateHashType(HashType::MD5_HASH); - } - - std::optional File::GetSHA1Hash() const { - LOG_VERBOSE(3, "Attempting to get SHA1 hash of " << FilePath); - return CalculateHashType(HashType::SHA1_HASH); - } - - std::optional File::GetSHA256Hash() const { - LOG_VERBOSE(3, "Attempting to get SHA256 hash of " << FilePath); - return CalculateHashType(HashType::SHA256_HASH); - } - - bool File::Create() { - LOG_VERBOSE(1, "Attempting to create file: " << FilePath); - if(bFileExists) { - LOG_ERROR("Can't create " << FilePath << ". File already exists."); - return false; - } - hFile = CreateFileW(FilePath.c_str(), - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, - NULL, - CREATE_NEW, - FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, - NULL); - if(INVALID_HANDLE_VALUE == hFile) - { - DWORD dwStatus = GetLastError(); - LOG_ERROR("Error creating file " << FilePath << ". Error code = " << dwStatus); - bFileExists = false; - return false; - } - LOG_VERBOSE(1, FilePath << " successfully created."); - bFileExists = true; - bReadAccess = true; - bWriteAccess = true; - return true; - } - - bool File::Delete() { - LOG_VERBOSE(1, "Attempting to delete file " << FilePath); - if(!bFileExists) { - LOG_ERROR("Can't delete file " << FilePath << ". File doesn't exist"); - return false; - } - CloseHandle(hFile); - if(!DeleteFileW(FilePath.c_str())) { - DWORD dwStatus = GetLastError(); - LOG_ERROR("Deleting file " << FilePath << " failed with error " << dwStatus); - hFile = CreateFileW(FilePath.c_str(), - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - CREATE_NEW, - FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, - NULL); - if(INVALID_HANDLE_VALUE == hFile) - { - DWORD dwStatus = GetLastError(); - LOG_ERROR("Couldn't reopen " << FilePath << ". Error = " << dwStatus); - bFileExists = false; - return false; - } - bFileExists = true; - return false; - } - LOG_VERBOSE(1, FilePath << "deleted."); - bFileExists = false; - return true; - } - - bool FileSystem::File::ChangeFileLength(IN const long length) const { - SCOPE_LOCK(SetFilePointer(0), ResetFilePointer); - LOG_VERBOSE(2, "Attempting to change length of " << FilePath << " to " << length); - - if(!SetFilePointer(length)) { - LOG_ERROR("Couldn't change file pointer to " << length << " in file " << FilePath); - return false; - } - - if(!SetEndOfFile(hFile)) { - LOG_ERROR("Couldn't change the length of file " << FilePath); - return false; - } - LOG_VERBOSE(2, "Changed length of " << FilePath << " to " << length); - return true; - } - - DWORD64 File::GetFileSize() const { - DWORD high = {}; - auto size = ::GetFileSize(hFile, &high); - return (static_cast(high) << 32) + size; - } - - std::wstring File::ToString() const { - return FilePath; - } - - std::optional File::GetFileOwner() const { - if (!bFileExists) { - LOG_ERROR("Can't get owner of nonexistent file " << FilePath); - return std::nullopt; - } - PSID psOwnerSID = NULL; - PISECURITY_DESCRIPTOR pDesc = NULL; - if (GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &psOwnerSID, nullptr, nullptr, nullptr, reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { - LOG_ERROR("Error getting file owner for file " << FilePath << ". Error: " << GetLastError()); - return std::nullopt; - } - pDesc->Owner = psOwnerSID; - - Permissions::SecurityDescriptor secDesc(pDesc); - return Permissions::Owner(secDesc); - } - - bool File::SetFileOwner(const Permissions::Owner& owner) { - if (!bFileExists) { - LOG_ERROR("Can't set owner of nonexistent file " << FilePath); - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - if (!this->bWriteAccess) { - LOG_ERROR("Can't write owner of file " << FilePath << ". Lack permissions"); - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - if (SetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, owner.GetSID(), nullptr, nullptr, nullptr) != ERROR_SUCCESS) { - LOG_ERROR("Error setting the file owner for file " << FilePath << " to " << owner << ". Error: " << GetLastError()); - return false; - } - LOG_VERBOSE(3, "Set the owner for file " << FilePath << " to " << owner << "."); - return true; - } - - ACCESS_MASK File::GetAccessPermissions(const Permissions::Owner& owner) { - if (!bFileExists) { - LOG_ERROR("Can't get permissions of nonexistent file " << FilePath); - SetLastError(ERROR_FILE_NOT_FOUND); - return 0; - } - PACL paDACL = NULL; - PISECURITY_DESCRIPTOR pDesc = NULL; - if (GetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &paDACL, nullptr, reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { - LOG_ERROR("Error getting permissions on file " << FilePath << " for owner " << owner << ". Error: " << GetLastError()); - return 0; - } - - //Correct positional memory of the ACL is weird and doesn't naturally work with the SecurityDescriptor class. - //This gets the right data to the right place - Permissions::SecurityDescriptor secDesc = Permissions::SecurityDescriptor::CreateDACL(paDACL->AclSize); - memcpy(secDesc.GetDACL(), paDACL, paDACL->AclSize); - LocalFree(pDesc); - return Permissions::GetOwnerRightsFromACL(owner, secDesc); - } - - ACCESS_MASK File::GetEveryonePermissions() { - Permissions::Owner everyone(L"Everyone"); - return this->GetAccessPermissions(everyone); - } - - bool File::TakeOwnership() { - std::optional BluespawnOwner = Permissions::GetProcessOwner(); - if (BluespawnOwner == std::nullopt) { - return false; - } - return this->SetFileOwner(*BluespawnOwner); - } - - bool File::GrantPermissions(const Permissions::Owner& owner, const ACCESS_MASK& amAccess) { - return Permissions::UpdateObjectACL(FilePath, SE_FILE_OBJECT, owner, amAccess); - } - - bool File::DenyPermissions(const Permissions::Owner& owner, const ACCESS_MASK& amAccess) { - return Permissions::UpdateObjectACL(FilePath, SE_FILE_OBJECT, owner, amAccess, true); - } - - bool File::Quarantine() { - if (!bFileExists) { - LOG_ERROR("Can't quarantine file " << FilePath << ". File doesn't exist"); - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - ACCESS_MASK amEveryoneDeniedAccess{ 0 }; - Permissions::AccessAddAll(amEveryoneDeniedAccess); - return DenyPermissions(Permissions::Owner(L"Everyone"), amEveryoneDeniedAccess); - } - - std::optional File::GetCreationTime() const { - if (!bFileExists) { - LOG_ERROR("Can't get creation time of " << FilePath << ", file doesn't exist"); - SetLastError(ERROR_FILE_NOT_FOUND); - return std::nullopt; - } - FILETIME fReturnInfo; - if (GetFileTime(hFile, &fReturnInfo, nullptr, nullptr)) { - return fReturnInfo; - } - else { - LOG_ERROR("Error getting creation time of " << FilePath << ". (Error: " << GetLastError() << ")"); - return std::nullopt; - } - } - - std::optional File::GetModifiedTime() const { - if (!bFileExists) { - LOG_ERROR("Can't get last modified time of " << FilePath << ", file doesn't exist"); - SetLastError(ERROR_FILE_NOT_FOUND); - return std::nullopt; - } - FILETIME fReturnInfo; - if (GetFileTime(hFile, nullptr, nullptr, &fReturnInfo)) { - return fReturnInfo; - } - else { - LOG_ERROR("Error getting last modified time of " << FilePath << ". (Error: " << GetLastError() << ")"); - return std::nullopt; - } - } - - std::optional File::GetAccessTime() const { - if (!bFileExists) { - LOG_ERROR("Can't get last access time of " << FilePath << ", file doesn't exist"); - SetLastError(ERROR_FILE_NOT_FOUND); - return std::nullopt; - } - FILETIME fReturnInfo; - if (GetFileTime(hFile, nullptr, &fReturnInfo, nullptr)) { - return fReturnInfo; - } - else { - LOG_ERROR("Error getting last access time of " << FilePath << ". (Error: " << GetLastError() << ")"); - return std::nullopt; - } - } - - Folder::Folder(const std::wstring& path) : hCurFile{ nullptr } { - FolderPath = ExpandEnvStringsW(path); - std::wstring searchName = FolderPath; - searchName += L"\\*"; - bFolderExists = true; - auto f = FindFirstFileW(searchName.c_str(), &ffd); - hCurFile = { f }; - if(hCurFile == INVALID_HANDLE_VALUE) { - LOG_ERROR("Couldn't open folder " << FolderPath); - bFolderExists = false; - } - if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - bIsFile = false; - } else { - bIsFile = true; - } - } - - std::wstring Folder::GetFolderPath() const { - return FolderPath; - } - - bool Folder::MoveToNextFile() { - if(FindNextFileW(hCurFile, &ffd) != 0) { - if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - bIsFile = false; - } else { - bIsFile = true; - } - return true; - } - return false; - } - - bool Folder::MoveToBeginning() { - std::wstring searchName = FolderPath; - searchName += L"\\*"; - hCurFile = FindFirstFileW(searchName.c_str(), &ffd); - if(hCurFile == INVALID_HANDLE_VALUE) { - LOG_ERROR("Couldn't open folder " << FolderPath); - return false; - } - if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - bIsFile = false; - } else { - bIsFile = true; - } - return true; - } - - bool Folder::GetFolderExists() const { - return bFolderExists; - } - - bool Folder::GetCurIsFile() const { - return bIsFile; - } - - std::optional Folder::Open() const { - if(bIsFile) { - std::wstring fileName(ffd.cFileName); - std::wstring filePath(FolderPath); - filePath += std::wstring(L"\\") + fileName; - std::wstring out = filePath.c_str(); - File file = FileSystem::File(out); - if(file.GetFileExists()) { - return file; - } - } - return std::nullopt; - } - - std::optional Folder::EnterDir() { - if(!bIsFile) { - std::wstring folderName = FolderPath; - folderName += L"\\"; - folderName += ffd.cFileName; - Folder folder = Folder(folderName.c_str()); - if(folder.GetFolderExists()) return folder; - } - return std::nullopt; - } - - std::optional Folder::AddFile(IN const std::wstring& fileName) const { - std::wstring filePath = FolderPath; - std::wstring fName = fileName; - filePath += L"\\" + fName; - File file = File(filePath.c_str()); - if(file.GetFileExists()) { - return file; - } - if(file.Create()) { - return file; - } - return std::nullopt; - } - - bool FileSystem::Folder::RemoveFile() const { - if(GetCurIsFile()) { - std::optional f = Open(); - if(f && f->GetFileExists()) { - if(f->Delete()){ - return true; - } - } - } - return false; - } - - std::vector Folder::GetFiles(__in_opt std::optional attribs, __in_opt int recurDepth) { - if(MoveToBeginning() == 0) { - LOG_ERROR("Couldn't get to beginning of folder " << FolderPath); - return std::vector(); - } - std::vector toRet = std::vector(); - do { - if(GetCurIsFile()) { - std::optional f = Open(); - if(f) { - if(!attribs) { - toRet.emplace_back(*f); - } - else { - if (f->MatchesAttributes(attribs.value())) { - toRet.emplace_back(*f); - } - } - } - } else if(recurDepth != 0 && ffd.cFileName != std::wstring{ L"." } && ffd.cFileName != std::wstring{ L".." }){ - std::vector temp; - std::optional f = EnterDir(); - if(f) { - if(recurDepth == -1) { - temp = f->GetFiles(attribs, recurDepth); - } else { - temp = f->GetFiles(attribs, recurDepth - 1); - } - while(!temp.empty()) { - File file = temp.at(temp.size() - 1); - temp.pop_back(); - toRet.emplace_back(file); - } - } - } - } while(MoveToNextFile()); - return toRet; - } - - std::vector Folder::GetSubdirectories(__in_opt int recurDepth) { - if(MoveToBeginning() == 0) { - LOG_ERROR("Couldn't get to beginning of folder " << FolderPath); - return {}; - } - std::vector toRet = {}; - do { - if(!GetCurIsFile() && recurDepth != 0 && ffd.cFileName != std::wstring{ L"." } && ffd.cFileName != std::wstring{ L".." }){ - std::vector temp; - std::optional f = EnterDir(); - if(f.has_value()) { - toRet.emplace_back(f.value()); - if(recurDepth == -1) { - temp = f->GetSubdirectories(recurDepth); - } else { - temp = f->GetSubdirectories(recurDepth - 1); - } - while(!temp.empty()) { - auto folder = temp.at(temp.size() - 1); - temp.pop_back(); - toRet.emplace_back(folder); - } - } - } - } while(MoveToNextFile()); - return toRet; - } - - std::optional Folder::GetFolderOwner() const { - if (!bFolderExists) { - LOG_ERROR("Can't get owner of nonexistent folder " << FolderPath); - return std::nullopt; - } - PSID psOwnerSID = NULL; - PISECURITY_DESCRIPTOR pDesc = NULL; - if (GetNamedSecurityInfoW((LPWSTR) FolderPath.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &psOwnerSID, nullptr, nullptr, nullptr, reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { - LOG_ERROR("Error getting file owner for folder " << FolderPath << ". Error: " << GetLastError()); - return std::nullopt; - } - pDesc->Owner = psOwnerSID; - - Permissions::SecurityDescriptor secDesc(pDesc); - return Permissions::Owner(secDesc); - } - - bool Folder::SetFolderOwner(const Permissions::Owner& owner) { - if (!bFolderExists) { - LOG_ERROR("Can't write owner of folder " << FolderPath << ". Folder doesn't exist"); - return false; - } - if (SetNamedSecurityInfoW((LPWSTR)FolderPath.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, owner.GetSID(), nullptr, nullptr, nullptr) != ERROR_SUCCESS) { - LOG_ERROR("Error setting the folder owner for folder " << FolderPath << " to " << owner << ". Error: " << GetLastError()); - return false; - } - LOG_VERBOSE(3, "Set the owner for folder " << FolderPath << " to " << owner << "."); - return true; - } - - ACCESS_MASK Folder::GetAccessPermissions(const Permissions::Owner& owner) { - if (!bFolderExists) { - LOG_ERROR("Can't get permissions of nonexistent folder " << FolderPath); - return 0; - } - PACL paDACL = NULL; - PISECURITY_DESCRIPTOR pDesc = NULL; - if (GetNamedSecurityInfoW((LPWSTR) FolderPath.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &paDACL, nullptr, reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { - LOG_ERROR("Error getting permissions on file " << FolderPath << " for owner " << owner << ". Error: " << GetLastError()); - return 0; - } - //Correct positional memory of the ACL is weird and doesn't naturally work with the SecurityDescriptor class. - //This gets the right data to the right place - Permissions::SecurityDescriptor secDesc = Permissions::SecurityDescriptor::CreateDACL(paDACL->AclSize); - memcpy(secDesc.GetDACL(), paDACL, paDACL->AclSize); - LocalFree(pDesc); - return Permissions::GetOwnerRightsFromACL(owner, secDesc); - } - - ACCESS_MASK Folder::GetEveryonePermissions() { - Permissions::Owner everyone(L"Everyone"); - return this->GetAccessPermissions(everyone); - } - - bool Folder::TakeOwnership() { - std::optional BluespawnOwner = Permissions::GetProcessOwner(); - if (BluespawnOwner == std::nullopt) { - return false; - } - return this->SetFolderOwner(*BluespawnOwner); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/filesystem/YaraScanner.cpp b/BLUESPAWN-client/src/util/filesystem/YaraScanner.cpp deleted file mode 100644 index ef787e20..00000000 --- a/BLUESPAWN-client/src/util/filesystem/YaraScanner.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "util/filesystem/YaraScanner.h" -#include "../resources/resource.h" -#include "common/wrappers.hpp" -#include "util/log/Log.h" - -#include - -const YaraScanner YaraScanner::instance{}; - -AllocationWrapper GetResourceRule(DWORD identifier){ - auto hRsrcInfo = FindResourceW(nullptr, MAKEINTRESOURCE(identifier), L"yararule"); - if(!hRsrcInfo){ - return { nullptr, 0 }; - } - - auto hRsrc = LoadResource(nullptr, hRsrcInfo); - if(!hRsrc){ - return { nullptr, 0 }; - } - - zip_error_t err{}; - auto lpZipSource = zip_source_buffer_create(LockResource(hRsrc), SizeofResource(nullptr, hRsrcInfo), 0, &err); - if(lpZipSource){ - auto zip = zip_open_from_source(lpZipSource, 0, &err); - if(zip){ - auto fdRules = zip_fopen(zip, "data", 0); - if(fdRules){ - zip_stat_t stats{}; - if(-1 != zip_stat(zip, "data", ZIP_STAT_SIZE, &stats)){ - - if(-1 != stats.size){ - AllocationWrapper data{ HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, static_cast(stats.size)), static_cast(stats.size), AllocationWrapper::HEAP_ALLOC }; - if(-1 != zip_fread(fdRules, data, stats.size)){ - zip_fclose(fdRules); - zip_close(zip); - zip_source_close(lpZipSource); - - return data; - } - } - } - - zip_fclose(fdRules); - } - zip_close(zip); - } - zip_source_close(lpZipSource); - } - - return { nullptr, 0 }; -} - -struct AllocationWrapperStream { - AllocationWrapper wrapper; - size_t offset; -}; - -size_t ReadAllocationWrapper(LPVOID dest, size_t size, size_t count, AllocationWrapperStream* data){ - size_t desired_amnt = size * count; - size_t actual_amnt = min(desired_amnt, data->wrapper.GetSize() - data->offset); - - CopyMemory(dest, reinterpret_cast((LPVOID) data->wrapper) + data->offset, actual_amnt); - - data->offset += actual_amnt; - return actual_amnt / size; -} - -YR_RULES* LoadRules(const AllocationWrapper& memory){ - AllocationWrapperStream stream_data = { memory, 0 }; - YR_STREAM stream = { - &stream_data, - YR_STREAM_READ_FUNC(ReadAllocationWrapper) - }; - YR_RULES* rules; - auto status = yr_rules_load_stream(&stream, &rules); - if(status != ERROR_SUCCESS){ - return nullptr; - } - return rules; -} - -YaraScanner::YaraScanner() : - status{ YaraStatus::Success }{ - yr_initialize(); - - auto hSevereYara = GetResourceRule(YaraSevere); - if(!hSevereYara){ - status = YaraStatus::RulesMissing; - return; - } - KnownBad = LoadRules(hSevereYara); - if(!KnownBad){ - status = YaraStatus::RulesMissing; - return; - } - - auto hSevereYara2 = GetResourceRule(YaraSevere2); - if(!hSevereYara2){ - status = YaraStatus::RulesMissing; - return; - } - KnownBad2 = LoadRules(hSevereYara2); - if(!KnownBad2){ - status = YaraStatus::RulesMissing; - return; - } - - auto hIndicatorsYara = GetResourceRule(YaraIndicators); - if(!hIndicatorsYara){ - status = YaraStatus::RulesInvalid; - return; - } - Indicators = LoadRules(hIndicatorsYara); - if(!Indicators){ - status = YaraStatus::RulesInvalid; - return; - } -} - -YaraScanner::~YaraScanner(){ - if(KnownBad){ - yr_rules_destroy(KnownBad); - KnownBad = nullptr; - } - - if(KnownBad2){ - yr_rules_destroy(KnownBad2); - KnownBad2 = nullptr; - } - - if(Indicators){ - yr_rules_destroy(Indicators); - Indicators = nullptr; - } - yr_finalize(); -} - -const YaraScanner& YaraScanner::GetInstance(){ - return instance; -} - -struct YaraScanArg { - YaraScanResult result; - enum { - Severe, - Indicator - } type; -}; - -int YaraCallbackFunction(int message, LPVOID lpMessageData, YaraScanArg* arg){ - if(message == CALLBACK_MSG_RULE_MATCHING){ - auto rule = reinterpret_cast(lpMessageData); - if(arg->type == arg->Severe){ - arg->result.AddBadRule(rule->identifier); - } else if(arg->type == arg->Indicator){ - arg->result.AddIndicatorRule(rule->identifier); - } - } - return CALLBACK_CONTINUE; -} - -YaraScanResult YaraScanner::ScanFile(const FileSystem::File& file) const { - if(status != YaraStatus::Success){ - YaraScanResult res = {}; - res.status = status; - return res; - } - - YaraScanArg arg = {}; - arg.result.status = YaraStatus::Success; - auto memory = file.Read(); - if(!memory){ - arg.result.status = YaraStatus::Failure; - return arg.result; - } - - arg.type = arg.Severe; - auto status = yr_rules_scan_mem(KnownBad, reinterpret_cast((LPVOID) memory), memory.GetSize(), 0, YR_CALLBACK_FUNC(YaraCallbackFunction), &arg, 0); - if(status != ERROR_SUCCESS){ - arg.result.status = YaraStatus::Failure; - } - - arg.type = arg.Severe; - status = yr_rules_scan_mem(KnownBad2, reinterpret_cast((LPVOID) memory), memory.GetSize(), 0, YR_CALLBACK_FUNC(YaraCallbackFunction), &arg, 0); - if(status != ERROR_SUCCESS){ - arg.result.status = YaraStatus::Failure; - } - - arg.type = arg.Indicator; - status = yr_rules_scan_mem(Indicators, reinterpret_cast((LPVOID) memory), memory.GetSize(), 0, YR_CALLBACK_FUNC(YaraCallbackFunction), &arg, 0); - if(status != ERROR_SUCCESS){ - arg.result.status = YaraStatus::Failure; - } - - for (auto identifier : arg.result.vKnownBadRules) { - LOG_INFO(file.GetFilePath() << L" matches known malicious identifier " << StringToWidestring(identifier)); - } - for (auto identifier : arg.result.vIndicatorRules) { - LOG_INFO(file.GetFilePath() << L" matches known indicator identifier " << StringToWidestring(identifier)); - } - - return arg.result; -} - -void YaraScanResult::AddBadRule(const char* identifier){ - vKnownBadRules.emplace_back(identifier); -} - -void YaraScanResult::AddIndicatorRule(const char* identifier){ - vIndicatorRules.emplace_back(identifier); -} - -YaraScanResult::operator bool(){ - return vKnownBadRules.empty() && status == YaraStatus::Success; -} - -bool YaraScanResult::operator!(){ - return !(vKnownBadRules.empty() && status == YaraStatus::Success); -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/CLISink.cpp b/BLUESPAWN-client/src/util/log/CLISink.cpp deleted file mode 100644 index ab623114..00000000 --- a/BLUESPAWN-client/src/util/log/CLISink.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include - -#include - -#include "util/log/CLISink.h" - -namespace Log { - - void CLISink::SetConsoleColor(CLISink::MessageColor color){ - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, static_cast(color)); - } - - CLISink::CLISink() : hMutex{ CreateMutexW(nullptr, false, L"Local\\CLI-Mutex") } {} - - void CLISink::LogMessage(const LogLevel& level, const std::string& message, const std::optional info, const std::vector>& detections){ - auto mutex = AcquireMutex(hMutex); - if(level.Enabled()){ - SetConsoleColor(CLISink::PrependColors[static_cast(level.severity)]); - - if(level.severity == Severity::LogHunt){ - std::wstring aggressiveness = info->HuntAggressiveness == Aggressiveness::Intensive ? L"Intensive" : - info->HuntAggressiveness == Aggressiveness::Normal ? L"Normal" : L"Cursory"; - std::wcout << L"[" << info->HuntName << L": " << aggressiveness << L"] "; - SetConsoleColor(CLISink::MessageColor::LIGHTGREY); - std::wcout << L" - " << detections.size() << " detection" << (detections.size() == 1 ? L"" : L"s") << L"!" << std::endl; - for(auto detection : detections){ - if(detection->Type == DetectionType::File){ - auto lpFileDetection = std::static_pointer_cast(detection); - std::wcout << L"\tPotentially malicious file detected - " << lpFileDetection->wsFilePath << L" (MD5 is " << lpFileDetection->md5 << L")" << std::endl; - } else if(detection->Type == DetectionType::Process){ - auto lpProcessDetection = std::static_pointer_cast(detection); - std::wcout << L"\tPotentially malicious process detected - " << lpProcessDetection->wsImagePath << L" (PID is " << lpProcessDetection->PID << L")" << std::endl; - } else if(detection->Type == DetectionType::Service){ - auto lpServiceDetection = std::static_pointer_cast(detection); - std::wcout << L"\tPotentially malicious service detected - " << lpServiceDetection->wsServiceName << L" (PID is " << lpServiceDetection->ServicePID << L")" << std::endl; - } else if(detection->Type == DetectionType::Registry){ - auto lpRegistryDetection = std::static_pointer_cast(detection); - std::wcout << L"\tPotentially malicious registry key detected - " << lpRegistryDetection->value.key.ToString() << L": " << lpRegistryDetection->value.GetPrintableName() << L" with data " << lpRegistryDetection->value.ToString() << std::endl; - } else if (detection->Type == DetectionType::Event) { - auto lpEvtDet = std::static_pointer_cast(detection); - std::wcout << L"\tPotentially malicious event detected:" << std::endl; - std::wcout << "\t\tChannel: " << lpEvtDet->channel << std::endl; - std::wcout << "\t\tEvent ID: " << lpEvtDet->eventID << std::endl; - std::wcout << "\t\tEvent Record ID: " << lpEvtDet->eventRecordID << std::endl; - std::wcout << "\t\tTime Created: " << lpEvtDet->timeCreated << std::endl; - for (auto iter = lpEvtDet->params.begin(); iter != lpEvtDet->params.end(); ++iter) { - std::wcout << "\t\t" << iter->first << ": " << iter->second << std::endl; - } - - } else { - std::wcout << L"\tUnknown detection type!" << std::endl; - } - } - if(message.size() > 0){ - std::cout << "\tAssociated Message: " << message << std::endl; - } - } else { - std::cout << CLISink::MessagePrepends[static_cast(level.severity)] << " "; - SetConsoleColor(CLISink::MessageColor::LIGHTGREY); - std::cout << message << std::endl; - } - } - } - - bool CLISink::operator==(const LogSink& sink) const { - return (bool) dynamic_cast(&sink); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/DebugSink.cpp b/BLUESPAWN-client/src/util/log/DebugSink.cpp deleted file mode 100644 index 71607c9d..00000000 --- a/BLUESPAWN-client/src/util/log/DebugSink.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include - -#include - -#include "util/log/DebugSink.h" - -namespace Log { - void DebugSink::LogMessage(const LogLevel& level, const std::string& message, const std::optional info, - const std::vector>& detections){ - if(level.Enabled()){ - if(level.severity == Severity::LogHunt){ - std::wstring aggressiveness = info->HuntAggressiveness == Aggressiveness::Intensive ? L"Intensive" : - info->HuntAggressiveness == Aggressiveness::Normal ? L"Normal" : L"Cursory"; - std::wstring sLogHeader = L"[" + info->HuntName + L": " + aggressiveness + L"] - "; - OutputDebugStringW((sLogHeader + std::to_wstring(detections.size()) + L" detection" + (detections.size() == 1 ? L"!" : L"s!")).c_str()); - for(auto detection : detections){ - if(detection->Type == DetectionType::File){ - auto lpFileDetection = std::static_pointer_cast(detection); - OutputDebugStringW((sLogHeader + L"\tPotentially malicious file detected - " + lpFileDetection->wsFilePath).c_str()); - } else if(detection->Type == DetectionType::Process){ - auto lpProcessDetection = std::static_pointer_cast(detection); - OutputDebugStringW((sLogHeader + L"\tPotentially malicious process detected - " + lpProcessDetection->wsCmdline + L" (PID is " + std::to_wstring(lpProcessDetection->PID) + L")").c_str()); - } else if(detection->Type == DetectionType::Service){ - auto lpServiceDetection = std::static_pointer_cast(detection); - OutputDebugStringW((sLogHeader + L"\tPotentially malicious service detected - " + lpServiceDetection->wsServiceName + L" (PID is " + std::to_wstring(lpServiceDetection->ServicePID) + L")").c_str()); - } else if(detection->Type == DetectionType::Registry){ - auto lpRegistryDetection = std::static_pointer_cast(detection); - OutputDebugStringW((sLogHeader + L"\tPotentially malicious registry key detected - " + lpRegistryDetection->value.key.ToString() + L": " + lpRegistryDetection->value.GetPrintableName() + L" with value " + - lpRegistryDetection->value.ToString()).c_str()); - } else { - OutputDebugStringW((sLogHeader + L"\tUnknown detection type!").c_str()); - } - } - if(message.size() > 0){ - LPCSTR lpwMessage = message.c_str(); - LPWSTR lpMessage = new WCHAR[message.length() + 1]{}; - MultiByteToWideChar(CP_ACP, 0, lpwMessage, static_cast(message.length()), lpMessage, static_cast(message.length())); - - OutputDebugStringW((sLogHeader + L"\tAssociated Message: " + lpMessage).c_str()); - } - } else { - OutputDebugStringA((DebugSink::MessagePrepends[static_cast(level.severity)] + " " + message).c_str()); - } - } - } - - bool DebugSink::operator==(const LogSink& sink) const { - return (bool) dynamic_cast(&sink); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/HuntLogMessage.cpp b/BLUESPAWN-client/src/util/log/HuntLogMessage.cpp deleted file mode 100644 index 3cd67275..00000000 --- a/BLUESPAWN-client/src/util/log/HuntLogMessage.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "util/log/HuntLogMessage.h" - -#include "util/log/LogLevel.h" - -namespace Log { - - std::vector> _LogHuntSinks{}; - - HuntLogMessage::HuntLogMessage(const HuntInfo& Hunt, const std::vector>& sinks) : - LogMessage(sinks, LogLevel::LogHunt), - HuntName{ Hunt }, - Detections{}{} - - HuntLogMessage::HuntLogMessage(const HuntInfo& Hunt, const std::shared_ptr& sink) : - LogMessage(sink, LogLevel::LogHunt), - HuntName{ Hunt }, - Detections{}{} - - void HuntLogMessage::AddDetection(std::shared_ptr detection){ - this->Detections.emplace_back(detection); - } - - LogMessage& HuntLogMessage::operator<<(const LogTerminator& terminator){ - std::string message = InternalStream.str(); - - InternalStream.str(std::string{}); - for(int idx = 0; idx < Sinks.size(); idx++){ - Sinks[idx]->LogMessage(Level, message, HuntName, Detections); - } - - Detections = {}; - - return *this; - } - - bool AddHuntSink(const std::shared_ptr& sink){ - for(int idx = 0; idx < _LogHuntSinks.size(); idx++){ - if(*_LogHuntSinks[idx] == *sink){ - return false; - } - } - - _LogHuntSinks.emplace_back(sink); - return true; - } - - bool RemoveHuntSink(const std::shared_ptr& sink){ - for(int idx = 0; idx < _LogHuntSinks.size(); idx++){ - if(*_LogHuntSinks[idx] == *sink){ - _LogHuntSinks.erase(_LogHuntSinks.begin() + idx); - return true; - } - } - - return false; - } - - HuntLogMessage HuntLogMessage::operator =(const HuntLogMessage& message){ - this->HuntName = message.HuntName; - this->InternalStream << message.InternalStream.str(); - this->Sinks = message.Sinks; - this->Detections = message.Detections; - - return *this; - } - - HuntLogMessage::HuntLogMessage(const HuntLogMessage& message) : - LogMessage{ message.Sinks, message.Level }, - HuntName{ message.HuntName }{ - this->InternalStream << message.InternalStream.str(); - this->HuntName = message.HuntName; - this->Detections = message.Detections; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/Log.cpp b/BLUESPAWN-client/src/util/log/Log.cpp deleted file mode 100644 index 761700ad..00000000 --- a/BLUESPAWN-client/src/util/log/Log.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "util/log/Log.h" -#include - -namespace Log { - std::vector> _LogCurrentSinks; - LogTerminator endlog{}; - - LogMessage& LogMessage::operator<<(const std::wstring& message){ - LPCWSTR lpwMessage = message.c_str(); - LPSTR lpMessage = new CHAR[message.length() + 1]{}; - WideCharToMultiByte(CP_ACP, 0, lpwMessage, static_cast(message.length()), lpMessage, static_cast(message.length()), 0, nullptr); - - InternalStream << std::string(lpMessage); - return *this; - } - LogMessage& LogMessage::operator<<(PCWSTR pointer){ - return operator<<(std::wstring(pointer)); - } - LogMessage& LogMessage::operator<<(const LogTerminator& terminator){ - std::string message = InternalStream.str(); - - InternalStream = std::stringstream(); - for(int idx = 0; idx < Sinks.size(); idx++){ - Sinks[idx]->LogMessage(Level, message); - } - return *this; - } - - LogMessage::LogMessage(const std::shared_ptr& Sink, LogLevel Level) : Level{ Level } { - Sinks.emplace_back(Sink); - } - LogMessage::LogMessage(std::vector> Sinks, LogLevel Level) : LogMessage(Sinks, Level, std::stringstream{}) {} - LogMessage::LogMessage(std::vector> Sinks, LogLevel Level, std::stringstream Stream) : Level{ Level } { - this->Sinks = Sinks; - std::string StreamContents = Stream.str(); - InternalStream << StreamContents; - } - - bool AddSink(const std::shared_ptr& Sink){ - for(int idx = 0; idx < _LogCurrentSinks.size(); idx++){ - if(*_LogCurrentSinks[idx] == *Sink){ - return false; - } - } - - _LogCurrentSinks.emplace_back(Sink); - return true; - } - - bool RemoveSink(const std::shared_ptr& Sink){ - for(int idx = 0; idx < _LogCurrentSinks.size(); idx++){ - if(*_LogCurrentSinks[idx] == *Sink){ - _LogCurrentSinks.erase(_LogCurrentSinks.begin() + idx); - return true; - } - } - - return false; - } - - std::wstring FormatErrorMessage(DWORD dwErrorCode) { - //https://stackoverflow.com/a/45565001/3302799 - LPWSTR psz{ nullptr }; - auto cchMsg = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS - | FORMAT_MESSAGE_ALLOCATE_BUFFER, - nullptr, - dwErrorCode, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&psz), - 0, - nullptr); - if (cchMsg) { - auto delfunc{ [](void* p) { ::LocalFree(p); } }; - std::unique_ptr ptrBuffer(psz, delfunc); - return std::wstring(ptrBuffer.get(), cchMsg); - } - else { - auto error_code{ ::GetLastError() }; - return L"Unable to format error message!"; - } - } -} diff --git a/BLUESPAWN-client/src/util/log/LogLevel.cpp b/BLUESPAWN-client/src/util/log/LogLevel.cpp deleted file mode 100644 index 76ca3ffd..00000000 --- a/BLUESPAWN-client/src/util/log/LogLevel.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "util/log/LogLevel.h" - -namespace Log { - - LogLevel::LogLevel(Severity severity) : enabled{ true }, severity{ severity } {} - LogLevel::LogLevel(Severity severity, bool enabled) : enabled{ enabled }, severity{ severity } {} - - LogLevel LogLevel::LogError{Severity::LogError, true }; - - LogLevel LogLevel::LogWarn{Severity::LogWarn, true }; - - LogLevel LogLevel::LogInfo{ Severity::LogInfo, true }; - - LogLevel LogLevel::LogHunt{ Severity::LogHunt, true }; - - LogLevel LogLevel::LogVerbose1{ Severity::LogInfo, false }; - - LogLevel LogLevel::LogVerbose2{ Severity::LogInfo, false }; - - LogLevel LogLevel::LogVerbose3{ Severity::LogInfo, false }; - - void LogLevel::Enable(){ enabled = true; } - void LogLevel::Disable(){ enabled = false; } - bool LogLevel::Toggle(){ return enabled = !enabled; } - bool LogLevel::Enabled() const { return enabled; } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/XMLSink.cpp b/BLUESPAWN-client/src/util/log/XMLSink.cpp deleted file mode 100644 index 735149f9..00000000 --- a/BLUESPAWN-client/src/util/log/XMLSink.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "util/log/XMLSink.h" -#include "common/StringUtils.h" - -#include -#include -#include - -namespace Log{ - - std::wstring ToWstringPad(DWORD value, size_t length=2){ - wchar_t* buf = new wchar_t[length + 1]; - swprintf(buf, (L"%0" + std::to_wstring(length) + L"d").c_str(), value); - std::wstring str = buf; - delete[] buf; - return str; - } - - void UpdateLog(XMLSink* sink){ - HandleWrapper hRecordEvent{ CreateEventW(nullptr, false, false, L"Local\\FlushLogs") }; - while(true){ - WaitForSingleObject(hRecordEvent, INFINITE); - sink->Flush(); - } - } - - XMLSink::XMLSink() : - hMutex{ CreateMutexW(nullptr, false, nullptr) } , - Root{ XMLDoc.NewElement("bluespawn") }, - thread{ CreateThread(nullptr, 0, PTHREAD_START_ROUTINE(UpdateLog), this, CREATE_SUSPENDED, nullptr) }{ - SYSTEMTIME time{}; - GetLocalTime(&time); - wFileName = L"bluespawn-" + ToWstringPad(time.wMonth) + L"-" + ToWstringPad(time.wDay) + L"-" + ToWstringPad(time.wYear, 4) + L"-" - + ToWstringPad(time.wHour) + ToWstringPad(time.wMinute) + L"-" + ToWstringPad(time.wSecond) + L".xml"; - XMLDoc.InsertEndChild(Root); - ResumeThread(thread); - } - - XMLSink::XMLSink(const std::wstring& wFileName) : - hMutex{ CreateMutexW(nullptr, false, nullptr) }, - Root { XMLDoc.NewElement("bluespawn") }, - wFileName{ wFileName }, - thread{ CreateThread(nullptr, 0, PTHREAD_START_ROUTINE(UpdateLog), this, 0, nullptr) }{ - XMLDoc.InsertEndChild(Root); - } - - XMLSink::~XMLSink(){ - XMLDoc.SaveFile(WidestringToString(wFileName).c_str()); - TerminateThread(thread, 0); - } - - tinyxml2::XMLElement* CreateDetctionXML(const std::shared_ptr& detection, tinyxml2::XMLDocument& XMLDoc){ - auto detect = XMLDoc.NewElement("detection"); - if(detection->Type == DetectionType::Registry){ - detect->SetAttribute("type", "Registry"); - auto RegistryDetection = std::static_pointer_cast(detection); - auto Key = XMLDoc.NewElement("key"); - auto Value = XMLDoc.NewElement("value"); - auto Data = XMLDoc.NewElement("data"); - Key->SetText(WidestringToString(RegistryDetection->value.key.ToString()).c_str()); - Value->SetText(WidestringToString(RegistryDetection->value.GetPrintableName()).c_str()); - Data->SetText(WidestringToString(RegistryDetection->value.ToString()).c_str()); - detect->InsertEndChild(Key); - detect->InsertEndChild(Value); - detect->InsertEndChild(Data); - } else if(detection->Type == DetectionType::File){ - detect->SetAttribute("type", "File"); - auto FileDetection = std::static_pointer_cast(detection); - auto Name = XMLDoc.NewElement("name"); - auto Path = XMLDoc.NewElement("path"); - auto MD5 = XMLDoc.NewElement("md5"); - auto SHA1 = XMLDoc.NewElement("sha1"); - auto SHA256 = XMLDoc.NewElement("sha256"); - auto Created = XMLDoc.NewElement("created"); - auto Modified = XMLDoc.NewElement("modified"); - auto Accessed = XMLDoc.NewElement("accessed"); - Name->SetText(WidestringToString(FileDetection->wsFileName).c_str()); - Path->SetText(WidestringToString(FileDetection->wsFilePath).c_str()); - MD5->SetText(WidestringToString(FileDetection->md5).c_str()); - SHA1->SetText(WidestringToString(FileDetection->sha1).c_str()); - SHA256->SetText(WidestringToString(FileDetection->sha256).c_str()); - Created->SetText(WidestringToString(FileDetection->created).c_str()); - Modified->SetText(WidestringToString(FileDetection->modified).c_str()); - Accessed->SetText(WidestringToString(FileDetection->accessed).c_str()); - detect->InsertEndChild(Name); - detect->InsertEndChild(Path); - detect->InsertEndChild(MD5); - detect->InsertEndChild(SHA1); - detect->InsertEndChild(SHA256); - detect->InsertEndChild(Created); - detect->InsertEndChild(Modified); - detect->InsertEndChild(Accessed); - } else if(detection->Type == DetectionType::Process){ - detect->SetAttribute("type", "Process"); - auto ProcessDetection = std::static_pointer_cast(detection); - auto Path = XMLDoc.NewElement("path"); - auto Cmd = XMLDoc.NewElement("cmdline"); - auto Pid = XMLDoc.NewElement("pid"); - Path->SetText(WidestringToString(ProcessDetection->wsImagePath).c_str()); - Cmd->SetText(WidestringToString(ProcessDetection->wsCmdline).c_str()); - Pid->SetText(std::to_string(ProcessDetection->PID).c_str()); - detect->InsertEndChild(Path); - detect->InsertEndChild(Cmd); - detect->InsertEndChild(Pid); - } else if(detection->Type == DetectionType::Service){ - detect->SetAttribute("type", "Service"); - auto ServiceDetection = std::static_pointer_cast(detection); - auto Name = XMLDoc.NewElement("name"); - auto Path = XMLDoc.NewElement("path"); - auto Dll = XMLDoc.NewElement("dll"); - auto Pid = XMLDoc.NewElement("pid"); - Name->SetText(WidestringToString(ServiceDetection->wsServiceName).c_str()); - Path->SetText(WidestringToString(ServiceDetection->wsServiceExecutablePath).c_str()); - Dll->SetText(WidestringToString(ServiceDetection->wsServiceDll).c_str()); - Pid->SetText(std::to_string(ServiceDetection->ServicePID).c_str()); - detect->InsertEndChild(Name); - detect->InsertEndChild(Path); - detect->InsertEndChild(Dll); - detect->InsertEndChild(Pid); - } else if(detection->Type == DetectionType::Event){ - detect->SetAttribute("type", "Event"); - auto EventDetection = std::static_pointer_cast(detection); - auto ID = XMLDoc.NewElement("id"); - auto RecordID = XMLDoc.NewElement("recordid"); - auto Time = XMLDoc.NewElement("time"); - auto Channel = XMLDoc.NewElement("channel"); - auto Raw = XMLDoc.NewElement("raw"); - ID->SetText(std::to_string(EventDetection->eventID).c_str()); - RecordID->SetText(std::to_string(EventDetection->eventRecordID).c_str()); - Time->SetText(WidestringToString(EventDetection->timeCreated).c_str()); - Channel->SetText(WidestringToString(EventDetection->channel).c_str()); - Raw->SetText(WidestringToString(EventDetection->rawXML).c_str()); - detect->InsertEndChild(ID); - detect->InsertEndChild(RecordID); - detect->InsertEndChild(Time); - detect->InsertEndChild(Channel); - detect->InsertEndChild(Raw); - for(auto key : EventDetection->params){ - auto name = WidestringToString(key.first); - auto idx1 = name.find("'") + 1; - auto tag = XMLDoc.NewElement(name.substr(idx1, name.find_last_of("'") - idx1).c_str()); - tag->SetText(WidestringToString(key.second).c_str()); - detect->InsertEndChild(tag); - } - } - return detect; - } - - void XMLSink::LogMessage(const LogLevel& level, const std::string& message, const std::optional info, const std::vector>& detections){ - auto mutex = AcquireMutex(hMutex); - if(level.Enabled() && level.severity == Severity::LogHunt && info){ - auto hunt = XMLDoc.NewElement("hunt"); - hunt->SetAttribute("agressiveness", info->HuntAggressiveness == Aggressiveness::Intensive ? "Intensive" : - info->HuntAggressiveness == Aggressiveness::Normal ? "Normal" : "Cursory"); - hunt->SetAttribute("categories", static_cast(info->HuntCategories)); - hunt->SetAttribute("datasources", static_cast(info->HuntDatasources)); - hunt->SetAttribute("tactics", static_cast(info->HuntTactics)); - hunt->SetAttribute("time", SystemTimeToInteger(info->HuntStartTime)); - hunt->SetAttribute("datetime", WidestringToString(FormatWindowsTime(info->HuntStartTime)).c_str()); - - auto name = XMLDoc.NewElement("name"); - name->SetText(WidestringToString(info->HuntName).c_str()); - hunt->InsertFirstChild(name); - - if(message.length() > 0){ - auto msg = XMLDoc.NewElement("message"); - msg->SetText(message.c_str()); - hunt->InsertEndChild(msg); - } - for(auto detection : detections){ - hunt->InsertEndChild(CreateDetctionXML(detection, XMLDoc)); - } - - Root->InsertEndChild(hunt); - } else if(level.Enabled()) { - auto msg = XMLDoc.NewElement(MessageTags[static_cast(level.severity)].c_str()); - SYSTEMTIME st; - GetSystemTime(&st); - msg->SetAttribute("time", SystemTimeToInteger(st)); - msg->SetText(message.c_str()); - Root->InsertEndChild(msg); - } - } - - bool XMLSink::operator==(const LogSink& sink) const { - return (bool) dynamic_cast(&sink) && dynamic_cast(&sink)->wFileName == wFileName; - } - - void XMLSink::Flush(){ - XMLDoc.SaveFile(WidestringToString(wFileName).c_str()); - } -}; diff --git a/BLUESPAWN-client/src/util/processes/CheckLolbin.cpp b/BLUESPAWN-client/src/util/processes/CheckLolbin.cpp deleted file mode 100644 index 4faddfa2..00000000 --- a/BLUESPAWN-client/src/util/processes/CheckLolbin.cpp +++ /dev/null @@ -1,205 +0,0 @@ - - -#include -#include -#include -#include - -#include "util/processes/ProcessUtils.h" -#include "util/filesystem/FileSystem.h" -#include "util/processes/CommandParser.h" -#include "util/log/Log.h" - -std::vector lolbins{ - L"cmd.exe", - L"powershell.exe", - L"explorer.exe", - L"net.exe", - L"net1.exe", - L"At.exe", - L"Atbroker.exe", - L"Bash.exe", - L"Bitsadmin.exe", - L"Cmstp.exe", - L"Diskshadow.exe", - L"Dnscmd.exe", - L"Extexport.exe", - L"Forfiles.exe", - L"Ftp.exe", - L"Gpscript.exe", - L"Hh.exe", - L"Ie4uinit.exe", - L"Ieexec.exe", - L"Infdefaultinstall.exe", - L"Installutil.exe", - L"Mavinject.exe", - L"Microsoft.Workflow.Compiler.exe", - L"Mmc.exe", - L"Msbuild.exe", - L"Msconfig.exe", - L"Msdt.exe", - L"Mshta.exe", - L"Msiexec.exe", - L"Netsh.exe", - L"Odbcconf.exe", - L"Pcalua.exe", - L"Pcwrun.exe", - L"Presentationhost.exe", - L"Rasautou.exe", - L"Regasm.exe", - L"Register-cimprovider.exe", - L"Regsvcs.exe", - L"Regsvr32.exe", - L"Rundll32.exe", - L"Runonce.exe", - L"Runscripthelper.exe", - L"Schtasks.exe", - L"Scriptrunner.exe", - L"SyncAppvPublishingServer.exe", - L"Tttracer.exe", - L"Verclsid.exe", - L"Wab.exe", - L"Wmic.exe", - L"Xwizard.exe", - L"Appvlp.exe", - L"Bginfo.exe", - L"Cdb.exe", - L"csi.exe", - L"Devtoolslauncher.exe", - L"dnx.exe", - L"Dotnet.exe", - L"Dxcap.exe", - L"Mftrace.exe", - L"Msdeploy.exe", - L"msxsl.exe", - L"rcsi.exe", - L"Sqlps.exe", - L"SQLToolsPS.exe", - L"Squirrel.exe", - L"te.exe", - L"Tracker.exe", - L"Update.exe", - L"vsjitdebugger.exe", - L"Wsl.exe", - L"Advpack.dll", - L"Ieadvpack.dll", - L"Ieaframe.dll", - L"Mshtml.dll", - L"Pcwutl.dll", - L"Setupapi.dll", - L"Shdocvw.dll", - L"Shell32.dll", - L"Syssetup.dll", - L"Url.dll", - L"Zipfldr.dll" -}; - -std::set LolbinHashes{}; - -std::map hashmap{}; - -bool IsLolbin(const FileSystem::File& file){ - if(!file.GetFileExists()){ - return false; - } - - if(!LolbinHashes.size()){ - for(auto name : lolbins){ - auto path{ FileSystem::SearchPathExecutable(name) }; - if(path){ - auto hash{ FileSystem::File{ *path }.GetSHA256Hash() }; - if(hash){ - LolbinHashes.emplace(*hash); - hashmap.emplace(name, *hash); - } - } - } - } - - auto hash{ file.GetSHA256Hash() }; - if(hash && LolbinHashes.count(*hash)){ - return true; - } - - return false; -} - -bool IsLolbinMalicious(const std::wstring& command){ - std::wstring executable{ GetImagePathFromCommand(command) }; - - LOG_VERBOSE(1, "Checking if " << command << " will execute a lolbin maliciously"); - - if(!IsLolbin(executable)){ - return false; - } - - auto args{ GetArgumentTokens(command) }; - - LOG_VERBOSE(3, "Getting hash of " << executable); - auto hash{ FileSystem::File(executable).GetSHA256Hash() }; - - LOG_VERBOSE(3, "Checking if " << executable << " is rundll32"); - if(hashmap.count(L"Rundll32.exe") && hashmap.at(L"Rundll32.exe") == hash){ - if(args.size()){ - auto arg{ args[0] }; - auto br{ arg.find_first_of(L" \t,") }; - auto dll{ arg.substr(0, br) }; - auto dllpath{ FileSystem::SearchPathExecutable(dll) }; - - FileSystem::File dllfile{ *dllpath }; - if(!dllpath || !dllfile.GetFileSigned()){ - LOG_INFO("rundll32 found to be executing " << dll); - return true; - } - - if(hashmap.count(L"Shell32.dll") && hashmap.at(L"Shell32.dll") == dllfile.GetSHA256Hash() && br != std::wstring::npos){ - auto start{ arg.find_first_not_of(L" ,\t", br) }; - auto func{ arg.substr(start, arg.find_first_of(L" ,\t", start)) }; - LOG_INFO("rundll32 found to be executing shell32"); - return !CompareIgnoreCaseW(func, L"SHCreateLocalServerRunDll"); - } else{ - LOG_INFO("rundll32 found to be executing " << dll); - return true; - } - } - return false; - } - - LOG_VERBOSE(3, "Checking if " << executable << " is mmc.exe"); - if(hashmap.count(L"Mmc.exe") && hashmap.at(L"Mmc.exe") == hash){ - for(auto& arg : args){ - if(FileSystem::SearchPathExecutable(arg)){ - LOG_INFO("mmc found to be executing " << arg); - return true; - } - } - return false; - } - - LOG_VERBOSE(3, "Checking if " << executable << " is presentationhost"); - if(hashmap.count(L"Presentationhost.exe") && hashmap.at(L"Presentationhost.exe") == hash){ - for(auto& arg : args){ - if(FileSystem::SearchPathExecutable(arg)){ - LOG_INFO("mmc found to be executing " << arg); - return true; - } - } - return false; - } - - LOG_VERBOSE(3, "Checking if " << executable << " is Mshta.exe"); - if(hashmap.count(L"Mshta.exe") && hashmap.at(L"Mshta.exe") == hash){ - return args.size(); - } - - LOG_VERBOSE(3, "Checking if " << executable << " is explorer.exe"); - if(hashmap.count(L"explorer.exe") && hashmap.at(L"explorer.exe") == hash){ - for(auto& arg : args){ - if(FileSystem::SearchPathExecutable(arg)){ - LOG_INFO("explorer found to be executing " << arg); - return true; - } - } - return false; - } else return true; -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/processes/ProcessUtils.cpp b/BLUESPAWN-client/src/util/processes/ProcessUtils.cpp deleted file mode 100644 index 0f8f80de..00000000 --- a/BLUESPAWN-client/src/util/processes/ProcessUtils.cpp +++ /dev/null @@ -1,315 +0,0 @@ -#include "util/processes/ProcessUtils.h" - -#include - -#include "util/filesystem/FileSystem.h" -#include "shlwapi.h" - -#include "util/log/Log.h" - -typedef struct _CURDIR { - UNICODE_STRING DosPath; - PVOID Handle; -} CURDIR, * PCURDIR; - -typedef struct _RTL_DRIVE_LETTER_CURDIR { - WORD Flags; - WORD Length; - ULONG TimeStamp; - STRING DosPath; -} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR; - - -typedef struct _RTL_USER_PROCESS_PARAMETERS_ { - ULONG MaximumLength; - ULONG Length; - ULONG Flags; - ULONG DebugFlags; - PVOID ConsoleHandle; - ULONG ConsoleFlags; - PVOID StandardInput; - PVOID StandardOutput; - PVOID StandardError; - CURDIR CurrentDirectory; - UNICODE_STRING DllPath; - UNICODE_STRING ImagePathName; - UNICODE_STRING CommandLine; - PVOID Environment; - ULONG StartingX; - ULONG StartingY; - ULONG CountX; - ULONG CountY; - ULONG CountCharsX; - ULONG CountCharsY; - ULONG FillAttribute; - ULONG WindowFlags; - ULONG ShowWindowFlags; - UNICODE_STRING WindowTitle; - UNICODE_STRING DesktopInfo; - UNICODE_STRING ShellInfo; - UNICODE_STRING RuntimeData; - RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32]; - ULONG EnvironmentSize; -} RTL_USER_PROCESS_PARAMETERS_, * PRTL_USER_PROCESS_PARAMETERS_; - -bool HookIsOkay(const Hook& hook){ - // Once Detours is set up, this will become significantly more complicated... - return false; -} - -std::vector GetExecutableNonImageSections(DWORD pid){ - // Make use of APIs in PE Sieve... - return {}; -} - -std::wstring GetProcessCommandline(const HandleWrapper& process){ - if(process){ - PROCESS_BASIC_INFORMATION information{}; - NTSTATUS status = Linker::NtQueryInformationProcess(process, ProcessBasicInformation, &information, sizeof(information), nullptr); - if(NT_SUCCESS(status)){ - auto peb = information.PebBaseAddress; - - ULONG_PTR pointer{}; - if(!ReadProcessMemory(process, &peb->ProcessParameters, &pointer, sizeof(pointer), nullptr)){ - LOG_ERROR("Unable to read memory from process with PID " << GetProcessId(process) << " to find its command line (error " << GetLastError() << ")"); - return {}; - } - RTL_USER_PROCESS_PARAMETERS_ params{}; - if(!ReadProcessMemory(process, LPVOID(pointer), ¶ms, sizeof(params), nullptr)){ - LOG_ERROR("Unable to read memory from process with PID " << GetProcessId(process) << " to find its command line (error " << GetLastError() << ")"); - return {}; - } - - DWORD dwLength = params.CommandLine.Length; - auto cmdline = AllocationWrapper{ new WCHAR[dwLength / 2 + 1], dwLength + 2, AllocationWrapper::CPP_ARRAY_ALLOC }; - if(!ReadProcessMemory(process, params.CommandLine.Buffer, cmdline, dwLength, nullptr)){ - LOG_ERROR("Unable to read memory from process with PID " << GetProcessId(process) << " to find its command line (error " << GetLastError() << ")"); - return {}; - } - cmdline.SetByte(dwLength, 0); - cmdline.SetByte(dwLength + 1, 0); - - return std::wstring{ reinterpret_cast(LPVOID(cmdline)) }; - } else{ - LOG_ERROR("Unable to query information from process with PID " << GetProcessId(process) << " to find its command line (error " << status << ")"); - return {}; - } - } else{ - LOG_ERROR("Unable to get command line of invalid process"); - return {}; - } -} - -std::wstring GetProcessCommandline(DWORD dwPID){ - HandleWrapper process{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, dwPID) }; - if(process){ - return GetProcessCommandline(process); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to find its command line (error " << GetLastError() << ")"); - return {}; - } -} - -std::wstring GetProcessImage(const HandleWrapper& process){ - if(process){ - PROCESS_BASIC_INFORMATION information{}; - NTSTATUS status = Linker::NtQueryInformationProcess(process, ProcessBasicInformation, &information, sizeof(information), nullptr); - if(NT_SUCCESS(status)){ - auto peb = information.PebBaseAddress; - RTL_USER_PROCESS_PARAMETERS_ params{}; - if(!ReadProcessMemory(process, &peb->ProcessParameters, ¶ms, sizeof(params), nullptr)){ - LOG_ERROR("Unable to read memory from process with PID " << GetProcessId(process) << " to find its image path (error " << GetLastError() << ")"); - return {}; - } - - DWORD dwLength = params.DllPath.Length; - auto path = AllocationWrapper{ new WCHAR[dwLength / 2 + 1], dwLength + 2, AllocationWrapper::CPP_ARRAY_ALLOC }; - if(!ReadProcessMemory(process, &peb->ProcessParameters, ¶ms, sizeof(params), nullptr)){ - LOG_ERROR("Unable to read memory from process with PID " << GetProcessId(process) << " to find its image path (error " << GetLastError() << ")"); - return {}; - } - - if(!ReadProcessMemory(process, ¶ms.DllPath.Buffer, path, dwLength, nullptr)){ - LOG_ERROR("Unable to read memory from process with PID " << GetProcessId(process) << " to find its image path (error " << GetLastError() << ")"); - return {}; - } - path.SetByte(dwLength, 0); - path.SetByte(dwLength + 1, 0); - - return std::wstring{ reinterpret_cast(LPVOID(path)) }; - } else{ - LOG_ERROR("Unable to query information from process with PID " << GetProcessId(process) << " to find its image path (error " << status << ")"); - return {}; - } - } else{ - LOG_ERROR("Unable to get command line of invalid process"); - return {}; - } -} - -std::wstring GetProcessImage(DWORD dwPID){ - HandleWrapper process{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, dwPID) }; - if(process){ - return GetProcessImage(process); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to find its command line (error " << GetLastError() << ")"); - return {}; - } -} - -std::vector EnumModules(DWORD dwPID){ - HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); - if(hProcess){ - return EnumModules(hProcess); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to enumerate its modules (error " << GetLastError() << ")"); - return {}; - } - -} - -std::vector EnumModules(const HandleWrapper& hProcess){ - std::vector modules(1024); - DWORD dwBytesNeeded{}; - auto status{ EnumProcessModules(hProcess, modules.data(), 1024 * sizeof(HMODULE), &dwBytesNeeded) }; - if(dwBytesNeeded > 1024 * sizeof(HMODULE)){ - modules.resize(dwBytesNeeded / sizeof(HMODULE)); - status = EnumProcessModules(hProcess, modules.data(), dwBytesNeeded, &dwBytesNeeded); - } - - std::vector vModules{}; - - if(status){ - for(auto mod : modules){ - WCHAR path[MAX_PATH]; - if(GetModuleFileNameExW(hProcess, mod, path, MAX_PATH)){ - vModules.emplace_back(path); - } else{ - LOG_ERROR("Unable to get name of module at " << mod << " in process with PID " << GetProcessId(hProcess)); - } - } - } else{ - LOG_ERROR("Unable to enumerate modules in process with PID " << GetProcessId(hProcess) << " (Error " << GetLastError() << ")"); - } - - return vModules; -} - -LPVOID GetModuleAddress(DWORD dwPID, const std::wstring& wsModuleName){ - HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); - if(hProcess){ - return GetModuleAddress(hProcess, wsModuleName); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to enumerate its modules (error " << GetLastError() << ")"); - return {}; - } - -} - -LPVOID GetModuleAddress(const HandleWrapper& hProcess, const std::wstring& wsModuleName){ - std::vector modules(1024); - DWORD dwBytesNeeded{}; - auto status{ EnumProcessModules(hProcess, modules.data(), 1024 * sizeof(HMODULE), &dwBytesNeeded) }; - if(dwBytesNeeded > 1024 * sizeof(HMODULE)){ - modules.resize(dwBytesNeeded / sizeof(HMODULE)); - status = EnumProcessModules(hProcess, modules.data(), dwBytesNeeded, &dwBytesNeeded); - } - - if(status){ - for(auto mod : modules){ - WCHAR path[MAX_PATH]; - if(GetModuleFileNameExW(hProcess, mod, path, MAX_PATH)){ - if(path == wsModuleName){ - return mod; - } - } else{ - LOG_ERROR("Unable to get name of module at " << mod << " in process with PID " << GetProcessId(hProcess)); - } - } - } else{ - LOG_ERROR("Unable to enumerate modules in process with PID " << GetProcessId(hProcess) << " (Error " << GetLastError() << ")"); - } - - LOG_ERROR("Unable to find address of module " << wsModuleName << " in process with PID " << GetProcessId(hProcess)); - return nullptr; -} - -DWORD GetRegionSize(const HandleWrapper& hProcess, LPVOID lpBaseAddress){ - DWORD dwImageSize = 0; - ULONG_PTR address = reinterpret_cast(lpBaseAddress); - - while(true){ - MEMORY_BASIC_INFORMATION memory{}; - if(VirtualQueryEx(hProcess, reinterpret_cast(address), &memory, sizeof(memory))){ - if(memory.AllocationBase == lpBaseAddress){ - dwImageSize += memory.RegionSize; - address += memory.RegionSize; - } else break; - } else break; - } - - LOG_VERBOSE(2, "Determined the size of the region to remove is " << dwImageSize); - return dwImageSize; -} - -DWORD GetRegionSize(DWORD dwPID, LPVOID lpBaseAddress){ - HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); - if(hProcess){ - return GetRegionSize(hProcess, lpBaseAddress); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to determine size of region at " << lpBaseAddress << " (error " << GetLastError() << ")"); - return {}; - } - -} - -std::optional GetMappedFile(DWORD dwPID, LPVOID lpAllocationBase){ - HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); - if(hProcess){ - return GetMappedFile(hProcess, lpAllocationBase); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to determine size of region at " << lpAllocationBase << " (error " << GetLastError() << ")"); - return {}; - } -} - -std::optional GetMappedFile(const HandleWrapper& hProcess, LPVOID lpAllocationBase){ - std::vector filename(MAX_PATH); - auto len = GetMappedFileNameW(hProcess, lpAllocationBase, filename.data(), MAX_PATH); - if(!len){ - return std::nullopt; - } - - return FileSystem::File(std::wstring{ filename.data(), len }); -} - -namespace Utils::Process{ - AllocationWrapper ReadProcessMemory(const HandleWrapper& hProcess, LPVOID lpBaseAddress, DWORD dwSize){ - if(hProcess){ - if(dwSize == -1){ - dwSize = GetRegionSize(hProcess, lpBaseAddress); - } - - AllocationWrapper wrapper{ VirtualAlloc(nullptr, dwSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE), dwSize }; - - if(::ReadProcessMemory(hProcess, lpBaseAddress, wrapper, dwSize, nullptr)){ - return wrapper; - } else{ - LOG_ERROR("Unable to read memory at " << lpBaseAddress << " in process with PID " << GetProcessId(hProcess) << " (error " << GetLastError() << ")"); - } - } else{ - LOG_ERROR("Unable to read memory from invalid process!"); - } - return { nullptr, 0 }; - } - - AllocationWrapper ReadProcessMemory(DWORD dwPID, LPVOID lpBaseAddress, DWORD dwSize){ - HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); - if(hProcess){ - return ReadProcessMemory(hProcess, lpBaseAddress, dwSize); - } else{ - LOG_ERROR("Unable to open process with PID " << dwPID << " to read memory at " << lpBaseAddress << " (error " << GetLastError() << ")"); - return { nullptr, 0 }; - } - } -} diff --git a/BLUESPAWN-client/src/yara/args.c b/BLUESPAWN-client/src/yara/args.c deleted file mode 100644 index ab48a429..00000000 --- a/BLUESPAWN-client/src/yara/args.c +++ /dev/null @@ -1,293 +0,0 @@ -/* -Copyright (c) 2014. The YARA Authors. All Rights Reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include - -#include - -#include "args.h" - -#define args_is_long_arg(arg) \ - (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0') - - -#define args_is_short_arg(arg) \ - (arg[0] == '-' && arg[1] != '-' && arg[1] != '\0') - - -args_option_t* args_get_short_option( - args_option_t *options, - const char opt) -{ - while (options->type != ARGS_OPT_END) - { - if (opt == options->short_name) - return options; - - options++; - } - - return NULL; -} - - -args_option_t* args_get_long_option( - args_option_t *options, - const char* arg) -{ - arg += 2; // skip starting -- - - while (options->type != ARGS_OPT_END) - { - if (options->long_name != NULL) - { - size_t l = strlen(options->long_name); - - if ((arg[l] == '\0' || arg[l] == '=') && - strstr(arg, options->long_name) == arg) - { - return options; - } - } - - options++; - } - - return NULL; -} - - -args_error_type_t args_parse_option( - args_option_t* opt, - const char* opt_arg, - int* opt_arg_was_used) -{ - char *endptr = NULL; - - if (opt_arg_was_used != NULL) - *opt_arg_was_used = 0; - - if (opt->count == opt->max_count) - return ARGS_ERROR_TOO_MANY; - - switch (opt->type) - { - case ARGS_OPT_BOOLEAN: - *(bool*) opt->value = true; - break; - - case ARGS_OPT_INTEGER: - - if (opt_arg == NULL) - return ARGS_ERROR_REQUIRED_INTEGER_ARG; - - *(int*) opt->value = strtol(opt_arg, &endptr, 0); - - if (*endptr != '\0') - return ARGS_ERROR_REQUIRED_INTEGER_ARG; - - if (opt_arg_was_used != NULL) - *opt_arg_was_used = 1; - - break; - - case ARGS_OPT_STRING: - - if (opt_arg == NULL) - return ARGS_ERROR_REQUIRED_STRING_ARG; - - if (opt->max_count > 1) - ((const char**)opt->value)[opt->count] = opt_arg; - else - *(const char**) opt->value = opt_arg; - - if (opt_arg_was_used != NULL) - *opt_arg_was_used = 1; - - break; - - default: - assert(0); - } - - opt->count++; - - return ARGS_ERROR_OK; -} - - -void args_print_error( - args_error_type_t error, - const char* option) -{ - switch(error) - { - case ARGS_ERROR_UNKNOWN_OPT: - fprintf(stderr, "unknown option `%s`\n", option); - break; - case ARGS_ERROR_TOO_MANY: - fprintf(stderr, "too many `%s` options\n", option); - break; - case ARGS_ERROR_REQUIRED_INTEGER_ARG: - fprintf(stderr, "option `%s` requires an integer argument\n", option); - break; - case ARGS_ERROR_REQUIRED_STRING_ARG: - fprintf(stderr, "option `%s` requires a string argument\n", option); - break; - case ARGS_ERROR_UNEXPECTED_ARG: - fprintf(stderr, "option `%s` doesn't expect an argument\n", option); - break; - default: - return; - } -} - - -int args_parse( - args_option_t *options, - int argc, - const char **argv) -{ - args_error_type_t error = ARGS_ERROR_OK; - - int i = 1; // start with i = 1, argv[0] is the program name - int o = 0; - - while (i < argc) - { - const char* arg = argv[i]; - - if (args_is_long_arg(arg)) - { - args_option_t* opt = args_get_long_option(options, arg); - - if (opt != NULL) - { - const char* equal = strchr(arg, '='); - - if (equal) - error = args_parse_option(opt, equal + 1, NULL); - else - error = args_parse_option(opt, NULL, NULL); - } - else - { - error = ARGS_ERROR_UNKNOWN_OPT; - } - } - else if (args_is_short_arg(arg)) - { - for (int j = 1; arg[j] != '\0'; j++) - { - args_option_t* opt = args_get_short_option(options, arg[j]); - - if (opt != NULL) - { - if (arg[j + 1] == '\0') - { - int arg_used; - - // short option followed by a space, argv[i + 1] could be - // an argument for the option (i.e: -a ) - error = args_parse_option(opt, argv[i + 1], &arg_used); - - // argv[i + 1] was actually an argument to the option, skip it. - if (arg_used) - i++; - } - else - { - // short option followed by another option (i.e: -ab), no - // argument for this option - error = args_parse_option(opt, NULL, NULL); - } - } - else - { - error = ARGS_ERROR_UNKNOWN_OPT; - } - - if (error != ARGS_ERROR_OK) - break; - } - } - else - { - argv[o++] = arg; - } - - if (error != ARGS_ERROR_OK) - { - args_print_error(error, arg); - exit(1); - } - - i++; - } - - return o; -} - - -void args_print_usage( - args_option_t *options, - int help_alignment) -{ - char buffer[128]; - - for (; options->type != ARGS_OPT_END; options++) - { - int len = sprintf(buffer, " "); - - if (options->short_name != '\0') - len += sprintf(buffer + len, "-%c", options->short_name); - else - len += sprintf(buffer + len, " "); - - if (options->short_name != '\0' && options->long_name != NULL) - len += sprintf(buffer + len, ", "); - - if (options->long_name != NULL) - len += sprintf(buffer + len, "--%s", options->long_name); - - if (options->type == ARGS_OPT_STRING || - options->type == ARGS_OPT_INTEGER) - { - len += sprintf( - buffer + len, - "%s%s", - (options->long_name != NULL) ? "=" : " ", - options->type_help); - } - - printf("%-*s%s\n", help_alignment, buffer, options->help); - } -} diff --git a/BLUESPAWN-client/src/yara/common.h b/BLUESPAWN-client/src/yara/common.h deleted file mode 100644 index 721a2762..00000000 --- a/BLUESPAWN-client/src/yara/common.h +++ /dev/null @@ -1,155 +0,0 @@ -/* -Copyright (c) 2017. The YARA Authors. All Rights Reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#ifndef COMMON_H -#define COMMON_H - -#include - -#ifdef _WIN32 - #include - #define access _access_s -#else - #include -#endif - -#define exit_with_code(code) { result = code; goto _exit; } - - -bool compile_files( - YR_COMPILER* compiler, - int argc, - const char** argv) -{ - for (int i = 0; i < argc - 1; i++) - { - FILE* rule_file; - const char* ns; - const char* file_name; - char* colon = NULL; - int errors; - - if (access(argv[i], 0) != 0) - { - // A file with the name specified by the command-line argument wasn't - // found, it may be because the name is prefixed with a namespace, so - // lets try to find the colon that separates the namespace from the - /// actual file name. - colon = (char*) strchr(argv[i], ':'); - } - - // The namespace delimiter must be a colon not followed by a slash or - // backslash. - if (colon && *(colon + 1) != '\\' && *(colon + 1) != '/') - { - - file_name = colon + 1; - *colon = '\0'; - ns = argv[i]; - } - else - { - file_name = argv[i]; - ns = NULL; - } - - if (strcmp(file_name, "-") == 0) - rule_file = stdin; - else - rule_file = fopen(file_name, "r"); - - if (rule_file == NULL) - { - fprintf(stderr, "error: could not open file: %s\n", file_name); - return false; - } - - errors = yr_compiler_add_file(compiler, rule_file, ns, file_name); - - fclose(rule_file); - - if (errors > 0) - return false; - } - - return true; -} - - -bool is_integer(const char *str) -{ - if (*str == '-') - str++; - - if (*str == '\0') - return false; - - while(*str) - { - if (!isdigit(*str)) - return false; - str++; - } - - return true; -} - - -bool is_float(const char *str) -{ - bool has_dot = false; - - if (*str == '-') // skip the minus sign if present - str++; - - if (*str == '.') // float can't start with a dot - return false; - - while(*str) - { - if (*str == '.') - { - if (has_dot) // two dots, not a float - return false; - - has_dot = true; - } - else if (!isdigit(*str)) - { - return false; - } - - str++; - } - - return has_dot; // to be float must contain a dot -} - -#endif diff --git a/BLUESPAWN-client/src/yara/yarac.c b/BLUESPAWN-client/src/yara/yarac.c deleted file mode 100644 index 8b27930a..00000000 --- a/BLUESPAWN-client/src/yara/yarac.c +++ /dev/null @@ -1,346 +0,0 @@ -/* -Copyright (c) 2013. The YARA Authors. All Rights Reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef _WIN32 - -#include -#include -#include -#include - -#else - -#include - -#endif - -#include -#include -#include -#include -#include - -#include "args.h" -#include "common.h" - -/// BEGIN MODIFICATIONS -#include -#include -/// END MODITIFICATIONS - -#ifndef MAX_PATH -#define MAX_PATH 256 -#endif - -#define MAX_ARGS_EXT_VAR 32 - - -typedef struct COMPILER_RESULTS -{ - int errors; - int warnings; - -} COMPILER_RESULTS; - - -static char* atom_quality_table; -static char* ext_vars[MAX_ARGS_EXT_VAR + 1]; -static bool ignore_warnings = false; -static bool show_version = false; -static bool show_help = false; -static bool fail_on_warnings = false; -static int max_strings_per_rule = 10000; - - -#define USAGE_STRING \ - "Usage: yarac [OPTION]... [NAMESPACE:]SOURCE_FILE... OUTPUT_FILE" - -args_option_t options[] = -{ - OPT_STRING(0, "atom-quality-table", &atom_quality_table, - "path to a file with the atom quality table", "FILE"), - - OPT_STRING_MULTI('d', "define", &ext_vars, MAX_ARGS_EXT_VAR, - "define external variable", "VAR=VALUE"), - - OPT_BOOLEAN(0, "fail-on-warnings", &fail_on_warnings, - "fail on warnings"), - - OPT_BOOLEAN('h', "help", &show_help, - "show this help and exit"), - - OPT_INTEGER(0, "max-strings-per-rule", &max_strings_per_rule, - "set maximum number of strings per rule (default=10000)", "NUMBER"), - - OPT_BOOLEAN('w', "no-warnings", &ignore_warnings, - "disable warnings"), - - OPT_BOOLEAN('v', "version", &show_version, - "show version information"), - - OPT_END() -}; - - -static void report_error( - int error_level, - const char* file_name, - int line_number, - const YR_RULE* rule, - const char* message, - void* user_data) -{ - char* msg_type; - - if (error_level == YARA_ERROR_LEVEL_ERROR) - { - msg_type = "error"; - } - else if (!ignore_warnings) - { - COMPILER_RESULTS* compiler_results = (COMPILER_RESULTS*) user_data; - compiler_results->warnings++; - msg_type = "warning"; - } - else - { - return; - } - - if (rule != NULL) - { - fprintf( - stderr, - "%s(%d): %s in rule \"%s\": %s\n", - file_name, - line_number, - msg_type, - rule->identifier, - message); - } - else - { - fprintf( - stderr, - "%s(%d): %s: %s\n", - file_name, - line_number, - msg_type, - message); - } -} - - -static bool define_external_variables( - YR_COMPILER* compiler) -{ - for (int i = 0; ext_vars[i] != NULL; i++) - { - char* equal_sign = strchr(ext_vars[i], '='); - - if (!equal_sign) - { - fprintf(stderr, "error: wrong syntax for `-d` option.\n"); - return false; - } - - // Replace the equal sign with null character to split the external - // variable definition (i.e: myvar=somevalue) in two strings: identifier - // and value. - - *equal_sign = '\0'; - - char* identifier = ext_vars[i]; - char* value = equal_sign + 1; - - if (is_float(value)) - { - yr_compiler_define_float_variable( - compiler, - identifier, - atof(value)); - } - else if (is_integer(value)) - { - yr_compiler_define_integer_variable( - compiler, - identifier, - atoi(value)); - } - else if (strcmp(value, "true") == 0 || strcmp(value, "false") == 0) - { - yr_compiler_define_boolean_variable( - compiler, - identifier, - strcmp(value, "true") == 0); - } - else - { - yr_compiler_define_string_variable( - compiler, - identifier, - value); - } - } - - return true; -} - - -int main( - int argc, - const char** argv) -{ - COMPILER_RESULTS cr; - - YR_COMPILER* compiler = NULL; - YR_RULES* rules = NULL; - - int result; - - argc = args_parse(options, argc, argv); - - if (show_version) - { - printf("%s\n", YR_VERSION); - return EXIT_SUCCESS; - } - - if (show_help) - { - printf("%s\n\n", USAGE_STRING); - - args_print_usage(options, 40); - printf("\nSend bug reports and suggestions to: vmalvarez@virustotal.com\n"); - - return EXIT_SUCCESS; - } - - if (argc < 2) - { - fprintf(stderr, "yarac: wrong number of arguments\n"); - fprintf(stderr, "%s\n\n", USAGE_STRING); - fprintf(stderr, "Try `--help` for more options\n"); - - exit_with_code(EXIT_FAILURE); - } - - result = yr_initialize(); - - if (result != ERROR_SUCCESS) - exit_with_code(EXIT_FAILURE); - - if (yr_compiler_create(&compiler) != ERROR_SUCCESS) - exit_with_code(EXIT_FAILURE); - - if (!define_external_variables(compiler)) - exit_with_code(EXIT_FAILURE); - - if (atom_quality_table != NULL) - { - result = yr_compiler_load_atom_quality_table( - compiler, atom_quality_table, 0); - - if (result != ERROR_SUCCESS) - { - fprintf(stderr, "error loading atom quality table\n"); - exit_with_code(EXIT_FAILURE); - } - } - - cr.errors = 0; - cr.warnings = 0; - - yr_set_configuration(YR_CONFIG_MAX_STRINGS_PER_RULE, &max_strings_per_rule); - - if (!compile_files(compiler, argc, argv)) - exit_with_code(EXIT_FAILURE); - - if (cr.errors > 0) - exit_with_code(EXIT_FAILURE); - - if (fail_on_warnings && cr.warnings > 0) - exit_with_code(EXIT_FAILURE); - - result = yr_compiler_get_rules(compiler, &rules); - - if (result != ERROR_SUCCESS) - { - fprintf(stderr, "error: %d\n", result); - exit_with_code(EXIT_FAILURE); - } - - result = yr_rules_save(rules, argv[argc - 1]); - - if (result != ERROR_SUCCESS) - { - fprintf(stderr, "error: %d\n", result); - exit_with_code(EXIT_FAILURE); - } - /// BEGIN MODIFICATIONS - else { - std::string name = std::string{ argv[argc - 1] } +".z"; - int err{}; - auto zip = zip_open(name.c_str(), ZIP_CREATE, &err); - if(zip){ - auto source = zip_source_file(zip, argv[argc - 1], 0, 0); - if(source){ - if(-1 == zip_file_add(zip, "data", source, ZIP_FL_OVERWRITE)){ - zip_close(zip); - zip_source_close(source); - goto _exit; - } - zip_source_close(source); - zip_close(zip); - MoveFileExA(name.c_str(), argv[argc - 1], MOVEFILE_REPLACE_EXISTING); - } - if(!source){ - zip_close(zip); - goto _exit; - } - } else { - goto _exit; - } - } - /// END MODIFICATIONS - - result = EXIT_SUCCESS; - -_exit: - - if (compiler != NULL) - yr_compiler_destroy(compiler); - - if (rules != NULL) - yr_rules_destroy(rules); - - yr_finalize(); - - return result; -} diff --git a/BLUESPAWN-common/CommonLib.aps b/BLUESPAWN-common/CommonLib.aps deleted file mode 100644 index 015d9be6..00000000 Binary files a/BLUESPAWN-common/CommonLib.aps and /dev/null differ diff --git a/BLUESPAWN-common/CommonLib.props b/BLUESPAWN-common/CommonLib.props deleted file mode 100644 index 9adc9b18..00000000 --- a/BLUESPAWN-common/CommonLib.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - $(SolutionDir)BLUESPAWN-common\headers\;%(AdditionalIncludeDirectories) - _UNICODE;UNICODE;%(PreprocessorDefinitions) - - - - \ No newline at end of file diff --git a/BLUESPAWN-common/CommonLib.rc b/BLUESPAWN-common/CommonLib.rc deleted file mode 100644 index 3b3fdcbf..00000000 --- a/BLUESPAWN-common/CommonLib.rc +++ /dev/null @@ -1,60 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// - -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE 9, 1 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/BLUESPAWN-common/CommonLib.vcxproj b/BLUESPAWN-common/CommonLib.vcxproj deleted file mode 100644 index b28945ce..00000000 --- a/BLUESPAWN-common/CommonLib.vcxproj +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log - - - - - - - - - - - - - - - - - - - - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1} - CommonLib - - - - StaticLibrary - v142 - - - - - - - - - - \ No newline at end of file diff --git a/BLUESPAWN-common/CommonLib.vcxproj.user b/BLUESPAWN-common/CommonLib.vcxproj.user deleted file mode 100644 index 429333de..00000000 --- a/BLUESPAWN-common/CommonLib.vcxproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - \ No newline at end of file diff --git a/BLUESPAWN-common/resource.h b/BLUESPAWN-common/resource.h deleted file mode 100644 index 76c076a8..00000000 --- a/BLUESPAWN-common/resource.h +++ /dev/null @@ -1,14 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by CommonLib.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/BLUESPAWN-server/BLUESPAWN-server.sln b/BLUESPAWN-server/BLUESPAWN-server.sln deleted file mode 100644 index 9271ccd3..00000000 --- a/BLUESPAWN-server/BLUESPAWN-server.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29230.47 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BLUESPAWN-server", "BLUESPAWN-server\BLUESPAWN-server.csproj", "{C964698A-1857-4628-A95B-B053E40E09B6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C964698A-1857-4628-A95B-B053E40E09B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C964698A-1857-4628-A95B-B053E40E09B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C964698A-1857-4628-A95B-B053E40E09B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C964698A-1857-4628-A95B-B053E40E09B6}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CAB9AF27-48F5-4540-967D-5B87524573CE} - EndGlobalSection -EndGlobal diff --git a/BLUESPAWN-server/BLUESPAWN-server/App.config b/BLUESPAWN-server/BLUESPAWN-server/App.config deleted file mode 100644 index 8e156463..00000000 --- a/BLUESPAWN-server/BLUESPAWN-server/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/BLUESPAWN-server/BLUESPAWN-server/BLUESPAWN-server.csproj b/BLUESPAWN-server/BLUESPAWN-server/BLUESPAWN-server.csproj deleted file mode 100644 index 57f448ae..00000000 --- a/BLUESPAWN-server/BLUESPAWN-server/BLUESPAWN-server.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Debug - AnyCPU - {C964698A-1857-4628-A95B-B053E40E09B6} - Exe - BLUESPAWN_server - BLUESPAWN-server - v4.5 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/BLUESPAWN-server/BLUESPAWN-server/Program.cs b/BLUESPAWN-server/BLUESPAWN-server/Program.cs deleted file mode 100644 index e112e8f8..00000000 --- a/BLUESPAWN-server/BLUESPAWN-server/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BLUESPAWN_server -{ - class Program - { - static void Main(string[] args) - { - } - } -} diff --git a/BLUESPAWN-server/BLUESPAWN-server/Properties/AssemblyInfo.cs b/BLUESPAWN-server/BLUESPAWN-server/Properties/AssemblyInfo.cs deleted file mode 100644 index 44beb4ba..00000000 --- a/BLUESPAWN-server/BLUESPAWN-server/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BLUESPAWN-server")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BLUESPAWN-server")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c964698a-1857-4628-a95b-b053e40e09b6")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BLUESPAWN-server/grpc/generated/ReactionData.cs b/BLUESPAWN-server/grpc/generated/ReactionData.cs deleted file mode 100644 index 99e07714..00000000 --- a/BLUESPAWN-server/grpc/generated/ReactionData.cs +++ /dev/null @@ -1,2638 +0,0 @@ -// -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: ReactionData.proto -// -#pragma warning disable 1591, 0612, 3021 -#region Designer generated code - -using pb = global::Google.Protobuf; -using pbc = global::Google.Protobuf.Collections; -using pbr = global::Google.Protobuf.Reflection; -using scg = global::System.Collections.Generic; -namespace Gpb { - - /// Holder for reflection information generated from ReactionData.proto - public static partial class ReactionDataReflection { - - #region Descriptor - /// File descriptor for ReactionData.proto - public static pbr::FileDescriptor Descriptor { - get { return descriptor; } - } - private static pbr::FileDescriptor descriptor; - - static ReactionDataReflection() { - byte[] descriptorData = global::System.Convert.FromBase64String( - string.Concat( - "ChJSZWFjdGlvbkRhdGEucHJvdG8SA2dwYiJDCghBQ0xFbnRyeRIMCgR1c2Vy", - "GAEgASgJEg0KBWdyb3VwGAIgASgJEhoKEnNlY3VyaXR5QXR0cmlidXRlcxgD", - "IAEoDSLcAgoQRmlsZVJlYWN0aW9uRGF0YRIPCgdtaXRyZUlEGAEgASgNEhAK", - "CGZpbGVOYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg0KBW93bmVy", - "GAQgASgJEgwKBHNpemUYBSABKA0SEwoLdGltZUNyZWF0ZWQYBiABKA0SFAoM", - "dGltZU1vZGlmaWVkGAcgASgNEhQKDHRpbWVBY2Nlc3NlZBgIIAEoDRIUCgxm", - "aWxlQ29udGVudHMYCSABKAwSMwoKc2lnbmF0dXJlcxgKIAMoCzIfLmdwYi5G", - "aWxlUmVhY3Rpb25EYXRhLlNpZ25hdHVyZRIaCgNBQ0wYCyADKAsyDS5ncGIu", - "QUNMRW50cnkaSwoJU2lnbmF0dXJlEhIKCnNpZ25lck5hbWUYASABKAkSFwoP", - "ZGlnZXN0QWxnb3JpdGhtGAIgASgJEhEKCXRpbWVzdGFtcBgDIAEoDSLyAQoU", - "UmVnaXN0cnlSZWFjdGlvbkRhdGESDwoHbWl0cmVJRBgBIAEoDRILCgNrZXkY", - "AiABKAkSDAoEZGF0YRgDIAEoDBIPCgduZXdEYXRhGAQgASgMEi8KBHR5cGUY", - "BSABKA4yIS5ncGIuUmVnaXN0cnlSZWFjdGlvbkRhdGEuUmVnVHlwZRIaCgNB", - "Q0wYBiADKAsyDS5ncGIuQUNMRW50cnkiUAoHUmVnVHlwZRIGCgJTWhAAEgwK", - "CE1VTFRJX1NaEAESDQoJRVhQQU5EX1NaEAISCgoGQklOQVJZEAMSCQoFRFdP", - "UkQQBBIJCgVRV09SRBAFIs0CChNQcm9jZXNzUmVhY3Rpb25EYXRhEg8KB21p", - "dHJlSUQYASABKA0SCwoDcGlkGAIgASgNEgwKBG5hbWUYAyABKAkSEwoLZGVz", - "Y3JpcHRpb24YBCABKAkSEwoLY29tbWFuZExpbmUYBSABKAkSDAoEcGF0aBgG", - "IAEoCRIYChBjdXJyZW50RGlyZWN0b3J5GAcgASgJEhkKEWF1dG9zdGFydExv", - "Y2F0aW9uGAggASgJEg4KBnBhcmVudBgJIAEoCRIMCgR1c2VyGAogASgJEhMK", - "C3RpbWVTdGFydGVkGAsgASgNEhUKDXJlY2lldmVkQnl0ZXMYDCABKA0SEQoJ", - "c2VudEJ5dGVzGA0gASgNEhQKDHByaXZhdGVCeXRlcxgOIAEoDRISCgp3b3Jr", - "aW5nU2V0GA8gASgNEhYKDmJpbmFyeUNvbnRlbnRzGBAgASgMIvcBChNTZXJ2", - "aWNlUmVhY3Rpb25EYXRhEg8KB21pdHJlSUQYASABKA0SCwoDcGlkGAIgASgN", - "EgwKBG5hbWUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSDwoHbG9nT25B", - "cxgFIAEoCRITCgtzdGFydHVwVHlwZRgGIAEoCRIXCg9zdGFydFBhcmFtZXRl", - "cnMYByABKAkSHAoUYWxsb3dEZXNrdG9wSW50ZXJhY3QYCCABKAgSFgoOcnVu", - "UHJvZ3JhbVBhdGgYCSABKAkSEgoKYmluYXJ5UGF0aBgKIAEoCRIWCg5iaW5h", - "cnlDb250ZW50cxgLIAEoDCLXAQoISHVudEluZm8SEAoIaHVudE5hbWUYASAB", - "KAkSLwoSaHVudEFnZ3Jlc3NpdmVuZXNzGAIgASgOMhMuZ3BiLkFnZ3Jlc3Np", - "dmVuZXNzEiAKC2h1bnRUYWN0aWNzGAMgAygOMgsuZ3BiLlRhY3RpYxIlCg5o", - "dW50Q2F0ZWdvcmllcxgEIAMoDjINLmdwYi5DYXRlZ29yeRIoCg9odW50RGF0", - "YXNvdXJjZXMYBSADKA4yDy5ncGIuRGF0YVNvdXJjZRIVCg1odW50U3RhcnRU", - "aW1lGAYgASgNIpACCgtIdW50TWVzc2FnZRIbCgRpbmZvGAEgASgLMg0uZ3Bi", - "Lkh1bnRJbmZvEhQKDGV4dHJhTWVzc2FnZRgDIAEoCRItCg5maWxlRGV0ZWN0", - "aW9ucxgEIAMoCzIVLmdwYi5GaWxlUmVhY3Rpb25EYXRhEjUKEnJlZ2lzdHJ5", - "RGV0ZWN0aW9ucxgFIAMoCzIZLmdwYi5SZWdpc3RyeVJlYWN0aW9uRGF0YRIz", - "ChFwcm9jZXNzRGV0ZWN0aW9ucxgGIAMoCzIYLmdwYi5Qcm9jZXNzUmVhY3Rp", - "b25EYXRhEjMKEXNlcnZpY2VEZXRlY3Rpb25zGAcgAygLMhguZ3BiLlNlcnZp", - "Y2VSZWFjdGlvbkRhdGEq5AEKBlRhY3RpYxIRCg1Jbml0aWFsQWNjZXNzEAAS", - "DQoJRXhlY3V0aW9uEAESDwoLUGVyc2lzdGVuY2UQAhIXChNQcml2aWxlZ2VF", - "c2NhbGF0aW9uEAMSEgoORGVmZW5zZUV2YXNpb24QBBIUChBDcmVkZW50aWFs", - "QWNjZXNzEAUSDQoJRGlzY292ZXJ5EAYSEwoPTGF0ZXJhbE1vdmVtZW50EAcS", - "DgoKQ29sbGVjdGlvbhAIEhIKDkNvbW1hbmRDb250cm9sEAkSEAoMRXhmaWx0", - "cmF0aW9uEAoSCgoGSW1wYWN0EAwqpQEKCkRhdGFTb3VyY2USEQoNU2Vydmlj", - "ZVNvdXJjZRAAEhEKDVByb2Nlc3NTb3VyY2UQARIQCgxEcml2ZXJTb3VyY2UQ", - "AhIUChBGaWxlU3lzdGVtU291cmNlEAMSEgoOUmVnaXN0cnlTb3VyY2UQBBIN", - "CglHUE9Tb3VyY2UQBRITCg9FdmVudExvZ3NTb3VyY2UQBhIRCg1OZXR3b3Jr", - "U291cmNlEAcqRQoIQ2F0ZWdvcnkSEgoOQ29uZmlndXJhdGlvbnMQABINCglQ", - "cm9jZXNzZXMQARIJCgVGaWxlcxACEgsKB05ldHdvcmsQAypICg5BZ2dyZXNz", - "aXZlbmVzcxILCgdDdXJzb3J5EAASDAoITW9kZXJhdGUQARILCgdDYXJlZnVs", - "EAISDgoKQWdncmVzc2l2ZRADYgZwcm90bzM=")); - descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, - new pbr::FileDescriptor[] { }, - new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Gpb.Tactic), typeof(global::Gpb.DataSource), typeof(global::Gpb.Category), typeof(global::Gpb.Aggressiveness), }, new pbr::GeneratedClrTypeInfo[] { - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.ACLEntry), global::Gpb.ACLEntry.Parser, new[]{ "User", "Group", "SecurityAttributes" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.FileReactionData), global::Gpb.FileReactionData.Parser, new[]{ "MitreID", "FileName", "Description", "Owner", "Size", "TimeCreated", "TimeModified", "TimeAccessed", "FileContents", "Signatures", "ACL" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.FileReactionData.Types.Signature), global::Gpb.FileReactionData.Types.Signature.Parser, new[]{ "SignerName", "DigestAlgorithm", "Timestamp" }, null, null, null)}), - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.RegistryReactionData), global::Gpb.RegistryReactionData.Parser, new[]{ "MitreID", "Key", "Data", "NewData", "Type", "ACL" }, null, new[]{ typeof(global::Gpb.RegistryReactionData.Types.RegType) }, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.ProcessReactionData), global::Gpb.ProcessReactionData.Parser, new[]{ "MitreID", "Pid", "Name", "Description", "CommandLine", "Path", "CurrentDirectory", "AutostartLocation", "Parent", "User", "TimeStarted", "RecievedBytes", "SentBytes", "PrivateBytes", "WorkingSet", "BinaryContents" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.ServiceReactionData), global::Gpb.ServiceReactionData.Parser, new[]{ "MitreID", "Pid", "Name", "Description", "LogOnAs", "StartupType", "StartParameters", "AllowDesktopInteract", "RunProgramPath", "BinaryPath", "BinaryContents" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.HuntInfo), global::Gpb.HuntInfo.Parser, new[]{ "HuntName", "HuntAggressiveness", "HuntTactics", "HuntCategories", "HuntDatasources", "HuntStartTime" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.HuntMessage), global::Gpb.HuntMessage.Parser, new[]{ "Info", "ExtraMessage", "FileDetections", "RegistryDetections", "ProcessDetections", "ServiceDetections" }, null, null, null) - })); - } - #endregion - - } - #region Enums - public enum Tactic { - [pbr::OriginalName("InitialAccess")] InitialAccess = 0, - [pbr::OriginalName("Execution")] Execution = 1, - [pbr::OriginalName("Persistence")] Persistence = 2, - [pbr::OriginalName("PrivilegeEscalation")] PrivilegeEscalation = 3, - [pbr::OriginalName("DefenseEvasion")] DefenseEvasion = 4, - [pbr::OriginalName("CredentialAccess")] CredentialAccess = 5, - [pbr::OriginalName("Discovery")] Discovery = 6, - [pbr::OriginalName("LateralMovement")] LateralMovement = 7, - [pbr::OriginalName("Collection")] Collection = 8, - [pbr::OriginalName("CommandControl")] CommandControl = 9, - [pbr::OriginalName("Exfiltration")] Exfiltration = 10, - [pbr::OriginalName("Impact")] Impact = 12, - } - - public enum DataSource { - [pbr::OriginalName("ServiceSource")] ServiceSource = 0, - [pbr::OriginalName("ProcessSource")] ProcessSource = 1, - [pbr::OriginalName("DriverSource")] DriverSource = 2, - [pbr::OriginalName("FileSystemSource")] FileSystemSource = 3, - [pbr::OriginalName("RegistrySource")] RegistrySource = 4, - [pbr::OriginalName("GPOSource")] Gposource = 5, - [pbr::OriginalName("EventLogsSource")] EventLogsSource = 6, - [pbr::OriginalName("NetworkSource")] NetworkSource = 7, - } - - public enum Category { - [pbr::OriginalName("Configurations")] Configurations = 0, - [pbr::OriginalName("Processes")] Processes = 1, - [pbr::OriginalName("Files")] Files = 2, - [pbr::OriginalName("Network")] Network = 3, - } - - public enum Aggressiveness { - [pbr::OriginalName("Cursory")] Cursory = 0, - [pbr::OriginalName("Moderate")] Moderate = 1, - [pbr::OriginalName("Careful")] Careful = 2, - [pbr::OriginalName("Aggressive")] Aggressive = 3, - } - - #endregion - - #region Messages - public sealed partial class ACLEntry : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ACLEntry()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[0]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ACLEntry() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ACLEntry(ACLEntry other) : this() { - user_ = other.user_; - group_ = other.group_; - securityAttributes_ = other.securityAttributes_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ACLEntry Clone() { - return new ACLEntry(this); - } - - /// Field number for the "user" field. - public const int UserFieldNumber = 1; - private string user_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string User { - get { return user_; } - set { - user_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "group" field. - public const int GroupFieldNumber = 2; - private string group_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Group { - get { return group_; } - set { - group_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "securityAttributes" field. - public const int SecurityAttributesFieldNumber = 3; - private uint securityAttributes_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint SecurityAttributes { - get { return securityAttributes_; } - set { - securityAttributes_ = value; - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as ACLEntry); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(ACLEntry other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (User != other.User) return false; - if (Group != other.Group) return false; - if (SecurityAttributes != other.SecurityAttributes) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (User.Length != 0) hash ^= User.GetHashCode(); - if (Group.Length != 0) hash ^= Group.GetHashCode(); - if (SecurityAttributes != 0) hash ^= SecurityAttributes.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (User.Length != 0) { - output.WriteRawTag(10); - output.WriteString(User); - } - if (Group.Length != 0) { - output.WriteRawTag(18); - output.WriteString(Group); - } - if (SecurityAttributes != 0) { - output.WriteRawTag(24); - output.WriteUInt32(SecurityAttributes); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (User.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(User); - } - if (Group.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Group); - } - if (SecurityAttributes != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(SecurityAttributes); - } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(ACLEntry other) { - if (other == null) { - return; - } - if (other.User.Length != 0) { - User = other.User; - } - if (other.Group.Length != 0) { - Group = other.Group; - } - if (other.SecurityAttributes != 0) { - SecurityAttributes = other.SecurityAttributes; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - User = input.ReadString(); - break; - } - case 18: { - Group = input.ReadString(); - break; - } - case 24: { - SecurityAttributes = input.ReadUInt32(); - break; - } - } - } - } - - } - - public sealed partial class FileReactionData : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FileReactionData()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[1]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public FileReactionData() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public FileReactionData(FileReactionData other) : this() { - mitreID_ = other.mitreID_; - fileName_ = other.fileName_; - description_ = other.description_; - owner_ = other.owner_; - size_ = other.size_; - timeCreated_ = other.timeCreated_; - timeModified_ = other.timeModified_; - timeAccessed_ = other.timeAccessed_; - fileContents_ = other.fileContents_; - signatures_ = other.signatures_.Clone(); - aCL_ = other.aCL_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public FileReactionData Clone() { - return new FileReactionData(this); - } - - /// Field number for the "mitreID" field. - public const int MitreIDFieldNumber = 1; - private uint mitreID_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint MitreID { - get { return mitreID_; } - set { - mitreID_ = value; - } - } - - /// Field number for the "fileName" field. - public const int FileNameFieldNumber = 2; - private string fileName_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string FileName { - get { return fileName_; } - set { - fileName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "description" field. - public const int DescriptionFieldNumber = 3; - private string description_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Description { - get { return description_; } - set { - description_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "owner" field. - public const int OwnerFieldNumber = 4; - private string owner_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Owner { - get { return owner_; } - set { - owner_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "size" field. - public const int SizeFieldNumber = 5; - private uint size_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint Size { - get { return size_; } - set { - size_ = value; - } - } - - /// Field number for the "timeCreated" field. - public const int TimeCreatedFieldNumber = 6; - private uint timeCreated_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint TimeCreated { - get { return timeCreated_; } - set { - timeCreated_ = value; - } - } - - /// Field number for the "timeModified" field. - public const int TimeModifiedFieldNumber = 7; - private uint timeModified_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint TimeModified { - get { return timeModified_; } - set { - timeModified_ = value; - } - } - - /// Field number for the "timeAccessed" field. - public const int TimeAccessedFieldNumber = 8; - private uint timeAccessed_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint TimeAccessed { - get { return timeAccessed_; } - set { - timeAccessed_ = value; - } - } - - /// Field number for the "fileContents" field. - public const int FileContentsFieldNumber = 9; - private pb::ByteString fileContents_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString FileContents { - get { return fileContents_; } - set { - fileContents_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "signatures" field. - public const int SignaturesFieldNumber = 10; - private static readonly pb::FieldCodec _repeated_signatures_codec - = pb::FieldCodec.ForMessage(82, global::Gpb.FileReactionData.Types.Signature.Parser); - private readonly pbc::RepeatedField signatures_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField Signatures { - get { return signatures_; } - } - - /// Field number for the "ACL" field. - public const int ACLFieldNumber = 11; - private static readonly pb::FieldCodec _repeated_aCL_codec - = pb::FieldCodec.ForMessage(90, global::Gpb.ACLEntry.Parser); - private readonly pbc::RepeatedField aCL_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField ACL { - get { return aCL_; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as FileReactionData); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(FileReactionData other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (MitreID != other.MitreID) return false; - if (FileName != other.FileName) return false; - if (Description != other.Description) return false; - if (Owner != other.Owner) return false; - if (Size != other.Size) return false; - if (TimeCreated != other.TimeCreated) return false; - if (TimeModified != other.TimeModified) return false; - if (TimeAccessed != other.TimeAccessed) return false; - if (FileContents != other.FileContents) return false; - if(!signatures_.Equals(other.signatures_)) return false; - if(!aCL_.Equals(other.aCL_)) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (MitreID != 0) hash ^= MitreID.GetHashCode(); - if (FileName.Length != 0) hash ^= FileName.GetHashCode(); - if (Description.Length != 0) hash ^= Description.GetHashCode(); - if (Owner.Length != 0) hash ^= Owner.GetHashCode(); - if (Size != 0) hash ^= Size.GetHashCode(); - if (TimeCreated != 0) hash ^= TimeCreated.GetHashCode(); - if (TimeModified != 0) hash ^= TimeModified.GetHashCode(); - if (TimeAccessed != 0) hash ^= TimeAccessed.GetHashCode(); - if (FileContents.Length != 0) hash ^= FileContents.GetHashCode(); - hash ^= signatures_.GetHashCode(); - hash ^= aCL_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (MitreID != 0) { - output.WriteRawTag(8); - output.WriteUInt32(MitreID); - } - if (FileName.Length != 0) { - output.WriteRawTag(18); - output.WriteString(FileName); - } - if (Description.Length != 0) { - output.WriteRawTag(26); - output.WriteString(Description); - } - if (Owner.Length != 0) { - output.WriteRawTag(34); - output.WriteString(Owner); - } - if (Size != 0) { - output.WriteRawTag(40); - output.WriteUInt32(Size); - } - if (TimeCreated != 0) { - output.WriteRawTag(48); - output.WriteUInt32(TimeCreated); - } - if (TimeModified != 0) { - output.WriteRawTag(56); - output.WriteUInt32(TimeModified); - } - if (TimeAccessed != 0) { - output.WriteRawTag(64); - output.WriteUInt32(TimeAccessed); - } - if (FileContents.Length != 0) { - output.WriteRawTag(74); - output.WriteBytes(FileContents); - } - signatures_.WriteTo(output, _repeated_signatures_codec); - aCL_.WriteTo(output, _repeated_aCL_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (MitreID != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MitreID); - } - if (FileName.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(FileName); - } - if (Description.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Description); - } - if (Owner.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Owner); - } - if (Size != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Size); - } - if (TimeCreated != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(TimeCreated); - } - if (TimeModified != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(TimeModified); - } - if (TimeAccessed != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(TimeAccessed); - } - if (FileContents.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(FileContents); - } - size += signatures_.CalculateSize(_repeated_signatures_codec); - size += aCL_.CalculateSize(_repeated_aCL_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(FileReactionData other) { - if (other == null) { - return; - } - if (other.MitreID != 0) { - MitreID = other.MitreID; - } - if (other.FileName.Length != 0) { - FileName = other.FileName; - } - if (other.Description.Length != 0) { - Description = other.Description; - } - if (other.Owner.Length != 0) { - Owner = other.Owner; - } - if (other.Size != 0) { - Size = other.Size; - } - if (other.TimeCreated != 0) { - TimeCreated = other.TimeCreated; - } - if (other.TimeModified != 0) { - TimeModified = other.TimeModified; - } - if (other.TimeAccessed != 0) { - TimeAccessed = other.TimeAccessed; - } - if (other.FileContents.Length != 0) { - FileContents = other.FileContents; - } - signatures_.Add(other.signatures_); - aCL_.Add(other.aCL_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - MitreID = input.ReadUInt32(); - break; - } - case 18: { - FileName = input.ReadString(); - break; - } - case 26: { - Description = input.ReadString(); - break; - } - case 34: { - Owner = input.ReadString(); - break; - } - case 40: { - Size = input.ReadUInt32(); - break; - } - case 48: { - TimeCreated = input.ReadUInt32(); - break; - } - case 56: { - TimeModified = input.ReadUInt32(); - break; - } - case 64: { - TimeAccessed = input.ReadUInt32(); - break; - } - case 74: { - FileContents = input.ReadBytes(); - break; - } - case 82: { - signatures_.AddEntriesFrom(input, _repeated_signatures_codec); - break; - } - case 90: { - aCL_.AddEntriesFrom(input, _repeated_aCL_codec); - break; - } - } - } - } - - #region Nested types - /// Container for nested types declared in the FileReactionData message type. - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static partial class Types { - public sealed partial class Signature : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Signature()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.FileReactionData.Descriptor.NestedTypes[0]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Signature() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Signature(Signature other) : this() { - signerName_ = other.signerName_; - digestAlgorithm_ = other.digestAlgorithm_; - timestamp_ = other.timestamp_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Signature Clone() { - return new Signature(this); - } - - /// Field number for the "signerName" field. - public const int SignerNameFieldNumber = 1; - private string signerName_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string SignerName { - get { return signerName_; } - set { - signerName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "digestAlgorithm" field. - public const int DigestAlgorithmFieldNumber = 2; - private string digestAlgorithm_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string DigestAlgorithm { - get { return digestAlgorithm_; } - set { - digestAlgorithm_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "timestamp" field. - public const int TimestampFieldNumber = 3; - private uint timestamp_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint Timestamp { - get { return timestamp_; } - set { - timestamp_ = value; - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as Signature); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(Signature other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (SignerName != other.SignerName) return false; - if (DigestAlgorithm != other.DigestAlgorithm) return false; - if (Timestamp != other.Timestamp) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (SignerName.Length != 0) hash ^= SignerName.GetHashCode(); - if (DigestAlgorithm.Length != 0) hash ^= DigestAlgorithm.GetHashCode(); - if (Timestamp != 0) hash ^= Timestamp.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (SignerName.Length != 0) { - output.WriteRawTag(10); - output.WriteString(SignerName); - } - if (DigestAlgorithm.Length != 0) { - output.WriteRawTag(18); - output.WriteString(DigestAlgorithm); - } - if (Timestamp != 0) { - output.WriteRawTag(24); - output.WriteUInt32(Timestamp); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (SignerName.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(SignerName); - } - if (DigestAlgorithm.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(DigestAlgorithm); - } - if (Timestamp != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Timestamp); - } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(Signature other) { - if (other == null) { - return; - } - if (other.SignerName.Length != 0) { - SignerName = other.SignerName; - } - if (other.DigestAlgorithm.Length != 0) { - DigestAlgorithm = other.DigestAlgorithm; - } - if (other.Timestamp != 0) { - Timestamp = other.Timestamp; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - SignerName = input.ReadString(); - break; - } - case 18: { - DigestAlgorithm = input.ReadString(); - break; - } - case 24: { - Timestamp = input.ReadUInt32(); - break; - } - } - } - } - - } - - } - #endregion - - } - - public sealed partial class RegistryReactionData : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RegistryReactionData()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[2]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RegistryReactionData() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RegistryReactionData(RegistryReactionData other) : this() { - mitreID_ = other.mitreID_; - key_ = other.key_; - data_ = other.data_; - newData_ = other.newData_; - type_ = other.type_; - aCL_ = other.aCL_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RegistryReactionData Clone() { - return new RegistryReactionData(this); - } - - /// Field number for the "mitreID" field. - public const int MitreIDFieldNumber = 1; - private uint mitreID_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint MitreID { - get { return mitreID_; } - set { - mitreID_ = value; - } - } - - /// Field number for the "key" field. - public const int KeyFieldNumber = 2; - private string key_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Key { - get { return key_; } - set { - key_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "data" field. - public const int DataFieldNumber = 3; - private pb::ByteString data_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString Data { - get { return data_; } - set { - data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "newData" field. - public const int NewDataFieldNumber = 4; - private pb::ByteString newData_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString NewData { - get { return newData_; } - set { - newData_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "type" field. - public const int TypeFieldNumber = 5; - private global::Gpb.RegistryReactionData.Types.RegType type_ = 0; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Gpb.RegistryReactionData.Types.RegType Type { - get { return type_; } - set { - type_ = value; - } - } - - /// Field number for the "ACL" field. - public const int ACLFieldNumber = 6; - private static readonly pb::FieldCodec _repeated_aCL_codec - = pb::FieldCodec.ForMessage(50, global::Gpb.ACLEntry.Parser); - private readonly pbc::RepeatedField aCL_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField ACL { - get { return aCL_; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as RegistryReactionData); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(RegistryReactionData other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (MitreID != other.MitreID) return false; - if (Key != other.Key) return false; - if (Data != other.Data) return false; - if (NewData != other.NewData) return false; - if (Type != other.Type) return false; - if(!aCL_.Equals(other.aCL_)) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (MitreID != 0) hash ^= MitreID.GetHashCode(); - if (Key.Length != 0) hash ^= Key.GetHashCode(); - if (Data.Length != 0) hash ^= Data.GetHashCode(); - if (NewData.Length != 0) hash ^= NewData.GetHashCode(); - if (Type != 0) hash ^= Type.GetHashCode(); - hash ^= aCL_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (MitreID != 0) { - output.WriteRawTag(8); - output.WriteUInt32(MitreID); - } - if (Key.Length != 0) { - output.WriteRawTag(18); - output.WriteString(Key); - } - if (Data.Length != 0) { - output.WriteRawTag(26); - output.WriteBytes(Data); - } - if (NewData.Length != 0) { - output.WriteRawTag(34); - output.WriteBytes(NewData); - } - if (Type != 0) { - output.WriteRawTag(40); - output.WriteEnum((int) Type); - } - aCL_.WriteTo(output, _repeated_aCL_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (MitreID != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MitreID); - } - if (Key.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Key); - } - if (Data.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); - } - if (NewData.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(NewData); - } - if (Type != 0) { - size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Type); - } - size += aCL_.CalculateSize(_repeated_aCL_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(RegistryReactionData other) { - if (other == null) { - return; - } - if (other.MitreID != 0) { - MitreID = other.MitreID; - } - if (other.Key.Length != 0) { - Key = other.Key; - } - if (other.Data.Length != 0) { - Data = other.Data; - } - if (other.NewData.Length != 0) { - NewData = other.NewData; - } - if (other.Type != 0) { - Type = other.Type; - } - aCL_.Add(other.aCL_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - MitreID = input.ReadUInt32(); - break; - } - case 18: { - Key = input.ReadString(); - break; - } - case 26: { - Data = input.ReadBytes(); - break; - } - case 34: { - NewData = input.ReadBytes(); - break; - } - case 40: { - Type = (global::Gpb.RegistryReactionData.Types.RegType) input.ReadEnum(); - break; - } - case 50: { - aCL_.AddEntriesFrom(input, _repeated_aCL_codec); - break; - } - } - } - } - - #region Nested types - /// Container for nested types declared in the RegistryReactionData message type. - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static partial class Types { - public enum RegType { - [pbr::OriginalName("SZ")] Sz = 0, - [pbr::OriginalName("MULTI_SZ")] MultiSz = 1, - [pbr::OriginalName("EXPAND_SZ")] ExpandSz = 2, - [pbr::OriginalName("BINARY")] Binary = 3, - [pbr::OriginalName("DWORD")] Dword = 4, - [pbr::OriginalName("QWORD")] Qword = 5, - } - - } - #endregion - - } - - public sealed partial class ProcessReactionData : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ProcessReactionData()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[3]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ProcessReactionData() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ProcessReactionData(ProcessReactionData other) : this() { - mitreID_ = other.mitreID_; - pid_ = other.pid_; - name_ = other.name_; - description_ = other.description_; - commandLine_ = other.commandLine_; - path_ = other.path_; - currentDirectory_ = other.currentDirectory_; - autostartLocation_ = other.autostartLocation_; - parent_ = other.parent_; - user_ = other.user_; - timeStarted_ = other.timeStarted_; - recievedBytes_ = other.recievedBytes_; - sentBytes_ = other.sentBytes_; - privateBytes_ = other.privateBytes_; - workingSet_ = other.workingSet_; - binaryContents_ = other.binaryContents_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ProcessReactionData Clone() { - return new ProcessReactionData(this); - } - - /// Field number for the "mitreID" field. - public const int MitreIDFieldNumber = 1; - private uint mitreID_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint MitreID { - get { return mitreID_; } - set { - mitreID_ = value; - } - } - - /// Field number for the "pid" field. - public const int PidFieldNumber = 2; - private uint pid_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint Pid { - get { return pid_; } - set { - pid_ = value; - } - } - - /// Field number for the "name" field. - public const int NameFieldNumber = 3; - private string name_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Name { - get { return name_; } - set { - name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "description" field. - public const int DescriptionFieldNumber = 4; - private string description_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Description { - get { return description_; } - set { - description_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "commandLine" field. - public const int CommandLineFieldNumber = 5; - private string commandLine_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string CommandLine { - get { return commandLine_; } - set { - commandLine_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "path" field. - public const int PathFieldNumber = 6; - private string path_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Path { - get { return path_; } - set { - path_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "currentDirectory" field. - public const int CurrentDirectoryFieldNumber = 7; - private string currentDirectory_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string CurrentDirectory { - get { return currentDirectory_; } - set { - currentDirectory_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "autostartLocation" field. - public const int AutostartLocationFieldNumber = 8; - private string autostartLocation_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string AutostartLocation { - get { return autostartLocation_; } - set { - autostartLocation_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "parent" field. - public const int ParentFieldNumber = 9; - private string parent_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Parent { - get { return parent_; } - set { - parent_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "user" field. - public const int UserFieldNumber = 10; - private string user_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string User { - get { return user_; } - set { - user_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "timeStarted" field. - public const int TimeStartedFieldNumber = 11; - private uint timeStarted_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint TimeStarted { - get { return timeStarted_; } - set { - timeStarted_ = value; - } - } - - /// Field number for the "recievedBytes" field. - public const int RecievedBytesFieldNumber = 12; - private uint recievedBytes_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint RecievedBytes { - get { return recievedBytes_; } - set { - recievedBytes_ = value; - } - } - - /// Field number for the "sentBytes" field. - public const int SentBytesFieldNumber = 13; - private uint sentBytes_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint SentBytes { - get { return sentBytes_; } - set { - sentBytes_ = value; - } - } - - /// Field number for the "privateBytes" field. - public const int PrivateBytesFieldNumber = 14; - private uint privateBytes_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint PrivateBytes { - get { return privateBytes_; } - set { - privateBytes_ = value; - } - } - - /// Field number for the "workingSet" field. - public const int WorkingSetFieldNumber = 15; - private uint workingSet_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint WorkingSet { - get { return workingSet_; } - set { - workingSet_ = value; - } - } - - /// Field number for the "binaryContents" field. - public const int BinaryContentsFieldNumber = 16; - private pb::ByteString binaryContents_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString BinaryContents { - get { return binaryContents_; } - set { - binaryContents_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as ProcessReactionData); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(ProcessReactionData other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (MitreID != other.MitreID) return false; - if (Pid != other.Pid) return false; - if (Name != other.Name) return false; - if (Description != other.Description) return false; - if (CommandLine != other.CommandLine) return false; - if (Path != other.Path) return false; - if (CurrentDirectory != other.CurrentDirectory) return false; - if (AutostartLocation != other.AutostartLocation) return false; - if (Parent != other.Parent) return false; - if (User != other.User) return false; - if (TimeStarted != other.TimeStarted) return false; - if (RecievedBytes != other.RecievedBytes) return false; - if (SentBytes != other.SentBytes) return false; - if (PrivateBytes != other.PrivateBytes) return false; - if (WorkingSet != other.WorkingSet) return false; - if (BinaryContents != other.BinaryContents) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (MitreID != 0) hash ^= MitreID.GetHashCode(); - if (Pid != 0) hash ^= Pid.GetHashCode(); - if (Name.Length != 0) hash ^= Name.GetHashCode(); - if (Description.Length != 0) hash ^= Description.GetHashCode(); - if (CommandLine.Length != 0) hash ^= CommandLine.GetHashCode(); - if (Path.Length != 0) hash ^= Path.GetHashCode(); - if (CurrentDirectory.Length != 0) hash ^= CurrentDirectory.GetHashCode(); - if (AutostartLocation.Length != 0) hash ^= AutostartLocation.GetHashCode(); - if (Parent.Length != 0) hash ^= Parent.GetHashCode(); - if (User.Length != 0) hash ^= User.GetHashCode(); - if (TimeStarted != 0) hash ^= TimeStarted.GetHashCode(); - if (RecievedBytes != 0) hash ^= RecievedBytes.GetHashCode(); - if (SentBytes != 0) hash ^= SentBytes.GetHashCode(); - if (PrivateBytes != 0) hash ^= PrivateBytes.GetHashCode(); - if (WorkingSet != 0) hash ^= WorkingSet.GetHashCode(); - if (BinaryContents.Length != 0) hash ^= BinaryContents.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (MitreID != 0) { - output.WriteRawTag(8); - output.WriteUInt32(MitreID); - } - if (Pid != 0) { - output.WriteRawTag(16); - output.WriteUInt32(Pid); - } - if (Name.Length != 0) { - output.WriteRawTag(26); - output.WriteString(Name); - } - if (Description.Length != 0) { - output.WriteRawTag(34); - output.WriteString(Description); - } - if (CommandLine.Length != 0) { - output.WriteRawTag(42); - output.WriteString(CommandLine); - } - if (Path.Length != 0) { - output.WriteRawTag(50); - output.WriteString(Path); - } - if (CurrentDirectory.Length != 0) { - output.WriteRawTag(58); - output.WriteString(CurrentDirectory); - } - if (AutostartLocation.Length != 0) { - output.WriteRawTag(66); - output.WriteString(AutostartLocation); - } - if (Parent.Length != 0) { - output.WriteRawTag(74); - output.WriteString(Parent); - } - if (User.Length != 0) { - output.WriteRawTag(82); - output.WriteString(User); - } - if (TimeStarted != 0) { - output.WriteRawTag(88); - output.WriteUInt32(TimeStarted); - } - if (RecievedBytes != 0) { - output.WriteRawTag(96); - output.WriteUInt32(RecievedBytes); - } - if (SentBytes != 0) { - output.WriteRawTag(104); - output.WriteUInt32(SentBytes); - } - if (PrivateBytes != 0) { - output.WriteRawTag(112); - output.WriteUInt32(PrivateBytes); - } - if (WorkingSet != 0) { - output.WriteRawTag(120); - output.WriteUInt32(WorkingSet); - } - if (BinaryContents.Length != 0) { - output.WriteRawTag(130, 1); - output.WriteBytes(BinaryContents); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (MitreID != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MitreID); - } - if (Pid != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Pid); - } - if (Name.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); - } - if (Description.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Description); - } - if (CommandLine.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(CommandLine); - } - if (Path.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Path); - } - if (CurrentDirectory.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(CurrentDirectory); - } - if (AutostartLocation.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(AutostartLocation); - } - if (Parent.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Parent); - } - if (User.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(User); - } - if (TimeStarted != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(TimeStarted); - } - if (RecievedBytes != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(RecievedBytes); - } - if (SentBytes != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(SentBytes); - } - if (PrivateBytes != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(PrivateBytes); - } - if (WorkingSet != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(WorkingSet); - } - if (BinaryContents.Length != 0) { - size += 2 + pb::CodedOutputStream.ComputeBytesSize(BinaryContents); - } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(ProcessReactionData other) { - if (other == null) { - return; - } - if (other.MitreID != 0) { - MitreID = other.MitreID; - } - if (other.Pid != 0) { - Pid = other.Pid; - } - if (other.Name.Length != 0) { - Name = other.Name; - } - if (other.Description.Length != 0) { - Description = other.Description; - } - if (other.CommandLine.Length != 0) { - CommandLine = other.CommandLine; - } - if (other.Path.Length != 0) { - Path = other.Path; - } - if (other.CurrentDirectory.Length != 0) { - CurrentDirectory = other.CurrentDirectory; - } - if (other.AutostartLocation.Length != 0) { - AutostartLocation = other.AutostartLocation; - } - if (other.Parent.Length != 0) { - Parent = other.Parent; - } - if (other.User.Length != 0) { - User = other.User; - } - if (other.TimeStarted != 0) { - TimeStarted = other.TimeStarted; - } - if (other.RecievedBytes != 0) { - RecievedBytes = other.RecievedBytes; - } - if (other.SentBytes != 0) { - SentBytes = other.SentBytes; - } - if (other.PrivateBytes != 0) { - PrivateBytes = other.PrivateBytes; - } - if (other.WorkingSet != 0) { - WorkingSet = other.WorkingSet; - } - if (other.BinaryContents.Length != 0) { - BinaryContents = other.BinaryContents; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - MitreID = input.ReadUInt32(); - break; - } - case 16: { - Pid = input.ReadUInt32(); - break; - } - case 26: { - Name = input.ReadString(); - break; - } - case 34: { - Description = input.ReadString(); - break; - } - case 42: { - CommandLine = input.ReadString(); - break; - } - case 50: { - Path = input.ReadString(); - break; - } - case 58: { - CurrentDirectory = input.ReadString(); - break; - } - case 66: { - AutostartLocation = input.ReadString(); - break; - } - case 74: { - Parent = input.ReadString(); - break; - } - case 82: { - User = input.ReadString(); - break; - } - case 88: { - TimeStarted = input.ReadUInt32(); - break; - } - case 96: { - RecievedBytes = input.ReadUInt32(); - break; - } - case 104: { - SentBytes = input.ReadUInt32(); - break; - } - case 112: { - PrivateBytes = input.ReadUInt32(); - break; - } - case 120: { - WorkingSet = input.ReadUInt32(); - break; - } - case 130: { - BinaryContents = input.ReadBytes(); - break; - } - } - } - } - - } - - public sealed partial class ServiceReactionData : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServiceReactionData()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[4]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ServiceReactionData() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ServiceReactionData(ServiceReactionData other) : this() { - mitreID_ = other.mitreID_; - pid_ = other.pid_; - name_ = other.name_; - description_ = other.description_; - logOnAs_ = other.logOnAs_; - startupType_ = other.startupType_; - startParameters_ = other.startParameters_; - allowDesktopInteract_ = other.allowDesktopInteract_; - runProgramPath_ = other.runProgramPath_; - binaryPath_ = other.binaryPath_; - binaryContents_ = other.binaryContents_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ServiceReactionData Clone() { - return new ServiceReactionData(this); - } - - /// Field number for the "mitreID" field. - public const int MitreIDFieldNumber = 1; - private uint mitreID_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint MitreID { - get { return mitreID_; } - set { - mitreID_ = value; - } - } - - /// Field number for the "pid" field. - public const int PidFieldNumber = 2; - private uint pid_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint Pid { - get { return pid_; } - set { - pid_ = value; - } - } - - /// Field number for the "name" field. - public const int NameFieldNumber = 3; - private string name_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Name { - get { return name_; } - set { - name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "description" field. - public const int DescriptionFieldNumber = 4; - private string description_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Description { - get { return description_; } - set { - description_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "logOnAs" field. - public const int LogOnAsFieldNumber = 5; - private string logOnAs_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string LogOnAs { - get { return logOnAs_; } - set { - logOnAs_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "startupType" field. - public const int StartupTypeFieldNumber = 6; - private string startupType_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string StartupType { - get { return startupType_; } - set { - startupType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "startParameters" field. - public const int StartParametersFieldNumber = 7; - private string startParameters_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string StartParameters { - get { return startParameters_; } - set { - startParameters_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "allowDesktopInteract" field. - public const int AllowDesktopInteractFieldNumber = 8; - private bool allowDesktopInteract_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool AllowDesktopInteract { - get { return allowDesktopInteract_; } - set { - allowDesktopInteract_ = value; - } - } - - /// Field number for the "runProgramPath" field. - public const int RunProgramPathFieldNumber = 9; - private string runProgramPath_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string RunProgramPath { - get { return runProgramPath_; } - set { - runProgramPath_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "binaryPath" field. - public const int BinaryPathFieldNumber = 10; - private string binaryPath_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string BinaryPath { - get { return binaryPath_; } - set { - binaryPath_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "binaryContents" field. - public const int BinaryContentsFieldNumber = 11; - private pb::ByteString binaryContents_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString BinaryContents { - get { return binaryContents_; } - set { - binaryContents_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as ServiceReactionData); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(ServiceReactionData other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (MitreID != other.MitreID) return false; - if (Pid != other.Pid) return false; - if (Name != other.Name) return false; - if (Description != other.Description) return false; - if (LogOnAs != other.LogOnAs) return false; - if (StartupType != other.StartupType) return false; - if (StartParameters != other.StartParameters) return false; - if (AllowDesktopInteract != other.AllowDesktopInteract) return false; - if (RunProgramPath != other.RunProgramPath) return false; - if (BinaryPath != other.BinaryPath) return false; - if (BinaryContents != other.BinaryContents) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (MitreID != 0) hash ^= MitreID.GetHashCode(); - if (Pid != 0) hash ^= Pid.GetHashCode(); - if (Name.Length != 0) hash ^= Name.GetHashCode(); - if (Description.Length != 0) hash ^= Description.GetHashCode(); - if (LogOnAs.Length != 0) hash ^= LogOnAs.GetHashCode(); - if (StartupType.Length != 0) hash ^= StartupType.GetHashCode(); - if (StartParameters.Length != 0) hash ^= StartParameters.GetHashCode(); - if (AllowDesktopInteract != false) hash ^= AllowDesktopInteract.GetHashCode(); - if (RunProgramPath.Length != 0) hash ^= RunProgramPath.GetHashCode(); - if (BinaryPath.Length != 0) hash ^= BinaryPath.GetHashCode(); - if (BinaryContents.Length != 0) hash ^= BinaryContents.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (MitreID != 0) { - output.WriteRawTag(8); - output.WriteUInt32(MitreID); - } - if (Pid != 0) { - output.WriteRawTag(16); - output.WriteUInt32(Pid); - } - if (Name.Length != 0) { - output.WriteRawTag(26); - output.WriteString(Name); - } - if (Description.Length != 0) { - output.WriteRawTag(34); - output.WriteString(Description); - } - if (LogOnAs.Length != 0) { - output.WriteRawTag(42); - output.WriteString(LogOnAs); - } - if (StartupType.Length != 0) { - output.WriteRawTag(50); - output.WriteString(StartupType); - } - if (StartParameters.Length != 0) { - output.WriteRawTag(58); - output.WriteString(StartParameters); - } - if (AllowDesktopInteract != false) { - output.WriteRawTag(64); - output.WriteBool(AllowDesktopInteract); - } - if (RunProgramPath.Length != 0) { - output.WriteRawTag(74); - output.WriteString(RunProgramPath); - } - if (BinaryPath.Length != 0) { - output.WriteRawTag(82); - output.WriteString(BinaryPath); - } - if (BinaryContents.Length != 0) { - output.WriteRawTag(90); - output.WriteBytes(BinaryContents); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (MitreID != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(MitreID); - } - if (Pid != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Pid); - } - if (Name.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); - } - if (Description.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Description); - } - if (LogOnAs.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(LogOnAs); - } - if (StartupType.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(StartupType); - } - if (StartParameters.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(StartParameters); - } - if (AllowDesktopInteract != false) { - size += 1 + 1; - } - if (RunProgramPath.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(RunProgramPath); - } - if (BinaryPath.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(BinaryPath); - } - if (BinaryContents.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(BinaryContents); - } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(ServiceReactionData other) { - if (other == null) { - return; - } - if (other.MitreID != 0) { - MitreID = other.MitreID; - } - if (other.Pid != 0) { - Pid = other.Pid; - } - if (other.Name.Length != 0) { - Name = other.Name; - } - if (other.Description.Length != 0) { - Description = other.Description; - } - if (other.LogOnAs.Length != 0) { - LogOnAs = other.LogOnAs; - } - if (other.StartupType.Length != 0) { - StartupType = other.StartupType; - } - if (other.StartParameters.Length != 0) { - StartParameters = other.StartParameters; - } - if (other.AllowDesktopInteract != false) { - AllowDesktopInteract = other.AllowDesktopInteract; - } - if (other.RunProgramPath.Length != 0) { - RunProgramPath = other.RunProgramPath; - } - if (other.BinaryPath.Length != 0) { - BinaryPath = other.BinaryPath; - } - if (other.BinaryContents.Length != 0) { - BinaryContents = other.BinaryContents; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - MitreID = input.ReadUInt32(); - break; - } - case 16: { - Pid = input.ReadUInt32(); - break; - } - case 26: { - Name = input.ReadString(); - break; - } - case 34: { - Description = input.ReadString(); - break; - } - case 42: { - LogOnAs = input.ReadString(); - break; - } - case 50: { - StartupType = input.ReadString(); - break; - } - case 58: { - StartParameters = input.ReadString(); - break; - } - case 64: { - AllowDesktopInteract = input.ReadBool(); - break; - } - case 74: { - RunProgramPath = input.ReadString(); - break; - } - case 82: { - BinaryPath = input.ReadString(); - break; - } - case 90: { - BinaryContents = input.ReadBytes(); - break; - } - } - } - } - - } - - public sealed partial class HuntInfo : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new HuntInfo()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[5]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public HuntInfo() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public HuntInfo(HuntInfo other) : this() { - huntName_ = other.huntName_; - huntAggressiveness_ = other.huntAggressiveness_; - huntTactics_ = other.huntTactics_.Clone(); - huntCategories_ = other.huntCategories_.Clone(); - huntDatasources_ = other.huntDatasources_.Clone(); - huntStartTime_ = other.huntStartTime_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public HuntInfo Clone() { - return new HuntInfo(this); - } - - /// Field number for the "huntName" field. - public const int HuntNameFieldNumber = 1; - private string huntName_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string HuntName { - get { return huntName_; } - set { - huntName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "huntAggressiveness" field. - public const int HuntAggressivenessFieldNumber = 2; - private global::Gpb.Aggressiveness huntAggressiveness_ = 0; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Gpb.Aggressiveness HuntAggressiveness { - get { return huntAggressiveness_; } - set { - huntAggressiveness_ = value; - } - } - - /// Field number for the "huntTactics" field. - public const int HuntTacticsFieldNumber = 3; - private static readonly pb::FieldCodec _repeated_huntTactics_codec - = pb::FieldCodec.ForEnum(26, x => (int) x, x => (global::Gpb.Tactic) x); - private readonly pbc::RepeatedField huntTactics_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField HuntTactics { - get { return huntTactics_; } - } - - /// Field number for the "huntCategories" field. - public const int HuntCategoriesFieldNumber = 4; - private static readonly pb::FieldCodec _repeated_huntCategories_codec - = pb::FieldCodec.ForEnum(34, x => (int) x, x => (global::Gpb.Category) x); - private readonly pbc::RepeatedField huntCategories_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField HuntCategories { - get { return huntCategories_; } - } - - /// Field number for the "huntDatasources" field. - public const int HuntDatasourcesFieldNumber = 5; - private static readonly pb::FieldCodec _repeated_huntDatasources_codec - = pb::FieldCodec.ForEnum(42, x => (int) x, x => (global::Gpb.DataSource) x); - private readonly pbc::RepeatedField huntDatasources_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField HuntDatasources { - get { return huntDatasources_; } - } - - /// Field number for the "huntStartTime" field. - public const int HuntStartTimeFieldNumber = 6; - private uint huntStartTime_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public uint HuntStartTime { - get { return huntStartTime_; } - set { - huntStartTime_ = value; - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as HuntInfo); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(HuntInfo other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (HuntName != other.HuntName) return false; - if (HuntAggressiveness != other.HuntAggressiveness) return false; - if(!huntTactics_.Equals(other.huntTactics_)) return false; - if(!huntCategories_.Equals(other.huntCategories_)) return false; - if(!huntDatasources_.Equals(other.huntDatasources_)) return false; - if (HuntStartTime != other.HuntStartTime) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (HuntName.Length != 0) hash ^= HuntName.GetHashCode(); - if (HuntAggressiveness != 0) hash ^= HuntAggressiveness.GetHashCode(); - hash ^= huntTactics_.GetHashCode(); - hash ^= huntCategories_.GetHashCode(); - hash ^= huntDatasources_.GetHashCode(); - if (HuntStartTime != 0) hash ^= HuntStartTime.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (HuntName.Length != 0) { - output.WriteRawTag(10); - output.WriteString(HuntName); - } - if (HuntAggressiveness != 0) { - output.WriteRawTag(16); - output.WriteEnum((int) HuntAggressiveness); - } - huntTactics_.WriteTo(output, _repeated_huntTactics_codec); - huntCategories_.WriteTo(output, _repeated_huntCategories_codec); - huntDatasources_.WriteTo(output, _repeated_huntDatasources_codec); - if (HuntStartTime != 0) { - output.WriteRawTag(48); - output.WriteUInt32(HuntStartTime); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (HuntName.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(HuntName); - } - if (HuntAggressiveness != 0) { - size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) HuntAggressiveness); - } - size += huntTactics_.CalculateSize(_repeated_huntTactics_codec); - size += huntCategories_.CalculateSize(_repeated_huntCategories_codec); - size += huntDatasources_.CalculateSize(_repeated_huntDatasources_codec); - if (HuntStartTime != 0) { - size += 1 + pb::CodedOutputStream.ComputeUInt32Size(HuntStartTime); - } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(HuntInfo other) { - if (other == null) { - return; - } - if (other.HuntName.Length != 0) { - HuntName = other.HuntName; - } - if (other.HuntAggressiveness != 0) { - HuntAggressiveness = other.HuntAggressiveness; - } - huntTactics_.Add(other.huntTactics_); - huntCategories_.Add(other.huntCategories_); - huntDatasources_.Add(other.huntDatasources_); - if (other.HuntStartTime != 0) { - HuntStartTime = other.HuntStartTime; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - HuntName = input.ReadString(); - break; - } - case 16: { - HuntAggressiveness = (global::Gpb.Aggressiveness) input.ReadEnum(); - break; - } - case 26: - case 24: { - huntTactics_.AddEntriesFrom(input, _repeated_huntTactics_codec); - break; - } - case 34: - case 32: { - huntCategories_.AddEntriesFrom(input, _repeated_huntCategories_codec); - break; - } - case 42: - case 40: { - huntDatasources_.AddEntriesFrom(input, _repeated_huntDatasources_codec); - break; - } - case 48: { - HuntStartTime = input.ReadUInt32(); - break; - } - } - } - } - - } - - public sealed partial class HuntMessage : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new HuntMessage()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ReactionDataReflection.Descriptor.MessageTypes[6]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public HuntMessage() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public HuntMessage(HuntMessage other) : this() { - info_ = other.info_ != null ? other.info_.Clone() : null; - extraMessage_ = other.extraMessage_; - fileDetections_ = other.fileDetections_.Clone(); - registryDetections_ = other.registryDetections_.Clone(); - processDetections_ = other.processDetections_.Clone(); - serviceDetections_ = other.serviceDetections_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public HuntMessage Clone() { - return new HuntMessage(this); - } - - /// Field number for the "info" field. - public const int InfoFieldNumber = 1; - private global::Gpb.HuntInfo info_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Gpb.HuntInfo Info { - get { return info_; } - set { - info_ = value; - } - } - - /// Field number for the "extraMessage" field. - public const int ExtraMessageFieldNumber = 3; - private string extraMessage_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string ExtraMessage { - get { return extraMessage_; } - set { - extraMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } - - /// Field number for the "fileDetections" field. - public const int FileDetectionsFieldNumber = 4; - private static readonly pb::FieldCodec _repeated_fileDetections_codec - = pb::FieldCodec.ForMessage(34, global::Gpb.FileReactionData.Parser); - private readonly pbc::RepeatedField fileDetections_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField FileDetections { - get { return fileDetections_; } - } - - /// Field number for the "registryDetections" field. - public const int RegistryDetectionsFieldNumber = 5; - private static readonly pb::FieldCodec _repeated_registryDetections_codec - = pb::FieldCodec.ForMessage(42, global::Gpb.RegistryReactionData.Parser); - private readonly pbc::RepeatedField registryDetections_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField RegistryDetections { - get { return registryDetections_; } - } - - /// Field number for the "processDetections" field. - public const int ProcessDetectionsFieldNumber = 6; - private static readonly pb::FieldCodec _repeated_processDetections_codec - = pb::FieldCodec.ForMessage(50, global::Gpb.ProcessReactionData.Parser); - private readonly pbc::RepeatedField processDetections_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField ProcessDetections { - get { return processDetections_; } - } - - /// Field number for the "serviceDetections" field. - public const int ServiceDetectionsFieldNumber = 7; - private static readonly pb::FieldCodec _repeated_serviceDetections_codec - = pb::FieldCodec.ForMessage(58, global::Gpb.ServiceReactionData.Parser); - private readonly pbc::RepeatedField serviceDetections_ = new pbc::RepeatedField(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pbc::RepeatedField ServiceDetections { - get { return serviceDetections_; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as HuntMessage); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(HuntMessage other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (!object.Equals(Info, other.Info)) return false; - if (ExtraMessage != other.ExtraMessage) return false; - if(!fileDetections_.Equals(other.fileDetections_)) return false; - if(!registryDetections_.Equals(other.registryDetections_)) return false; - if(!processDetections_.Equals(other.processDetections_)) return false; - if(!serviceDetections_.Equals(other.serviceDetections_)) return false; - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (info_ != null) hash ^= Info.GetHashCode(); - if (ExtraMessage.Length != 0) hash ^= ExtraMessage.GetHashCode(); - hash ^= fileDetections_.GetHashCode(); - hash ^= registryDetections_.GetHashCode(); - hash ^= processDetections_.GetHashCode(); - hash ^= serviceDetections_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (info_ != null) { - output.WriteRawTag(10); - output.WriteMessage(Info); - } - if (ExtraMessage.Length != 0) { - output.WriteRawTag(26); - output.WriteString(ExtraMessage); - } - fileDetections_.WriteTo(output, _repeated_fileDetections_codec); - registryDetections_.WriteTo(output, _repeated_registryDetections_codec); - processDetections_.WriteTo(output, _repeated_processDetections_codec); - serviceDetections_.WriteTo(output, _repeated_serviceDetections_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (info_ != null) { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(Info); - } - if (ExtraMessage.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(ExtraMessage); - } - size += fileDetections_.CalculateSize(_repeated_fileDetections_codec); - size += registryDetections_.CalculateSize(_repeated_registryDetections_codec); - size += processDetections_.CalculateSize(_repeated_processDetections_codec); - size += serviceDetections_.CalculateSize(_repeated_serviceDetections_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(HuntMessage other) { - if (other == null) { - return; - } - if (other.info_ != null) { - if (info_ == null) { - Info = new global::Gpb.HuntInfo(); - } - Info.MergeFrom(other.Info); - } - if (other.ExtraMessage.Length != 0) { - ExtraMessage = other.ExtraMessage; - } - fileDetections_.Add(other.fileDetections_); - registryDetections_.Add(other.registryDetections_); - processDetections_.Add(other.processDetections_); - serviceDetections_.Add(other.serviceDetections_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - if (info_ == null) { - Info = new global::Gpb.HuntInfo(); - } - input.ReadMessage(Info); - break; - } - case 26: { - ExtraMessage = input.ReadString(); - break; - } - case 34: { - fileDetections_.AddEntriesFrom(input, _repeated_fileDetections_codec); - break; - } - case 42: { - registryDetections_.AddEntriesFrom(input, _repeated_registryDetections_codec); - break; - } - case 50: { - processDetections_.AddEntriesFrom(input, _repeated_processDetections_codec); - break; - } - case 58: { - serviceDetections_.AddEntriesFrom(input, _repeated_serviceDetections_codec); - break; - } - } - } - } - - } - - #endregion - -} - -#endregion Designer generated code diff --git a/BLUESPAWN-server/grpc/generated/ServerServices.cs b/BLUESPAWN-server/grpc/generated/ServerServices.cs deleted file mode 100644 index 12afe955..00000000 --- a/BLUESPAWN-server/grpc/generated/ServerServices.cs +++ /dev/null @@ -1,145 +0,0 @@ -// -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: ServerServices.proto -// -#pragma warning disable 1591, 0612, 3021 -#region Designer generated code - -using pb = global::Google.Protobuf; -using pbc = global::Google.Protobuf.Collections; -using pbr = global::Google.Protobuf.Reflection; -using scg = global::System.Collections.Generic; -namespace Gpb { - - /// Holder for reflection information generated from ServerServices.proto - public static partial class ServerServicesReflection { - - #region Descriptor - /// File descriptor for ServerServices.proto - public static pbr::FileDescriptor Descriptor { - get { return descriptor; } - } - private static pbr::FileDescriptor descriptor; - - static ServerServicesReflection() { - byte[] descriptorData = global::System.Convert.FromBase64String( - string.Concat( - "ChRTZXJ2ZXJTZXJ2aWNlcy5wcm90bxIDZ3BiGhJSZWFjdGlvbkRhdGEucHJv", - "dG8iBwoFRW1wdHkyOwoGU2VydmVyEjEKD1NlbmRIdW50TWVzc2FnZRIQLmdw", - "Yi5IdW50TWVzc2FnZRoKLmdwYi5FbXB0eSIAYgZwcm90bzM=")); - descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, - new pbr::FileDescriptor[] { global::Gpb.ReactionDataReflection.Descriptor, }, - new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { - new pbr::GeneratedClrTypeInfo(typeof(global::Gpb.Empty), global::Gpb.Empty.Parser, null, null, null, null) - })); - } - #endregion - - } - #region Messages - public sealed partial class Empty : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Empty()); - private pb::UnknownFieldSet _unknownFields; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::Gpb.ServerServicesReflection.Descriptor.MessageTypes[0]; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Empty() { - OnConstruction(); - } - - partial void OnConstruction(); - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Empty(Empty other) : this() { - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Empty Clone() { - return new Empty(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as Empty); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(Empty other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - return Equals(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } - return hash; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(Empty other) { - if (other == null) { - return; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - } - } - } - - } - - #endregion - -} - -#endregion Designer generated code diff --git a/BLUESPAWN-server/grpc/generated/ServerServicesGrpc.cs b/BLUESPAWN-server/grpc/generated/ServerServicesGrpc.cs deleted file mode 100644 index 3bbd2b2b..00000000 --- a/BLUESPAWN-server/grpc/generated/ServerServicesGrpc.cs +++ /dev/null @@ -1,106 +0,0 @@ -// -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: ServerServices.proto -// -#pragma warning disable 0414, 1591 -#region Designer generated code - -using grpc = global::Grpc.Core; - -namespace Gpb { - public static partial class Server - { - static readonly string __ServiceName = "gpb.Server"; - - static readonly grpc::Marshaller __Marshaller_gpb_HuntMessage = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Gpb.HuntMessage.Parser.ParseFrom); - static readonly grpc::Marshaller __Marshaller_gpb_Empty = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Gpb.Empty.Parser.ParseFrom); - - static readonly grpc::Method __Method_SendHuntMessage = new grpc::Method( - grpc::MethodType.Unary, - __ServiceName, - "SendHuntMessage", - __Marshaller_gpb_HuntMessage, - __Marshaller_gpb_Empty); - - /// Service descriptor - public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor - { - get { return global::Gpb.ServerServicesReflection.Descriptor.Services[0]; } - } - - /// Base class for server-side implementations of Server - public abstract partial class ServerBase - { - public virtual global::System.Threading.Tasks.Task SendHuntMessage(global::Gpb.HuntMessage request, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } - - } - - /// Client for Server - public partial class ServerClient : grpc::ClientBase - { - /// Creates a new client for Server - /// The channel to use to make remote calls. - public ServerClient(grpc::Channel channel) : base(channel) - { - } - /// Creates a new client for Server that uses a custom CallInvoker. - /// The callInvoker to use to make remote calls. - public ServerClient(grpc::CallInvoker callInvoker) : base(callInvoker) - { - } - /// Protected parameterless constructor to allow creation of test doubles. - protected ServerClient() : base() - { - } - /// Protected constructor to allow creation of configured clients. - /// The client configuration. - protected ServerClient(ClientBaseConfiguration configuration) : base(configuration) - { - } - - public virtual global::Gpb.Empty SendHuntMessage(global::Gpb.HuntMessage request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SendHuntMessage(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - } - public virtual global::Gpb.Empty SendHuntMessage(global::Gpb.HuntMessage request, grpc::CallOptions options) - { - return CallInvoker.BlockingUnaryCall(__Method_SendHuntMessage, null, options, request); - } - public virtual grpc::AsyncUnaryCall SendHuntMessageAsync(global::Gpb.HuntMessage request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SendHuntMessageAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - } - public virtual grpc::AsyncUnaryCall SendHuntMessageAsync(global::Gpb.HuntMessage request, grpc::CallOptions options) - { - return CallInvoker.AsyncUnaryCall(__Method_SendHuntMessage, null, options, request); - } - /// Creates a new instance of client from given ClientBaseConfiguration. - protected override ServerClient NewInstance(ClientBaseConfiguration configuration) - { - return new ServerClient(configuration); - } - } - - /// Creates service definition that can be registered with a server - /// An object implementing the server-side handling logic. - public static grpc::ServerServiceDefinition BindService(ServerBase serviceImpl) - { - return grpc::ServerServiceDefinition.CreateBuilder() - .AddMethod(__Method_SendHuntMessage, serviceImpl.SendHuntMessage).Build(); - } - - /// Register service method with a service binder with or without implementation. Useful when customizing the service binding logic. - /// Note: this method is part of an experimental API that can change or be removed without any prior notice. - /// Service methods will be bound by calling AddMethod on this object. - /// An object implementing the server-side handling logic. - public static void BindService(grpc::ServiceBinderBase serviceBinder, ServerBase serviceImpl) - { - serviceBinder.AddMethod(__Method_SendHuntMessage, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.SendHuntMessage)); - } - - } -} -#endregion diff --git a/BLUESPAWN-win-client/.clang-format b/BLUESPAWN-win-client/.clang-format new file mode 100644 index 00000000..272cb13c --- /dev/null +++ b/BLUESPAWN-win-client/.clang-format @@ -0,0 +1,92 @@ +BasedOnStyle: WebKit +AccessModifierOffset: 0 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: true +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DeriveLineEnding: true +DerivePointerAlignment: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '<.*\.h>' + Priority: 1 + - Regex: '<.*>' + Priority: 2 + - Regex: '"common.*"' + Priority: 3 + - Regex: '"util.*"' + Priority: 4 + - Regex: '.*' + Priority: 100 +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 40 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 3 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseCRLF: true +UseTab: Never \ No newline at end of file diff --git a/BLUESPAWN-client/BLUESPAWN-client.exe.manifest b/BLUESPAWN-win-client/BLUESPAWN-client.exe.manifest similarity index 97% rename from BLUESPAWN-client/BLUESPAWN-client.exe.manifest rename to BLUESPAWN-win-client/BLUESPAWN-client.exe.manifest index f44da4f6..b255bd83 100644 --- a/BLUESPAWN-client/BLUESPAWN-client.exe.manifest +++ b/BLUESPAWN-win-client/BLUESPAWN-client.exe.manifest @@ -1,25 +1,25 @@ - - - BLUESPAWN Active Defense and EDR Software - - - - - - - - - - - - - - - - - - - - - + + + BLUESPAWN Active Defense and EDR Software + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BLUESPAWN-client/BLUESPAWN-client.vcxproj b/BLUESPAWN-win-client/BLUESPAWN-client.vcxproj similarity index 56% rename from BLUESPAWN-client/BLUESPAWN-client.vcxproj rename to BLUESPAWN-win-client/BLUESPAWN-client.vcxproj index a733674a..5b5998d7 100644 --- a/BLUESPAWN-client/BLUESPAWN-client.vcxproj +++ b/BLUESPAWN-win-client/BLUESPAWN-client.vcxproj @@ -1,4 +1,4 @@ - + @@ -19,37 +19,24 @@ - - - - - - - - - - - - - - - - - + - - - - + + + + + + + @@ -58,6 +45,7 @@ + @@ -88,17 +76,30 @@ - true - true - true - true + true + + + + + + + + + + + + + + + + @@ -108,58 +109,38 @@ - + - + - + - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - - - + @@ -171,38 +152,28 @@ - - - - - - - - - - - - - - - + - - - - + + + + + + + + @@ -235,17 +206,23 @@ - true - true - true - true + true + + + + + + + + + @@ -253,66 +230,46 @@ - + - - + - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - true - true - true - true + true - - + + + + - - {25ae1d80-3e17-4e1d-bfb4-8afb375ebaf1} - {bec01f8e-5892-3f6f-a741-5bbd1d0f4ef9} @@ -321,6 +278,7 @@ + @@ -331,6 +289,7 @@ + BLUESPAWN-client false @@ -341,15 +300,10 @@ $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log - $(SolutionDir)BLUESPAWN-client\external\pe-sieve\libpeconv\libpeconv\include;$(SolutionDir)BLUESPAWN-client\external\pe-sieve\;$(SolutionDir)BLUESPAWN-client\external\pe-sieve\include;$(SolutionDir)BLUESPAWN-client\external\cxxopts\include;%(AdditionalIncludeDirectories) - MultiThreaded - MultiThreaded - MultiThreadedDebug - MultiThreadedDebug - Async - Async - Async - Async + $(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\libpeconv\libpeconv\include;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\include;%(AdditionalIncludeDirectories) + MultiThreaded + MultiThreadedDebug + Async Secur32.lib;DbgHelp.lib;Wintrust.lib;ws2_32.lib;Crypt32.lib;Shlwapi.lib;%(AdditionalDependencies) @@ -367,6 +321,7 @@ 10.0 x86-windows-static x64-windows-static + BLUESPAWN-win-client @@ -378,8 +333,6 @@ - - \ No newline at end of file diff --git a/BLUESPAWN-win-client/external/pe-sieve b/BLUESPAWN-win-client/external/pe-sieve new file mode 160000 index 00000000..78db1a1e --- /dev/null +++ b/BLUESPAWN-win-client/external/pe-sieve @@ -0,0 +1 @@ +Subproject commit 78db1a1e73614828f8d9e71a4502aae4414930b7 diff --git a/BLUESPAWN-win-client/external/signature-base b/BLUESPAWN-win-client/external/signature-base new file mode 160000 index 00000000..1ad15c60 --- /dev/null +++ b/BLUESPAWN-win-client/external/signature-base @@ -0,0 +1 @@ +Subproject commit 1ad15c60955e7f0604ac162cb717d6d4ed835801 diff --git a/BLUESPAWN-win-client/external/tinyxml2 b/BLUESPAWN-win-client/external/tinyxml2 new file mode 160000 index 00000000..2c5a6bfd --- /dev/null +++ b/BLUESPAWN-win-client/external/tinyxml2 @@ -0,0 +1 @@ +Subproject commit 2c5a6bfdd42ab919e55a613d33c83eb53de71af4 diff --git a/BLUESPAWN-win-client/external/yara-rules b/BLUESPAWN-win-client/external/yara-rules new file mode 160000 index 00000000..6636eafa --- /dev/null +++ b/BLUESPAWN-win-client/external/yara-rules @@ -0,0 +1 @@ +Subproject commit 6636eafa1298ad431b501f1c76df2977229dcbf7 diff --git a/BLUESPAWN-win-client/headers/hunt/Hunt.h b/BLUESPAWN-win-client/headers/hunt/Hunt.h new file mode 100644 index 00000000..0dc7fc22 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/Hunt.h @@ -0,0 +1,135 @@ +#pragma once +#include + +#include +#include +#include +#include + +#include "HuntInfo.h" +#include "Scope.h" +#include "monitor/Event.h" + +class HuntRegister; + +#define GET_INFO() \ + HuntInfo { \ + this->name, this->dwTacticsUsed, this->dwCategoriesAffected, this->dwSourcesInvolved, \ + (long) std::chrono::duration_cast( \ + std::chrono::system_clock::now().time_since_epoch()) \ + .count() \ + } + +#define HUNT_INIT() \ + std::vector> detections{}; \ + auto __name{ this->name }; \ + LOG_INFO(1, "Beginning hunt for " << __name); + +#define SUBTECHNIQUE_INIT(id, desc) \ + if(!scope.Subtechniques || *scope.Subtechniques & (1 << id)) { \ + auto __name{ (std::wstringstream{} << this->name << L" Subtechnique " << std::setfill(L'0') \ + << std::setw(3) << id << L": " #desc).str() }; +#define SUBTECHNIQUE_END() } + +#define SUBSECTION_INIT(id, intensity) \ + if(!scope.Subsections || *scope.Subsections & (1 << id)) { \ + if(Bluespawn::aggressiveness < Aggressiveness::##intensity) { \ + LOG_INFO(1, L"Skipping " << __name \ + << L" subsection " #id "; rerun BLUESPAWN at " #intensity " to run this."); \ + } else { +#define SUBSECTION_END() \ + } \ + } + +#define SCOPE(scope) \ + Scope::CreateSubhuntScope(1 << scope) + +#define HUNT_END() \ + LOG_INFO(2, "Finished hunt for " << __name); \ + return detections; + +#define CREATE_DETECTION(certainty, ...) \ + detections.emplace_back( \ + Bluespawn::detections.AddDetection(Detection{ __VA_ARGS__, DetectionContext{ __name } }, certainty)); + +#define CREATE_DETECTION_WITH_CONTEXT(certainty, ...) \ + detections.emplace_back(Bluespawn::detections.AddDetection( \ + Detection{ \ + __VA_ARGS__, \ + }, \ + certainty)); + +class Hunt { + protected: + /// The tactics used by the hunt, computed as a bitwise OR of entries in the enum Tactic + DWORD dwTacticsUsed; + + /// The data sources used by the hunt, computed as a bitwise OR of entries in the enum DataSource + DWORD dwSourcesInvolved; + + /// The categories affected by the hunt, computed as a bitwise OR of entries in the enum Category + DWORD dwCategoriesAffected; + + /// The name of the hunt + std::wstring name; + + public: + /** + * Instantiates a new hunt by the given name. Note that names should be unique and include + * the technique number as well as the name (Such as T1004 - Winlogon Helper). + * + * @param name The name of the hunt + */ + Hunt(IN CONST std::wstring& name); + + /** + * Retrieves the name of the hunt + * + * @return The name of the hunt + */ + std::wstring GetName(); + + /** + * Indicate whether the hunt uses all specified tactics from the Tactic enum. + * + * @param tactics The tactics to check, computed as a bitwise OR of the Tactic enum. + * + * @return True if the hunt uses all specified tactics; false otherwise. + */ + bool UsesTactics(IN DWORD tactics); + + /** + * Indicate whether the hunt uses all specified sources from the Source enum. + * + * @param source The source to check, computed as a bitwise OR of the Source enum. + * + * @return True if the hunt uses all specified sources; false otherwise. + */ + bool UsesSources(IN DWORD sources); + + /** + * Indicate whether the hunt uses all specified categories from the Category enum. + * + * @param categories The categories to check, computed as a bitwise OR of the Category enum. + * + * @return True if the hunt uses affects specified categories; false otherwise. + */ + bool AffectsCategory(IN DWORD categories); + + /** + * Runs the hunt, returning references to the detections found. + * + * @param scope The scope of the hunt + * + * @return A vector of references to the detections identified. + */ + virtual std::vector> RunHunt(IN CONST Scope& scope); + + /** + * Retrieves a vector of events to be signalled when the hunt should be rerun. This should only + * be called once, as the events will be duplicated if called multiple times. + * + * @return a vector of event pointers + */ + virtual std::vector, Scope>> GetMonitoringEvents(); +}; diff --git a/BLUESPAWN-client/headers/hunt/HuntInfo.h b/BLUESPAWN-win-client/headers/hunt/HuntInfo.h similarity index 78% rename from BLUESPAWN-client/headers/hunt/HuntInfo.h rename to BLUESPAWN-win-client/headers/hunt/HuntInfo.h index 1ce24dc0..d66ee149 100644 --- a/BLUESPAWN-client/headers/hunt/HuntInfo.h +++ b/BLUESPAWN-win-client/headers/hunt/HuntInfo.h @@ -41,16 +41,15 @@ enum class Category { enum class Aggressiveness { Cursory = 0x1, // Most obvious indicators (least false positives) Normal = 0x2, // Examine more things - Intensive = 0x4 // Check everything imaginable (most false positives) + Intensive = 0x3 // Check everything imaginable (most false positives) }; // This struct is a POD type for storing information about a hunt to be logged. struct HuntInfo { std::wstring HuntName; - Aggressiveness HuntAggressiveness; DWORD HuntTactics; DWORD HuntCategories; DWORD HuntDatasources; - SYSTEMTIME HuntStartTime; - HuntInfo(const std::wstring& HuntName, Aggressiveness HuntAggressiveness, DWORD HuntTactics, DWORD HuntCategories, DWORD HuntDatasources); -}; \ No newline at end of file + long HuntStartTime; + HuntInfo(const std::wstring& HuntName, DWORD HuntTactics, DWORD HuntCategories, DWORD HuntDatasources, long HuntStartTime); +}; diff --git a/BLUESPAWN-win-client/headers/hunt/HuntRegister.h b/BLUESPAWN-win-client/headers/hunt/HuntRegister.h new file mode 100644 index 00000000..c08300c5 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/HuntRegister.h @@ -0,0 +1,78 @@ +#pragma once +#include + +#include +#include +#include + +#include "Hunt.h" +#include "Scope.h" + +#include "util/Promise.h" +/** + * HuntRegister is a class meant to be used to manage running hunts and monitoring. + * Rather than make HuntRegister a singleton, all members are instead static. + */ +class HuntRegister { + private: + /// A vector of registered hunts + static std::vector> vRegisteredHunts; + + /* Called before a hunt is run or added to monitor mode to see if it should be enabled. This + * provide the ability to use the --hunts or --exclude-hunts flags to limit to looking for only + * certain MITRE ATT&CK Techniques + * @param hunt The particular hunt to check + * @param vExcludedHunts A vector of Technique IDs that should be excluded + * @param vIncludedHunts A vector of Technique IDs that should be the only ones to run + * + * @return A boolean whether or not the hunt should be included + */ + static bool HuntRegister::HuntShouldRun(IN Hunt* hunt, + IN CONST std::vector vIncludedHunts, + IN CONST std::vector vExcludedHunts); + + public: + /** + * Runs all hunts registered with the RegisterHunt function with the given scope. + * note that the resulting vector of detections contains only items that *may* be + * malicious. Furthermore, they may be duplicates and numerous false positives present. + * These detections should be passed to scan mode for further analysis. + * + * @param scope An optional scope object representing the limitations of the hunt + * @param async A boolean indicating whether this function should wait for all hunts + * to finish before returning. + * + * @return A vector of possibly malicious items. + */ + static std::vector>>> RunHunts( + IN CONST std::vector vIncludedHunts, + IN CONST std::vector vExcludedHunts, + IN CONST Scope& scope = {} OPTIONAL, + IN CONST bool async = false OPTIONAL); + + /** + * Queues a specified hunt, returning a promise for its result + * + * @param hunt The hunt to queue + * @param An optional scope object representing the limitations of the hunt + */ + static Promise>> RunHunt( + IN Hunt* hunt, IN CONST Scope& scope = {} OPTIONAL); + + /** + * Sets up monitoring mode by subscribing to all monitor events for each hunt in + * vRegisteredHunts. Note that in earlier versions of Windows, the thread that calls + * this function being terminated will result in the event subscriptions ending. + */ + static void SetupMonitoring(IN CONST std::vector vIncludedHunts, + IN CONST std::vector vExcludedHunts); + + /** + * Registers a hunt. This must be called prior to SetupMonitoring or RunHunts in + * order for the registered hunt to be run. Note that ownership of the hunt must be + * transferred to the HuntRegister. + * + * @param hunt A unique pointer to the hunt + */ + static void RegisterHunt(IN std::unique_ptr&& hunt); +}; diff --git a/BLUESPAWN-client/headers/hunt/RegistryHunt.h b/BLUESPAWN-win-client/headers/hunt/RegistryHunt.h similarity index 100% rename from BLUESPAWN-client/headers/hunt/RegistryHunt.h rename to BLUESPAWN-win-client/headers/hunt/RegistryHunt.h diff --git a/BLUESPAWN-win-client/headers/hunt/Scope.h b/BLUESPAWN-win-client/headers/hunt/Scope.h new file mode 100644 index 00000000..0a2d6ed0 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/Scope.h @@ -0,0 +1,23 @@ +#pragma once +#include + +#include +#include + +/** + * Used to define the scope of a hunt. Currently, this operates by requiring the programmer to + * define a new class for each new scope. This is less than ideal, as scopes should eventually + * be defined by the end user. Future implementation will allow the programmer to pass in lambdas + * which will be handled by the functions built in to the class, removing the need for new scopes. + */ +class Scope { + public: + /// This field is specific to the hunt being run. It is computed as a bitwise OR of segments of the hunt. Note that + /// subsections should be unique per hunt and that different subtechniques should not use the same hunt segments. + std::optional Subsections; + + /// This field is specific to the hunt being run. It is computed as a bitwise OR of subtechnique IDs to be run. + std::optional Subtechniques; + + static Scope CreateSubhuntScope(IN DWORD64 Subsections, IN DWORD Subtechniques = -1UL OPTIONAL); +}; diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1036.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1036.h new file mode 100644 index 00000000..6e051f95 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1036.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1036 examines the local file system for executables in user writable + * locations in %WINDIR% + * + * @scans Cursory checks all such writable folders for executable files + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. + */ + class HuntT1036 : public Hunt { + private: + // Credit: https://twitter.com/mattifestation/status/1172520995472756737/photo/1 + std::vector writableFolders = { L"%WINDIR%\\System32\\Microsoft\\crypto\\rsa\\machinekeys", + L"%WINDIR%\\System32\\tasks_" + L"migrated\\microsoft\\windows\\pla\\system", + L"%WINDIR%\\Syswow64\\tasks\\microsoft\\windows\\pla\\system", + L"%WINDIR%\\debug\\WIA", + L"%WINDIR%\\System32\\Tasks", + L"%WINDIR%\\Syswow64\\Tasks", + L"%WINDIR%\\Tasks", + L"%WINDIR%\\Registration\\crmlog", + L"%WINDIR%\\System32\\com\\dmp", + L"%WINDIR%\\System32\\fxstmp", + L"%WINDIR%\\System32\\spool\\drivers\\color", + L"%WINDIR%\\System32\\spool\\printers", + L"%WINDIR%\\System32\\spool\\servers", + L"%WINDIR%\\Syswow64\\com\\dmp", + L"%WINDIR%\\Syswow64\\fxstmp", + L"%WINDIR%\\Temp", + L"%WINDIR%\\tracing" }; + + public: + HuntT1036(); + + void Subtechnique005(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1037.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1037.h new file mode 100644 index 00000000..94dc4638 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1037.h @@ -0,0 +1,22 @@ +#pragma once +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1037 examines the registry and filesystem for logon scripts + */ + class HuntT1037 : public Hunt { + + public: + HuntT1037(); + + void Subtechnique001(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1053.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1053.h new file mode 100644 index 00000000..06c6f38c --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1053.h @@ -0,0 +1,25 @@ +#pragma once +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1053 looks for malicious activity hidden in scheduled tasks/jobs + * T1053.005: examines Windows events for new scheduled tasks + * + * @monitor Triggers a hunt whenever Security log event ID 4698/Task-Scheduler 106 is generated + */ + class HuntT1053 : public Hunt { + + public: + HuntT1053(); + + std::vector Get4698Events(); + std::vector Get106Events(); + + void Subtechnique005(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1055.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1055.h new file mode 100644 index 00000000..fb75a968 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1055.h @@ -0,0 +1,32 @@ +#pragma once +#include "util/DynamicLinker.h" +#include "util/Promise.h" + +#include "hunt/Hunt.h" +#include "pe_sieve.h" +#include "pe_sieve_types.h" + +namespace Hunts { + + /** + * HuntT1055 examines all processes for shellcode injections, injected PE images, + * function hooks, and doppelganging. This individual hunt will eventually be broken + * into separate hunts + */ + class HuntT1055 : public Hunt { + public: + HuntT1055(); + + /** + * Handles waiting for the promise to be fufilled, checking for invalidated data, and recording any detections + * that have been identified. + * + * @param detections A vector of detections to which any new detections will be added + * @param promise A promise for the result of a process scan + */ + void HuntT1055::HandleReport(OUT std::vector>& detections, + IN CONST Promise>& promise); + + virtual std::vector> RunHunt(const Scope& scope) override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1068.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1068.h new file mode 100644 index 00000000..0393ce9c --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1068.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1068 examines the registry and file system for evidence of CVE-2020-1048. + */ + class HuntT1068 : public Hunt { + public: + HuntT1068(); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1070.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1070.h new file mode 100644 index 00000000..c23133fe --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1070.h @@ -0,0 +1,22 @@ +#pragma once +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1070 looks for evidence attackers tried to cover their tracks. + * Currently examines Sysmon logs looking for timestomp events + * + * @monitor Triggers a hunt whenever Sysmon log event ID 2 is generated + */ + class HuntT1070 : public Hunt { + + public: + HuntT1070(); + + void Subtechnique006(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1136.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1136.h new file mode 100644 index 00000000..27ac1989 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1136.h @@ -0,0 +1,25 @@ +#pragma once +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1136 examines Windows events for new accounts created + * T1136.001: looks for local Windows accounts that were created + * + * @monitor Triggers a hunt whenever Security log event ID 4720 is generated + */ + class HuntT1136 : public Hunt { + private: + std::wstring t1136_001 = L"001: Local Account"; + + public: + HuntT1136(); + + void Subtechnique001(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts +#pragma once diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1484.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1484.h new file mode 100644 index 00000000..cd6cae3f --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1484.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1484 examines the local file system for presence of ntuser.man files which + * can be used to override GPO settings + */ + class HuntT1484 : public Hunt { + public: + HuntT1484(); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1505.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1505.h new file mode 100644 index 00000000..b86dac2d --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1505.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1505 looks for the abuse of legitimate extensible software components + * T1505.003: examines the locations of web roots, looking for files that are likely to be + * webshells. + */ + class HuntT1505 : public Hunt { + + std::vector web_directories = { L"C:\\inetpub\\wwwroot", L"C:\\xampp\\htdocs" }; + std::vector web_exts = { L".php", L".jsp", L".jspx", L".asp", + L".aspx", L".asmx", L".ashx", L".ascx" }; + std::regex php_vuln_functions{}; + std::regex asp_indicators{}; + std::regex jsp_indicators{}; + std::smatch match_index; + + public: + HuntT1505(); + + void Subtechnique003(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope); + virtual std::vector, Scope>> GetMonitoringEvents(); + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1543.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1543.h new file mode 100644 index 00000000..e57f1bcd --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1543.h @@ -0,0 +1,22 @@ +#pragma once +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1543 examines system services for evidence of bad. + * T1543.003: examines Windows events for new services created + * + * @monitor Triggers a hunt whenever System log event ID 7045 is generated + */ + class HuntT1543 : public Hunt { + + public: + HuntT1543(); + + void Subtechnique003(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1546.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1546.h new file mode 100644 index 00000000..eb618cca --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1546.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1546 looks for event triggered persistence attacks. Currently works below: + * T1547.007: examines the system for malicious Netsh Helper DLLs + * T1546.008: examines Windows Accessibility Features to see if they have been messed + * T1546.009: examines the installed AppCertDlls to see if any are malicious + * t1546.010: examines the installed AppInit_Dlls to see if any are malicious + * t1546.011: examines the installed shims to see if any are malicious + * T1546.012: examines IFEOs for debuggers and silent process exit hooks + * T1546.015: examines CLSID registry values to detect COM hijacking + */ + class HuntT1546 : public Hunt { + + std::vector vAccessibilityBinaries = { L"sethc.exe", L"utilman.exe", L"osk.exe", + L"Magnify.exe", L"Narrator.exe", L"DisplaySwitch.exe", + L"AtBroker.exe" }; + + std::wstring wsIFEO = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"; + + public: + HuntT1546(); + + void Subtechnique007(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique008(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique009(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique010(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique011(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique012(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique015(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(IN CONST Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1547.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1547.h new file mode 100644 index 00000000..f7ac37ed --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1547.h @@ -0,0 +1,35 @@ +#pragma once +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1547 looks for malicious boot or logon autostart execution activity + * + * T1547.001: examines the registry for run keys and filesystem for startup items + * T1547.002: examines the registry and filesystem for malicious APs + * T1547.004: examines the registry for Winlogon helper persistence + * T1547.005: examines the registry and filesystem for malicious SSPs + * T1547.010: examines the registry for bad port monitors + * + * @scans Cursory checks for bad DLLs configured as port monitors + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. + */ + class HuntT1547 : public Hunt { + + std::vector RunKeys; + + public: + HuntT1547(); + + void Subtechnique001(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique002(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique004(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique005(IN CONST Scope& scope, OUT std::vector>& detections); + void Subtechnique010(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1553.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1553.h new file mode 100644 index 00000000..08986f01 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1553.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1553 looks for attackers blending in by abusing trust on the system. + * T1553.003: examines Subject Interface Providers and Trust Providers, which can + * be used by malicious actors to cause malicious payloads to appear signed, and + * establish persistence + */ + class HuntT1553 : public Hunt { + + public: + HuntT1553(); + + void Subtechnique003(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1562.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1562.h new file mode 100644 index 00000000..83c35a3f --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1562.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1562 looks for ways attacks impair defenses. Currently examines the registry for firewall + * settings that allow applications to override the existing firewall rules. + */ + class HuntT1562 : public Hunt { + private: + std::wstring t1562_004 = L"004: Disable or Modify System Firewall"; + + public: + HuntT1562(); + + void Subtechnique004(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1569.h b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1569.h new file mode 100644 index 00000000..1a1ed9e8 --- /dev/null +++ b/BLUESPAWN-win-client/headers/hunt/hunts/HuntT1569.h @@ -0,0 +1,29 @@ +#pragma once +#include + +#include + +#include "../Hunt.h" + +namespace Hunts { + + /** + * HuntT1569 examines the system for malicious services + * + * @scans Cursory scans the services that are installed and their binaries + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. + */ + class HuntT1569 : public Hunt { + private: + std::wstring t1569_002 = L"002: Service Execution"; + + public: + HuntT1569(); + + void Subtechnique002(IN CONST Scope& scope, OUT std::vector>& detections); + + virtual std::vector> RunHunt(const Scope& scope) override; + virtual std::vector, Scope>> GetMonitoringEvents() override; + }; +} // namespace Hunts diff --git a/BLUESPAWN-client/headers/mitigation/Mitigation.h b/BLUESPAWN-win-client/headers/mitigation/Mitigation.h similarity index 100% rename from BLUESPAWN-client/headers/mitigation/Mitigation.h rename to BLUESPAWN-win-client/headers/mitigation/Mitigation.h diff --git a/BLUESPAWN-client/headers/mitigation/MitigationRegister.h b/BLUESPAWN-win-client/headers/mitigation/MitigationRegister.h similarity index 100% rename from BLUESPAWN-client/headers/mitigation/MitigationRegister.h rename to BLUESPAWN-win-client/headers/mitigation/MitigationRegister.h diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1025.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1025.h similarity index 90% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1025.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1025.h index 15c3e909..20e137e0 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1025.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1025.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1028-WFW.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1028-WFW.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1028-WFW.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1028-WFW.h index 1c10a830..9fd86637 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1028-WFW.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1028-WFW.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1035-RDP.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1035-RDP.h similarity index 73% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1035-RDP.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1035-RDP.h index a5849528..270ad698 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1035-RDP.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1035-RDP.h @@ -1,8 +1,6 @@ #pragma once -#include "../Mitigation.h" -#include -#include "reaction/Reaction.h" -#include "reaction/Log.h" +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" namespace Mitigations { /** diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h index c864f54d..4b8946d3 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-NBT.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-NBT.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-NBT.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-NBT.h index c3ab8f5a..85a7e2ac 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-NBT.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-NBT.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-WSH.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-WSH.h similarity index 91% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-WSH.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-WSH.h index 081c172e..99f82029 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-WSH.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1042-WSH.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1047.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1047.h similarity index 95% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1047.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1047.h index 25fa6cdd..dfb7c98f 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1047.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1047.h @@ -2,7 +2,6 @@ #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" namespace Mitigations { diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1054-RDP.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1054-RDP.h similarity index 72% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1054-RDP.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1054-RDP.h index 00ec1909..8cb889da 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1054-RDP.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1054-RDP.h @@ -1,8 +1,6 @@ #pragma once -#include "../Mitigation.h" -#include -#include "reaction/Reaction.h" -#include "reaction/Log.h" +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" namespace Mitigations { /** diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1054-WSC.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1054-WSC.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1054-WSC.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1054-WSC.h index bc1c0564..72b0ecf9 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1054-WSC.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateM1054-WSC.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1093.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV1093.h similarity index 100% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1093.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV1093.h diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1153.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV1153.h similarity index 100% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1153.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV1153.h diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3338.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3338.h similarity index 77% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3338.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3338.h index 827d1d50..826a98b8 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3338.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3338.h @@ -1,8 +1,6 @@ #pragma once -#include "../Mitigation.h" -#include -#include "reaction/Reaction.h" -#include "reaction/Log.h" +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" namespace Mitigations { diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3340.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3340.h similarity index 75% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3340.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3340.h index c57e1968..3c4ce32c 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3340.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3340.h @@ -1,8 +1,6 @@ #pragma once -#include "../Mitigation.h" -#include -#include "reaction/Reaction.h" -#include "reaction/Log.h" +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" namespace Mitigations { diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3344.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3344.h similarity index 88% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3344.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3344.h index 4b1ab010..fdcce0a2 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3344.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3344.h @@ -1,8 +1,6 @@ #pragma once #include "../Mitigation.h" #include -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3379.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3379.h similarity index 100% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3379.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3379.h diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3479.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3479.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3479.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3479.h index 40cb0b76..6f7c6bfc 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV3479.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV3479.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63597.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63597.h similarity index 90% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63597.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63597.h index 6c391859..f1422a63 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63597.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63597.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63687.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63687.h similarity index 88% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63687.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63687.h index a0e660ce..2aaf572d 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63687.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63687.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63753.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63753.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63753.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63753.h index 46f93b61..29c141c0 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63753.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63753.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63817.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63817.h similarity index 90% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63817.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63817.h index afcf59bd..ff4b8035 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63817.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63817.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63825.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63825.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63825.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63825.h index 75156aa9..03a39527 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63825.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63825.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63829.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63829.h similarity index 88% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63829.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63829.h index 671ca091..6460d5ea 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63829.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV63829.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV71769.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV71769.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV71769.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV71769.h index a47e6b42..66181eba 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV71769.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV71769.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV72753.h similarity index 88% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV72753.h index 0835e01b..94edef3b 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV72753.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations { diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73511.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73511.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73511.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73511.h index d57e0abe..22b1a2ee 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73511.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73511.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73519.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73519.h similarity index 88% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73519.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73519.h index dd883e14..41ead111 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73519.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73519.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73585.h b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73585.h similarity index 89% rename from BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73585.h rename to BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73585.h index e2bdb362..ef8e1c93 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73585.h +++ b/BLUESPAWN-win-client/headers/mitigation/mitigations/MitigateV73585.h @@ -1,8 +1,6 @@ #pragma once #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" -#include "reaction/Log.h" namespace Mitigations{ diff --git a/BLUESPAWN-client/headers/monitor/ETW_Wrapper.h b/BLUESPAWN-win-client/headers/monitor/ETW_Wrapper.h similarity index 100% rename from BLUESPAWN-client/headers/monitor/ETW_Wrapper.h rename to BLUESPAWN-win-client/headers/monitor/ETW_Wrapper.h diff --git a/BLUESPAWN-win-client/headers/monitor/Event.h b/BLUESPAWN-win-client/headers/monitor/Event.h new file mode 100644 index 00000000..cab08350 --- /dev/null +++ b/BLUESPAWN-win-client/headers/monitor/Event.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include + +#include "util/configurations/Registry.h" +#include "util/configurations/RegistryValue.h" +#include "util/eventlogs/EventSubscription.h" +#include "util/eventlogs/XpathQuery.h" +#include "util/filesystem/FileSystem.h" + +#include "hunt/Scope.h" + +enum class EventType { EventLog, Registry, FileSystem }; + +class Event { + public: + EventType type; + + void AddCallback(const std::function& callback, IN CONST Scope& scope = {} OPTIONAL); + + virtual void RunCallbacks() const; + + virtual bool Subscribe() = 0; + + virtual bool operator==(const Event& e) const = 0; + + protected: + Event(EventType type); + + std::vector, Scope>> callbacks; +}; + +class EventLogEvent : public Event { +public: + + /** + * Creates a new event triggered by an xml + */ + EventLogEvent( + IN CONST std::wstring& channel, + IN int eventID, + IN CONST std::vector& queries = {} OPTIONAL + ); + + std::function eventLogTrigger; + + std::wstring GetChannel() const; + int GetEventID() const; + std::vector GetQueries() const; + + virtual bool Subscribe(); + + virtual bool operator==(const Event& e) const; + +private: + std::optional eventSub; + std::wstring channel; + int eventID; + std::vector queries; +}; + +class RegistryEvent : public Event { + // Event that is triggered when the key changes + HandleWrapper hEvent; + + // True if this event watches subkeys. Note that this will be unable to determine + // which value (or subkey) was changed. + bool WatchSubkeys; + + // The registry key being watched + Registry::RegistryKey key; + + public: + RegistryEvent(const Registry::RegistryKey& key, bool WatchSubkeys = false); + + const HandleWrapper& GetEvent() const; + + const Registry::RegistryKey& GetKey() const; + + virtual bool Subscribe(); + + virtual bool operator==(const Event& e) const; +}; + +class FileEvent : public Event { + /// Directory to be watched + FileSystem::Folder directory; + + /// Event that is triggered when the key changes + GenericWrapper hEvent; + + public: + FileEvent(const FileSystem::Folder& file); + + const GenericWrapper& GetEvent() const; + + const FileSystem::Folder& GetFolder() const; + + virtual bool Subscribe(); + + virtual bool operator==(const Event& e) const; +}; + +/// Template specialization defining how unique_ptrs to Events should be hashed +template<> +class std::hash> { + size_t operator()(IN CONST std::unique_ptr& evt) const; +}; + +/// Template specialization defining how unique_ptrs to Events should be compared +template<> +class std::equal_to> { + bool operator()(IN CONST std::unique_ptr& left, IN CONST std::unique_ptr& right) const; +}; + +namespace Registry { + + /** + * Creates a vector of events to be triggered when any value under a specified registry key path changes, + * automatically mirrored across users if WatchUsers is true and mirrored to WoW64 if WatchWow64 is true. The + * events generated will also trigger when any subkey is changed if WatchSubkeys is set to true. + * + * @param dest The vector to which events created by this function will be added + * @param scope The scope for events that are added + * @param hkHive The hive under which the path will be searched. If WatchUsers is true, this will be also be + * substituted by each user's hive. In most cases, hkHive should be HKEY_LOCAL_MACHINE. + * @param path The path of the key under the hive. + * @param WatchWow64 Indicates whether events should be generated for Wow64 versions of keys, if present + * @param WatchUsers Indicates whether events should be generated for the key at the path under each user's hive, + * if present + * @param WatchSubkeys Indicates whether all events generated should be triggered when any subkey is modified + */ + void GetRegistryEvents(OUT std::vector, Scope>>& dest, + IN CONST Scope& scope, + IN HKEY hkHive, + IN CONST std::wstring& path, + IN bool WatchWow64 = true OPTIONAL, + IN bool WatchUsers = true OPTIONAL, + IN bool WatchSubkeys = false OPTIONAL); +} // namespace Registry diff --git a/BLUESPAWN-client/headers/monitor/EventListener.h b/BLUESPAWN-win-client/headers/monitor/EventListener.h similarity index 99% rename from BLUESPAWN-client/headers/monitor/EventListener.h rename to BLUESPAWN-win-client/headers/monitor/EventListener.h index 6e85be3b..54681d04 100644 --- a/BLUESPAWN-client/headers/monitor/EventListener.h +++ b/BLUESPAWN-win-client/headers/monitor/EventListener.h @@ -6,7 +6,7 @@ #include #include -#include "Common/wrappers.hpp" +#include "util/wrappers.hpp" /** * An event manager for seemlessly subscribing and unsubscribing to and from events. Since this diff --git a/BLUESPAWN-client/headers/monitor/EventManager.h b/BLUESPAWN-win-client/headers/monitor/EventManager.h similarity index 77% rename from BLUESPAWN-client/headers/monitor/EventManager.h rename to BLUESPAWN-win-client/headers/monitor/EventManager.h index e0dcacdb..0805ad57 100644 --- a/BLUESPAWN-client/headers/monitor/EventManager.h +++ b/BLUESPAWN-win-client/headers/monitor/EventManager.h @@ -2,7 +2,6 @@ #include #include -#include "reaction/Reaction.h" #include "hunt/Scope.h" #include "Event.h" #include "util/eventlogs/EventSubscription.h" @@ -10,7 +9,8 @@ class EventManager { public: - DWORD SubscribeToEvent(const std::shared_ptr& e, const std::function& callback); + DWORD SubscribeToEvent(std::unique_ptr&& e, const std::function& callback, + IN CONST Scope& scope); // EventManager is a singleton class; call GetInstance() to get an instance of it. static EventManager& GetInstance(); @@ -27,5 +27,5 @@ class EventManager { EventManager operator=(EventManager&&) = delete; static EventManager manager; - std::vector> vEventList; + std::vector> vEventList; }; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/reaction/CarveMemory.h b/BLUESPAWN-win-client/headers/reaction/CarveMemory.h new file mode 100644 index 00000000..0d46f53c --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/CarveMemory.h @@ -0,0 +1,36 @@ +#pragma once +#include "Reaction.h" + +#include "hunt/HuntInfo.h" +#include "user/iobase.h" + +namespace Reactions{ + + class CarveMemoryReaction : public Reaction { + + /** + * Reacts to a registry detection by carving infected memory sections from a process + * Note that this will cause a crash if function pointers are stored to the infected + * memory for functions with more than four arguments in x64 or any arguments with + * stdcall functions in x86. + * + * @param detection The detection to which the reaction will be applied. + */ + virtual void React( + IN Detection& detection + ); + + /** + * Function to determine if this reaction applies to a detection. This ensures that + * the detection is not stale and that it references a registry value. + * + * @param detection The detection to check + * + * @return True if this reaction applies; false otherwise + */ + virtual bool Applies( + IN CONST Detection& detection + ); + }; +} + diff --git a/BLUESPAWN-win-client/headers/reaction/DeleteFile.h b/BLUESPAWN-win-client/headers/reaction/DeleteFile.h new file mode 100644 index 00000000..a7ec8578 --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/DeleteFile.h @@ -0,0 +1,30 @@ +#pragma once +#include + +#include "util/DynamicLinker.h" + +#include "Reaction.h" +#include "hunt/HuntInfo.h" +#include "user/iobase.h" + +namespace Reactions { + + class DeleteFileReaction : public Reaction { + /** + * Reacts to a file detection by deleting it, taking ownership if necessary + * + * @param detection The detection to which the reaction will be applied. + */ + virtual void React(IN Detection& detection); + + /** + * Function to determine if this reaction applies to a detection. This ensures that + * the detection is not stale and reference a file + * + * @param detection The detection to check + * + * @return True if this reaction applies; false otherwise + */ + virtual bool Applies(IN CONST Detection& detection); + }; +} // namespace Reactions diff --git a/BLUESPAWN-client/src/util/pe/Resource_Section.cpp b/BLUESPAWN-win-client/headers/reaction/Detections.h similarity index 100% rename from BLUESPAWN-client/src/util/pe/Resource_Section.cpp rename to BLUESPAWN-win-client/headers/reaction/Detections.h diff --git a/BLUESPAWN-win-client/headers/reaction/QuarantineFile.h b/BLUESPAWN-win-client/headers/reaction/QuarantineFile.h new file mode 100644 index 00000000..7b51a77e --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/QuarantineFile.h @@ -0,0 +1,30 @@ +#pragma once +#include + +#include "util/DynamicLinker.h" + +#include "Reaction.h" +#include "hunt/HuntInfo.h" +#include "user/iobase.h" + +namespace Reactions { + + class QuarantineFileReaction : public Reaction { + /** + * Reacts to a file detection by quarantining it + * + * @param detection The detection to which the reaction will be applied. + */ + virtual void React(IN Detection& detection); + + /** + * Function to determine if this reaction applies to a detection. This ensures that + * the detection is not stale and reference a file + * + * @param detection The detection to check + * + * @return True if this reaction applies; false otherwise + */ + virtual bool Applies(IN CONST Detection& detection); + }; +} // namespace Reactions diff --git a/BLUESPAWN-win-client/headers/reaction/Reaction.h b/BLUESPAWN-win-client/headers/reaction/Reaction.h new file mode 100644 index 00000000..99947c2b --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/Reaction.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "scan/Detections.h" + +/// Forward declare ReactionManager to it can be a friend +class ReactionManager; + +class Reaction { + /// Indicates if this reaction runs even if a remediator already exists + bool IgnoreRemediator = false; + + friend class ReactionManager; + + public: + /** + * React to a detection. The reaction manager will ensure that this reaction + * applies to the detection before calling this function. + * + * @param detection The detection to which the reaction will be applied. + */ + virtual void React(IN Detection& detection) = 0; + + /** + * Function to determine if this reaction applies to a detection. + * + * @param detection The detection to check + * + * @return True if this reaction applies; false otherwise + */ + virtual bool Applies(IN CONST Detection& detection) = 0; +}; diff --git a/BLUESPAWN-win-client/headers/reaction/ReactionManager.h b/BLUESPAWN-win-client/headers/reaction/ReactionManager.h new file mode 100644 index 00000000..65c3ce59 --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/ReactionManager.h @@ -0,0 +1,41 @@ +#pragma once +#include + +#include +#include + +#include "reaction/Reaction.h" +#include "hunt/HuntInfo.h" +#include "scan/Detections.h" + +/** + * A container class for handling reactions to various types of detections. + */ +class ReactionManager { +protected: + + /// Handlers for detections + std::vector> reactions; + +public: + + /** + * Runs reactions applying to each detection. Note that if the detection has a remediator, + * only reactions with IgnoreRemediator set to true will be run. The caller must acquire the + * detection's critical section before attempting to call this function. + * + * @param detection The detection for which handlers will be run + */ + void React( + IN Detection& detection + ) CONST; + + /** + * Adds a handler to be run for each detection + * + * @param handler A reaction to be added to the manager's list of reactions + */ + void AddHandler( + IN std::unique_ptr&& reaction + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/reaction/RemoveValue.h b/BLUESPAWN-win-client/headers/reaction/RemoveValue.h new file mode 100644 index 00000000..d0556ecc --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/RemoveValue.h @@ -0,0 +1,36 @@ +#pragma once +#include "Reaction.h" + +#include "hunt/HuntInfo.h" +#include "user/iobase.h" +#include "util/DynamicLinker.h" + +#include + +namespace Reactions{ + + class RemoveValueReaction : public Reaction { + + /** + * Reacts to a registry detection by removing it from the registry + * + * @param detection The detection to which the reaction will be applied. + */ + virtual void React( + IN Detection& detection + ); + + /** + * Function to determine if this reaction applies to a detection. This ensures that + * the detection is not stale and that it references a registry value. + * + * @param detection The detection to check + * + * @return True if this reaction applies; false otherwise + */ + virtual bool Applies( + IN CONST Detection& detection + ); + }; +} + diff --git a/BLUESPAWN-win-client/headers/reaction/SuspendProcess.h b/BLUESPAWN-win-client/headers/reaction/SuspendProcess.h new file mode 100644 index 00000000..baca5ea3 --- /dev/null +++ b/BLUESPAWN-win-client/headers/reaction/SuspendProcess.h @@ -0,0 +1,39 @@ +#pragma once +#include "Reaction.h" + +#include "hunt/HuntInfo.h" +#include "user/iobase.h" +#include "util/DynamicLinker.h" + +#include + +DEFINE_FUNCTION(NTSTATUS, NtSuspendProcess, NTAPI, IN HANDLE ProcessHandle); + +namespace Reactions{ + + class SuspendProcessReaction : public Reaction { + + /** + * Reacts to a process detection by suspeding the process + * + * @param detection The detection to which the reaction will be applied. + */ + virtual void React( + IN Detection& detection + ); + + /** + * Function to determine if this reaction applies to a detection. This ensures that + * the detection is not stale and that it references a process. + * + * @param detection The detection to check + * + * @return True if this reaction applies; false otherwise + */ + virtual bool Applies( + IN CONST Detection& detection + ); + }; +} + + diff --git a/BLUESPAWN-win-client/headers/scan/DetectionRegister.h b/BLUESPAWN-win-client/headers/scan/DetectionRegister.h new file mode 100644 index 00000000..9c235aa3 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/DetectionRegister.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + +#include "scan/Detections.h" + +#include "util/Promise.h" + +/** + * Keeps track of all detections found so far + */ +class DetectionRegister { + + /// A vector containing all detections made + std::vector> detections; + + /// A mapping of detection IDs to their associated detections + std::unordered_map> ids; + + /// CriticalSection guarding accesses to `detections` and `ids`. + CriticalSection hGuard; + + /// A set containing all detections scanned + std::unordered_set> scanned; + + /// CriticalSection guarding access to `scanned` + CriticalSection hScannedGuard; + + /// A set containing all detections found but not done being scanned + std::unordered_set> queue; + + /// CriticalSection guarding accesses to `queue`. + CriticalSection hQueueGuard; + + /// Event to be signalled when there are no remaining queued detections + HandleWrapper hEvent; + + /// The The minimum level of certainty required to search for associated detections + Certainty threshold; + + /// Called behind the scenes when queueing a scan with AddDetection + void AddDetectionAsync( + IN CONST std::shared_ptr& detection, + IN CONST Certainty& level = Certainty::None OPTIONAL + ); + + /// Used to update the certainty of a detection, possibly triggering assocation scans + void UpdateDetectionCertainty( + IN CONST std::shared_ptr& detection, + IN CONST Certainty& level = Certainty::None OPTIONAL + ); + +public: + + /** + * Instantiates a new DetectionRegister with a specified threshold for running associativity + * scans. + * + * @param threshold The minimum level of certainty required to search for associated detections + * for any detection registered + */ + DetectionRegister( + IN CONST Certainty& threshold + ); + + /** + * Adds a task to the threadpool to scan a detection and add it to `detections`. If it is + * found to be malicious, associated detections will be identified and added as well. If the + * detection already exists, the certainty specified (if any) will be combined with the + * certainty it already has, which may trigger new detections being added. + * + * @param detection The detection to add + * @param level The degree of certainty that this detection is malicious. By default, this is + * Certainty::None + * + * @return A reference to the detection added. + */ + std::shared_ptr AddDetection( + IN Detection&& detection, + IN CONST Certainty& level = Certainty::None OPTIONAL + ); + + /** + * Retrieves all detections above a specified certainty level. This implicitly calls Wait() + * + * @param level The minimum certainty of detections to be retrieved + * + * @return A vector of detections above the specified level + */ + std::vector> GetAllDetections( + IN CONST Certainty& level = Certainty::Moderate OPTIONAL + ) CONST; + + /** + * Gets a detection by its ID + * + * @param ID The ID of the detection to retrieve + * + * @return A pointer to the requested detection if available; nullptr otherwise. + */ + std::shared_ptr GetByID( + IN DWORD ID + ) CONST; + + /** + * Waits for all queued detections to be finished being scanned. + */ + void Wait() CONST; + + /** + * Implicit cast to handle returns an event to be signalled when there are no more detection + * scans queued. + */ + operator HANDLE() CONST; +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/Detections.h b/BLUESPAWN-win-client/headers/scan/Detections.h new file mode 100644 index 00000000..52ba63a7 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/Detections.h @@ -0,0 +1,836 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "util/configurations/RegistryValue.h" +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "scan/YaraScanner.h" +#include "scan/ScanInfo.h" + +/// Identifies the type of detection a Detection object represents +enum class DetectionType { + ProcessDetection, + RegistryDetection, + FileDetection, + ServiceDetection, + OtherDetection, +}; + +/// Describes the type of registry entry associated with a RegistryDetectionData object +enum class RegistryDetectionType { + CommandReference, // The associated value is either a REG_SZ or REG_EXPAND_SZ that references a command used to run + // a program + FileReference, // The associated value is either a REG_SZ or REG_EXPAND_SZ that references a file + FolderReference, // The associated value is either a REG_SZ or REG_EXPAND_SZ that references a folder + PipeReference, // The associated value is either a REG_SZ that references a named pipe + ShareReference, // The associated value is either a REG_SZ that references a share + UserReference, // The associated value is either a REG_SZ that references a user + Configuration, // The associated value references a configuration for the operating system + Unknown, // The associated value is assumed malicious, though its usage is unknown +}; + +/// Describes the type of detection is associated with a ProcessDetectionData object +enum class ProcessDetectionType { + MaliciousProcess, // Refers to the process itself rather than something within it + MaliciousImage, // Refers to a specific image within the process + MaliciousMemory, // Refers to a location in memory of the process + MaliciousCommand, // Refers to a command to be used to spawn a process. If a specific process is identified, + // the type should be MaliciousProcess instead +}; + +/** + * Stores information about a process or memory location identified as possibly malicious + */ +struct ProcessDetectionData { + + /// Describes the type of detection is associated with this + ProcessDetectionType type; + + /// The process ID of the process + std::optional PID; + + /// The thread IDs of the threads within the process that triggered the detection. + /// This will rarely be used. + std::optional TID; + + /// An open handle to the process + std::optional ProcessHandle; + + /// The name of the process + std::optional ProcessName; + + /// The path to the executable image of the process + std::optional ProcessPath; + + /// The command used to spawn the process + std::optional ProcessCommand; + + /// The parent of the process + std::optional> ParentProcess; + + /// The base address of the potentially malicious memory segment inside the process + std::optional BaseAddress; + + /// The size of the potentially malicious memory segment + std::optional MemorySize; + + /// The name of the image in memory being referenced by the detection. + std::optional ImageName; + + /** + * Instantiates a ProcessDetectionData object representing a malicious image loaded in + * to a process. This constructor is intended to be used primarily when a handle to the + * process is infeasible to obtain. Note that this constructor is intended to be used + * when a loaded libary is determined to be malicious, not when the library is infected, + * hooked, stomped, hollowed, doppelganged, or similar. No arguments will be deduced + * when using this constructor. + * + * @param PID The process ID of the process + * @param ProcessName The name of the process + * @param ImageName The name of the image in memory being referenced by the detection + * @param BaseAddress The base address of the image in memory + * @param MemorySize The size of the image in memory + * @param ProcessPath The path to the executable image of the process + * @param ProcessCommand The command used to spawn the process + * @param ParentProcess An pointer to a ProcessDetectionData struct containing information + * on the parent process. + */ + static ProcessDetectionData CreateImageDetectionData( + IN DWORD PID, + IN CONST std::wstring& ProcessName, + IN CONST std::wstring& ImageName, + IN CONST std::optional& BaseAddress = std::nullopt OPTIONAL, + IN CONST std::optional& MemorySize = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessPath = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessCommand = std::nullopt OPTIONAL, + IN std::unique_ptr&& ParentProcess = nullptr OPTIONAL + ); + + /** + * Instantiates a ProcessDetectionData object representing a malicious image loaded in + * to a process. This constructor is intended to be used whenever a handle is available. + * Most of the arguments can be calculated, though if they are available, passing them + * improves efficiency. Note that this constructor is intended to be used when a loaded + * libary is determined to be malicious, not when the library is infected, hooked, stomped, + * hollowed, doppelganged, or similar. + * + * @param PID The process ID of the process + * @param ProcessName The name of the process + * @param ImageName The name of the image in memory being referenced by the detection + * @param BaseAddress The base address of the image in memory. If skipped, this will be + * automatically deduced + * @param MemorySize The size of the image in memory. If skipped, this will be automatically + * deduced + * @param ProcessPath The path to the executable image of the process. If skipped, this will + * be automatically deduced + * @param ProcessCommand The command used to spawn the process. If skipped, this will be + * automatically deduced + * @param ParentProcess An pointer to a ProcessDetectionData struct containing information + * on the parent process. If skipped, this will be automatically deduced + */ + static ProcessDetectionData CreateImageDetectionData( + IN CONST HandleWrapper& ProcessHandle, + IN CONST std::wstring& ProcessName, + IN CONST std::wstring& ImageName, + IN CONST std::optional& BaseAddress = std::nullopt OPTIONAL, + IN CONST std::optional& MemorySize = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessPath = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessCommand = std::nullopt OPTIONAL, + IN std::unique_ptr&& ParentProcess = nullptr OPTIONAL + ); + + /** + * + * This constructor is intended to be used primarily when a handle to the process is + * infeasible to obtain. This constructor generally will be used when there is little other + * information available as to what specifically is malicious or when the process is + * achieving a malicious purpose even though each image inside is benign (as with powershell). + * + * @param PID The process ID of the process + * @param ProcessName The name of the process + * @param ProcessPath The path to the executable image of the process + * @param ProcessCommand The command used to spawn the process + * @param ParentProcess An pointer to a ProcessDetectionData struct containing information + * on the parent process. + */ + static ProcessDetectionData CreateProcessDetectionData( + IN DWORD PID, + IN CONST std::wstring& ProcessName, + IN CONST std::optional& ProcessPath = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessCommand = std::nullopt OPTIONAL, + IN std::unique_ptr&& ParentProcess = nullptr OPTIONAL + ); + + /** + * Instantiates a ProcessDetectionData object representing a process which may be malicious. + * This constructor is intended to be used whenever a handle is available. Most of the + * arguments can be calculated, though if they are available, passing them improves efficiency. + * This constructor generally will be used when there is little other information available as + * to what specifically is malicious or when the process is achieving a malicious purpose even + * though each image inside is benign (as with powershell). + * + * @param PID The process ID of the process + * @param ProcessName The name of the process + * @param ProcessPath The path to the executable image of the process. If skipped, this will + * be automatically deduced + * @param ProcessCommand The command used to spawn the process. If skipped, this will be + * automatically deduced + * @param ParentProcess An pointer to a ProcessDetectionData struct containing information + * on the parent process. If skipped, this will be automatically deduced + */ + static ProcessDetectionData CreateProcessDetectionData( + IN CONST HandleWrapper& ProcessHandle, + IN CONST std::wstring& ProcessName, + IN CONST std::optional& ProcessPath = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessCommand = std::nullopt OPTIONAL, + IN std::unique_ptr&& ParentProcess = nullptr OPTIONAL + ); + + /** + * Instantiates a ProcessDetectionData object representing a malicious memory section loaded in to a process. This + * constructor is intended to be used primarily when a handle to the process is infeasible to obtain. Note that + * this constructor is intended to be used when the library is infected, hooked, stomped, hollowed, doppelganged, + * or similar, not when a loaded libary is determined to be malicious. No arguments will be deduced when using this + * constructor. + * + * @param PID The process ID of the process + * @param ProcessName The name of the process + * @param BaseAddress The base address of the memory section + * @param MemorySize The size of the memory section + * @param ImageName The name of the image in memory being referenced by the detection + * @param ProcessPath The path to the executable image of the process + * @param ProcessCommand The command used to spawn the process + * @param ParentProcess An pointer to a ProcessDetectionData struct containing information on the parent process + */ + static ProcessDetectionData CreateMemoryDetectionData( + IN DWORD PID, + IN CONST std::wstring& ProcessName, + IN PVOID64 BaseAddress, + IN DWORD MemorySize, + IN CONST std::optional& ImageName = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessPath = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessCommand = std::nullopt OPTIONAL, + IN std::unique_ptr&& ParentProcess = nullptr OPTIONAL + ); + + /** + * Instantiates a ProcessDetectionData object representing a malicious memory section loaded in to a process. This + * constructor is intended to be used whenever a handle is available. Most of the arguments can be calculated, + * though if they are available, passing them improves efficiency. Note that this constructor is intended to be + * used when the library is infected, hooked, stomped, hollowed, doppelganged, or similar, not when a loaded libary + * is determined to be malicious. + * + * @param ProcessHandle An open handle to the process + * @param ProcessName The name of the process + * @param BaseAddress The base address of the memory section + * @param MemorySize The size of the memory section + * @param ImageName The name of the image in memory being referenced by the detection. If skipped, this will be + * automatically deduced if possible. + * @param ProcessPath The path to the executable image of the process. If skipped, this will be automatically + * deduced + * @param ProcessCommand The command used to spawn the process. If skipped, this will be automatically deduced + * @param ParentProcess An pointer to a ProcessDetectionData struct containing information on the parent process. + * If skipped, this will be automatically deduced + */ + static ProcessDetectionData CreateMemoryDetectionData( + IN CONST HandleWrapper& ProcessHandle, + IN CONST std::wstring& ProcessName, + IN PVOID64 BaseAddress, + IN DWORD MemorySize, + IN CONST std::optional& ImageName = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessPath = std::nullopt OPTIONAL, + IN CONST std::optional& ProcessCommand = std::nullopt OPTIONAL, + IN std::unique_ptr&& ParentProcess = nullptr OPTIONAL + ); + + /** + * Instantiates a ProcessDetectionData object representing a malicious command used to spawn a process. + * + * @param ProcessCommand The command used to spawn a process + */ + static ProcessDetectionData CreateCommandDetectionData( + IN CONST std::wstring& ProcessCommand + ); + + /** + * Serialize the detection data in to a mapping of values. Note this should not include any internal representations + * but rather only include values that have meaning outside of BLUESPAWN's running. + * + * @return A mapping of properties to human-readable values + */ + const std::map& Serialize() CONST; + + /** + * Compute a hash for this detection data + * + * @return A hash for this detection data + */ + size_t Hash() CONST; + + /** + * Override comparison operator + * + * @param detection The data to compare + * + * @return True if the data is equal to this; false otherwise + */ + bool operator==(const ProcessDetectionData& detection) const = default; + +private: + + /// Record the hash of the data + size_t hash = 0; + + /// Record the serialization of the data + std::map serialization = {}; + + /// Raw constructor for a ProcessDetectionData + ProcessDetectionData( + IN ProcessDetectionType type, + IN CONST std::optional PID, + IN CONST std::optional & TID, + IN CONST std::optional& ProcessHandle, + IN CONST std::optional & ProcessName, + IN CONST std::optional& ProcessPath, + IN CONST std::optional& ProcessCommand, + IN std::unique_ptr&& ParentProcess, + IN CONST std::optional& BaseAddress, + IN CONST std::optional& MemorySize, + IN CONST std::optional& ImageName + ); +}; + +/** + * Stores information about a file identified as possibly malicious + */ +struct FileDetectionData { + + /// Indicates whether the file was found on the filesystem + bool FileFound; + + /// Information about the directory listing for the file + std::wstring FilePath; + std::wstring FileName; + std::optional FileExtension; + + /// The type of the file. This differs from extensions in that mutliple different + /// file extensions may correspond to the same filetype. + std::optional FileType; + + /// Command run to open the file. Stored in HKCR\\shell\open\command + std::optional Executor; + + /// A handle for the file + std::optional FileHandle; + + /// Hashes of the file + std::optional MD5; + std::optional SHA1; + std::optional SHA256; + + /// Timestamps associated with the file + std::optional LastOpened; + std::optional FileCreated; + + /// Information about a yara scan performed on the file + std::optional yara; + + /// Indicates whether the file is properly signed and the signature is trusted + std::optional FileSigned; + + /// The title of the signer of this file, given that the file is signed + std::optional Signer; + + /** + * Creates a FileDetectionData using an open handle to the file. This works under the assumption that the + * detection matches the file found on disk. If generating this detection from event logs or other records this + * may not be the case. If the file has already been scanned with yara, it is recommended that the result be passed + * in to the constructor so that it doesn't have to be scanned a second time. + * + * @param file A File object representing the file. + * @param scan The result of a yara scan performed on a file. This parameter is optional, but providing the result + * will avoid the need for the scan to be repeated. + */ + FileDetectionData( + IN CONST FileSystem::File& file, + IN CONST std::optional& scan = std::nullopt OPTIONAL + ); + + /** + * Creates a FileDetectionData using the file's path on disk. This constructor is best used when the file could not + * be found or it is infeasible to construct a File object representing the underlying file. If a File object is + * available, using the other constructor will be more efficient. + * + * @param FilePath The path of the file + */ + FileDetectionData( + IN CONST std::wstring& FilePath + ); + + /** + * Serialize the detection data in to a mapping of values. Note this should not include any internal representations + * but rather only include values that have meaning outside of BLUESPAWN's running. + * + * @return A mapping of properties to human-readable values + */ + const std::map& Serialize() CONST; + + /** + * Compute a hash for this detection data + * + * @return A hash for this detection data + */ + size_t Hash() CONST; + + /** + * Override comparison operator + * + * @param detection The data to compare + * + * @return True if the data is equal to this; false otherwise + */ + bool operator==(const FileDetectionData& detection) const; + +private: + + /// Record the hash of the data + size_t hash = 0; + + /// Record the serialization of the data + std::map serialization = {}; +}; + +/** + * Stores information about a registry entry identified as possibly malicious. This entry may be a whole registry key, + * a registry value, or just part of a registry value. + * + * If a registry value is detected on and it is a REG_MULTI_SZ, rather than creating one detection for the value as a + * whole, create a separate detection for each potentially malicious entry in the value as a REG_SZ. + */ +struct RegistryDetectionData { + + /// The path of the registry key associated with the registry entry + std::wstring KeyPath; + + /// The key associated with the registry entry. + Registry::RegistryKey key; + + /// An optional value under the key associated with the registry entry + std::optional value; + + /// The raw data contained in the registry entry. + std::optional data; + + /// The type of data in this registry detection + RegistryDetectionType type; + + /** + * Creates a RegistryDetectionData, referencing either a registry key, a registry value, or part of a registry + * value. If a registry value is detected on and it is a REG_MULTI_SZ, rather than creating one detection for the + * value as a whole, create a separate detection for each potentially malicious entry in the value as a REG_SZ. + * + * @param key The associated with the registry entry. + * @param value An optional value under the key associated with the registry entry + * @param type The type of data referenced by this registry value. This defaults to Unknown + * @param data An optional allocation wrapper storing the raw data associated with the registry entry. If `value` + * represents only part of a registry value's data, this should not be set. + */ + RegistryDetectionData( + IN CONST Registry::RegistryKey& key, + IN CONST std::optional& value = std::nullopt OPTIONAL, + IN RegistryDetectionType type = RegistryDetectionType::Unknown OPTIONAL, + IN CONST std::optional& data = std::nullopt OPTIONAL + ); + + /** + * Creates a RegistryDetectionData, referencing either a registry key, a registry value, or part of a registry + * value. If a registry value is detected on and it is a REG_MULTI_SZ, rather than creating one detection for the + * value as a whole, create a separate detection for each potentially malicious entry in the value as a REG_SZ. + * + * @param value A RegistryValue object containing information about the value + * @param type The type of data referenced by this registry value. This defaults to Unknown. + */ + RegistryDetectionData( + IN CONST Registry::RegistryValue& value, + IN RegistryDetectionType type = RegistryDetectionType::Unknown OPTIONAL + ); + + /** + * Serialize the detection data in to a mapping of values. Note this should not include any internal representations + * but rather only include values that have meaning outside of BLUESPAWN's running. + * + * @return A mapping of properties to human-readable values + */ + const std::map& Serialize() CONST; + + /** + * Compute a hash for this detection data + * + * @return A hash for this detection data + */ + size_t Hash() CONST; + + /** + * Override comparison operator + * + * @param detection The data to compare + * + * @return True if the data is equal to this; false otherwise + */ + bool operator==(const RegistryDetectionData& detection) const; + +private: + + /// Record the hash of the data + size_t hash = 0; + + /// Record the serialization of the data + std::map serialization = {}; +}; + +/** + * Stores information about a service identified as possibly malicious. Note that when creating a service detection + * object, it is recommended that the registry keys and files associated with the service should have separate + * detection objects. + */ +struct ServiceDetectionData { + + /// The name of the service + std::optional ServiceName; + + /// The display name of the service + std::optional DisplayName; + + /// The description of the service + std::optional Description; + + /// The service path + std::optional FilePath; + + /** + * Creates a ServiceDetectionData object, referencing a windows service that may be malicious. Either DisplayName + * or ServiceName is required. + * + * @param ServiceName The name of the service + * @param DisplayName The display name of the service + * @param FilePath The path the the service executable + * @param Description The description of the service + */ + ServiceDetectionData( + IN CONST std::optional& ServiceName = std::nullopt OPTIONAL, + IN CONST std::optional& DisplayName = std::nullopt OPTIONAL, + IN CONST std::optional& FilePath = std::nullopt OPTIONAL, + IN CONST std::optional& Description = std::nullopt OPTIONAL + ); + + /** + * Serialize the detection data in to a mapping of values. Note this should not include any internal representations + * but rather only include values that have meaning outside of BLUESPAWN's running. + * + * @return A mapping of properties to human-readable values + */ + const std::map& Serialize() CONST; + + /** + * Compute a hash for this detection data + * + * @return A hash for this detection data + */ + size_t Hash() CONST; + + /** + * Override comparison operator + * + * @param detection The data to compare + * + * @return True if the data is equal to this; false otherwise + */ + bool operator==(const ServiceDetectionData& detection) const = default; + +private: + + /// Record the hash of the data + size_t hash = 0; + + /// Record the serialization of the data + std::map serialization = {}; +}; + +/** + * Stores information about something not covered by other detection types identified as possibly malicious. This + * includes things such as users, groups, shares, pipes, and more. + */ +struct OtherDetectionData { + + /// A string describing the type of detection associated with this object. + std::wstring DetectionType; + + /// Stores data about the detection + std::map DetectionProperties; + + /** + * Creates an OtherDetectionData object, referencing something on the system identified as possibly malicious. + * OtherDetectionData objects consist of a type and a map of properties and their values, represented as strings. + * + * @param DetectionType A string describing the type of detection associated with this object + * @param DetectionProperties A mapping of property to value describing what's being referenced by this. + */ + OtherDetectionData( + IN CONST std::wstring& DetectionType, + IN CONST std::map& DetectionProperties + ); + + /** + * Serialize the detection data in to a mapping of values. Note this should not include any internal representations + * but rather only include values that have meaning outside of BLUESPAWN's running. + * + * @return A mapping of properties to human-readable values + */ + const std::map& Serialize() CONST; + + /** + * Compute a hash for this detection data + * + * @return A hash for this detection data + */ + size_t Hash() CONST; + + /** + * Override comparison operator + * + * @param detection The data to compare + * + * @return True if the data is equal to this; false otherwise + */ + bool operator==(const OtherDetectionData& detection) const = default; + +private: + + /// Record the hash of the data + size_t hash = 0; + + /// Record the serialization of the data + std::map serialization = {}; +}; + +/// Stores contextual information around a detection +struct DetectionContext { + + /// A set of the hunts that identified the detection + std::set hunts; + + /// The time at which the first evidence of the detection was created + std::optional FirstEvidenceTime; + + /// The time at which the detection was created + FILETIME DetectionCreatedTime; + + /// An optional note describing why this detection was marked as potentially malicious + std::optional note; + + /** + * Creates a DetectionContext for a detection. + * + * @param DetectionCreatedTime The time at which the detection was created + * @param hunt If the associated detection is created by a hunt, this is the hunt responsible for creating it + * @param FirstEvidenceTime The time at which the first evidence of this detection was created + */ + DetectionContext( + IN CONST std::optional& hunt = std::nullopt OPTIONAL, + IN CONST std::optional& FirstEvidenceTime = std::nullopt OPTIONAL, + IN CONST std::optional& note = std::nullopt OPTIONAL + ); +}; + +/// A container for the various type of detection data a Detection object may reference +typedef std::variant< + ProcessDetectionData, + FileDetectionData, + RegistryDetectionData, + ServiceDetectionData, + OtherDetectionData +> DetectionData; + +/** + * Represents something that has been identified as potentially malicious. Each detection object can be further broken + * down in to the types laid out in the DetectionType enum. Each type of detection then has an associated DetectionData + * object providing information on the details of what was detected. Each detection also may hold a remediator, which + * will handle the detection, either removing it, fixing it, or mitigating it. This remediator can be used by a + * reaction and should be set when the detection is created if possible. Finally, the the detection will hold a + * DetectionContext object, which holds information about the detection itself, such as the hunts that generated it, + * when it was generated, and when the thing being detected was first identified. + */ +class Detection { +private: + + /// Record the hash of the data + size_t hash; + + /// Record the serialization of the data + std::map serialization; + + /// A shared counter to keep track of detection IDs and ensure each new detection gets assigned + /// a unique identifier. + static volatile std::atomic IDCounter; + + /// A struct used to serialize detection data + static struct { + std::map operator()(ProcessDetectionData data){ + return data.Serialize(); + } + std::map operator()(FileDetectionData data){ + return data.Serialize(); + } + std::map operator()(RegistryDetectionData data){ + return data.Serialize(); + } + std::map operator()(ServiceDetectionData data){ + return data.Serialize(); + } + std::map operator()(OtherDetectionData data){ + return data.Serialize(); + } + } serializer; + + /// A struct used to hash detection data + static struct { + size_t operator()(ProcessDetectionData data){ return data.Hash(); } + size_t operator()(FileDetectionData data){ return data.Hash(); } + size_t operator()(RegistryDetectionData data){ return data.Hash(); } + size_t operator()(ServiceDetectionData data){ return data.Hash(); } + size_t operator()(OtherDetectionData data){ return data.Hash(); } + } hasher; + + /// Declare related hash classes to be friends + friend class std::hash; + friend class std::hash>; + +public: + + /// A unique identifier for this detection + DWORD dwID; + + /// Indicates whether the data represented by this detection is consistent with the current state of the operating + /// system. + bool DetectionStale; + + /// Indicates the type of this detection + DetectionType type; + + /// Describes what this detection object is representing + DetectionData data; + + /// Information related to the scans performed on this detection + ScanInfo info; + + /// A function that when run will remediate the detection, either removing it, fixing it, or mitigating it. + std::optional> remediator; + + /// Describes the context surrounding the detection such as when the first evidence of the detection was created, + /// the hunts generating this detection, and the time the detection was generated. + DetectionContext context; + + /// A critical section guarding access to members of this class + CriticalSection hGuard; + + /** + * Creates a Detection object, given associated data, optional context, an optional remediator, and an optional + * indicator as to whether the detection is stale. + * + * @param data The data associated with the detection to be created. The type will be deduced. + * @param context The context surrounding the detection. If not provided, this will default to only include the + * time. + * @param remediator A function that can be used to remediate the detection if it is determined to be malicious. + * By default, there is no remediator. + * @param DetectionStale A boolean indicating whether the data represented by this detection is consistent with + * the current state of the operating system. This defaults to false. + */ + Detection( + IN CONST DetectionData& data, + IN CONST std::optional& context = std::nullopt OPTIONAL, + IN CONST std::optional>& remediator = std::nullopt OPTIONAL, + IN bool DetectionStale = false OPTIONAL + ); + + /// Define a copy constructor + Detection( + IN CONST Detection& detection + ); + + /// Define assignment operator + Detection& operator=( + IN CONST Detection& detection + ); + + /** + * Override for equality comparison operator. This checks if the data matches, ignoring other fields. + * + * @param detection The detection to compare + * + * @return True if the detection is equal to this; false otherwise + */ + bool operator==( + IN CONST Detection& detection + ) const; + + /** + * Serialize the detection data in to a mapping of values. Note this should not include any internal + * representations but rather only include values that have meaning outside of BLUESPAWN's running. + * + * @return A mapping of properties to human-readable values + */ + const std::map& Serialize() const; + + /** + * Implicit cast to a CRITICAL_SECTION pointer for use in synchronization functions + * + * @return hGuard + */ + operator LPCRITICAL_SECTION(); + + /** + * Implicit cast to a CriticalSection pointer for use in synchronization functions + * + * @return hGuard + */ + operator CriticalSection(); +}; + +/// Template specialization defining how hashes of Detection objects should be calculated +template<> +struct std::hash { + + /// Hashes a detection using its data + size_t operator()( + IN CONST Detection& detection + ) const; +}; + +/// Template specialization defining how hashes of reference wrappers for Detection objects should be calculated +template<> +struct std::hash> { + + /// Hashes a detection using its data + size_t operator()( + IN CONST std::shared_ptr& detection + ) const; +}; + +/// Template specialization defining how equality of reference wrappers for Detection objects should be calculated +template<> +struct std::equal_to> { + + /// Compares reference wrappers by comparing their wrapped value + bool operator()( + IN CONST std::shared_ptr& _Left, + IN CONST std::shared_ptr& _Right + ) const; +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/FileScanner.h b/BLUESPAWN-win-client/headers/scan/FileScanner.h new file mode 100644 index 00000000..f1d72c1f --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/FileScanner.h @@ -0,0 +1,94 @@ +#pragma once + +#include "scan/ScanInfo.h" + +#include +#include +#include + +#include "scan/Scanner.h" + +class FileScanner : public Scanner { + + /// A mapping of module names to sets of the PIDs of processes with the module loaded + std::unordered_map> modules; + + /// A mapping of hashes to the modules that match the hash + std::unordered_map> hashes; + + /// The last time that `modules` was updated + FILETIME ModuleLastUpdateTime; + + /// The rate at which `modules` is updated, in milliseconds + static const DWORD MODULE_UPDATE_INTERVAL{ 300000 }; + + /// CriticalSection guarding access to `modules`, `hashes`, and `ModuleLastUpdateTime` + CriticalSection hGuard; + + /// Checks if `modules` needs to be updated, and if so, updates it and `ModuleLastUpdateTime` + void UpdateModules(); + +public: + + /** + * Searches through the strings given for strings referencing a file + * + * @param strings A vector of strings to search + * + * @return A vector of strings referencing files + */ + static std::vector ExtractFilePaths( + IN CONST std::vector& strings + ); + + /** + * Searches through memory for strings (hex range 0x20 to 0x79) of a certain minimum length, either + * in ascii or unicode + * + * @param data The memory to search through + * @param dwMinLength The minimum length of strings being searched for + * + * @return A vector containing all strings, converted to widestrings + */ + static std::vector ExtractStrings( + IN CONST AllocationWrapper& data, + IN DWORD dwMinLength = 5 OPTIONAL + ); + + /** + * Gets a vector of detections associated with the provided detection. This searches for processes with + * the file loaded into memory and depending on the aggressiveness, any file paths or registry paths in + * the file. + * + * @param detection The detection to find associations for + * + * @return A vector of detections associated with the provided detection + */ + virtual std::unordered_map, Association> GetAssociatedDetections( + IN CONST Detection& detection + ); + + /** + * Performs a fast scan to determine whether the info provided is potentially malicious. + * This checks if the file exists, and if so, returns true if the file is not signed. + * + * @param info A string used to identify some object + * + * @return True if the object represented by the string is potentially malicious + */ + static bool PerformQuickScan( + IN CONST std::wstring& info + ); + + /** + * Scans a detection and returns the certainty that the detection is malicious. This is computed as + * a combination of whether the file is signed and which (if any) yara rules it matches + * + * @param detection The Detection to scan + * + * @return A Certainty indicating the degree of certainty for which the detection is malicious + */ + virtual Certainty ScanDetection( + IN CONST Detection& detection + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/MemoryScanner.h b/BLUESPAWN-win-client/headers/scan/MemoryScanner.h new file mode 100644 index 00000000..8cc2ea85 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/MemoryScanner.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "scan/Detections.h" +#include "scan/Scanner.h" + +class MemoryScanner : public Scanner { +public: + + /** + * Gets a vector of detections associated with the provided detection. This is done by checking if the + * memory in question is mapped to a file, and returning the file if so. This also checks for file paths + * included in the memory section. + * + * @param detection The detection for which associations will be found + * + * @return A vector of detections associated with the provided detection + */ + virtual std::unordered_map, Association> GetAssociatedDetections( + IN CONST Detection& detection + ); + + /** + * This function will return false, as there is no "quick" scan to check if memory may be bad + * + * @param info Unused + * + * @return False + */ + virtual bool PerformQuickScan( + IN CONST std::wstring& info + ); + + /** + * Scans a detection and returns the certainty that the detection is malicious. This is done by checking + * memory protections and if the aggressiveness is intensive, scanning the memory with yara. + * + * @param detection The Detection object to scan + * + * @return A Certainty indicating the degree of certainty for which the detection is malicious + */ + virtual Certainty ScanDetection( + IN CONST Detection& detection + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/ProcessScanner.h b/BLUESPAWN-win-client/headers/scan/ProcessScanner.h new file mode 100644 index 00000000..a4f09878 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/ProcessScanner.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "scan/Detections.h" +#include "scan/Scanner.h" + +class ProcessScanner : public Scanner { +private: + + /** + * Scans a command for possibly associated detections. The intended use-case of this is to find things + * such as malware.exe in the command `cmd.exe /c "malware.exe"`. + */ + std::unordered_map, Association> SearchCommand( + IN CONST std::wstring& ProcessCommand + ); + +public: + + /** + * Gets a vector of detections associated with the provided detection. This is done by finding child + * processes and files referenced in the command used to spawn the process. + * + * @param detection The detection for which associations will be found + * + * @return A vector of detections associated with the provided detection + */ + virtual std::unordered_map, Association> GetAssociatedDetections( + IN CONST Detection& detection + ); + + /** + * This function will treat `info` as a command to determine if any process created with that command would + * be malicious. + * + * @param info A command to scan + * + * @return True if the command appears malicious; false otherwise + */ + static bool PerformQuickScan( + IN CONST std::wstring& info + ); + + /** + * Scans a detection and returns the certainty that the detection is malicious. + * + * @param detection The Detection to scan + * + * @return A Certainty indicating the degree of certainty for which the detection is malicious + */ + virtual Certainty ScanDetection( + IN CONST Detection& detection + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/RegistryScanner.h b/BLUESPAWN-win-client/headers/scan/RegistryScanner.h new file mode 100644 index 00000000..777dff98 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/RegistryScanner.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "scan/Detections.h" +#include "scan/Scanner.h" + +class RegistryScanner : public Scanner { +public: + + /** + * Extracts strings that match registry key names under any of the five default hives. + * + * @param strings The strings to search + * + * @return a vector of registry paths found in `strings`, including the hives under which they were found. + */ + static std::vector RegistryScanner::ExtractRegistryKeys( + IN CONST std::vector& strings + ); + + /** + * Gets a vector of detections associated with the provided detection. This is done by finding the associated + * item with the registry value, if such a value is present. + * + * @param detection The detection for which associations will be found + * + * @return A vector of detections associated with the provided detection + */ + virtual std::unordered_map, Association> GetAssociatedDetections( + IN CONST Detection& detection + ); + + /** + * Scans a detection and returns the certainty that the detection is malicious. This is done by checking for hidden + * information in the value, if such a value is present. + * + * @param detection The Detection object to scan + * + * @return A Certainty indicating the degree of certainty for which the detection is malicious + */ + virtual Certainty ScanDetection( + IN CONST Detection& detection + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/ScanInfo.h b/BLUESPAWN-win-client/headers/scan/ScanInfo.h new file mode 100644 index 00000000..4bbb564b --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/ScanInfo.h @@ -0,0 +1,169 @@ +#pragma once + +#include + +#include +#include +#include +#include + +class Scanner; +class Detection; + +/// Forward declare template specializaiton for hashing reference wrappers for detections +template<> +struct std::hash>; + +#include "util/wrappers.hpp" + +/// Represents the degree of certainty that a detection is malicious +class Certainty { + + /// A double holding a number between 0 and 1, indicating how strongly the referenced detection + /// is believed to be malicious, with 1 being the most certain that it is malicious + double confidence; + +public: + + /// Define static certainty values + const static Certainty Certain; // 1.00 + const static Certainty Strong; // 0.75 + const static Certainty Moderate; // 0.50 + const static Certainty Weak; // 0.25 + const static Certainty None; // 0.00 + + Certainty(double value); + operator double() const; + + /** + * If the strengths of two associations are to be combined, this function will compute the + * resulting association. Using the numerical value of associations, the formula is + * 1 - (1 - a1) * (1 - a2). + */ + Certainty operator+(Certainty c) const; + + /** + * If an association is to be the composite of two associations, this function will compute the + * resulting association. Using the numerical value of associations, the formula is + * a1 * a2. + */ + Certainty operator*(Certainty c) const; + + /** + * Used for comparing between certainties. Note that is computes approximate comparisons rather + * than exact comparisons. Thus, any value within 0.125 of `confidence` is considered equal + */ + bool operator==(Certainty c) const; + bool operator!=(Certainty c) const; + bool operator<=(Certainty c) const; + bool operator>=(Certainty c) const; + + // These functions use exact comparisons rather than approximate comparisons + bool operator>(Certainty c) const; + bool operator<(Certainty c) const; +}; + +/// An association is the degree of certainty that two detections are related +typedef Certainty Association; + +/** + * A ScanInfo is the core unit of BLUESPAWN's scan functionality. This records information + * such as associations, resulting certainty from scans, and associative certainty. + */ +class ScanInfo { + + /// A mapping of detections to their association strength with the current node. + std::unique_ptr, Association>> associations; + + /// The degree of certainty that the detection referenced by this scan node is malicious + /// Note that this ignores all associations + Certainty certainty; + + /// The degree of certainty that the detection referenced by this scan node is malicious + /// Note that this is calculated only based on associations + Certainty cAssociativeCertainty; + + /// Indicates whether cAssociativeCertainty has gone stale and must be recalculated + bool bAssociativeStale; + + /// Guards access to `associations` + CriticalSection hGuard; + + friend class DetectionRegister; + friend class RegistryScanner; + friend class FileScanner; + friend class ProcessScanner; + friend class MemoryScanner; + friend class ServiceScanner; + friend class Detection; + +public: + + /** + * Constructs a new ScanInfo object + */ + ScanInfo(); + + /** + * Gets a map of the associations of this node. + * + * @return The associations of this node + */ + std::unordered_map, Association> GetAssociations(); + + /** + * Retrieves the certainty that the detection this is a part of is malicious. If any association has + * been added since the last call to GetCertainty, the associative certainty will be recalculated. + * + * @return The certainty that the detection this is a part of is malicious + */ + Certainty GetCertainty(); + + /** + * Retrieves the certainty that the associated detection's data refers to something malicious. This function + * ignores assocaitivity certainty. + * + * @return The certainty that the detection this is a part of is malicious + */ + Certainty GetIntrinsicCertainty(); + + /** + * Sets the degree of certainty that the detection referenced by this scan node is malicious. This does not affect + * the associative certainty of this ScanNode. + * + * @param certainty The value of certainty to be set + */ + void SetCertainty( + IN CONST Certainty& certainty + ); + + /** + * Adds to the degree of certainty that the detection referenced by this scan node is malicious. This does not affect + * the associative certainty of this ScanNode. + * + * @param certainty The value of certainty to be added + */ + void AddCertainty( + IN CONST Certainty& certainty + ); + + /** + * Implicit cast to a CRITICAL_SECTION pointer for use in synchronization functions + * + * @return hGuard + */ + operator LPCRITICAL_SECTION() const; + + /** + * Adds an association between this node and the given node with the given strength. Note that + * this only adds the association one way; node->AddAssociation(*this) must be called separately + * for the association to be bidirectional (as all associations should be). + * + * @param node The node to add an association to. + * @param strength The strength of the association between the two nodes + */ + void AddAssociation( + IN CONST std::shared_ptr& node, + IN CONST Association& strength + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/Scanner.h b/BLUESPAWN-win-client/headers/scan/Scanner.h new file mode 100644 index 00000000..bf7ed3d1 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/Scanner.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "scan/Detections.h" +#include "scan/ScanInfo.h" + +class Scanner { +public: + + /// A static vector of publically accessible scanners + static std::vector> scanners; + + /** + * Gets a vector of detections associated with the provided detection + * + * @param detection The detection to find associations for + * + * @return A vector of detections associated with the provided detection + */ + virtual std::unordered_map, Association> GetAssociatedDetections( + IN CONST Detection& detection + ); + + /** + * Scans a detection and returns the certainty that the detection is malicious. + * + * @param detection The Detection to scan + * + * @return A Certainty indicating the degree of certainty to which the detection + * is malicious + */ + virtual Certainty ScanDetection( + IN CONST Detection& detection + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/ServiceScanner.h b/BLUESPAWN-win-client/headers/scan/ServiceScanner.h new file mode 100644 index 00000000..d0d39ea3 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/ServiceScanner.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include "scan/Detections.h" +#include "scan/Scanner.h" + +class ServiceScanner : public Scanner { +public: + + /** + * Gets a vector of detections associated with the provided detection. This is done by finding the associated + * registry entry and all associated files + * + * @param detection The detection for which associations will be found + * + * @return A vector of detections associated with the provided detection + */ + virtual std::unordered_map, Association> GetAssociatedDetections( + IN CONST Detection& detection + ); + + /** + * Performs a quick scan on the specified service to determine if it may be malicious. This is done by checking + * if the service path referenced by the service refers to a signed binary or if the service name / display name + * appear potentially malicious. At least one argument must be present. + * + * @param ServiceName The name of the service, if known. If not known, this may be set to nullopt + * @param ServiceDisplayName The display name of the service, if known. If not known, this may be set to nullopt + * @param ServicePath The path to the service executable, if known. If not known, this may be set to nullopt + * + * @return true if the service may be malicious, false otherwise + */ + static bool PerformQuickScan( + IN CONST std::optional& ServiceName, + IN CONST std::optional& ServiceDisplayName, + IN CONST std::optional& ServicePath = std::nullopt OPTIONAL + ); + + /** + * Scans a detection and returns the certainty that the detection is malicious. This is done by checking service + * names that meet certain properties, are known to have malicious uses, or appear generated by common offensive + * tools + * + * @param detection The Detection object to scan + * + * @return A Certainty indicating the degree of certainty for which the detection is malicious + */ + virtual Certainty ScanDetection( + IN CONST Detection& detection + ); +}; \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/scan/YaraScanner.h b/BLUESPAWN-win-client/headers/scan/YaraScanner.h new file mode 100644 index 00000000..0af0ce47 --- /dev/null +++ b/BLUESPAWN-win-client/headers/scan/YaraScanner.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "util/filesystem/FileSystem.h" +#include "util/wrappers.hpp" + +#include "yara/types.h" + +enum class YaraStatus { + Success, + RulesMissing, + RulesInvalid, + Failure, +}; + +struct YaraScanResult { + std::vector vKnownBadRules; + std::vector vIndicatorRules; + + YaraStatus status; + + operator bool(); + bool operator!(); + + void AddBadRule(IN CONST std::wstring& identifier); + void AddIndicatorRule(IN CONST std::wstring& identifier); +}; + +class YaraScanner { + private: + static const YaraScanner instance; + + YaraScanner(); + + YR_RULES* KnownBad = nullptr; + YR_RULES* KnownBad2 = nullptr; + YR_RULES* Indicators = nullptr; + + YaraStatus status; + + public: + static const YaraScanner& GetInstance(); + + ~YaraScanner(); + + YaraScanResult ScanFile(const FileSystem::File& file) const; + YaraScanResult ScanMemory(LPVOID location, DWORD size) const; + YaraScanResult ScanMemory(const AllocationWrapper& allocation) const; + YaraScanResult ScanMemory(const MemoryWrapper<>& memory) const; + + YaraScanner(const YaraScanner&) = delete; + YaraScanner operator=(const YaraScanner&) = delete; + YaraScanner(YaraScanner&&) = delete; + YaraScanner operator=(YaraScanner&&) = delete; +}; diff --git a/BLUESPAWN-client/headers/user/CLI.h b/BLUESPAWN-win-client/headers/user/CLI.h similarity index 99% rename from BLUESPAWN-client/headers/user/CLI.h rename to BLUESPAWN-win-client/headers/user/CLI.h index 34965965..93b0ed7e 100644 --- a/BLUESPAWN-client/headers/user/CLI.h +++ b/BLUESPAWN-win-client/headers/user/CLI.h @@ -1,7 +1,7 @@ #pragma once #include "user/iobase.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" #include diff --git a/BLUESPAWN-client/headers/user/banners.h b/BLUESPAWN-win-client/headers/user/banners.h similarity index 100% rename from BLUESPAWN-client/headers/user/banners.h rename to BLUESPAWN-win-client/headers/user/banners.h diff --git a/BLUESPAWN-win-client/headers/user/bluespawn.h b/BLUESPAWN-win-client/headers/user/bluespawn.h new file mode 100644 index 00000000..9e2dbee8 --- /dev/null +++ b/BLUESPAWN-win-client/headers/user/bluespawn.h @@ -0,0 +1,54 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +#include + +#include "util/configurations/Registry.h" +#include "util/log/DetectionSink.h" +#include "util/log/Log.h" + +#include "hunt/Hunt.h" +#include "hunt/HuntRegister.h" +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "reaction/ReactionManager.h" +#include "scan/DetectionRegister.h" +#include "user/banners.h" + +enum class BluespawnMode { HUNT, SCAN, MONITOR, MITIGATE }; + +class Bluespawn { + std::map modes; + std::vector vIncludedHunts; + std::vector vExcludedHunts; + + void RunMitigations(bool enforce, bool force); + void RunHunts(); + void RunMonitor(); + + public: + Bluespawn(); + + void AddReaction(std::unique_ptr&& reaction); + void EnableMode(BluespawnMode mode, int argument = 0); + void SetIncludedHunts(std::vector includedHunts); + void SetExcludedHunts(std::vector excludedHunts); + void Run(); + + void check_correct_arch(); + + static HuntRegister huntRecord; + static MitigationRegister mitigationRecord; + static Aggressiveness aggressiveness; + static DetectionRegister detections; + static std::vector> detectionSinks; + static bool EnablePreScanDetections; + + static ReactionManager reaction; + static const IOBase& io; +}; diff --git a/BLUESPAWN-client/headers/user/iobase.h b/BLUESPAWN-win-client/headers/user/iobase.h similarity index 100% rename from BLUESPAWN-client/headers/user/iobase.h rename to BLUESPAWN-win-client/headers/user/iobase.h diff --git a/BLUESPAWN-common/headers/common/DynamicLinker.h b/BLUESPAWN-win-client/headers/util/DynamicLinker.h similarity index 100% rename from BLUESPAWN-common/headers/common/DynamicLinker.h rename to BLUESPAWN-win-client/headers/util/DynamicLinker.h diff --git a/BLUESPAWN-common/headers/common/Internals.h b/BLUESPAWN-win-client/headers/util/Internals.h similarity index 95% rename from BLUESPAWN-common/headers/common/Internals.h rename to BLUESPAWN-win-client/headers/util/Internals.h index ee5b5b48..d15b82b3 100644 --- a/BLUESPAWN-common/headers/common/Internals.h +++ b/BLUESPAWN-win-client/headers/util/Internals.h @@ -1,19 +1,19 @@ -#pragma once - -#include - -typedef struct _KEY_VALUE_BASIC_INFORMATION { - ULONG TitleIndex; - ULONG Type; - ULONG NameLength; - WCHAR Name[1]; -} KEY_VALUE_BASIC_INFORMATION, * PKEY_VALUE_BASIC_INFORMATION; - -typedef struct _KEY_VALUE_FULL_INFORMATION { - ULONG TitleIndex; - ULONG Type; - ULONG DataOffset; - ULONG DataLength; - ULONG NameLength; - WCHAR Name[1]; -} KEY_VALUE_FULL_INFORMATION, * PKEY_VALUE_FULL_INFORMATION; +#pragma once + +#include + +typedef struct _KEY_VALUE_BASIC_INFORMATION { + ULONG TitleIndex; + ULONG Type; + ULONG NameLength; + WCHAR Name[1]; +} KEY_VALUE_BASIC_INFORMATION, * PKEY_VALUE_BASIC_INFORMATION; + +typedef struct _KEY_VALUE_FULL_INFORMATION { + ULONG TitleIndex; + ULONG Type; + ULONG DataOffset; + ULONG DataLength; + ULONG NameLength; + WCHAR Name[1]; +} KEY_VALUE_FULL_INFORMATION, * PKEY_VALUE_FULL_INFORMATION; diff --git a/BLUESPAWN-win-client/headers/util/Promise.h b/BLUESPAWN-win-client/headers/util/Promise.h new file mode 100644 index 00000000..d9517800 --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/Promise.h @@ -0,0 +1,280 @@ +#pragma once + +#include + +#include +#include + +#include "util/wrappers.hpp" + +// https://stackoverflow.com/questions/6534041/how-to-check-whether-operator-exists +// Used to avoid a requirement for all types used in a promise to have an == operator +// defined. +namespace detail{ + template + struct has_operator_equals_impl { + template // template parameters here to enable SFINAE + static auto test(T&& t, U&& u) -> decltype(t == u, void(), std::true_type{}); + static auto test(...) -> std::false_type{}; + using type = decltype(test(std::declval(), std::declval())); + }; +} // namespace detail + +/** + * Represents a value from an asynchronous function that may be available later. + */ +template +class Promise { +private: + + /// Indicates whether the promise is guaranteed to be fufilled + const bool guaranteed; + + /// An event that will be set when the promise is either fufilled or + /// invalidated + const HandleWrapper hEvent; + + /// A critical section used to guard access to members of this class + const CriticalSection hGuard; + + /// A struct containing members meant to be the same across all copies + /// of the same Promise. These will be held in a shared pointer. + struct Members { + /// Holds the value used to fufill the promise, if any + std::optional value; + + /// A vector of functions to be called when the promise is fufilled + std::vector> SuccessFunctions; + + /// A vector of functions to be called when the promise is invalidated + std::vector> FailureFunctions; + }; + + /// A shared pointer to members, to be shared across copies of the same + /// promise. + std::shared_ptr members; + + +public: + + /** + * Instantiates a promise. + * + * @param guaranteed Indicates whether this promise is guaranteed not to be + * invalidated. + */ + Promise( + IN bool guaranteed = false OPTIONAL + ) : guaranteed{ guaranteed }, + hEvent{ CreateEventW(nullptr, true, false, nullptr) }, + hGuard{}, + members{ std::make_shared() }{} + + /** + * Instantiates a promise already fufilled. + * + * @param guaranteed Indicates whether this promise is guaranteed not to be + * invalidated. + */ + Promise( + IN CONST T& value + ) : guaranteed{ true }, + hEvent{ CreateEventW(nullptr, true, true, nullptr) }, + hGuard{}, + members{ std::make_shared(value) }{} + + + /** + * Waits for the promise to be either fufilled or invalidated, and then + * returns an optional, holding the value used to fufill it or nullopt + * if invalidated. + * + * @return Returns an optional, holding the value used to fufill it or + * nullopt if invalidated. + */ + std::optional GetValue() const { + auto status{ WaitForSingleObject(hEvent, INFINITE) }; + if(status != WAIT_OBJECT_0){ + throw std::exception("Waiting for value failed"); + } + + return members->value; + } + + /** + * Adds a functioned to be called if the promise is fufilled. If the promise + * has already been fufilled, then the provided function will be immediately + * called. Note that the provided function will be called by the thread fufilling + * the promise if the promise has not yet been fufilled, so it may be preferable + * to design the callback to queue a task to a threadpool or start a new thread. + * + * @param callback The function to call if and when the promise is fufilled. + */ + void OnSuccess( + IN CONST std::function& callback + ){ + EnterCriticalSection(hGuard); + + if(!Fufilled()){ + members->SuccessFunctions.emplace_back(callback); + LeaveCriticalSection(hGuard); + } else{ + LeaveCriticalSection(hGuard); + if(value){ + callback(*value); + } + } + } + + /** + * Adds a functioned to be called if the promise is invalidated. If the promise + * has already been invalidated, then the provided function will be immediately + * called. Note that the provided function will be called by the thread invalidating + * the promise if the promise has not yet been invalidating, so it may be preferable + * to design the callback to queue a task to a threadpool or start a new thread. + * + * @param callback The function to call if and when the promise is invalidated. + */ + void OnFailure( + IN CONST std::function& callback + ){ + EnterCriticalSection(hGuard); + + if(!Fufilled()){ + members->SuccessFunctions.emplace_back(callback); + LeaveCriticalSection(hGuard); + } else{ + LeaveCriticalSection(hGuard); + if(!value){ + callback(); + } + } + } + + /** + * Attempts to fufill the promise with a value. This will trigger the functions + * registered with OnSuccess if successful. + * + * @return True if the promise has been fufilled with the value provided; false + * if the promise had already been fufilled using a different value or if + * the promise has been invalidated. + */ + bool Fufill( + IN CONST T& value + ){ + EnterCriticalSection(hGuard); + + if(!Finished()){ + members->value = value; + SetEvent(hEvent); + auto copy{ members->SuccessFunctions }; + + LeaveCriticalSection(hGuard); + + for(const auto& func : copy){ + func(value); + } + + return true; + } else{ + LeaveCriticalSection(hGuard); + + return false; + } + } + + /** + * Attempts to invalidate the promise, indicating that no value ever be + * returned. This will trigger the functions registered with OnFailure if + * successful. If the promise has already been fufilled, this will return + * false. Throws an exception if this promise is guaranteed. + * + * @return True if the promise has been invalidated; false otherwise. + */ + bool Invalidate(){ + if(guaranteed){ + throw std::exception("Invalidating a guaranteed promise"); + } + + EnterCriticalSection(hGuard); + + if(!Finished()){ + SetEvent(hEvent); + auto copy{ members->FailureFunctions }; + LeaveCriticalSection(hGuard); + + for(const auto& func : copy){ + func(); + } + + return true; + } else{ + LeaveCriticalSection(hGuard); + + return !members->value; + } + } + + /** + * Indicates whether the promise has been fufilled. + * + * @return true if this promise has been fufilled; false otherwise. + */ + bool Fufilled() const { + return Finished() && members->value; + } + + /** + * Indicates whether the promise has been invalidated. + * + * @return true if this promise has been invalidated; false otherwise. + */ + bool Invalidated() const { + return Finished() && !members->value; + } + + /** + * Indicates whether the promise has been either fufilled or invalidated. + * + * @return true if this promise has been either fufilled invalidated; false otherwise. + */ + bool Finished() const { + return WAIT_TIMEOUT != WaitForSingleObjectEx(hEvent, 0, true); + } + + /** + * Indicates whether this promise is guaranteed to not be invalidated. + * + * @return True if this promise is guaranteed to not be invalidated; false otherwise. + */ + bool IsGuaranteed() const { + return guaranteed; + } + + /** + * Provides an implicit cast to HANDLE for use in wait functions such as + * WaitForSingleObject and similar. This handle will be signalled when the promise + * is fufilled or invalidated. Note that this handle should not be set or reset. + * + * @return A handle to the underlying event for this promise. + */ + operator HANDLE() const { + return hEvent; + } + + /** + * Provides an implicit cast to the expected value type of the promise. Note that + * this is an unsafe method and will throw an exception if the promise is invalidated. + * + * @return The value used to fufill the promise. + */ + operator T(){ + GetValue(); + + if(!Fufilled()){ + throw std::exception("Attempting to get the value of invalidated promise"); + } + + return *members->value; + } +}; \ No newline at end of file diff --git a/BLUESPAWN-common/headers/common/StringUtils.h b/BLUESPAWN-win-client/headers/util/StringUtils.h similarity index 82% rename from BLUESPAWN-common/headers/common/StringUtils.h rename to BLUESPAWN-win-client/headers/util/StringUtils.h index 0652b925..1c83c52b 100644 --- a/BLUESPAWN-common/headers/common/StringUtils.h +++ b/BLUESPAWN-win-client/headers/util/StringUtils.h @@ -46,7 +46,7 @@ std::wstring ExpandEnvStringsW(const std::wstring& in); * * @return The string with all environment strings expanded. */ -std::string ExpandEnvStringsA(const std::string& in); +std::wstring ExpandEnvStringsA(const std::string& in); /** * Convert a string or wstring to uppercase. Note that the only @@ -88,6 +88,21 @@ bool CompareIgnoreCase(const T& in1, const T& in2); #define CompareIgnoreCaseA CompareIgnoreCase #define CompareIgnoreCaseW CompareIgnoreCase +/** + * Replaces all instances of a substring found inside of a string + * + * @param string The string to modify + * @param search The substring to be replaced + * @param replacement The replacement for the substring + * + * @return A string with the replacements applied. + */ +template +T StringReplace(const T& string, const T& search, const T& replacement); +#define StringReplaceA StringReplace +#define StringReplaceW StringReplace +#define CompareIgnoreCaseW CompareIgnoreCase + /** * Split a string into substrings based on a delimter. This currently * does not support regular expressions. @@ -100,4 +115,4 @@ bool CompareIgnoreCase(const T& in1, const T& in2); template std::vector> SplitString(const std::basic_string& in, const std::basic_string& delimiter); #define SplitStringA SplitString -#define SplitStringW SplitString \ No newline at end of file +#define SplitStringW SplitString diff --git a/BLUESPAWN-win-client/headers/util/ThreadPool.h b/BLUESPAWN-win-client/headers/util/ThreadPool.h new file mode 100644 index 00000000..38651fcd --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/ThreadPool.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "util/Promise.h" +#include "util/wrappers.hpp" + +class ThreadPool { +private: + + /// A queue of tasks to be executed by the threadpool + std::queue> tasks; + + /// A vector of worker threads + std::vector threads; + + /// A critical section guarding access to tasks and threads + CriticalSection hGuard; + + /// A semaphore counting the number of tasks in the queue + HandleWrapper hSemaphore; + + /// An event object that will be signalled whenever the threadpool has no remaining tasks + HandleWrapper hEvent; + + /// A boolean indicating whether the threadpool is active. If false, all threads will + /// terminate when they finish their tasks. + bool active; + + /// The number of tasks not finished being executed. Access is protected by hGuard + size_t count; + + /// The threadpool instance + static ThreadPool instance; + + /// Private constructor + ThreadPool(); + + /// A vector of functions to be called when an exception is raised + std::vector> vExceptionHandlers; + + /// The function that each worker thread runs in, for internal use only + void ThreadFunction(); + +public: + + ~ThreadPool(); + + /** + * Returns a reference to the threadpool instance + */ + static ThreadPool& GetInstance(); + + // Delete the move and copy constructors; this is a singleton class + ThreadPool(const ThreadPool&) = delete; + ThreadPool(ThreadPool&&) = delete; + ThreadPool operator=(const ThreadPool&) = delete; + ThreadPool operator=(ThreadPool&&) = delete; + + /** + * Enqueues a task to the threadpool. This task will be executed at some + * point in the future by the threadpool. + */ + void EnqueueTask( + IN CONST std::function& task + ); + + /** + * Enqueue a function to the threadpool and return a promise for its return value. The promise will be fufilled if + * the function returns a value or invalidated if the function throws an exception. If more complex fufillment or + * invalidation guidelines are required, design an std::function to handle creation of the promise and use + * EnqueueTask instead. + * + * @param function The function to return a promise for + * + * @return A promise which will hold the return value of the function + */ + template + Promise RequestPromise( + IN CONST std::function& function + ){ + Promise promise{ false }; + EnqueueTask([promise, function]() mutable { + try { + promise.Fufill(function()); + } catch(...){ + promise.Invalidate(); + } + }); + return promise; + } + + /** + * Adds a function to be called whenever a task raises an exception. This + * function call will be enqueued as a separate task to the threadpool rather + * than being immediately handled. + * + * @param function A function to be called whenever a task raises an exception. + * The exception will be passed as an argument to the function. + */ + void AddExceptionHandler( + IN CONST std::function& function + ); + + /** + * Waits for all tasks to be finished before returning. + */ + void Wait() const; +}; diff --git a/BLUESPAWN-common/headers/common/Utils.h b/BLUESPAWN-win-client/headers/util/Utils.h similarity index 65% rename from BLUESPAWN-common/headers/common/Utils.h rename to BLUESPAWN-win-client/headers/util/Utils.h index 0ca8cf4b..79ad0470 100644 --- a/BLUESPAWN-common/headers/common/Utils.h +++ b/BLUESPAWN-win-client/headers/util/Utils.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include @@ -12,7 +13,7 @@ } \ } -int64_t SystemTimeToInteger(const SYSTEMTIME st); -std::wstring FormatWindowsTime(const SYSTEMTIME systemtime); -std::wstring FormatWindowsTime(const FILETIME systemtime); -std::wstring FormatWindowsTime(const std::wstring& windowsTime); \ No newline at end of file +int64_t SystemTimeToInteger(const SYSTEMTIME& st); +std::wstring FormatWindowsTime(const SYSTEMTIME& systemtime); +std::wstring FormatWindowsTime(const FILETIME& systemtime); +std::wstring FormatWindowsTime(const std::wstring& windowsTime); diff --git a/BLUESPAWN-client/headers/util/configurations/CollectInfo.h b/BLUESPAWN-win-client/headers/util/configurations/CollectInfo.h similarity index 100% rename from BLUESPAWN-client/headers/util/configurations/CollectInfo.h rename to BLUESPAWN-win-client/headers/util/configurations/CollectInfo.h diff --git a/BLUESPAWN-client/headers/util/configurations/Registry.h b/BLUESPAWN-win-client/headers/util/configurations/Registry.h similarity index 83% rename from BLUESPAWN-client/headers/util/configurations/Registry.h rename to BLUESPAWN-win-client/headers/util/configurations/Registry.h index e0ccbb9c..29a27218 100644 --- a/BLUESPAWN-client/headers/util/configurations/Registry.h +++ b/BLUESPAWN-win-client/headers/util/configurations/Registry.h @@ -6,9 +6,10 @@ #include #include #include +#include -#include "common/DynamicLinker.h" -#include "common/wrappers.hpp" +#include "util/DynamicLinker.h" +#include "util/wrappers.hpp" #include "util/log/Loggable.h" @@ -42,6 +43,18 @@ namespace Registry { class RegistryKey : public Loggable { public: + + /** + * Checks if a given registry key exists. + * + * @param hive The registry hive to search for `name` + * @param name The path to the registry key under `hive` + * @param WoW64 True if the key should be reflected/redirected for WoW64; false otherwise. + * + * @return true if the key exists; false otherwise + */ + static bool CheckKeyExists(HKEY hive, const std::wstring& name, bool WoW64 = false); + /* Copy constructor for a RegistryKey */ RegistryKey(const RegistryKey& key) noexcept; @@ -82,7 +95,50 @@ namespace Registry { RegistryKey& operator=(RegistryKey&& key) noexcept; private: - static std::map _ReferenceCounts; + + /** + * This class handles reference tracking for registry key handles + */ + class Tracker { + private: + + /// A mapping of HKEYs to the number of references to that key + std::unordered_map counts; + + /// A critical section guarding access to counts + CriticalSection hGuard; + + public: + + explicit Tracker(); + + /** + * Increments the number of references for hKey + * + * @param hKey The handle to increment references for + */ + void Increment(IN HKEY hKey); + + /** + * Decrements the number of references for hKey, closing the handle if it reaches zero + * + * @param hKey The handle to decrement references for + */ + void Decrement(IN HKEY hKey); + + /** + * Gets the number of references to a given HKEY + * + * @param hKey The HKEY to check the number of references for + * + * @return The number of references to hKey + */ + int Get(IN HKEY hKey); + }; + + static std::shared_ptr __tracker; + + std::shared_ptr tracker; HKEY hkBackingKey; @@ -252,4 +308,11 @@ namespace Registry { operator HKEY() const; }; -} \ No newline at end of file +} + +template<> +struct std::hash { + size_t operator()( + IN CONST Registry::RegistryKey& key + ) const; +}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/configurations/RegistryValue.h b/BLUESPAWN-win-client/headers/util/configurations/RegistryValue.h similarity index 63% rename from BLUESPAWN-client/headers/util/configurations/RegistryValue.h rename to BLUESPAWN-win-client/headers/util/configurations/RegistryValue.h index 7f2f9282..1015a515 100644 --- a/BLUESPAWN-client/headers/util/configurations/RegistryValue.h +++ b/BLUESPAWN-win-client/headers/util/configurations/RegistryValue.h @@ -2,7 +2,7 @@ #include "Registry.h" #include "util/log/Loggable.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" #include #include @@ -29,10 +29,26 @@ namespace Registry { RegistryValue(const RegistryKey& key, const std::wstring& wValueName, AllocationWrapper&& lpData); RegistryValue(const RegistryKey& key, const std::wstring& wValueName, std::vector&& wData); + /** + * Attempts to create a RegistryValue object from a value name and the key under which the value can be found + * + * @param key The key under which the value can be found + * @param name The name of the value + * + * @return An optional containing the RegistryValue object if the value was found, and nullopt otherwise + */ + static std::optional Create( + IN CONST RegistryKey& key, + IN CONST std::wstring& name + ); + RegistryType GetType() const; std::wstring GetPrintableName() const; virtual std::wstring ToString() const; + + bool operator==(const RegistryValue& value) const; + bool operator<(const RegistryValue& value) const; }; } \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/configurations/ScheduledTasks.h b/BLUESPAWN-win-client/headers/util/configurations/ScheduledTasks.h similarity index 100% rename from BLUESPAWN-client/headers/util/configurations/ScheduledTasks.h rename to BLUESPAWN-win-client/headers/util/configurations/ScheduledTasks.h diff --git a/BLUESPAWN-client/headers/util/eventlogs/EventLogItem.h b/BLUESPAWN-win-client/headers/util/eventlogs/EventLogItem.h similarity index 97% rename from BLUESPAWN-client/headers/util/eventlogs/EventLogItem.h rename to BLUESPAWN-win-client/headers/util/eventlogs/EventLogItem.h index ca9dc5ef..9be52c9f 100644 --- a/BLUESPAWN-client/headers/util/eventlogs/EventLogItem.h +++ b/BLUESPAWN-win-client/headers/util/eventlogs/EventLogItem.h @@ -3,7 +3,7 @@ #include #include #include -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" namespace EventLogs { diff --git a/BLUESPAWN-client/headers/util/eventlogs/EventLogs.h b/BLUESPAWN-win-client/headers/util/eventlogs/EventLogs.h similarity index 95% rename from BLUESPAWN-client/headers/util/eventlogs/EventLogs.h rename to BLUESPAWN-win-client/headers/util/eventlogs/EventLogs.h index 771213f9..62cd3a1c 100644 --- a/BLUESPAWN-client/headers/util/eventlogs/EventLogs.h +++ b/BLUESPAWN-win-client/headers/util/eventlogs/EventLogs.h @@ -5,11 +5,10 @@ #include #include #include -#include "reaction/Reaction.h" #include #include "util/eventlogs/EventSubscription.h" #include "util/eventlogs/EventLogItem.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" #include "XpathQuery.h" #pragma comment(lib, "wevtapi.lib") @@ -54,7 +53,7 @@ namespace EventLogs { */ std::optional EventToEventLogItem(const EventWrapper& hEvent, const std::vector& params); - std::shared_ptr EventLogItemToDetection(const EventLogItem& pItem); + Detection EventLogItemToDetection(const EventLogItem& pItem); /** * Subscribe a HuntTriggerReaction to a specific Windows event diff --git a/BLUESPAWN-client/headers/util/eventlogs/EventSubscription.h b/BLUESPAWN-win-client/headers/util/eventlogs/EventSubscription.h similarity index 96% rename from BLUESPAWN-client/headers/util/eventlogs/EventSubscription.h rename to BLUESPAWN-win-client/headers/util/eventlogs/EventSubscription.h index f3953bf4..2d1dece6 100644 --- a/BLUESPAWN-client/headers/util/eventlogs/EventSubscription.h +++ b/BLUESPAWN-win-client/headers/util/eventlogs/EventSubscription.h @@ -4,7 +4,7 @@ #include #include #include -#include "reaction/Detections.h" +#include "scan/Detections.h" #include "util/eventlogs/EventLogItem.h" #pragma comment(lib, "wevtapi.lib") diff --git a/BLUESPAWN-client/headers/util/eventlogs/XpathQuery.h b/BLUESPAWN-win-client/headers/util/eventlogs/XpathQuery.h similarity index 100% rename from BLUESPAWN-client/headers/util/eventlogs/XpathQuery.h rename to BLUESPAWN-win-client/headers/util/eventlogs/XpathQuery.h diff --git a/BLUESPAWN-client/headers/util/filesystem/FileSystem.h b/BLUESPAWN-win-client/headers/util/filesystem/FileSystem.h similarity index 92% rename from BLUESPAWN-client/headers/util/filesystem/FileSystem.h rename to BLUESPAWN-win-client/headers/util/filesystem/FileSystem.h index ade08e45..8f40d9b8 100644 --- a/BLUESPAWN-client/headers/util/filesystem/FileSystem.h +++ b/BLUESPAWN-win-client/headers/util/filesystem/FileSystem.h @@ -8,9 +8,9 @@ #include #include "util/log/Loggable.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" #include "util/permissions/permissions.h" -#include "common/DynamicLinker.h" +#include "util/DynamicLinker.h" #define BUFSIZE 1024 #define MD5LEN 16 @@ -68,7 +68,7 @@ namespace FileSystem { class File : public Loggable { //Whether or not this current file actually exists on the filesystem - bool bFileExists; + bool bFileExists; //Whether or not the program has write access to the file bool bWriteAccess; @@ -104,7 +104,7 @@ namespace FileSystem { /** * Function to assist in retrieving file hashes - * + * * @param HashType * * return std::wstring value of the requested hash type @@ -115,7 +115,7 @@ namespace FileSystem { /** * Creates a file object with a given path * If the file already exists, opens a handle to it - * + * * @param path The path to the file to be opened */ File(IN const std::wstring& path); @@ -145,20 +145,20 @@ namespace FileSystem { /** * Function to write to arbitrary offset in the file - * + * * @param value The value to be written - * @param offset The offset to write to + * @param offset The offset to write to * @param truncate If true truncate the file after the write * @param insert If true insert the value at the offset. If false, overwrite the bytes at that location in the file * * @return true if write successful, false if write unsuccessful */ - bool Write(IN const LPVOID value, IN const long offset, IN const unsigned long length, __in_opt const bool truncate = false, - __in_opt const bool insert = false) const; + bool Write(IN const LPVOID value, IN const long offset, IN const unsigned long length, __in_opt const bool truncate = false, + __in_opt const bool insert = false) const; /** * Function to read from arbitrary offset in the file - * + * * @param buffer The buffer to read to * @param offset The offset to read from * @param amount How many bytes to read. Amount should be less than or equal to the size of the buffer - 1 @@ -210,6 +210,13 @@ namespace FileSystem { */ bool MatchesAttributes(IN const FileSearchAttribs& searchAttribs) const; + /** + * Finds the issuer of the certificate, if present + * + * @return the issuer of the certificate, if present + */ + std::optional File::GetCertificateIssuer() const; + /** * Returns whether or not the current file is signed. * @@ -226,7 +233,7 @@ namespace FileSystem { /** * Function to create the file if it doesn't exist - * + * * @return true if creation was successful, false if unsuccessful */ bool Create(); @@ -370,7 +377,7 @@ namespace FileSystem { /** * Constructor for the folder object - * + * * @param path - the path to the folder */ Folder(const std::wstring& path); @@ -387,7 +394,7 @@ namespace FileSystem { /** * Function to move to the beginnning of the directory - * + * * @return true if successful, false otherwise */ @@ -399,7 +406,7 @@ namespace FileSystem { /** * Function to check if current handle is directory or file * - * @return true if current is a file, false otherwise. + * @return true if current is a file, false otherwise. */ bool GetCurIsFile() const; @@ -490,4 +497,4 @@ namespace FileSystem { */ bool TakeOwnership(); }; -} \ No newline at end of file +} diff --git a/BLUESPAWN-win-client/headers/util/log/CLISink.h b/BLUESPAWN-win-client/headers/util/log/CLISink.h new file mode 100644 index 00000000..885dd40c --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/log/CLISink.h @@ -0,0 +1,120 @@ +#pragma once + +#include + +#include "LogSink.h" +#include "LogLevel.h" +#include "DetectionSink.h" + +namespace Log { + + /** + * CLISink provides a sink for the logger that directs output to the console. + * + * Each log message is prepended with the severity of the log, as defined in MessagePrepends. This prepended text + * is colored with the color indicated in PrependColors. + */ + class CLISink : public LogSink, public DetectionSink { + private: + + /// Enum containing color codes for console colors + enum class MessageColor { + BLACK = 0x0, + DARKBLUE = 0x1, + DARKGREEN = 0x2, + CYAN = 0x3, + DARKRED = 0x4, + DARKPINK = 0x5, + GOLD = 0x6, + LIGHTGREY = 0x7, + DARKGREY = 0x8, + BLUE = 0x9, + GREEN = 0xA, + LIGHTBLUE = 0xB, + RED = 0xC, + PINK = 0xD, + YELLOW = 0xE, + WHITE = 0xF, + }; + + /// Prepends for messages + std::wstring MessagePrepends[5] = { L"[ERROR]", L"[WARNING]", L"[INFO]", L"[VERBOSE]", L"[DETECTION]" }; + + /// Colors for the message prepends + MessageColor PrependColors[5] = { MessageColor::RED, MessageColor::YELLOW, MessageColor::BLUE, + MessageColor::LIGHTBLUE, MessageColor::GOLD }; + + /// Mutex guarding accesses to the console + HandleWrapper hMutex; + + /** + * Sets the color of text written to the console. The low order nibble is the color of the text, and the high + * order nibble is the color of the background. Colors are defined in the MessageColor enum. Note that this + * function is for internal use, and any external calls to it will be overridden by the next log message. + * + * @param color The color to set the console + */ + void SetConsoleColor(MessageColor color); + + public: + + CLISink(); + + /** + * Outputs a message to the console if its logging level is enabled. The log message is prepended with its + * severity level. + * + * @param level The level at which the message is being logged + * @param message The message to log + */ + virtual void LogMessage( + IN CONST LogLevel& level, + IN CONST std::wstring& message + ) override; + + /** + * Compares this CLISink to another LogSink. Currently, as only one console is supported, any other CLISink is + * considered to be equal. This is subject to change in the event that support for more consoles is added. + * + * @param sink The LogSink to compare + * + * @return Whether or not the argument and this sink are considered equal. + */ + virtual bool operator==( + IN CONST LogSink& sink + ) const; + + /** + * Updates the raw and combined certainty values associated with a detection + * + * @param detection The detection to update + */ + virtual void UpdateCertainty( + IN CONST std::shared_ptr& detection + ); + + /** + * Records a detection to the console. + * + * @param detection The detection to record + * @param type The type of record this is, either PreScan or PostScan + */ + virtual void RecordDetection( + IN CONST std::shared_ptr& detection, + IN RecordType type + ); + + /** + * Records an association between two detections to the console + * + * @param first The first detection in the assocation. This detection's ID will be lower than the second's. + * @param second The second detection in the association. + * @param strength The strength of the connection + */ + virtual void RecordAssociation( + IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength + ); + }; +} diff --git a/BLUESPAWN-win-client/headers/util/log/DebugSink.h b/BLUESPAWN-win-client/headers/util/log/DebugSink.h new file mode 100644 index 00000000..44475322 --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/log/DebugSink.h @@ -0,0 +1,84 @@ +#pragma once + +#include "LogSink.h" +#include "DetectionSink.h" + +#include "util/wrappers.hpp" + +namespace Log { + + /** + * DebugSink provides a sink for the logger that directs output to the debug console. + * + * Each log message is prepended with the severity of the log, as defined in MessagePrepends. + */ + class DebugSink : public LogSink, public DetectionSink { + private: + + /// A list of different prepends to be used at each log level + static inline std::wstring MessagePrepends[4] = { L"[ERROR]", L"[WARNING]", L"[INFO]", L"[VERBOSE]" }; + + /// A critical section ensuring associated messages occur consecutively + CriticalSection hGuard; + + public: + + /** + * Outputs a message to the debug console if its logging level is enabled. The log message is prepended with + * its severity level. + * + * @param level The level at which the message is being logged + * @param message The message to log + */ + virtual void LogMessage( + IN CONST LogLevel& level, + IN CONST std::wstring& message + ) override; + + /** + * Compares this Debug to another LogSink. Currently, as only one debug console is supported, any other + * DebugSink is considered to be equal. This is subject to change in the event that support for more debug + * consoles is added. + * + * @param sink The LogSink to compare + * + * @return Whether or not the argument and this sink are considered equal. + */ + virtual bool operator==( + IN CONST LogSink& sink + ) const; + + /** + * Records a detection to the debug console. + * + * @param detection The detection to record + * @param type The type of record this is, either PreScan or PostScan + */ + virtual void RecordDetection( + IN CONST std::shared_ptr& detection, + IN RecordType type + ); + + /** + * Records an association between two detections to the console + * + * @param first The first detection in the assocation. This detection's ID will be lower than the second's. + * @param second The second detection in the association. + * @param strength The strength of the connection + */ + virtual void RecordAssociation( + IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength + ); + + /** + * Updates the raw and combined certainty values associated with a detection + * + * @param detection The detection to update + */ + virtual void UpdateCertainty( + IN CONST std::shared_ptr& detection + ); + }; +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/headers/util/log/DetectionSink.h b/BLUESPAWN-win-client/headers/util/log/DetectionSink.h new file mode 100644 index 00000000..a7268e8b --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/log/DetectionSink.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include "scan/Detections.h" + +enum class RecordType { + + /** + * Detections will be sent to the DetectionSink with PreScan every time a new Detection object + * is sent to the DetectionRegister. This will occur before any de-duplication or scans. There + * are expected to be a number of false positives of type PreScan + */ + PreScan, + + /** + * Detections will be sent to the DetectionSink with PostScan every time a Detection is scanned. + * This may be triggered multiple times for the same detection if a duplicate exists and causes the + * non-associative certainty to change. For duplicates where the non-associative certainty does + * not change, this will not be triggered. + */ + PostScan +}; + +class DetectionSink { +public: + + /** + * Records a detection to the sink. This may be recorded before the detection has been scanned or + * immediately after the scan. If all scans have not yet been finished, there may be associations + * between detections not yet discovered. Be sure to acquire appropriate mutices before accessing + * fields of the detection in implementations of this interface. Note: callers of this function + * must not hold either of the CriticalSections of the detection, or else a deadlock may arise. + * + * @param detection The detection to record + * @param type The type of record this is, either PreScan or PostScan + */ + virtual void RecordDetection( + IN CONST std::shared_ptr& detection, + IN RecordType type + ) = 0; + + /** + * Records an association between two detections. If an association between the two already exists, + * then this represents a second assocation between the two, which should be added to the pre-existing + * association. + * + * @param first The first detection in the assocation. This detection's ID will be lower than the second's. + * @param second The second detection in the association. + * @param strength The strength of the connection + */ + virtual void RecordAssociation( + IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength + ) = 0; + + /** + * Updates the raw and combined certainty values associated with a detection + * + * @param detection The detection to update + */ + virtual void UpdateCertainty( + IN CONST std::shared_ptr& detection + ) = 0; +}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/log/Log.h b/BLUESPAWN-win-client/headers/util/log/Log.h similarity index 54% rename from BLUESPAWN-client/headers/util/log/Log.h rename to BLUESPAWN-win-client/headers/util/log/Log.h index 02a375f7..07750cf5 100644 --- a/BLUESPAWN-client/headers/util/log/Log.h +++ b/BLUESPAWN-win-client/headers/util/log/Log.h @@ -9,41 +9,42 @@ #include "LogLevel.h" #include "Loggable.h" #include "LogSink.h" -#include "common/Utils.h" +#include "util/Utils.h" // A generic macro to log a message with a given set of sinks at a given level -#define LOG(SINK, LEVEL, ...) \ - Log::LogMessage(SINK, LEVEL) << __VA_ARGS__ +#define LOG(LEVEL, ...) \ + Log::LogMessage(LEVEL) << __VA_ARGS__ // A macro to log an error in the set of sinks specified by AddSink and RemoveSink #define LOG_ERROR(...) \ - LOG(Log::_LogCurrentSinks, Log::LogLevel::LogError, __VA_ARGS__ << Log::endlog) + LOG(Log::LogLevel::LogError, __VA_ARGS__ << Log::endlog) -// A macro to print out system error information +// A macro to log an LSTATUS and/or HRESULT error #define LOG_SYSTEM_ERROR(ERROR_ID) \ - LOG(Log::_LogCurrentSinks, Log::LogLevel::LogError, "System Error Code 0x" << std::uppercase << std::hex << ERROR_ID << ": " << Log::FormatErrorMessage(ERROR_ID) << Log::endlog); + LOG_ERROR("System Error Code 0x" << std::uppercase << std::hex << ERROR_ID << ": " << Log::FormatErrorMessage(ERROR_ID)); +// A macro that evaluates to a string describing the code in GetLastError() #define SYSTEM_ERROR \ "System Error Code 0x" << std::uppercase << std::hex << GetLastError() << ": " << Log::FormatErrorMessage(GetLastError()) // A macro to log a warning in the set of sinks specified by AddSink and RemoveSink #define LOG_WARNING(...) \ - LOG(Log::_LogCurrentSinks, Log::LogLevel::LogWarn, __VA_ARGS__ << Log::endlog) + LOG(Log::LogLevel::LogWarn, __VA_ARGS__ << Log::endlog) // A macro to log information in the set of sinks specified by AddSink and RemoveSink -#define LOG_INFO(...) \ - LOG(Log::_LogCurrentSinks, Log::LogLevel::LogInfo, __VA_ARGS__ << Log::endlog) +#define LOG_INFO(VERBOSITY, ...) \ + LOG(Log::LogLevel::LogInfo##VERBOSITY, __VA_ARGS__ << Log::endlog) // A macro to log verbose information in the set of sinks specified by AddSink and RemoveSink // at a specified verbosity. Under current configurations, this should be between 1 and 3 inclusive. #define LOG_VERBOSE(VERBOSITY, ...) \ - LOG(Log::_LogCurrentSinks, Log::LogLevel::LogVerbose##VERBOSITY, __VA_ARGS__ << Log::endlog) + LOG(Log::LogLevel::LogVerbose##VERBOSITY, __VA_ARGS__ << Log::endlog) namespace Log { // A vector containing the set of sinks to be used when LOG_ERROR, LOG_WARNING, etc are used. // This vector is updated by the AddSink and RemoveSink functions. - extern std::vector> _LogCurrentSinks; + extern std::vector> _LogSinks; // A dummy class to indicate the termination of a log message. class LogTerminator {}; @@ -58,135 +59,114 @@ namespace Log { class LogMessage { protected: // The internal stream used to keep track of the log message - std::stringstream InternalStream{}; + std::wstringstream stream{}; // The level at which the log message is being logged. - LogLevel Level; - - // A vector containing the sinks to which this message is being logged. - std::vector> Sinks{}; + LogLevel level; /** * An internal constructor used create a log message based off of an already existing * stream. - */ - LogMessage(std::vector>, LogLevel, std::stringstream); - - public: - - /** - * Creates a log message at a given level and with a vector of sinks - * - * @param sinks The sinks that this message will log itself to. - * @param level The log level at which this message is logged. - */ - LogMessage(std::vector> sinks, LogLevel level); - - /** - * Creates a log message at a given level and with a sink * - * @param sink The sink that this message will log itself to. * @param level The log level at which this message is logged. + * @param message The pre-existing contents of the message */ - LogMessage(const std::shared_ptr& sink, LogLevel level); + LogMessage( + IN CONST LogLevel& level, + IN CONST std::wstringstream& message + ); /** - * When the LogTerminator is supplied to the stream, the stream is terminated and forwarded to - * the sinks for recording. After this happens, the log message is emptied and able to be used - * again. + * StringStream does most of the work needed to handle a stream of values being logged + * to this message. This function serves as a wrapper around adding an object to the internal + * stream. * - * @param terminator An instance of the LogTerminator class used to denote the termination of a - * message + * @param LogItem The item to add to the log message. * * @return a reference to this log message. */ - virtual LogMessage& operator<<(const LogTerminator& termiantor); + template + LogMessage& InnerLog( + IN CONST T LogItem, + IN CONST std::false_type& + ){ + stream << LogItem; + return *this; + } /** - * StringStreams don't support wide strings, so this serves as a handler for - * wide strings being logged. + * At some point, it may become beneficial to log the current state of a component. + * This is meant to serve as a handler for components implementing the Loggable + * interface. * - * @param string The wide string to add to the message + * @param loggable The component to log * * @return a reference to this log message. */ - LogMessage& operator<<(const std::wstring& string); + LogMessage& InnerLog( + IN CONST Loggable& loggable, + IN CONST std::true_type& + ); + + public: /** - * StringStreams don't support wide strings, so this serves as a handler for - * wide strings being logged. - * - * @param string The wide string to add to the message + * Creates a log message at a given level and with a sink * - * @return a reference to this log message. + * @param level The log level at which this message is logged. */ - LogMessage& operator<<(LPCWSTR pointer); - - protected: + LogMessage( + IN CONST LogLevel& level + ); /** - * StringStream does most of the work needed to handle a stream of values being logged - * to this message. This function serves as a wrapper around adding an object to the internal - * stream. + * When the LogTerminator is supplied to the stream, the stream is terminated and forwarded to + * the sinks for recording. After this happens, the log message is emptied and able to be used + * again. * - * @param LogItem The item to add to the log message. + * @param terminator An instance of the LogTerminator class used to denote the termination of a + * message * * @return a reference to this log message. */ - template - LogMessage& InnerLog(T LogItem, std::false_type){ - InternalStream << LogItem; - return *this; - } - - /** - * At some point, it may become beneficial to log the current state of a component. - * This is meant to serve as a handler for components implementing the Loggable - * interface. - */ - LogMessage& InnerLog(const Loggable& loggable, std::true_type){ - return operator<<(loggable.ToString()); - } - - public: + virtual LogMessage& operator<<( + IN CONST LogTerminator& termiantor + ); /** - * Tag dispatcher for the InnerLog functions + * Tag dispatcher for the InnerLog functions. Used to add elements to this log message + * + * @param LogItem Item to add to this log message + * + * @return a reference to this log message */ template - LogMessage& operator<<(T LogItem){ + LogMessage& operator<<( + IN CONST T& LogItem + ){ return InnerLog(LogItem, std::is_base_of{}); } }; /** - * Adds a sink to the vector of default sinks to be used in LOG_ERROR, LOG_WARNING, etc. - * If the provided sink is equal to any sink in the vector already, this will return false + * Adds a given LogSink to the specified levels as a sink for all log messages of that level. + * If the provided sink is equal to any sink in the vector already, this will return false, * and the sink will not be added. * * @param sink The sink to be added - * - * @return A boolean indicating whether or not the sink was added - */ - bool AddSink(const std::shared_ptr& Sink); - - /** - * Removes a sink from the vector of default sinks to be used in LOG_ERROR, LOG_WARNING, etc. - * If the provided sink is not equal to any sink in the vector already, this will return false - * and nothing will happen. - * - * @param sink The sink to be removed - * - * @return A boolean indicating whether or not the sink was removed + * @param levels A vector of the log levels that will be logged to the sink */ - bool RemoveSink(const std::shared_ptr& Sink); + void AddSink( + IN CONST std::shared_ptr& sink, + IN CONST std::vector>& levels + ); /** * Gets a System Error Message's Description given the error code - * + * * @param DWORD returned from GetLastError() * * @return A std::wstring containing the System Error Message Description */ std::wstring FormatErrorMessage(DWORD dwNum); -} \ No newline at end of file +} diff --git a/BLUESPAWN-client/headers/util/log/LogLevel.h b/BLUESPAWN-win-client/headers/util/log/LogLevel.h similarity index 51% rename from BLUESPAWN-client/headers/util/log/LogLevel.h rename to BLUESPAWN-win-client/headers/util/log/LogLevel.h index 3866dc6b..d9e4ba08 100644 --- a/BLUESPAWN-client/headers/util/log/LogLevel.h +++ b/BLUESPAWN-win-client/headers/util/log/LogLevel.h @@ -1,4 +1,11 @@ #pragma once + +#include + +#include +#include +#include + namespace Log { /** * This denotes the severity of a log message. This is intended to be used by @@ -8,10 +15,21 @@ namespace Log { LogError = 0, LogWarn = 1, LogInfo = 2, - LogOther = 3, - LogHunt = 4 + LogVerbose = 3 }; + /** + * This indicates the level of detail in the log level. + */ + enum class Detail { + Low = 0, + Moderate = 1, + High = 2 + }; + + /// Forward declare log sink + class LogSink; + /** * This class represents the "level" of a log message. This is similar to Severity * in that it categorizes logs, but it's inteded to extend the functionality present @@ -20,19 +38,27 @@ namespace Log { */ class LogLevel { private: - // Whether or not sinks should record log messages under this level + + /// Whether or not sinks should record log messages under this level bool enabled; + /// The sinks to which messages at this level will be recorded + std::vector sinks; + public: - // The severity at which this log level operates + /// The severity at which this log level operates const Severity severity; - // Default logging levels available, though custom ones can be created + /// The level of detail present at this logging level + const std::optional detail; + + /// Default logging levels available, though custom ones can be created static LogLevel - LogHunt, // Intended for logging hunts LogError, // Intended for logging errors LogWarn, // Intended for logging warnings - LogInfo, // Intended for logging information and statuses of hunts + LogInfo1, // Intended for logging high level operational information + LogInfo2, // Intended for logging moderately detailed operational information + LogInfo3, // Intended for logging very detailed operational information LogVerbose1, // Intended for a low level of verbosity LogVerbose2, // Intended for a moderate level of verbosity LogVerbose3; // Intended for a high level of verbosity @@ -41,8 +67,12 @@ namespace Log { * Creates a new log level, enabled by default, with a given severity. * * @param severity The severity of messages under this logging level + * @param detail The level of detail present at this logging level */ - LogLevel(Severity severity); + LogLevel( + IN Severity severity, + IN CONST std::optional& detail = std::nullopt OPTIONAL + ); /** * Creates a new log level with a given severity. @@ -51,7 +81,11 @@ namespace Log { * @param DefaultState Indicates whether or not log messages should be recorded * by default when logged at this logging level. */ - LogLevel(Severity severity, bool DefaultState); + LogLevel( + IN Severity severity, + IN bool DefaultState, + IN CONST std::optional& detail = std::nullopt OPTIONAL + ); /** * Enables logging at this level @@ -75,5 +109,24 @@ namespace Log { * be recorded. */ bool Enabled() const; + + /** + * Adds a sink to which messages logged at this level are recorded. If the level already + * is logging to the sink, this has no effect. + * + * @param sink The sink to add + */ + void AddSink( + IN LogSink* sink + ); + + /** + * Logs the given message at this level in the sinks configured for this level + * + * @param message The message to log + */ + void LogMessage( + IN CONST std::wstring& message + ); }; } \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/log/LogSink.h b/BLUESPAWN-win-client/headers/util/log/LogSink.h similarity index 83% rename from BLUESPAWN-client/headers/util/log/LogSink.h rename to BLUESPAWN-win-client/headers/util/log/LogSink.h index 0e76aec9..e88c0436 100644 --- a/BLUESPAWN-client/headers/util/log/LogSink.h +++ b/BLUESPAWN-win-client/headers/util/log/LogSink.h @@ -5,7 +5,7 @@ #include #include -#include "reaction/Detections.h" +#include "scan/Detections.h" #include "hunt/HuntInfo.h" #include "LogLevel.h" @@ -30,8 +30,7 @@ namespace Log { * @param level The level at which to log * @param message The message to be logged */ - virtual void LogMessage(const LogLevel& level, const std::string& message, const std::optional info = std::nullopt, - const std::vector>& detections = {}) = 0; + virtual void LogMessage(const LogLevel& level, const std::wstring& message) = 0; /** * This function should be implemented to determine whether two log sinks are equal. diff --git a/BLUESPAWN-client/headers/util/log/Loggable.h b/BLUESPAWN-win-client/headers/util/log/Loggable.h similarity index 100% rename from BLUESPAWN-client/headers/util/log/Loggable.h rename to BLUESPAWN-win-client/headers/util/log/Loggable.h diff --git a/BLUESPAWN-client/headers/util/log/ServerSink.h b/BLUESPAWN-win-client/headers/util/log/ServerSink.h similarity index 86% rename from BLUESPAWN-client/headers/util/log/ServerSink.h rename to BLUESPAWN-win-client/headers/util/log/ServerSink.h index 65c79d3c..36102a56 100644 --- a/BLUESPAWN-client/headers/util/log/ServerSink.h +++ b/BLUESPAWN-win-client/headers/util/log/ServerSink.h @@ -31,10 +31,10 @@ namespace Log { std::vector HuntDatasourcesToGPB(DWORD info); gpb::HuntInfo HuntInfoToGPB(const HuntInfo& info); - std::vector GetFileReactions(const std::vector>& detections); - std::vector GetRegistryReactions(const std::vector>& detections); - std::vector GetProcessReactions(const std::vector>& detections); - std::vector GetServiceReactions(const std::vector>& detections); + std::vector GetFileReactions(const std::vector>& detections); + std::vector GetRegistryReactions(const std::vector>& detections); + std::vector GetProcessReactions(const std::vector>& detections); + std::vector GetServiceReactions(const std::vector>& detections); public: @@ -46,7 +46,7 @@ namespace Log { * @param message The message to log */ virtual void LogMessage(const LogLevel& level, const std::string& message, const std::optional info = std::nullopt, - const std::vector>& detections = {}) override; + const std::vector>& detections = {}) override; /** * Compares this ServerSink to another LogSink. Currently, as only one console is supported, diff --git a/BLUESPAWN-win-client/headers/util/log/XMLSink.h b/BLUESPAWN-win-client/headers/util/log/XMLSink.h new file mode 100644 index 00000000..bb2fd2bc --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/log/XMLSink.h @@ -0,0 +1,117 @@ +#pragma once + +#include "LogSink.h" +#include "DetectionSink.h" +#include "../tinyxml2/tinyxml2.h" + +namespace Log { + + /** + * XMLSink provides a sink for the logger that saves log messages to an XML file. + */ + class XMLSink : public LogSink, public DetectionSink { + + /// Guards access to the XML document + CriticalSection hGuard; + + /// The XML document + tinyxml2::XMLDocument XMLDoc; + + /// The root element in the XML document + tinyxml2::XMLElement* Root; + + /// The element to which logs will be added + tinyxml2::XMLElement* LogRoot; + + /// The name of the file to which the XML will be written + std::wstring wFileName; + + /// Tags for messages sent at different levels + std::string MessageTags[4] = { "error", "warning", "info", "other" }; + + /// A handle to a thread that periodically flushes the log to the file + HandleWrapper thread; + + /// A mapping of IDs to XML entries created for detections + std::unordered_map detections; + + public: + + /** + * Default constructor for XMLSink. By default, the log will be saved to a file including the date and time in + * the name. + */ + XMLSink(); + + /** + * Constructor for XMLSink. The log will be saved with the name passed as the argument + * + * @param wFileName The name of the file to save the log as. + */ + XMLSink(const std::wstring& wFileName); + + /// Delete copy and move constructors and assignment operators + XMLSink operator=(const XMLSink&) = delete; + XMLSink operator=(XMLSink&&) = delete; + XMLSink(const XMLSink&) = delete; + XMLSink(XMLSink&&) = delete; + + /// Custom destructor + ~XMLSink(); + + /** + * Outputs a message to the debug console if its logging level is enabled. + * + * @param level The level at which the message is being logged + * @param message The message to log + */ + virtual void LogMessage(const LogLevel& level, const std::wstring& message); + + /** + * Compares this XMLSink to another LogSink. All LogSink objects referring to the same file are considered + * equal + * + * @param sink The LogSink to compare + * + * @return Whether or not the argument and this sink are considered equal. + */ + virtual bool operator==(const LogSink& sink) const; + + /** + * Flushes the log to the file. + */ + void Flush(); + + /** + * Updates the raw and combined certainty values associated with a detection + * + * @param detection The detection to update + */ + virtual void UpdateCertainty( + IN CONST std::shared_ptr& detection + ); + + /** + * Records a detection to the XML document. + * + * @param detection The detection to record + * @param type The type of record this is, either PreScan or PostScan + */ + virtual void RecordDetection( + IN CONST std::shared_ptr& detection, + IN RecordType type + ); + + /** + * Records an association between two detections to the XML document + * + * @param first The first detection in the assocation. This detection's ID will be lower than the second's. + * @param second The second detection in the association. + */ + virtual void RecordAssociation( + IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength + ); + }; +} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/pe/Export_Section.h b/BLUESPAWN-win-client/headers/util/pe/Export_Section.h similarity index 95% rename from BLUESPAWN-client/headers/util/pe/Export_Section.h rename to BLUESPAWN-win-client/headers/util/pe/Export_Section.h index 0da1f742..bad0b943 100644 --- a/BLUESPAWN-client/headers/util/pe/Export_Section.h +++ b/BLUESPAWN-win-client/headers/util/pe/Export_Section.h @@ -4,7 +4,7 @@ #include #include -#include "common/DynamicLinker.h" +#include "util/DynamicLinker.h" DEFINE_FUNCTION(NTSTATUS, LdrpPreprocessDllName, NTAPI, __in PUNICODE_STRING input, __out PUNICODE_STRING output, PULONG_PTR zero1, PULONG_PTR zero2); diff --git a/BLUESPAWN-client/headers/util/pe/Image_Loader.h b/BLUESPAWN-win-client/headers/util/pe/Image_Loader.h similarity index 98% rename from BLUESPAWN-client/headers/util/pe/Image_Loader.h rename to BLUESPAWN-win-client/headers/util/pe/Image_Loader.h index 516ba7b6..f352c1d8 100644 --- a/BLUESPAWN-client/headers/util/pe/Image_Loader.h +++ b/BLUESPAWN-win-client/headers/util/pe/Image_Loader.h @@ -2,8 +2,8 @@ #include -#include "common/wrappers.hpp" -#include "common/DynamicLinker.h" +#include "util/wrappers.hpp" +#include "util/DynamicLinker.h" #include "util/pe/PE_Image.h" diff --git a/BLUESPAWN-client/headers/util/pe/Import_Section.h b/BLUESPAWN-win-client/headers/util/pe/Import_Section.h similarity index 100% rename from BLUESPAWN-client/headers/util/pe/Import_Section.h rename to BLUESPAWN-win-client/headers/util/pe/Import_Section.h diff --git a/BLUESPAWN-client/headers/util/pe/PE_Image.h b/BLUESPAWN-win-client/headers/util/pe/PE_Image.h similarity index 97% rename from BLUESPAWN-client/headers/util/pe/PE_Image.h rename to BLUESPAWN-win-client/headers/util/pe/PE_Image.h index 420c6a18..ba59c268 100644 --- a/BLUESPAWN-client/headers/util/pe/PE_Image.h +++ b/BLUESPAWN-win-client/headers/util/pe/PE_Image.h @@ -7,7 +7,7 @@ #include #include -#include "common/Wrappers.hpp" +#include "util/Wrappers.hpp" #include "util/pe/PE_Section.h" #include "Relocation_Section.h" diff --git a/BLUESPAWN-client/headers/util/pe/PE_Section.h b/BLUESPAWN-win-client/headers/util/pe/PE_Section.h similarity index 95% rename from BLUESPAWN-client/headers/util/pe/PE_Section.h rename to BLUESPAWN-win-client/headers/util/pe/PE_Section.h index 7dc81e1c..2c4bdb73 100644 --- a/BLUESPAWN-client/headers/util/pe/PE_Section.h +++ b/BLUESPAWN-win-client/headers/util/pe/PE_Section.h @@ -4,7 +4,7 @@ #include -#include "Common/Wrappers.hpp" +#include "util/Wrappers.hpp" class PE_Image; class PE_Section { diff --git a/BLUESPAWN-client/headers/util/pe/Relocation_Section.h b/BLUESPAWN-win-client/headers/util/pe/Relocation_Section.h similarity index 96% rename from BLUESPAWN-client/headers/util/pe/Relocation_Section.h rename to BLUESPAWN-win-client/headers/util/pe/Relocation_Section.h index cbba6c7e..6ace7a19 100644 --- a/BLUESPAWN-client/headers/util/pe/Relocation_Section.h +++ b/BLUESPAWN-win-client/headers/util/pe/Relocation_Section.h @@ -5,7 +5,7 @@ #include #include "util/pe/PE_Section.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" struct Relocation_Block; diff --git a/BLUESPAWN-client/headers/util/pe/Resource_Section.h b/BLUESPAWN-win-client/headers/util/pe/Resource_Section.h similarity index 91% rename from BLUESPAWN-client/headers/util/pe/Resource_Section.h rename to BLUESPAWN-win-client/headers/util/pe/Resource_Section.h index db9e70fc..9d8a338a 100644 --- a/BLUESPAWN-client/headers/util/pe/Resource_Section.h +++ b/BLUESPAWN-win-client/headers/util/pe/Resource_Section.h @@ -4,7 +4,7 @@ #include #include -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" class PE_Resource { public: diff --git a/BLUESPAWN-client/headers/util/permissions/permissions.h b/BLUESPAWN-win-client/headers/util/permissions/permissions.h similarity index 95% rename from BLUESPAWN-client/headers/util/permissions/permissions.h rename to BLUESPAWN-win-client/headers/util/permissions/permissions.h index 76d1e98e..31a6d3c7 100644 --- a/BLUESPAWN-client/headers/util/permissions/permissions.h +++ b/BLUESPAWN-win-client/headers/util/permissions/permissions.h @@ -7,12 +7,12 @@ #include #include "util/log/Loggable.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" -namespace Permissions { +namespace Permissions{ /** * Functions to check if an access mask includes a permission - * + * * @param access - the access mask to check * @return true if the access mask includes the permission or ALL, false otherwise */ @@ -40,7 +40,7 @@ namespace Permissions { PSID lpGroupSID; PACL dacl; PACL sacl; - + protected: enum class SecurityDataType { USER_SID, GROUP_SID, DACL, SACL @@ -51,9 +51,9 @@ namespace Permissions { public: /** * Create a SecurityDescriptor to hold a UserSID - * + * * @param dwSize The size in bytes of the SID - * + * * @return a SecurityDescriptor with the lpUserSID value set to a pointer to dwSize * bytes of memory */ @@ -69,9 +69,9 @@ namespace Permissions { static SecurityDescriptor CreateGroupSID(DWORD dwSize); /** * Create a SecurityDescriptor to hold a DACL - * + * * @param dwSize The size in bytes of the dacl - * + * * @return a SecurityDescriptor with the dacl value set to a pointer to dwSize * bytes of memory */ @@ -88,13 +88,13 @@ namespace Permissions { /** * Constructor to create a security descriptor from a PISECURITY_DESCRIPTOR - * - * @param lpSecurity A PISECURITY_DESCRIPTOR object. All valid fields in lpSecurity + * + * @param lpSecurity A PISECURITY_DESCRIPTOR object. All valid fields in lpSecurity * will be copied to the corresponding field in the SecurityDescriptor object, if * such a field exists */ SecurityDescriptor(PISECURITY_DESCRIPTOR lpSecurity = nullptr); - + /*Getter for the lpUserSID field*/ PSID GetUserSID() const; /*Getter for the lpGroupSID field*/ @@ -107,7 +107,7 @@ namespace Permissions { /*Enum for storing type of Owner an Owner object is*/ enum OwnerType { - NONE, USER, GROUP + NONE, USER, GROUP }; class Owner : public Loggable { @@ -137,9 +137,9 @@ namespace Permissions { Owner(IN const std::wstring& name); /** * Constructor for an owner object based off sid - * + * * @param sid A SecurityDescriptor with lpUserSID set to the sid of the owner. Other - * fields will be filled in if an owner of that sid exists. + * fields will be filled in if an owner of that sid exists. */ Owner(IN const SecurityDescriptor& sid); /** @@ -153,7 +153,7 @@ namespace Permissions { /** * Constructor for an owner object that sets sdSID, bExists, and otOwnerType, but no other fields * - * @param sid A SecurityDescriptor containing value to be copied to sdSID. Should have lpUserSID set + * @param sid A SecurityDescriptor containing value to be copied to sdSID. Should have lpUserSID set * to valid PSID if t is USER, and lpGroupSID set to valid PSID if t is GROUP. * @param exists A boolean containing value to be copied ot bExists * @param t An OwnerType containing value to be copied to otOwnerType @@ -161,11 +161,11 @@ namespace Permissions { Owner(IN const SecurityDescriptor& sid, IN const bool& exists, IN const OwnerType& t); /** * Constructor for an owner object that sets all fields to given values. Performs no checking - * that given name and sid line up. + * that given name and sid line up. * * @param name A wstring containing value to be copied to wName * @ param domain A wstring containing value to be copied to wDomain - * @param sid A SecurityDescriptor containing value to be copied to sdSID. Should have lpUserSID set + * @param sid A SecurityDescriptor containing value to be copied to sdSID. Should have lpUserSID set * to valid PSID if t is USER, and lpGroupSID set to valid PSID if t is GROUP. * @param exists A boolean containing value to be copied ot bExists * @param t An OwnerType containing value to be copied to otOwnerType @@ -201,7 +201,7 @@ namespace Permissions { /** * Function to get the owner type - * + * * @return OwnerType value of GROUP, USER, or NONE */ OwnerType GetOwnerType() const; @@ -217,7 +217,7 @@ namespace Permissions { class User : public Owner { - public: + public: /** * Creates a User object based off a qualified user name @@ -251,10 +251,10 @@ namespace Permissions { */ Group(IN const SecurityDescriptor& sid); }; - + /** * Gets the rights a specific owner object has under a given acl - * + * * @param owner The owner object for whom to check rights * @param acl The acl from which to read rights * @@ -265,7 +265,7 @@ namespace Permissions { /** * Get the owner of the Bluespawn process * - * @return An Owner object representing the owner of the Bluespawn process, + * @return An Owner object representing the owner of the Bluespawn process, * or std::nullopt if the function failed */ std::optional GetProcessOwner(); @@ -278,7 +278,7 @@ namespace Permissions { * @param amDesiredAccess An ACCESS_MASK containing the permissions to grant or deny to oOwner * @param bDeny If false grant access to amDesiredAccess, if true deny access. Defaults to false * - * @return true if the objects ACL was updated. False otherwise. If false, GetLastError will contain the error. + * @return true if the objects ACL was updated. False otherwise. If false, GetLastError will contain the error. */ bool UpdateObjectACL(const std::wstring& wsObjectName, const SE_OBJECT_TYPE& seObjectType, const Owner& oOwner, const ACCESS_MASK& amDesiredAccess, const bool& bDeny = false); } \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/processes/Analyzer.h b/BLUESPAWN-win-client/headers/util/processes/Analyzer.h similarity index 100% rename from BLUESPAWN-client/headers/util/processes/Analyzer.h rename to BLUESPAWN-win-client/headers/util/processes/Analyzer.h diff --git a/BLUESPAWN-client/headers/util/processes/CheckLolbin.h b/BLUESPAWN-win-client/headers/util/processes/CheckLolbin.h similarity index 100% rename from BLUESPAWN-client/headers/util/processes/CheckLolbin.h rename to BLUESPAWN-win-client/headers/util/processes/CheckLolbin.h diff --git a/BLUESPAWN-client/headers/util/processes/CommandParser.h b/BLUESPAWN-win-client/headers/util/processes/CommandParser.h similarity index 100% rename from BLUESPAWN-client/headers/util/processes/CommandParser.h rename to BLUESPAWN-win-client/headers/util/processes/CommandParser.h diff --git a/BLUESPAWN-client/headers/util/processes/PERemover.h b/BLUESPAWN-win-client/headers/util/processes/PERemover.h similarity index 92% rename from BLUESPAWN-client/headers/util/processes/PERemover.h rename to BLUESPAWN-win-client/headers/util/processes/PERemover.h index 3b687f38..26167ddb 100644 --- a/BLUESPAWN-client/headers/util/processes/PERemover.h +++ b/BLUESPAWN-win-client/headers/util/processes/PERemover.h @@ -4,8 +4,8 @@ #include -#include "Common/wrappers.hpp" -#include "Common/dynamiclinker.h" +#include "util/wrappers.hpp" +#include "util/dynamiclinker.h" DEFINE_FUNCTION(NTSTATUS, NtResumeProcess, NTAPI, IN HANDLE ProcessHandle); diff --git a/BLUESPAWN-client/headers/util/processes/ParseCobalt.h b/BLUESPAWN-win-client/headers/util/processes/ParseCobalt.h similarity index 70% rename from BLUESPAWN-client/headers/util/processes/ParseCobalt.h rename to BLUESPAWN-win-client/headers/util/processes/ParseCobalt.h index 9ca190fd..21d7dbef 100644 --- a/BLUESPAWN-client/headers/util/processes/ParseCobalt.h +++ b/BLUESPAWN-win-client/headers/util/processes/ParseCobalt.h @@ -1,5 +1,5 @@ #pragma once -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" bool DumpBeaconInformation(const MemoryWrapper<>& memory); \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/processes/ProcessChecker.h b/BLUESPAWN-win-client/headers/util/processes/ProcessChecker.h similarity index 100% rename from BLUESPAWN-client/headers/util/processes/ProcessChecker.h rename to BLUESPAWN-win-client/headers/util/processes/ProcessChecker.h diff --git a/BLUESPAWN-client/headers/util/processes/ProcessUtils.h b/BLUESPAWN-win-client/headers/util/processes/ProcessUtils.h similarity index 94% rename from BLUESPAWN-client/headers/util/processes/ProcessUtils.h rename to BLUESPAWN-win-client/headers/util/processes/ProcessUtils.h index f68080ab..c4a775b6 100644 --- a/BLUESPAWN-client/headers/util/processes/ProcessUtils.h +++ b/BLUESPAWN-win-client/headers/util/processes/ProcessUtils.h @@ -5,8 +5,8 @@ #include #include -#include "common/wrappers.hpp" -#include "common/dynamiclinker.h" +#include "util/wrappers.hpp" +#include "util/dynamiclinker.h" #include "util/pe/Image_Loader.h" #include "util/filesystem/FileSystem.h" @@ -37,7 +37,7 @@ DWORD GetRegionSize(const HandleWrapper& hProcess, LPVOID lpRegionAddress); std::optional GetMappedFile(DWORD dwPID, LPVOID address); std::optional GetMappedFile(const HandleWrapper& hProcess, LPVOID address); -namespace Utils::Process{ +namespace Utils::Process { AllocationWrapper ReadProcessMemory(const HandleWrapper& hProcess, LPVOID lpBaseAddress, DWORD dwSize); AllocationWrapper ReadProcessMemory(DWORD dwPID, LPVOID lpBaseAddress, DWORD dwSize); } diff --git a/BLUESPAWN-common/headers/common/wrappers.hpp b/BLUESPAWN-win-client/headers/util/wrappers.hpp similarity index 69% rename from BLUESPAWN-common/headers/common/wrappers.hpp rename to BLUESPAWN-win-client/headers/util/wrappers.hpp index ad781b6d..c273f74c 100644 --- a/BLUESPAWN-common/headers/common/wrappers.hpp +++ b/BLUESPAWN-win-client/headers/util/wrappers.hpp @@ -10,43 +10,41 @@ template class GenericWrapper { protected: - std::shared_ptr ReferenceCounter; - - T WrappedObject; + std::shared_ptr ReferenceCounter; std::optional BadValue; public: - GenericWrapper(T object, std::function freeFunction = [](T object){ delete object; }, std::optional BadValue = std::nullopt) : - WrappedObject{ object }, + GenericWrapper(T object, std::function freeFunction = [](T object){ delete object; }, std::optional BadValue = std::nullopt) : BadValue{ BadValue }, - ReferenceCounter{ nullptr, [object, BadValue, freeFunction](LPVOID memory){ - if((!BadValue || object != BadValue) && object){ freeFunction(object); } - }}{} - - operator T() const { return WrappedObject; } - T operator *() const{ return WrappedObject; } - T operator ->() const{ return WrappedObject; } - T* operator &() const{ return const_cast(&WrappedObject); } - bool operator ==(T object) const{ return WrappedObject == object; } - bool operator !() const{ return !WrappedObject || WrappedObject == BadValue; } - operator bool() const{ return !operator!(); } - T Release(){ auto tmp = WrappedObject; WrappedObject = BadValue; return tmp; } - T Get() const { return WrappedObject; } + ReferenceCounter{ new T(object), [BadValue, freeFunction](auto* object){ + if((!BadValue || *BadValue != *object) && *object){ freeFunction(*object); } + delete object; + } }{} + + operator T() const{ return *ReferenceCounter; } + T operator *() const{ return *ReferenceCounter; } + T operator ->() const{ return *ReferenceCounter; } + T* operator &() const{ return const_cast(&*ReferenceCounter); } + bool operator ==(T object) const{ return *ReferenceCounter == object; } + void reassign(T object){ *ReferenceCounter = object; } + bool operator !() const{ return !*ReferenceCounter || *ReferenceCounter == BadValue; } + operator bool() const{ return !operator!(); } + T Release(){ auto tmp = *ReferenceCounter; *ReferenceCounter = BadValue; return tmp; } + T Get() const{ return *ReferenceCounter; } }; class HandleWrapper : public GenericWrapper { public: HandleWrapper(HANDLE handle) : - GenericWrapper(handle, std::function(SafeCloseHandle), INVALID_HANDLE_VALUE){}; - static void SafeCloseHandle(HANDLE handle) { + GenericWrapper(handle, SafeCloseHandle, INVALID_HANDLE_VALUE){}; + static void SafeCloseHandle(HANDLE handle){ BY_HANDLE_FILE_INFORMATION hInfo; - if (GetFileInformationByHandle(handle, &hInfo)) { + if(GetFileInformationByHandle(handle, &hInfo)){ CloseHandle(handle); - } - else { + } else{ HRESULT a = GetLastError(); - if (a != ERROR_INVALID_HANDLE) { + if(a != ERROR_INVALID_HANDLE){ CloseHandle(handle); } } @@ -56,47 +54,49 @@ class HandleWrapper : public GenericWrapper { class FindWrapper : public GenericWrapper { public: FindWrapper(HANDLE handle) : - GenericWrapper(handle, std::function(FindClose), INVALID_HANDLE_VALUE){}; + GenericWrapper(handle, FindClose, INVALID_HANDLE_VALUE){}; }; typedef HandleWrapper MutexType; class AcquireMutex { - MutexType hMutex; - std::shared_ptr tracker; + std::shared_ptr tracker; public: - explicit AcquireMutex(const MutexType& mutex) : - hMutex{ mutex }, - tracker{ nullptr, [&](LPVOID nul){ ReleaseMutex(hMutex); } }{ - ::WaitForSingleObject(hMutex, INFINITE); + explicit AcquireMutex(MutexType mutex) : + tracker{ new MutexType(mutex), [](auto* m){ ReleaseMutex(*m); } }{ + ::WaitForSingleObject(*tracker, INFINITE); } }; + class CriticalSection { - CRITICAL_SECTION section; - std::shared_ptr tracker; + std::shared_ptr counter; public: - CriticalSection() : - section{ nullptr, 0, 0, nullptr, nullptr, 0 }, - tracker{ §ion, [](PCRITICAL_SECTION section){ DeleteCriticalSection(section); } }{ - InitializeCriticalSection(§ion); + CriticalSection(){ + counter = { + new CRITICAL_SECTION{}, [](PCRITICAL_SECTION section) mutable { + DeleteCriticalSection(section); + delete section; + } + }; + InitializeCriticalSection(&*counter); } - operator PCRITICAL_SECTION(){ return §ion; } - operator CRITICAL_SECTION(){ return section; } + operator LPCRITICAL_SECTION() const { return static_cast(&*counter); } }; class BeginCriticalSection { CriticalSection critsec; - std::shared_ptr tracker; public: explicit BeginCriticalSection(const CriticalSection& section) : - critsec{ section }, - tracker{ nullptr, [&](LPVOID nul){ LeaveCriticalSection(critsec); } }{ + critsec{ section }{ ::EnterCriticalSection(critsec); } + ~BeginCriticalSection(){ + ::LeaveCriticalSection(critsec); + } }; class AllocationWrapper { @@ -133,37 +133,41 @@ class AllocationWrapper { }, AllocationSize{ size }{} - CHAR operator[](int i) const { - return Memory && i < AllocationSize ? pointer[i] : 0; + CHAR& operator[](int i) const{ + return pointer[i]; } - operator bool() const { + operator bool() const{ return Memory.has_value(); } - operator LPVOID() const { + bool operator !() const{ + return !static_cast(*this); + } + + operator LPVOID() const{ return pointer; } - DWORD GetSize() const { + DWORD GetSize() const{ return Memory.has_value() ? AllocationSize : 0; } template - std::optional operator*() const { + std::optional operator*() const{ return Dereference(); } template - std::optional Dereference() const { + std::optional Dereference() const{ if(AllocationSize < sizeof(T) || !Memory.has_value()){ return std::nullopt; - } else { + } else{ return *reinterpret_cast(pointer); } } - std::optional ReadWString() const { + std::optional ReadWString() const{ if(Memory.has_value()){ SIZE_T size = wcsnlen(reinterpret_cast(pointer), AllocationSize / 2); PWCHAR buffer = new WCHAR[size + 1]; @@ -175,7 +179,7 @@ class AllocationWrapper { } else return std::nullopt; } - std::optional ReadString() const { + std::optional ReadString() const{ if(Memory.has_value()){ SIZE_T size = strnlen(reinterpret_cast(pointer), AllocationSize); PCHAR buffer = new CHAR[size + 1]; @@ -187,21 +191,22 @@ class AllocationWrapper { } else return std::nullopt; } - bool CompareMemory(const AllocationWrapper& wrapper) const { + bool CompareMemory(const AllocationWrapper& wrapper) const{ if(!wrapper && !Memory.has_value()){ return true; } else if(!wrapper || !Memory.has_value()){ return false; } else if(wrapper.AllocationSize == AllocationSize){ - for(int i = 0; i < AllocationSize; i++) - if(pointer[i] != wrapper[i]) - return false; - return true; - } else { + return RtlEqualMemory(wrapper.pointer, pointer, AllocationSize); + } else{ return false; } } + bool operator==(const AllocationWrapper& wrapper) const{ + return CompareMemory(wrapper); + } + bool SetByte(SIZE_T offset, char value){ if(offset < AllocationSize){ pointer[offset] = value; @@ -228,52 +233,52 @@ class MemoryWrapper { MemoryWrapper(LPVOID lpMemoryBase, SIZE_T size = sizeof(T), HANDLE process = GetCurrentProcess()) : address{ reinterpret_cast(lpMemoryBase) }, process{ process }, MemorySize{ size } {} - T Dereference() const { + T Dereference() const{ if(!process){ return *address; - } else { + } else{ T mem = {}; ReadProcessMemory(process, address, &mem, sizeof(T), nullptr); return mem; } } - T operator *() const { + T operator *() const{ return Dereference(); } - T** operator &() const { + T** operator &() const{ return &(address); } - operator T* () const { + operator T* () const{ return (address); } T* operator->(){ if(!process){ return address; - } else { + } else{ LocalCopy = {}; if(ReadProcessMemory(process, address, &LocalCopy, sizeof(LocalCopy), nullptr)){ return &LocalCopy; - } else { + } else{ return nullptr; } } } template - MemoryWrapper Convert() const { + MemoryWrapper Convert() const{ return { reinterpret_cast(address), MemorySize, process }; } - MemoryWrapper GetOffset(SIZE_T offset) const { + MemoryWrapper GetOffset(SIZE_T offset) const{ if(offset > MemorySize){ return { nullptr, 0, process }; - } else { + } else{ return { reinterpret_cast(PCHAR(address) + offset), MemorySize - offset, process }; } } - bool CompareMemory(MemoryWrapper memory) const { + bool CompareMemory(MemoryWrapper memory) const{ auto data1 = Dereference(); auto data2 = memory.Dereference(); return !memcmp(&data1, &data2, min(memory.MemorySize, MemorySize)); @@ -284,7 +289,7 @@ class MemoryWrapper { DWORD dwOldProtections{}; if(!process){ return VirtualProtect(address, size, protections, &dwOldProtections); - } else { + } else{ return VirtualProtectEx(process, address, size, protections, &dwOldProtections); } } @@ -292,7 +297,7 @@ class MemoryWrapper { std::string ReadString(){ if(!process){ return std::string{ reinterpret_cast(address) }; - } else { + } else{ int idx = 0; int maxIdx = 10; char* memory = new char[maxIdx * 2]; @@ -309,7 +314,7 @@ class MemoryWrapper { } if(valid){ return std::string{ memory }; - } else { + } else{ return std::string{}; } } @@ -318,7 +323,7 @@ class MemoryWrapper { std::wstring ReadWstring(){ if(!process){ return std::wstring{ reinterpret_cast(address) }; - } else { + } else{ int idx = 0; int maxIdx = 10; wchar_t* memory = new wchar_t[maxIdx * 2]; @@ -335,19 +340,20 @@ class MemoryWrapper { } if(valid){ return std::wstring{ memory }; - } else { + } else{ return std::wstring{}; } } } - operator bool() const { return address; } - bool operator !() const { return !address; } + operator bool() const{ return address; } + bool operator !() const{ return !address; } - AllocationWrapper ToAllocationWrapper(DWORD size = MemorySize){ + AllocationWrapper ToAllocationWrapper(DWORD size = -1UL) const{ size = min(size, MemorySize); if(size > 0x8000){ - AllocationWrapper wrapper{ ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), size, AllocationWrapper::VIRTUAL_ALLOC }; + AllocationWrapper wrapper{ ::VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), size, + AllocationWrapper::VIRTUAL_ALLOC }; if(process){ if(ReadProcessMemory(process, address, wrapper, size, nullptr)){ return wrapper; @@ -359,7 +365,8 @@ class MemoryWrapper { return wrapper; } } else{ - AllocationWrapper wrapper{ ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size), size, AllocationWrapper::HEAP_ALLOC }; + AllocationWrapper wrapper{ ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size), size, + AllocationWrapper::HEAP_ALLOC }; if(process){ if(ReadProcessMemory(process, address, wrapper, size, nullptr)){ return wrapper; diff --git a/BLUESPAWN-win-client/libpeconv.vcxproj b/BLUESPAWN-win-client/libpeconv.vcxproj new file mode 100644 index 00000000..1d9056fe --- /dev/null +++ b/BLUESPAWN-win-client/libpeconv.vcxproj @@ -0,0 +1,119 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + Win32 + + + Release + Win32 + + + + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3} + libpeconv + + + + $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log + + + $(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\include;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\libpeconv\libpeconv\include;%(AdditionalIncludeDirectories) + UNICODE + + + Secur32.lib;DbgHelp.lib;Wintrust.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + StaticLibrary + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BLUESPAWN-win-client/pe-sieve.vcxproj b/BLUESPAWN-win-client/pe-sieve.vcxproj new file mode 100644 index 00000000..fe617d36 --- /dev/null +++ b/BLUESPAWN-win-client/pe-sieve.vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + Win32 + + + Release + Win32 + + + + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9} + pe-sieve + + + + $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log + + + $(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\include;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\libpeconv\libpeconv\include;%(AdditionalIncludeDirectories) + UNICODE + + + Secur32.lib;DbgHelp.lib;Wintrust.lib;%(AdditionalDependencies) + + + + + StaticLibrary + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3} + libpeconv + + + \ No newline at end of file diff --git a/BLUESPAWN-client/resources/BLUESPAWN-client.rc b/BLUESPAWN-win-client/resources/BLUESPAWN-client.rc similarity index 93% rename from BLUESPAWN-client/resources/BLUESPAWN-client.rc rename to BLUESPAWN-win-client/resources/BLUESPAWN-client.rc index 1aaa2537..3dd35390 100644 --- a/BLUESPAWN-client/resources/BLUESPAWN-client.rc +++ b/BLUESPAWN-win-client/resources/BLUESPAWN-client.rc @@ -44,6 +44,7 @@ END #endif // APSTUDIO_INVOKED +MAINICON ICON "resources\\BLUESPAWN.ico" ///////////////////////////////////////////////////////////////////////////// // @@ -51,8 +52,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,4,4 - PRODUCTVERSION 0,4,4 + FILEVERSION 0,5,0 + PRODUCTVERSION 0,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +70,12 @@ BEGIN BEGIN VALUE "CompanyName", "BLUESPAWN Project" VALUE "FileDescription", "BLUESPAWN Active Defense & EDR Software" - VALUE "FileVersion", "0.4.4" + VALUE "FileVersion", "0.5.0" VALUE "InternalName", "BLUESPAWN-client.exe" VALUE "LegalCopyright", "Copyright (C) 2020 BLUESPAWN" VALUE "OriginalFilename", "BLUESPAWN-client.exe" VALUE "ProductName", "BLUESPAWN Client" - VALUE "ProductVersion", "0.4.4" + VALUE "ProductVersion", "0.5.0" END END BLOCK "VarFileInfo" diff --git a/BLUESPAWN-win-client/resources/BLUESPAWN.ico b/BLUESPAWN-win-client/resources/BLUESPAWN.ico new file mode 100644 index 00000000..ffcad5b0 Binary files /dev/null and b/BLUESPAWN-win-client/resources/BLUESPAWN.ico differ diff --git a/BLUESPAWN-client/resources/SIP b/BLUESPAWN-win-client/resources/SIP similarity index 100% rename from BLUESPAWN-client/resources/SIP rename to BLUESPAWN-win-client/resources/SIP diff --git a/BLUESPAWN-client/resources/TrustProviders b/BLUESPAWN-win-client/resources/TrustProviders similarity index 100% rename from BLUESPAWN-client/resources/TrustProviders rename to BLUESPAWN-win-client/resources/TrustProviders diff --git a/BLUESPAWN-client/resources/bluespawn-original/kernel32_kernelbase_ror13.yar b/BLUESPAWN-win-client/resources/bluespawn-original/kernel32_kernelbase_ror13.yar similarity index 100% rename from BLUESPAWN-client/resources/bluespawn-original/kernel32_kernelbase_ror13.yar rename to BLUESPAWN-win-client/resources/bluespawn-original/kernel32_kernelbase_ror13.yar diff --git a/BLUESPAWN-client/resources/indicators.yar b/BLUESPAWN-win-client/resources/indicators.yar similarity index 100% rename from BLUESPAWN-client/resources/indicators.yar rename to BLUESPAWN-win-client/resources/indicators.yar diff --git a/BLUESPAWN-client/resources/resource.h b/BLUESPAWN-win-client/resources/resource.h similarity index 100% rename from BLUESPAWN-client/resources/resource.h rename to BLUESPAWN-win-client/resources/resource.h diff --git a/BLUESPAWN-client/resources/severe.yar b/BLUESPAWN-win-client/resources/severe.yar similarity index 100% rename from BLUESPAWN-client/resources/severe.yar rename to BLUESPAWN-win-client/resources/severe.yar diff --git a/BLUESPAWN-client/resources/severe2.yar b/BLUESPAWN-win-client/resources/severe2.yar similarity index 100% rename from BLUESPAWN-client/resources/severe2.yar rename to BLUESPAWN-win-client/resources/severe2.yar diff --git a/BLUESPAWN-client/resources/third-party-integration/ConventionEngine.yar b/BLUESPAWN-win-client/resources/third-party-integration/ConventionEngine.yar similarity index 100% rename from BLUESPAWN-client/resources/third-party-integration/ConventionEngine.yar rename to BLUESPAWN-win-client/resources/third-party-integration/ConventionEngine.yar diff --git a/BLUESPAWN-client/resources/third-party-integration/core.webshell_detection.yara b/BLUESPAWN-win-client/resources/third-party-integration/core.webshell_detection.yara similarity index 100% rename from BLUESPAWN-client/resources/third-party-integration/core.webshell_detection.yara rename to BLUESPAWN-win-client/resources/third-party-integration/core.webshell_detection.yara diff --git a/BLUESPAWN-client/resources/third-party-integration/extended.webshell_detection.yara b/BLUESPAWN-win-client/resources/third-party-integration/extended.webshell_detection.yara similarity index 98% rename from BLUESPAWN-client/resources/third-party-integration/extended.webshell_detection.yara rename to BLUESPAWN-win-client/resources/third-party-integration/extended.webshell_detection.yara index ad7509d7..5be7c2dd 100644 --- a/BLUESPAWN-client/resources/third-party-integration/extended.webshell_detection.yara +++ b/BLUESPAWN-win-client/resources/third-party-integration/extended.webshell_detection.yara @@ -294,9 +294,6 @@ private rule DodgyStrings strings: $ = ".bash_history" - $ = "404 not found" nocase - $ = "file not found" nocase - $ = "forbidden" nocase $ = /AddType\s+application\/x-httpd-(php|cgi)/ nocase $ = /php_value\s*auto_prepend_file/ nocase $ = /SecFilterEngine\s+Off/ nocase // disable modsec @@ -319,8 +316,6 @@ private rule DodgyStrings $ = "b374k" fullword nocase $ = "backdoor" fullword nocase $ = /(c99|r57|fx29)shell/ - $ = "cmd.exe" fullword nocase - $ = "powershell.exe" fullword nocase $ = /defac(ed|er|ement|ing)/ fullword nocase $ = "evilc0ders" fullword nocase $ = "exploit" fullword nocase @@ -328,7 +323,6 @@ private rule DodgyStrings $ = "hashcrack" nocase $ = "id_rsa" fullword $ = "ipconfig" fullword nocase - $ = "kernel32.dll" fullword nocase $ = "kingdefacer" nocase $ = "Wireghoul" nocase fullword $ = "LD_PRELOAD" fullword @@ -486,5 +480,5 @@ rule possibleIndicator description = "Artifacts common to web shells and less common in benign files" condition: - DodgyPhp or DangerousPhp or DodgyStrings + DodgyPhp or DangerousPhp or DodgyStrings } diff --git a/BLUESPAWN-client/resources/third-party-integration/kiwi_passwords.yar b/BLUESPAWN-win-client/resources/third-party-integration/kiwi_passwords.yar similarity index 100% rename from BLUESPAWN-client/resources/third-party-integration/kiwi_passwords.yar rename to BLUESPAWN-win-client/resources/third-party-integration/kiwi_passwords.yar diff --git a/BLUESPAWN-client/resources/third-party-integration/win_metasploit_related.yara b/BLUESPAWN-win-client/resources/third-party-integration/win_metasploit_related.yara similarity index 100% rename from BLUESPAWN-client/resources/third-party-integration/win_metasploit_related.yara rename to BLUESPAWN-win-client/resources/third-party-integration/win_metasploit_related.yara diff --git a/BLUESPAWN-win-client/src/hunt/Hunt.cpp b/BLUESPAWN-win-client/src/hunt/Hunt.cpp new file mode 100644 index 00000000..df91d17d --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/Hunt.cpp @@ -0,0 +1,40 @@ +#include "hunt/Hunt.h" +#include "hunt/HuntRegister.h" + +HuntInfo::HuntInfo(const std::wstring& HuntName, DWORD HuntTactics, DWORD HuntCategories, DWORD HuntDatasources, long HuntStartTime) : + HuntName{ HuntName }, + HuntTactics{ HuntTactics }, + HuntCategories{ HuntCategories }, + HuntDatasources{ HuntDatasources }, + HuntStartTime{ HuntStartTime }{} + +Hunt::Hunt(IN CONST std::wstring& name) : + name{ name }{ + dwTacticsUsed = 0; + dwSourcesInvolved = 0; + dwCategoriesAffected = 0; +} + +std::wstring Hunt::GetName() { + return name; +} + +std::vector> Hunt::RunHunt(IN CONST Scope& scope){ + return {}; +} + +std::vector, Scope>> Hunt::GetMonitoringEvents() { + return std::vector, Scope>>(); +} + +bool Hunt::AffectsCategory(IN DWORD dwStuff){ + return (dwStuff && dwCategoriesAffected) == dwStuff; +} + +bool Hunt::UsesTactics(IN DWORD dwTactics){ + return (dwTactics && dwTacticsUsed) == dwTactics; +} + +bool Hunt::UsesSources(IN DWORD dwSources){ + return (dwSources && dwSourcesInvolved) == dwSources; +} diff --git a/BLUESPAWN-win-client/src/hunt/HuntRegister.cpp b/BLUESPAWN-win-client/src/hunt/HuntRegister.cpp new file mode 100644 index 00000000..0aaf225c --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/HuntRegister.cpp @@ -0,0 +1,107 @@ +#include "hunt/HuntRegister.h" + +#include +#include + +#include "util/log/Log.h" + +#include "monitor/EventManager.h" +#include "user/bluespawn.h" +#include "util/Utils.h" +#include "util/ThreadPool.h" +#include "util/Promise.h" + +decltype(HuntRegister::vRegisteredHunts) HuntRegister::vRegisteredHunts{}; + +void HuntRegister::RegisterHunt(std::unique_ptr&& hunt) { + vRegisteredHunts.emplace_back(std::move(hunt)); +} + +bool HuntRegister::HuntShouldRun(IN Hunt* hunt, + IN CONST std::vector vIncludedHunts, + IN CONST std::vector vExcludedHunts) { + if(vExcludedHunts.size() != 0) { + for(auto name : vExcludedHunts) { + if(hunt->GetName().find(name) != std::wstring::npos) { + return false; + } + } + return true; + } + if(vIncludedHunts.size() != 0) { + for(auto name : vIncludedHunts) { + if(hunt->GetName().find(name) != std::wstring::npos) { + return true; + } + } + return false; + } + return true; +} + +std::vector>>> +HuntRegister::RunHunts(IN CONST std::vector vIncludedHunts, + IN CONST std::vector vExcludedHunts, + IN CONST Scope& scope OPTIONAL, + IN CONST bool async OPTIONAL) { + if(vExcludedHunts.size() != 0) { + Bluespawn::io.InformUser(L"Starting a hunt for " + + std::to_wstring(vRegisteredHunts.size() - vExcludedHunts.size()) + L" techniques."); + } else if(vIncludedHunts.size() != 0) { + Bluespawn::io.InformUser(L"Starting a hunt for " + std::to_wstring(vIncludedHunts.size()) + L" techniques."); + } else { + Bluespawn::io.InformUser(L"Starting a hunt for " + std::to_wstring(vRegisteredHunts.size()) + L" techniques."); + } + + std::vector>>> detections{}; + for(auto& hunt : vRegisteredHunts) { + if(HuntShouldRun(hunt.get(), vIncludedHunts, vExcludedHunts)) { + detections.emplace_back(RunHunt(hunt.get(), scope)); + } + } + + if(async) { + std::vector handles(detections.begin(), detections.end()); + + for(size_t idx{ 0 }; idx < handles.size(); idx += MAXIMUM_WAIT_OBJECTS) { + auto count{ min(handles.size() - idx, MAXIMUM_WAIT_OBJECTS) }; + auto result{ WaitForMultipleObjects(count, handles.data() + idx, true, INFINITE) }; + if(result != WAIT_OBJECT_0) { + LOG_ERROR("Failed to wait for hunts to finish (status 0x" << std::hex << result << ", error " + << std::hex << GetLastError() << ")"); + throw std::exception("Failed to wait for hunts to finish!"); + } + } + + auto successes{ std::count_if(detections.begin(), detections.end(), + [](auto result) { return result.Fufilled(); }) }; + + Bluespawn::io.InformUser(L"Successfully ran " + std::to_wstring(successes) + L" hunts."); + } + + return detections; +} + +Promise>> HuntRegister::RunHunt(IN Hunt* hunt, IN CONST Scope& scope OPTIONAL) { + Bluespawn::io.InformUser(L"Starting scan for " + hunt->GetName()); + + return ThreadPool::GetInstance().RequestPromise>>( + [hunt, scope]() mutable { return hunt->RunHunt(scope); }); +} + +void HuntRegister::SetupMonitoring(IN CONST std::vector vIncludedHunts, + IN CONST std::vector vExcludedHunts) { + auto& EvtManager{ EventManager::GetInstance() }; + for(auto& hunt : vRegisteredHunts) { + if(HuntShouldRun(hunt.get(), vIncludedHunts, vExcludedHunts)) { + Bluespawn::io.InformUser(L"Setting up monitoring for " + hunt->GetName()); + for(auto& event : hunt->GetMonitoringEvents()) { + auto callback{ std::bind(&Hunt::RunHunt, hunt.get(), std::placeholders::_1) }; + DWORD status{ EvtManager.SubscribeToEvent(std::move(event.first), callback, event.second) }; + if(status != ERROR_SUCCESS) { + LOG_ERROR(L"Monitoring for " << hunt->GetName() << L" failed with error code " << status); + } + } + } + } +} diff --git a/BLUESPAWN-client/src/hunt/RegistryHunt.cpp b/BLUESPAWN-win-client/src/hunt/RegistryHunt.cpp similarity index 87% rename from BLUESPAWN-client/src/hunt/RegistryHunt.cpp rename to BLUESPAWN-win-client/src/hunt/RegistryHunt.cpp index cab05cdd..26f99676 100644 --- a/BLUESPAWN-client/src/hunt/RegistryHunt.cpp +++ b/BLUESPAWN-win-client/src/hunt/RegistryHunt.cpp @@ -1,7 +1,4 @@ #include "hunt/RegistryHunt.h" -#include "reaction/Reaction.h" - -#include "util/log/HuntLogMessage.h" #include "util/log/Log.h" #include @@ -124,48 +121,48 @@ namespace Registry { auto data = key.GetValue(check.name); if(!data.has_value()){ if(check.MissingBad){ - LOG_INFO("Under key " << key << ", desired value " << check.name << " was missing."); + LOG_INFO(3, "Under key " << key << ", desired value " << check.name << " was missing."); vIdentifiedValues.emplace_back(RegistryValue{ key, check.name, std::move(std::wstring{}) }); } } else if(!check(*data)){ auto value = RegistryValue{ key, check.name, std::move(*data) }; - LOG_INFO("Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); + LOG_INFO(2, "Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); vIdentifiedValues.emplace_back(value); } } else if(check.GetType() == RegistryType::REG_MULTI_SZ_T){ auto data = key.GetValue>(check.name); if(!data.has_value()){ if(check.MissingBad){ - LOG_INFO("Under key " << key << ", desired value " << check.name << " was missing."); + LOG_INFO(3, "Under key " << key << ", desired value " << check.name << " was missing."); vIdentifiedValues.emplace_back(RegistryValue{ key, check.name, std::move(std::vector{}) }); } } else if(!check(*data)){ auto value = RegistryValue{ key, check.name, std::move(*data) }; - LOG_INFO("Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); + LOG_INFO(2, "Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); vIdentifiedValues.emplace_back(value); } } else if(check.GetType() == RegistryType::REG_DWORD_T){ auto data = key.GetValue(check.name); if(!data.has_value()){ if(check.MissingBad){ - LOG_INFO("Under key " << key << ", desired value " << check.name << " was missing."); + LOG_INFO(3, "Under key " << key << ", desired value " << check.name << " was missing."); vIdentifiedValues.emplace_back(RegistryValue{ key, check.name, std::move(0) }); } } else if(!check(*data)){ auto value = RegistryValue{ key, check.name, std::move(*data) }; - LOG_INFO("Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); + LOG_INFO(2, "Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); vIdentifiedValues.emplace_back(value); } } else if(check.GetType() == RegistryType::REG_BINARY_T){ auto data = key.GetRawValue(check.name); if(!data){ if(check.MissingBad){ - LOG_INFO("Under key " << key << ", desired value " << check.name << " was missing."); + LOG_INFO(3, "Under key " << key << ", desired value " << check.name << " was missing."); vIdentifiedValues.emplace_back(RegistryValue{ key, check.name, std::move(AllocationWrapper{ nullptr, 0 }) }); } } else if(!check(data)){ auto value = RegistryValue{ key, check.name, std::move(data) }; - LOG_INFO("Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); + LOG_INFO(2, "Under key " << key << ", value " << value.GetPrintableName() << " had potentially malicious data " << value); vIdentifiedValues.emplace_back(value); } } @@ -209,28 +206,28 @@ namespace Registry { auto data = key.GetValue(value); auto regValue = RegistryValue{ key, value, std::move(!data ? L"" : *data) }; - LOG_INFO("Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); + LOG_INFO(2, "Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); vRegValues.emplace_back(regValue); } else if(type == RegistryType::REG_MULTI_SZ_T){ auto data = key.GetValue>(value); auto regValue = RegistryValue{ key, value, std::move(!data ? std::vector{} : *data) }; - LOG_INFO("Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); + LOG_INFO(2, "Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); vRegValues.emplace_back(regValue); } else if(type == RegistryType::REG_DWORD_T){ auto data = key.GetValue(value); auto regValue = RegistryValue{ key, value, std::move(!data ? 0 : *data) }; - LOG_INFO("Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); + LOG_INFO(2, "Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); vRegValues.emplace_back(regValue); } else { auto data = key.GetRawValue(value); auto regValue = RegistryValue{ key, value, std::move(data) }; - LOG_INFO("Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); + LOG_INFO(2, "Under key " << key << ", value " << regValue.GetPrintableName() << " was present with data " << regValue); vRegValues.emplace_back(regValue); } @@ -274,4 +271,4 @@ namespace Registry { } return subkeys; } -} \ No newline at end of file +} diff --git a/BLUESPAWN-win-client/src/hunt/Scope.cpp b/BLUESPAWN-win-client/src/hunt/Scope.cpp new file mode 100644 index 00000000..d135e224 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/Scope.cpp @@ -0,0 +1,11 @@ +#include "hunt/Scope.h" + +Scope Scope::CreateSubhuntScope(IN DWORD64 Subsections, IN DWORD Subtechniques OPTIONAL) { + Scope scope{}; + scope.Subtechniques = Subtechniques; + if(Subsections != -1ULL) { + scope.Subsections = Subsections; + } + + return scope; +} diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1036.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1036.cpp new file mode 100644 index 00000000..e40852e4 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1036.cpp @@ -0,0 +1,65 @@ +#include "hunt/hunts/HuntT1036.h" + +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "scan/FileScanner.h" +#include "user/bluespawn.h" + +#define SEARCH_WRITABLE 0 + +namespace Hunts { + + HuntT1036::HuntT1036() : Hunt(L"T1036 - Masquerading") { + dwCategoriesAffected = (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::FileSystem; + dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; + } + + void HuntT1036::Subtechnique005(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(005, Match Legitimate Name or Location); + + SUBSECTION_INIT(SEARCH_WRITABLE, Intensive); + for(auto folder : writableFolders) { + auto f = FileSystem::Folder(folder); + if(f.GetFolderExists()) { + LOG_INFO(2, L"Scanning " << f.GetFolderPath()); + for(auto value : f.GetFiles(std::nullopt, -1)) { + if(FileScanner::PerformQuickScan(value.GetFilePath())) { + CREATE_DETECTION(Certainty::None, FileDetectionData{ value }); + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1036::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique005(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1036::GetMonitoringEvents() { + std::vector, Scope>> events; + + if(Bluespawn::aggressiveness >= Aggressiveness::Intensive) { + Scope scope{ Scope::CreateSubhuntScope(1 << SEARCH_WRITABLE) }; + for(auto folder : writableFolders) { + auto f = FileSystem::Folder(folder); + if(f.GetFolderExists()) { + events.push_back(std::make_pair(std::make_unique(f), scope)); + for(auto subdir : f.GetSubdirectories(-1)) { + events.push_back(std::make_pair(std::make_unique(subdir), scope)); + } + } + } + } + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1037.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1037.cpp new file mode 100644 index 00000000..87ce7749 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1037.cpp @@ -0,0 +1,50 @@ +#include "hunt/hunts/HuntT1037.h" + +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "hunt/RegistryHunt.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define LOGON_SCRIPT 0 + +namespace Hunts { + HuntT1037::HuntT1037() : Hunt(L"T1037 - Boot or Logon Initialization Scripts") { + dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; + dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; + } + + void HuntT1037::Subtechnique001(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(001, Logon Script[Windows]); + + SUBSECTION_INIT(LOGON_SCRIPT, Cursory); + for(auto detection : CheckValues(HKEY_CURRENT_USER, L"Environment", + { { L"UserInitMprLogonScript", L"", false, CheckSzEmpty } }, true, true)) { + // Moderate contextual certainty due to the infequency of use for this registry value in legitimate cases + CREATE_DETECTION(Certainty::Moderate, RegistryDetectionData{ detection }); + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1037::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique001(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1037::GetMonitoringEvents() { + std::vector, Scope>> events; + + // Looks for T1037.001: Logon Script (Windows) + Registry::GetRegistryEvents(events, SCOPE(LOGON_SCRIPT), HKEY_CURRENT_USER, L"Environment", true, true, false); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1053.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1053.cpp new file mode 100644 index 00000000..3e4845bd --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1053.cpp @@ -0,0 +1,92 @@ +#include "hunt/hunts/HuntT1053.h" + +#include "util/Utils.h" +#include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" + +#include "scan/YaraScanner.h" +#include "user/bluespawn.h" + +#define EVT_4698 0 +#define EVT_106 1 + +namespace Hunts { + + HuntT1053::HuntT1053() : Hunt(L"T1053 - Scheduled Task/Job") { + dwCategoriesAffected = (DWORD) Category::Files | (DWORD) Category::Processes; + dwSourcesInvolved = (DWORD) DataSource::EventLogs; + dwTacticsUsed = (DWORD) Tactic::Execution | (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; + } + + void HuntT1053::Subtechnique005(IN CONST Scope& scope, OUT std::vector>& detections){ + SUBTECHNIQUE_INIT(005, Scheduled Task); + + SUBSECTION_INIT(EVT_4698, Cursory); + std::vector queries; + auto param1 = EventLogs::ParamList(); + auto param2 = EventLogs::ParamList(); + auto param3 = EventLogs::ParamList(); + auto param4 = EventLogs::ParamList(); + param1.push_back(std::make_pair(L"Name", L"'SubjectUserName'")); + param2.push_back(std::make_pair(L"Name", L"'SubjectDomainName'")); + param3.push_back(std::make_pair(L"Name", L"'TaskName'")); + param4.push_back(std::make_pair(L"Name", L"'TaskContent'")); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); + + auto queryResults = EventLogs::QueryEvents(L"Security", 4698, queries); + + // clang-format off + for(auto result : queryResults){ + CREATE_DETECTION(Certainty::Moderate, OtherDetectionData{ L"Scheduled Task", { + { L"Name", result.GetProperty(L"Event/EventData/Data[@Name='TaskName']") }, + { L"User", result.GetProperty(L"Event/EventData/Data[@Name='SubjectUserName']") }, + { L"Content", result.GetProperty(L"Event/EventData/Data[@Name='TaskContent']") } + }}); + } + SUBSECTION_END(); + + SUBSECTION_INIT(EVT_106, Cursory); + std::vector queries2; + auto param5 = EventLogs::ParamList(); + auto param6 = EventLogs::ParamList(); + param5.push_back(std::make_pair(L"Name", L"'TaskName'")); + param6.push_back(std::make_pair(L"Name", L"'UserContext'")); + queries2.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param5)); + queries2.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param6)); + + auto queryResults2 = EventLogs::QueryEvents(L"Microsoft-Windows-TaskScheduler/Operational", 106, queries2); + + for(auto result : queryResults2){ + CREATE_DETECTION(Certainty::Moderate, OtherDetectionData{ L"Scheduled Task", { + { L"Name", result.GetProperty(L"Event/EventData/Data[@Name='TaskName']") }, + { L"User", result.GetProperty(L"Event/EventData/Data[@Name='SubjectUserName']") } + }}); + } + // clang-format on + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1053::RunHunt(const Scope& scope) { + HUNT_INIT(); + + // Looks for T1053.005: Scheduled Task + Subtechnique005(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1053::GetMonitoringEvents() { + std::vector, Scope>> events; + + events.push_back(std::make_pair(std::make_unique(L"Security", 4698), SCOPE(EVT_4698))); + events.push_back(std::make_pair(std::make_unique(L"Microsoft-Windows-TaskScheduler/Operational", + 106), SCOPE(EVT_106))); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1055.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1055.cpp new file mode 100644 index 00000000..ca0cf7c4 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1055.cpp @@ -0,0 +1,128 @@ +#include "hunt/hunts/HuntT1055.h" + +#include +#pragma pack(push, 8) +#include +#pragma pack(pop) +#include + +#include "util/StringUtils.h" +#include "util/ThreadPool.h" +#include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" +#include "util/processes/ProcessUtils.h" +#include "util/wrappers.hpp" + +#include "pe_sieve.h" +#include "pe_sieve_types.h" +#include "user/bluespawn.h" + +extern "C" { +void __stdcall PESieve_help(void); +DWORD __stdcall PESieve_version(void); +pesieve::t_report __stdcall PESieve_scan(pesieve::t_params args); +}; + +namespace Hunts { + + HuntT1055::HuntT1055() : Hunt(L"T1055 - Process Injection") { + dwCategoriesAffected = (DWORD) Category::Processes; + dwSourcesInvolved = (DWORD) DataSource::Processes; + dwTacticsUsed = (DWORD) Tactic::PrivilegeEscalation | (DWORD) Tactic::DefenseEvasion; + } + + void HuntT1055::HandleReport(OUT std::vector>& detections, + IN CONST Promise>& promise) { + auto __name{ this->name }; + auto value{ promise.GetValue() }; + if(value) { + auto report{ *value }; + auto summary{ report->scan_report->generateSummary() }; + if(summary.skipped) { + LOG_INFO(2, "Skipped scanning " << summary.skipped << " modules in process " + << report->scan_report->getPid() + << ". This is likely due to use of .NET"); + } + + if(summary.suspicious && !summary.errors) { + std::wstring path = StringToWidestring(report->scan_report->mainImagePath); + + for(auto module : report->scan_report->moduleReports) { + if(module->status == pesieve::SCAN_SUSPICIOUS) { + CREATE_DETECTION_WITH_CONTEXT(Certainty::Strong, + ProcessDetectionData::CreateMemoryDetectionData( + report->scan_report->getPid(), path, module->module, + static_cast(module->moduleSize), + StringToWidestring(module->moduleFile), path), + DetectionContext{ name }); + } + } + } + } + } + + std::vector> HuntT1055::RunHunt(const Scope& scope) { + HUNT_INIT(); + + SUBSECTION_INIT(0, Normal); + HandleWrapper snapshot{ CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; + if(snapshot) { + PROCESSENTRY32W info{}; + info.dwSize = sizeof(info); + if(Process32FirstW(snapshot, &info)) { + std::vector>> results{}; + do { + auto pid{ info.th32ProcessID }; + if(info.szExeFile == std::wstring{ L"vmmem" }) { + LOG_INFO(2, L"Skipping scans for process with PID " << pid << "."); + continue; + } + + results.emplace_back( + ThreadPool::GetInstance().RequestPromise>([pid]() { + pesieve::t_params params{ + pid, + 3, + Bluespawn::aggressiveness == Aggressiveness::Intensive ? pesieve::PE_DNET_NONE : + pesieve::PE_DNET_SKIP_HOOKS, + pesieve::PE_IMPREC_NONE, + true, + pesieve::OUT_NO_DIR, + Bluespawn::aggressiveness != Aggressiveness::Intensive, + Bluespawn::aggressiveness == Aggressiveness::Intensive, + Bluespawn::aggressiveness == Aggressiveness::Intensive ? pesieve::PE_IATS_FILTERED : + pesieve::PE_IATS_NONE, + Bluespawn::aggressiveness == Aggressiveness::Intensive ? pesieve::PE_DATA_SCAN_NO_DEP : + pesieve::PE_DATA_NO_SCAN, + false, + pesieve::PE_DUMP_AUTO, + false, + 0 + }; + + WRAP(pesieve::ReportEx*, report, scan_and_dump(params), delete data); + if(!report) { + LOG_INFO(2, "Unable to scan process " << pid << " due to an error in PE-Sieve.dll"); + throw std::exception{ "Failed to scan process" }; + } + + return report; + })); + } while(Process32NextW(snapshot, &info)); + + for(auto& promise : results) { + HandleReport(detections, promise); + } + } else { + auto error{ GetLastError() }; + LOG_ERROR("Unable to enumerate processes - Process related hunts will not run." << GetLastError()); + } + } else { + LOG_ERROR("Unable to enumerate processes - Process related hunts will not run."); + } + SUBSECTION_END(); + + HUNT_END(); + } + +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1068.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1068.cpp new file mode 100644 index 00000000..02f67226 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1068.cpp @@ -0,0 +1,75 @@ +#include "hunt/hunts/HuntT1068.h" + +#include + +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "user/bluespawn.h" + +using namespace Registry; + +#define PRINTERS 0 +#define PORTS 1 + +namespace Hunts { + + HuntT1068::HuntT1068() : Hunt(L"T1068 - Exploitation for Privilege Escalation") { + dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; + dwTacticsUsed = (DWORD) Tactic::PrivilegeEscalation; + } + + std::vector> HuntT1068::RunHunt(const Scope& scope) { + HUNT_INIT(); + + SUBSECTION_INIT(PRINTERS, Cursory) + RegistryKey printers{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Printers", + true }; + for(auto printer : printers.EnumerateSubkeys()) { + if(printer.ValueExists(L"Port")) { + auto value{ RegistryValue::Create(printer, L"Port") }; + FileSystem::File filepath{ value->ToString() }; + + // Regex ensures the file is an actual drive and not, say, a COM port + if(std::regex_match(filepath.GetFilePath(), std::wregex(L"([a-zA-z]{1}:\\\\)(.*)")) && + filepath.GetFileExists() && filepath.HasReadAccess()) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *value, RegistryDetectionType::FileReference }); + } + } + } + SUBSECTION_END(); + + SUBSECTION_INIT(PORTS, Cursory); + RegistryKey ports{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports", true }; + for(auto value : ports.EnumerateValues()) { + FileSystem::File filepath{ value }; + + // Regex ensures the file is an actual drive and not, say, a COM port + if(std::regex_match(filepath.GetFilePath(), std::wregex(L"([a-zA-z]{1}:\\\\)(.*)")) && + filepath.GetFileExists() && filepath.HasReadAccess()) { + CREATE_DETECTION(Certainty::Strong, RegistryDetectionData{ *RegistryValue::Create(ports, value), + RegistryDetectionType::Unknown }); + CREATE_DETECTION(Certainty::Strong, FileDetectionData{ filepath }); + } + } + SUBSECTION_END(); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1068::GetMonitoringEvents() { + std::vector, Scope>> events; + + // CVE-2020-1048 + Registry::GetRegistryEvents(events, SCOPE(PRINTERS), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Printers", true, false, + true); + Registry::GetRegistryEvents(events, SCOPE(PORTS), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports", true, false, false); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1070.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1070.cpp new file mode 100644 index 00000000..d846ddb8 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1070.cpp @@ -0,0 +1,80 @@ +#include "hunt/hunts/HuntT1070.h" + +#include + +#include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" +#include "util/processes/ProcessUtils.h" + +#include "user/bluespawn.h" + +#define TIMESTOMP 0 + +namespace Hunts { + + HuntT1070::HuntT1070() : Hunt(L"T1070 - Indicator Removal on Host") { + dwCategoriesAffected = (DWORD) Category::Files | (DWORD) Category::Processes; + dwSourcesInvolved = (DWORD) DataSource::EventLogs; + dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; + } + + void HuntT1070::Subtechnique006(IN CONST Scope& scope, OUT std::vector>& detections){ + SUBTECHNIQUE_INIT(006, Timestomp); + + SUBSECTION_INIT(TIMESTOMP, Normal); + // Looks for T1070.006 Timestomp + // Create existance queries so interesting data is output + std::vector queries; + auto param1 = EventLogs::ParamList(); + auto param2 = EventLogs::ParamList(); + auto param3 = EventLogs::ParamList(); + auto param4 = EventLogs::ParamList(); + auto param5 = EventLogs::ParamList(); + param1.push_back(std::make_pair(L"Name", L"'Image'")); + param2.push_back(std::make_pair(L"Name", L"'ProcessId'")); + param3.push_back(std::make_pair(L"Name", L"'TargetFilename'")); + param4.push_back(std::make_pair(L"Name", L"'CreationUtcTime'")); + param5.push_back(std::make_pair(L"Name", L"'PreviousCreationUtcTime'")); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param5)); + + auto queryResults = EventLogs::QueryEvents(L"Microsoft-Windows-Sysmon/Operational", 2, queries); + for(auto query : queryResults){ + FileSystem::File file{ query.GetProperty(L"Event/EventData/Data[@Name='TargetFilename']") }; + CREATE_DETECTION(Certainty::Strong, FileDetectionData{ file }); + + // Scan process + HandleWrapper hProcess{ + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, + std::stoi(query.GetProperty(L"Event/EventData/Data[@Name='ProcessId']"))) }; + if(hProcess){ + auto image{ GetProcessImage(hProcess) }; + CREATE_DETECTION(Certainty::Moderate, + ProcessDetectionData::CreateProcessDetectionData(GetProcessId(hProcess), image)); + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1070::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique006(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1070::GetMonitoringEvents() { + std::vector, Scope>> events{}; + + events.push_back(std::make_pair(std::make_unique(L"Microsoft-Windows-Sysmon/Operational", 2), + SCOPE(TIMESTOMP))); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1136.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1136.cpp new file mode 100644 index 00000000..250f40e7 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1136.cpp @@ -0,0 +1,63 @@ +#include "hunt/hunts/HuntT1136.h" + +#include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" + +#include "user/bluespawn.h" + +#define USER_LOG 0 + +namespace Hunts { + + HuntT1136::HuntT1136() : Hunt(L"T1136 - Create Account") { + dwCategoriesAffected = (DWORD) Category::Configurations; + dwSourcesInvolved = (DWORD) DataSource::EventLogs; + dwTacticsUsed = (DWORD) Tactic::Persistence; + } + + void HuntT1136::Subtechnique001(IN CONST Scope& scope, OUT std::vector>& detections){ + SUBTECHNIQUE_INIT(001, Local Account); + + SUBSECTION_INIT(USER_LOG, Normal); + + // Looks for T1136.001: Local Account + // Create existance queries so interesting data is output + std::vector queries; + auto param1 = EventLogs::ParamList(); + auto param2 = EventLogs::ParamList(); + param1.push_back(std::make_pair(L"Name", L"'TargetUserName'")); + param2.push_back(std::make_pair(L"Name", L"'SubjectUserName'")); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); + + auto results = EventLogs::QueryEvents(L"Security", 4720, queries); + for(auto result : results){ + // clang-format off + CREATE_DETECTION(Certainty::None, OtherDetectionData{ L"User", { + { L"Username", result.GetProperty(L"Event/EventData/Data[@Name='TargetUserName']") }, + { L"Creator", result.GetProperty(L"Event/EventData/Data[@Name='SubjectUserName']") } + }}); + // clang-format on + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1136::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique001(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1136::GetMonitoringEvents() { + std::vector, Scope>> events; + + // Looks for T1136.001: Local Account + events.push_back(std::make_pair(std::make_unique(L"Security", 4720), SCOPE(USER_LOG))); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1484.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1484.cpp new file mode 100644 index 00000000..cfea9483 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1484.cpp @@ -0,0 +1,46 @@ +#include "hunt/hunts/HuntT1484.h" + +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "user/bluespawn.h" + +#define NTUSER_MAN 0 + +namespace Hunts { + + HuntT1484::HuntT1484() : Hunt(L"T1484 - Group Policy Modification") { + dwCategoriesAffected = (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::FileSystem | (DWORD) DataSource::GPO; + dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; + } + + std::vector> HuntT1484::RunHunt(const Scope& scope) { + HUNT_INIT(); + + SUBSECTION_INIT(NTUSER_MAN, Normal) + auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); + for(auto userFolder : userFolders) { + FileSystem::File ntuserman{ userFolder.GetFolderPath() + L"\\ntuser.man" }; + if(ntuserman.GetFileExists()) { + CREATE_DETECTION(Certainty::Moderate, FileDetectionData{ ntuserman }); + } + } + SUBSECTION_END(); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1484::GetMonitoringEvents() { + std::vector, Scope>> events; + + auto scope{ SCOPE(NTUSER_MAN) }; + events.push_back(std::make_pair(std::make_unique(FileSystem::Folder(L"C:\\Users")), scope)); + auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); + for(auto userFolder : userFolders) { + events.push_back(std::make_pair(std::make_unique(userFolder), scope)); + } + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1505.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1505.cpp new file mode 100644 index 00000000..f246c6c9 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1505.cpp @@ -0,0 +1,122 @@ +#include "hunt/hunts/HuntT1505.h" + +#include "util/StringUtils.h" +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "scan/YaraScanner.h" +#include "user/bluespawn.h" + +#define WEB_SHELL 0 + +namespace Hunts { + HuntT1505::HuntT1505() : Hunt(L"T1505 - Server Software Component") { + dwCategoriesAffected = (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::FileSystem; + dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; + } + + void HuntT1505::Subtechnique003(IN CONST Scope& scope, OUT std::vector>& detections){ + SUBTECHNIQUE_INIT(003, Web Shell); + + SUBSECTION_INIT(WEB_SHELL, Normal); + // Looks for T1505.003: Web Shell + //PHP regex credit to: https://github.com/emposha/PHP-Shell-Detector + php_vuln_functions.assign( + R"(preg_replace.*\/e|`.*?\$.*?`|\bcreate_function\b|\bpassthru\b|\bshell_exec\b|\bexec\b|\bbase64_decode\b|\bedoced_46esab\b|\beval\b|\bsystem\b|\bproc_open\b|\bpopen\b|\bcurl_exec\b|\bcurl_multi_exec\b|\bparse_ini_file\b|\bshow_source\b)"); + asp_indicators.assign( + R"(\bcmd.exe\b|\bpowershell.exe\b|\bwscript.shell\b|\bprocessstartinfo\b|\bcreatenowindow\b|\bcmd\b|\beval request\b|\bexecute request\b|\boscriptnet\b|createobject\("scripting.filesystemobject"\))"); + jsp_indicators.assign(R"(\bcmd.exe\b|\bpowershell.exe\b|\bgetruntime\(\)\.exec\b)"); + + for(std::wstring path : web_directories){ + auto hWebRoot = FileSystem::Folder(path); + FileSystem::FileSearchAttribs attribs; + attribs.extensions = web_exts; + std::vector files = hWebRoot.GetFiles(attribs, -1); + + for(const auto& file : files){ + long offset = 0; + unsigned long targetAmount = 1000000; + DWORD amountRead = 0; + auto file_ext = ToLowerCaseW(file.GetFileAttribs().extension); + bool detected = false; + + do{ + auto read = file.Read(targetAmount, offset, &amountRead); + read.SetByte(amountRead, '\0'); + std::string sus_file = ToLowerCaseA(*read.ReadString()); + if(file_ext.compare(L".php") == 0){ + if(regex_search(sus_file, match_index, php_vuln_functions)){ + CREATE_DETECTION(Certainty::Strong, FileDetectionData{ file }); + LOG_INFO(1, L"Located likely web shell in file " + << file.GetFilePath() << L" in text " + << StringToWidestring( + sus_file.substr(match_index.position(), match_index.length()))); + detected = true; + break; + } + } else if(file_ext.substr(0, 4).compare(L".jsp") == 0){ + if(regex_search(sus_file, match_index, jsp_indicators)){ + CREATE_DETECTION(Certainty::Strong, FileDetectionData{ file }); + LOG_INFO(1, L"Located likely web shell in file " + << file.GetFilePath() << L" in text " + << StringToWidestring( + sus_file.substr(match_index.position(), match_index.length()))); + detected = true; + break; + } + } else if(file_ext.substr(0, 3).compare(L".as") == 0){ + if(regex_search(sus_file, match_index, asp_indicators)){ + CREATE_DETECTION(Certainty::Strong, FileDetectionData{ file }); + LOG_INFO(1, L"Located likely web shell in file " + << file.GetFilePath() << L" in text " + << StringToWidestring( + sus_file.substr(match_index.position(), match_index.length()))); + detected = true; + break; + } + } + offset += amountRead - 1000; + } while(static_cast(offset + 1000) < file.GetFileSize()); + + // Use YARA to also scan the files if our regex didn't detect anything suspicious + if(!detected){ + const auto& yara = YaraScanner::GetInstance(); + YaraScanResult result{ yara.ScanFile(file) }; + if(!result && result.vKnownBadRules.size() > 0){ + CREATE_DETECTION(Certainty::Strong, FileDetectionData{ file, result }); + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1505::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique003(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1505::GetMonitoringEvents() { + std::vector, Scope>> events; + + auto scope{ SCOPE(WEB_SHELL) }; + // Looks for T1505.003: Web Shell + for(auto dir : web_directories) { + auto folder = FileSystem::Folder{ dir }; + if(folder.GetFolderExists()) { + events.push_back(std::make_pair(std::make_unique(folder), scope)); + for(auto subdir : folder.GetSubdirectories()) { + events.push_back(std::make_pair(std::make_unique(subdir), scope)); + } + } + } + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1543.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1543.cpp new file mode 100644 index 00000000..9c4f1242 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1543.cpp @@ -0,0 +1,202 @@ +#include "hunt/hunts/HuntT1543.h" + +#include +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" +#include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" +#include "util/processes/ProcessUtils.h" + +#include "hunt/RegistryHunt.h" +#include "scan/FileScanner.h" +#include "scan/ProcessScanner.h" +#include "scan/ServiceScanner.h" +#include "scan/YaraScanner.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define DNS_SECTION 0 +#define NTDS_SECTION 1 +#define WINSOCK_PARAMS 2 +#define WINSOCK_CATALOG 3 +#define WINSOCK_CUR_CATALOG 4 +#define FAILURE_SECTION 5 +#define LOGS_SECTION 6 + +namespace Hunts { + + HuntT1543::HuntT1543() : Hunt(L"T1543 - Create or Modify System Process") { + dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem | + (DWORD) DataSource::EventLogs; + dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; + } + + void HuntT1543::Subtechnique003(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(003, Windows Service); + + // DNS Service Audit + SUBSECTION_INIT(DNS_SECTION, Cursory); + for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\DNS\\Parameters", + { + { L"ServerLevelPluginDll", L"", false, CheckSzEmpty }, + }, + false, false)) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ detection, RegistryDetectionType::FileReference }); + } + SUBSECTION_END(); + + // NTDS Service Audit + SUBSECTION_INIT(NTDS_SECTION, Cursory); + for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NTDS", + { + { L"LsaDbExtPt", L"", false, CheckSzEmpty }, + { L"DirectoryServiceExtPt", L"", false, CheckSzEmpty }, + }, + false, false)) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ detection, RegistryDetectionType::FileReference }); + } + SUBSECTION_END(); + + // Winsock2 Service Audit + auto winsock2 = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters" }; + SUBSECTION_INIT(WINSOCK_PARAMS, Cursory); + for(auto paramdll : { L"AutodialDLL", L"NameSpace_Callout" }) { + auto detection{ Registry::RegistryValue::Create(winsock2, paramdll) }; + if(detection && FileScanner::PerformQuickScan(detection->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *detection, RegistryDetectionType::FileReference }); + } + } + SUBSECTION_END(); + + SUBSECTION_INIT(WINSOCK_CATALOG, Cursory); + auto appids = RegistryKey{ winsock2, L"AppId_Catalog" }; + for(auto subkey : appids.EnumerateSubkeys()) { + auto detection{ Registry::RegistryValue::Create(winsock2, L"AppFullPath") }; + if(detection && FileScanner::PerformQuickScan(detection->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *detection, RegistryDetectionType::FileReference }); + } + } + SUBSECTION_END(); + + SUBSECTION_INIT(WINSOCK_CUR_CATALOG, Cursory); + auto currentCallout = winsock2.GetValue(L"Current_NameSpace_Catalog"); + if(currentCallout) { + auto namespaceCatalog = RegistryKey{ winsock2, currentCallout.value() + L"\\Catalog_Entries" }; + auto namespaceCatalog64 = RegistryKey{ winsock2, currentCallout.value() + L"\\Catalog_Entries64" }; + for(auto subkey : { namespaceCatalog, namespaceCatalog64 }) { + for(auto entry : subkey.EnumerateSubkeys()) { + auto detection{ Registry::RegistryValue::Create(winsock2, L"LibraryPath") }; + if(detection && FileScanner::PerformQuickScan(detection->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *detection, RegistryDetectionType::FileReference }); + } + } + } + } + SUBSECTION_END(); + + // Service Failure Audit + SUBSECTION_INIT(FAILURE_SECTION, Normal); + auto services = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services" }; + for(auto service : services.EnumerateSubkeys()) { + auto detection{ Registry::RegistryValue::Create(service, L"FailureCommand") }; + if(detection && ProcessScanner::PerformQuickScan(detection->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *detection, RegistryDetectionType::CommandReference }); + } + } + SUBSECTION_END(); + + // Looks for 7045 New Service Created events + SUBSECTION_INIT(LOGS_SECTION, Normal); + std::vector queries; + auto param1 = EventLogs::ParamList(); + auto param2 = EventLogs::ParamList(); + auto param3 = EventLogs::ParamList(); + auto param4 = EventLogs::ParamList(); + param1.push_back(std::make_pair(L"Name", L"'ServiceName'")); + param2.push_back(std::make_pair(L"Name", L"'ImagePath'")); + param3.push_back(std::make_pair(L"Name", L"'ServiceType'")); + param4.push_back(std::make_pair(L"Name", L"'StartType'")); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param1)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param2)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param3)); + queries.push_back(EventLogs::XpathQuery(L"Event/EventData/Data", param4)); + + auto queryResults = EventLogs::QueryEvents(L"System", 7045, queries); + + for(auto result : queryResults) { + auto imageName{ result.GetProperty(L"Event/EventData/Data[@Name='ServiceName']") }; + auto imagePath{ GetImagePathFromCommand(result.GetProperty(L"Event/EventData/Data[@Name='ImagePath']")) }; + + FILETIME ft{}; + + ULONGLONG time = (ULONGLONG) stoull(result.GetTimeCreated()); + ULONGLONG nano = 0; + + ft.dwHighDateTime = (DWORD)((time >> 32) & 0xFFFFFFFF); + ft.dwLowDateTime = (DWORD)(time & 0xFFFFFFFF); + + auto malicious{ Certainty::None }; + + bool svchost{ false }; + if(imagePath.find(L"svchost.exe") != std::wstring::npos) { + // svchost services are rarely if ever should have 7045 events + malicious = malicious + Certainty::Weak; + svchost = true; + } else if(ServiceScanner::PerformQuickScan(std::nullopt, imageName, imagePath)) { + malicious = malicious + Certainty::Moderate; + } + + if(malicious > Certainty::None) { + // clang-format off + CREATE_DETECTION_WITH_CONTEXT( + malicious, ServiceDetectionData{ std::nullopt, imageName, imagePath }, + DetectionContext{ __name, ft, svchost ? std::optional{ + L"Most if not all svchost services should come preinstalled and therefore should not show up in " + "the event logs. However, this can sometimes happen legitimately" } : std::nullopt }); + // clang-format on + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1543::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique003(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1543::GetMonitoringEvents() { + std::vector, Scope>> events; + + // Looks for T1543.003: Windows Service + GetRegistryEvents(events, SCOPE(FAILURE_SECTION), HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services", + false, false); + GetRegistryEvents(events, SCOPE(NTDS_SECTION), HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NTDS", + false, false); + GetRegistryEvents(events, SCOPE(DNS_SECTION), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Services\\DNS\\Parameters", false, false); + GetRegistryEvents(events, Scope::CreateSubhuntScope((1 << WINSOCK_PARAMS) | (1 << WINSOCK_CUR_CATALOG)), + HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters", false, + false, true); + GetRegistryEvents(events, SCOPE(WINSOCK_CATALOG), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters\\AppId_Catalog", false, false); + events.push_back(std::make_pair(std::make_unique(L"System", 7045), SCOPE(LOGS_SECTION))); + + return events; + } + +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1546.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1546.cpp new file mode 100644 index 00000000..fb1cf179 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1546.cpp @@ -0,0 +1,346 @@ +#include "hunt/hunts/HuntT1546.h" + +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" +#include "util/configurations/Registry.h" +#include "util/filesystem/Filesystem.h" +#include "util/log/Log.h" +#include "util/processes/CheckLolbin.h" +#include "util/processes/ProcessUtils.h" + +#include "hunt/RegistryHunt.h" +#include "scan/FileScanner.h" +#include "scan/ProcessScanner.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define NETSH_HELPER 0 +#define ACCESSIBILITY_HIJACK 1 +#define ACCESSIBILITY_REPLACE 2 +#define APPCERT_DLL 3 +#define APPINIT_DLL 4 +#define APPLICATION_SHIM 5 +#define IFEO_HIJACK 6 +#define COM_HIJACK 7 + +namespace Hunts { + + HuntT1546::HuntT1546() : Hunt(L"T1546 - Event Triggered Execution") { + dwCategoriesAffected = (DWORD) Category::Configurations; + dwSourcesInvolved = (DWORD) DataSource::Registry; + dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::DefenseEvasion; + } + +#define ADD_FILE(file, ...) \ + if(files.find(file) != files.end()) { \ + files.at(file).emplace_back(__VA_ARGS__); \ + } else { \ + files.emplace(file, std::vector{ __VA_ARGS__ }); \ + } + + void HuntT1546::Subtechnique007(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(7, Netsh Helper DLL); + + SUBSECTION_INIT(NETSH_HELPER, Cursory); + for(auto& helperDllValue : CheckKeyValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Netsh", true, false)) { + if(FileScanner::PerformQuickScan(helperDllValue.ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ helperDllValue, RegistryDetectionType::FileReference }); + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + void HuntT1546::Subtechnique008(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(8, Accessibility Features); + + SUBSECTION_INIT(ACCESSIBILITY_HIJACK, Cursory); + for(auto& key : vAccessibilityBinaries) { + std::vector debugger{ CheckValues(HKEY_LOCAL_MACHINE, wsIFEO + key, + { + { L"Debugger", L"", false, CheckSzEmpty }, + }, + true, false) }; + for(auto& detection : debugger) { + CREATE_DETECTION(Certainty::Certain, + RegistryDetectionData{ detection.key, detection, + RegistryDetectionType::CommandReference, + detection.key.GetRawValue(detection.wValueName) }); + } + } + SUBSECTION_END(); + + SUBSECTION_INIT(ACCESSIBILITY_REPLACE, Normal); + for(auto name : vAccessibilityBinaries) { + FileSystem::File file{ FileSystem::File(L"C:\\Windows\\System32\\" + name) }; + + if(!file.IsMicrosoftSigned()) { + CREATE_DETECTION(Certainty::Certain, FileDetectionData{ file }); + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + void HuntT1546::Subtechnique009(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(9, AppCert DLLs); + + SUBSECTION_INIT(APPCERT_DLL, Cursory); + Registry::RegistryKey appcert_key{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Session Manager" }; + if(appcert_key.ValueExists(L"AppCertDLLs")) { + for(auto dll : *appcert_key.GetValue>(L"AppCertDLLs")) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ appcert_key, + RegistryValue{ appcert_key, L"AppCertDLLs", std::move(dll) }, + RegistryDetectionType::FileReference }); + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + void HuntT1546::Subtechnique010(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(10, AppInit DLLs); + + SUBSECTION_INIT(APPINIT_DLL, Cursory); + for(auto& detection : + CheckValues(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + { + { L"AppInit_Dlls", L"", false, CheckSzEmpty }, + { L"LoadAppInit_Dlls", 0, false, CheckDwordEqual }, + { L"RequireSignedAppInit_DLLs", 1, false, CheckDwordEqual }, + }, + true, false)) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ detection, detection.type == RegistryType::REG_DWORD_T ? + RegistryDetectionType::Configuration : + RegistryDetectionType::FileReference }); + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + void HuntT1546::Subtechnique011(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(11, Application Shimming); + + SUBSECTION_INIT(APPLICATION_SHIM, Normal); + auto& shims{ CheckKeyValues(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows " + L"NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB", + true, true) }; + ADD_ALL_VECTOR(shims, CheckKeyValues(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows " + L"NT\\CurrentVersion\\AppCompatFlags\\Custom", + true, true)); + + for(const auto& detection : shims) { + CREATE_DETECTION(Certainty::Strong, RegistryDetectionData{ detection, RegistryDetectionType::Unknown }); + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + void HuntT1546::Subtechnique012(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(12, Image File Execution Options Injection); + + SUBSECTION_INIT(IFEO_HIJACK, Normal); + auto IFEO = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File " + L"Execution Options" }; + for(auto name : IFEO.EnumerateSubkeyNames()) { + std::vector values{ CheckValues( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\" + name, + { + { L"Debugger", L"", false, CheckSzEmpty }, + { L"GlobalFlag", 0, false, [](DWORD d1, DWORD d2) { return !(d1 & 0x200); } }, + }, + true, false) }; + + for(const auto& detection : values) { + if(detection.wValueName == L"GlobalFlag") { + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Strong, RegistryDetectionData{ detection, RegistryDetectionType::FileReference }, + DetectionContext{ __name }, [detection]() { + detection.key.SetValue(L"GlobalFlag", std::get(detection.data) & ~0x200); + }); + } else { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ detection, RegistryDetectionType::FileReference }); + } + } + + RegistryKey subkey{ IFEO, name }; + auto GFlags = subkey.GetValue(L"GlobalFlag"); + if(GFlags && *GFlags & 0x200) { + auto name = subkey.GetName(); + name = name.substr(name.find_last_of(L"\\") + 1); + + std::vector values2{ CheckValues( + HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\" + name, + { + { L"ReportingMode", 0, false, CheckDwordEqual }, + { L"MonitorProcess", L"", false, CheckSzEmpty }, + }, + true, false) }; + + for(const auto& detection : values2) { + if(detection.type == RegistryType::REG_DWORD_T) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ detection, RegistryDetectionType::Configuration }); + } else { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ detection, RegistryDetectionType::CommandReference }); + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + void HuntT1546::Subtechnique015(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(15, Component Object Model Hijacking); + + SUBSECTION_INIT(COM_HIJACK, Intensive); + // Looks for T1546.015: Component Object Model Hijacking + if(Bluespawn::aggressiveness >= Aggressiveness::Intensive) { + std::map> files{}; + + for(auto key : CheckSubkeys(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID", true, true)) { + RegistryKey subkey{ key, L"InprocServer32" }; + if(subkey.Exists() && subkey.ValueExists(L"")) { + auto filename{ *subkey.GetValue(L"") }; + auto path{ FileSystem::SearchPathExecutable(filename) }; + if(path) { + ADD_FILE(*path, RegistryValue{ subkey, L"", std::move(filename) }); + } + } + subkey = { key, L"InprocServer" }; + if(subkey.Exists() && subkey.ValueExists(L"")) { + auto filename{ *subkey.GetValue(L"") }; + auto path{ FileSystem::SearchPathExecutable(filename) }; + if(path) { + ADD_FILE(*path, RegistryValue{ subkey, L"", std::move(filename) }); + } + } + if(key.ValueExists(L"InprocHandler32")) { + auto filename{ *key.GetValue(L"InprocHandler32") }; + auto path{ FileSystem::SearchPathExecutable(filename) }; + if(path) { + ADD_FILE(*path, RegistryValue{ key, L"InprocHandler32", std::move(filename) }); + } + } + if(key.ValueExists(L"InprocHandler")) { + auto filename{ *key.GetValue(L"InprocHandler") }; + auto path{ FileSystem::SearchPathExecutable(filename) }; + if(path) { + ADD_FILE(*path, RegistryValue{ key, L"InprocHandler", std::move(filename) }); + } + } + if(key.ValueExists(L"LocalServer")) { + auto filename{ *key.GetValue(L"LocalServer") }; + ADD_FILE(filename, RegistryValue{ key, L"LocalServer", *subkey.GetValue(L"") }); + } + subkey = { key, L"LocalServer32" }; + if(subkey.Exists() && subkey.ValueExists(L"")) { + auto filename{ *subkey.GetValue(L"") }; + ADD_FILE(filename, RegistryValue{ subkey, L"", *subkey.GetValue(L"") }); + } + if(subkey.Exists() && subkey.ValueExists(L"ServerExecutable")) { + auto filename{ *subkey.GetValue(L"ServerExecutable") }; + ADD_FILE(filename, + RegistryValue{ subkey, L"ServerExecutable", *subkey.GetValue(L"") }); + } + } + + for(auto pair : files) { + auto path{ pair.first }; + if(!FileSystem::CheckFileExists(path)) { + path = GetImagePathFromCommand(path); + if(!FileSystem::CheckFileExists(path)) { + continue; + } + } + + auto dll{ path.find(L".dll") != std::wstring::npos }; + if((dll && FileScanner::PerformQuickScan(path)) || + (!dll && ProcessScanner::PerformQuickScan(pair.first))) { + for(auto& value : pair.second) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ value, dll ? RegistryDetectionType::FileReference : + RegistryDetectionType::CommandReference }); + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1546::RunHunt(IN CONST Scope& scope) { + HUNT_INIT(); + + Subtechnique007(scope, detections); + Subtechnique008(scope, detections); + Subtechnique009(scope, detections); + Subtechnique010(scope, detections); + Subtechnique011(scope, detections); + Subtechnique012(scope, detections); + Subtechnique015(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1546::GetMonitoringEvents() { + std::vector, Scope>> events; + + // Looks for T1546.007: Netsh Helper DLL + GetRegistryEvents(events, SCOPE(NETSH_HELPER), HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Netsh", true, false, + false); + + // Looks for T1546.008: Accessibility Features + for(auto key : vAccessibilityBinaries) { + Registry::GetRegistryEvents(events, SCOPE(ACCESSIBILITY_HIJACK), HKEY_LOCAL_MACHINE, wsIFEO + key, true, + false, false); + } + + // Looks for T1546.009: AppCert DLLs + GetRegistryEvents(events, SCOPE(APPCERT_DLL), HKEY_LOCAL_MACHINE, + L"System\\CurrentControlSet\\Control\\Session Manager", true, false, false); + + // Looks for T1546.010: AppInit DLLs + GetRegistryEvents(events, SCOPE(APPINIT_DLL), HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", true, false, false); + + // Looks for T1546.011: Application Shimming + GetRegistryEvents(events, SCOPE(APPLICATION_SHIM), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB"); + GetRegistryEvents(events, SCOPE(APPLICATION_SHIM), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom"); + events.push_back( + std::make_pair(std::make_unique(FileSystem::Folder{ L"C:\\Windows\\AppPatch\\Custom" }), + SCOPE(APPLICATION_SHIM))); + events.push_back(std::make_pair(std::make_unique(FileSystem::Folder{ L"C:" + L"\\Windows\\AppPatch\\Custom\\" + L"Custom64" }), + SCOPE(APPLICATION_SHIM))); + + // Looks for T1546.012: Image File Execution Options Injection + Registry::GetRegistryEvents(events, SCOPE(IFEO_HIJACK), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", + true, false, true); + + // Looks for T1546.015: Component Object Model Hijacking + if(Bluespawn::aggressiveness >= Aggressiveness::Intensive) { + Registry::GetRegistryEvents(events, SCOPE(COM_HIJACK), HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID", + true, true, true); + } + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1547.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1547.cpp new file mode 100644 index 00000000..fd0f7086 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1547.cpp @@ -0,0 +1,336 @@ +#include "hunt/hunts/HuntT1547.h" + +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "hunt/RegistryHunt.h" +#include "scan/FileScanner.h" +#include "scan/ProcessScanner.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define RUN_KEY 0 +#define COMMAND_PROCESSOR 1 +#define STARTUP_FOLDER 2 +#define STARTUP_ITEMS 3 +#define AUTH_PACKAGE 4 +#define LSA_EXTENSION 5 +#define WINLOGON 6 +#define WINLOGON_NOTIFY 7 +#define SSP 8 +#define PORT_MON 9 + +namespace Hunts { + + HuntT1547::HuntT1547() : Hunt(L"T1547 - Boot or Logon Autostart Execution") { + dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files; + dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; + dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::PrivilegeEscalation; + + auto HKLMRun = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }; + auto HKLMRunServices = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices" }; + auto HKLMRunOnce = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }; + auto HKLMRunServicesOnce = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceServices" }; + auto HKLMRunOnceEx = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }; + auto HKLMRunServicesOnceEx = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceServicesEx" }; + auto HKLMExplorerRun = std::wstring{ L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }; + + RunKeys = { + HKLMRun, HKLMRunServices, HKLMRunOnce, HKLMRunServicesOnce, + HKLMRunOnceEx, HKLMRunServicesOnceEx, HKLMExplorerRun, + }; + } + + void HuntT1547::Subtechnique001(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(001, Registry Run Keys / Startup Folder); + + SUBSECTION_INIT(RUN_KEY, Cursory); + for(auto& key : RunKeys) { + for(auto& detection : CheckKeyValues(HKEY_LOCAL_MACHINE, key)) { + if(ProcessScanner::PerformQuickScan(std::get(detection.data))) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ detection, RegistryDetectionType::CommandReference }); + } + } + } + SUBSECTION_END(); + + SUBSECTION_INIT(COMMAND_PROCESSOR, Cursory); + for(auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Command Processor", + { { L"AutoRun", L"", false, CheckSzEmpty } })) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ detection, RegistryDetectionType::CommandReference }); + } + SUBSECTION_END(); + + SUBSECTION_INIT(STARTUP_FOLDER, Cursory); + for(auto& detection : CheckValues( + HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", + { { L"Startup", L"%USERPROFILE%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", + false, CheckSzEqual } })) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ detection, RegistryDetectionType::FolderReference }); + } + + for(auto& detection : + CheckValues(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", + { { L"Common Startup", L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", + false, CheckSzEqual } })) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ detection, RegistryDetectionType::FolderReference }); + } + SUBSECTION_END(); + + SUBSECTION_INIT(STARTUP_ITEMS, Cursory); + std::vector startup_directories = { FileSystem::Folder( + L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp") }; + auto userFolders = FileSystem::Folder{ L"C:\\Users" }.GetSubdirectories(1); + for(auto userFolder : userFolders) { + FileSystem::Folder folder{ userFolder.GetFolderPath() + L"\\AppData\\Roaming\\Microsoft\\Windows\\Start " + L"Menu\\Programs\\StartUp" }; + if(folder.GetFolderExists()) { + startup_directories.emplace_back(folder); + } + } + for(auto folder : startup_directories) { + LOG_VERBOSE(1, L"Scanning " << folder.GetFolderPath()); + for(auto value : folder.GetFiles(std::nullopt, -1)) { + if(FileScanner::PerformQuickScan(value.GetFilePath())) { + CREATE_DETECTION(Certainty::Weak, FileDetectionData{ value }); + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + void HuntT1547::Subtechnique002(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(002, Authentication Package); + + SUBSECTION_INIT(AUTH_PACKAGE, Cursory); + auto lsa = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; + for(auto PackageGroup : { L"Authentication Packages", L"Notification Packages" }) { + auto packages = lsa.GetValue>(PackageGroup); + if(packages) { + for(auto package : *packages) { + auto filepath = FileSystem::SearchPathExecutable(package + L".dll"); + + if(filepath && FileScanner::PerformQuickScan(*filepath)) { + // Can't use a macro since we need the shared_ptr of the detection + auto value{ Bluespawn::detections.AddDetection( + Detection{ RegistryDetectionData{ RegistryValue{ lsa, PackageGroup, std::move(package) }, + RegistryDetectionType::FileReference }, + DetectionContext{ __name } }, + Certainty::Moderate) }; + detections.emplace_back(value); + + // Since the security package is missing the dll extension, the scanner may not find the + // associated file + auto file{ Bluespawn::detections.AddDetection( + Detection{ FileDetectionData{ *filepath }, DetectionContext{ __name } }, Certainty::Weak) }; + detections.emplace_back(file); + + // Define the association here since the scanner may not pick up on it + file->info.AddAssociation(value, Association::Certain); + value->info.AddAssociation(file, Association::Certain); + } + } + } + } + SUBSECTION_END(); + + SUBSECTION_INIT(LSA_EXTENSION, Cursory); + auto lsaext = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\LsaExtensionConfig" }; + for(auto subkeyName : lsaext.EnumerateSubkeyNames()) { + if(subkeyName == L"Interfaces") { + for(auto subkey : RegistryKey{ lsaext, L"Interfaces" }.EnumerateSubkeys()) { + auto ext{ RegistryValue::Create(subkey, L"Extension") }; + if(ext && FileScanner::PerformQuickScan(ext->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *ext, RegistryDetectionType::FileReference }); + } + } + } else { + auto subkey = RegistryKey{ lsaext, subkeyName }; + auto exts = subkey.GetValue>(L"Extensions"); + if(exts) { + for(auto ext : *exts) { + if(FileScanner::PerformQuickScan(ext)) { + CREATE_DETECTION( + Certainty::Moderate, + RegistryDetectionData{ RegistryValue{ subkey, L"Extensions", std::move(ext) }, + RegistryDetectionType::FileReference }); + } + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + void HuntT1547::Subtechnique004(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(004, Winlogon Helper DLL); + + SUBSECTION_INIT(WINLOGON, Cursory); + // clang-format off + auto userinitRegex{ + L"(C:\\\\[Ww](INDOWS|indows)\\\\[Ss](YSTEM32|ystem32)\\\\)?[Uu](SERINIT|serinit)\\.(exe|EXE),?" }; + std::vector winlogons{ CheckValues(HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", { + { L"Shell", L"explorer\\.exe,?", false, CheckSzRegexMatch }, + { L"UserInit", userinitRegex, false, CheckSzRegexMatch } + }, true, true) }; + // clang-format on + + for(auto& detection : winlogons) { + // Moderate contextual certainty due to how rarely these values are used legitimately + CREATE_DETECTION(Certainty::Moderate, RegistryDetectionData{ detection, RegistryDetectionType::FileReference }); + } + SUBSECTION_END(); + + SUBSECTION_INIT(WINLOGON_NOTIFY, Cursory); + std::vector notifies{ CheckKeyValues( + HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify", true, true) }; + for(auto& notify : CheckSubkeys( + HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify", true, true)) { + if(notify.ValueExists(L"DllName")) { + notifies.emplace_back(RegistryValue{ notify, L"DllName", *notify.GetValue(L"DllName") }); + } + } + + for(auto& detection : notifies) { + // Weak contextual certainty due to how rarely these values are used legitimately + CREATE_DETECTION(Certainty::Weak, RegistryDetectionData{ detection, RegistryDetectionType::FileReference }); + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + void HuntT1547::Subtechnique005(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(005, Security Support Provider); + + SUBSECTION_INIT(SSP, Cursory); + RegistryKey lsa3{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; + RegistryKey lsa4{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa\\OSConfig" }; + + for(auto& key : { lsa3, lsa4 }) { + auto packages{ key.GetValue>(L"Security Packages") }; + if(packages) { + for(auto& package : *packages) { + if(package != L"\"\"") { + auto filepath = FileSystem::SearchPathExecutable(package + L".dll"); + + if(filepath && FileScanner::PerformQuickScan(*filepath)) { + // Can't use a macro since we need the shared_ptr of the detection + auto value{ Bluespawn::detections.AddDetection( + Detection{ RegistryDetectionData{ + RegistryValue{ key, L"Security Packages", std::move(package) }, + RegistryDetectionType::FileReference }, + DetectionContext{ __name } }, + Certainty::Moderate) }; + detections.emplace_back(value); + + // Since the security package is missing the dll extension, the scanner may not find the + // associated file + auto file{ Bluespawn::detections.AddDetection(Detection{ FileDetectionData{ *filepath }, + DetectionContext{ __name } }, + Certainty::Weak) }; + detections.emplace_back(file); + + // Define the association ourself + file->info.AddAssociation(value, Association::Certain); + value->info.AddAssociation(file, Association::Certain); + } + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + void HuntT1547::Subtechnique010(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(010, Port Monitors); + + SUBSECTION_INIT(PORT_MON, Cursory); + RegistryKey monitors{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors" }; + + for(auto monitor : monitors.EnumerateSubkeys()) { + if(monitor.ValueExists(L"Driver")) { + auto filepath{ FileSystem::SearchPathExecutable(monitor.GetValue(L"Driver").value()) }; + + if(filepath && FileScanner::PerformQuickScan(*filepath)) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *RegistryValue::Create(monitor, L"Driver"), + RegistryDetectionType::FileReference }); + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1547::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique001(scope, detections); + Subtechnique002(scope, detections); + Subtechnique004(scope, detections); + Subtechnique005(scope, detections); + Subtechnique010(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1547::GetMonitoringEvents() { + std::vector, Scope>> events; + + // Looks for T1547.001: Registry Run Keys / Startup Folder + for(auto key : RunKeys) { + GetRegistryEvents(events, SCOPE(RUN_KEY), HKEY_LOCAL_MACHINE, key); + } + GetRegistryEvents(events, SCOPE(COMMAND_PROCESSOR), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Command Processor"); + GetRegistryEvents(events, SCOPE(STARTUP_FOLDER), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"); + GetRegistryEvents(events, SCOPE(STARTUP_FOLDER), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); + auto userFolders = FileSystem::Folder(L"C:\\Users").GetSubdirectories(1); + for(auto userFolder : userFolders) { + FileSystem::Folder folder{ userFolder.GetFolderPath() + L"\\AppData\\Roaming\\Microsoft\\Windows\\Start " + L"Menu\\Programs\\StartUp" }; + if(folder.GetFolderExists()) { + events.push_back( + std::make_pair(std::make_unique(folder), SCOPE(STARTUP_ITEMS))); + } + } + + // Looks for T1547.002 (Authentication Package) and T1547.005 (Security Support Provider) + GetRegistryEvents(events, Scope::CreateSubhuntScope((1 << SSP) | (1 << AUTH_PACKAGE)), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Lsa", false, false); + GetRegistryEvents(events, Scope::CreateSubhuntScope((1 << SSP) | (1 << AUTH_PACKAGE)), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Lsa\\OSConfig", false, false); + GetRegistryEvents(events, SCOPE(LSA_EXTENSION), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\LsaExtensionConfig", false, false); + + // Looks for T1547.004: Winlogon Helper DLL + GetRegistryEvents(events, SCOPE(WINLOGON), HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"); + GetRegistryEvents(events, SCOPE(WINLOGON_NOTIFY), HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify", true, true, true); + + // Looks for T1547.010: Port Monitors + GetRegistryEvents(events, SCOPE(PORT_MON), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors", false, false, true); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1553.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1553.cpp new file mode 100644 index 00000000..509f700e --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1553.cpp @@ -0,0 +1,283 @@ +#include "hunt/hunts/HuntT1553.h" + +#include +#include +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" +#include "util/configurations/Registry.h" +#include "util/filesystem/Filesystem.h" +#include "util/log/Log.h" +#include "util/processes/ProcessUtils.h" + +#include "../resources/resource.h" +#include "hunt/RegistryHunt.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define SIPS 0 +#define PROVIDERS 1 +#define SIGNED 2 + +namespace Hunts { + + HuntT1553::HuntT1553() : Hunt(L"T1553 - Subvert Trust Controls") { + dwCategoriesAffected = (DWORD) Category::Configurations; + dwSourcesInvolved = (DWORD) DataSource::Registry; + dwTacticsUsed = (DWORD) Tactic::Persistence | (DWORD) Tactic::DefenseEvasion; + } + + std::wstring GetResource(DWORD identifier) { + auto hRsrcInfo = FindResourceW(nullptr, MAKEINTRESOURCE(identifier), L"textfile"); + if(!hRsrcInfo) { + return { nullptr, 0 }; + } + + auto hRsrc = LoadResource(nullptr, hRsrcInfo); + if(!hRsrc) { + return { nullptr, 0 }; + } + + return StringToWidestring( + { reinterpret_cast(LockResource(hRsrc)), SizeofResource(nullptr, hRsrcInfo) }); + } + + std::unordered_map>> + ParseResource(DWORD dwResourceID) { + auto resource{ GetResource(dwResourceID) }; + + std::unordered_map>> map{}; + + auto lines{ SplitStringW(resource, L"\n") }; + for(auto& line : lines) { + std::unordered_map> values; + auto type{ line.substr(0, line.find(L":")) }; + auto entries{ SplitStringW(line.substr(line.find(L":") + 1), L" ") }; + for(auto& entry : entries) { + auto parts{ SplitStringW(entry, L",") }; + auto path{ FileSystem::SearchPathExecutable(parts[1]) }; + if(path) { + values.emplace(parts[0], std::pair{ ToLowerCaseW(*path), parts[2] }); + } else { + values.emplace(parts[0], std::pair{ ToLowerCaseW(parts[1]), parts[2] }); + } + } + map.emplace(type, std::move(values)); + } + + return map; + } + + void HuntT1553::Subtechnique003(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(3, SIP and Trust Provider Hijacking); + + std::unordered_map>> files{}; + + // Verify SIPs + SUBSECTION_INIT(SIPS, Intensive); + auto goodSIP{ ParseResource(GoodSIP) }; + for(auto keypath : { L"SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0", + L"SOFTWARE\\WoW6432Node\\Microsoft\\Cryptography\\OID\\EncodingType 0" }) { + RegistryKey key{ HKEY_LOCAL_MACHINE, keypath }; + for(auto subkey : key.EnumerateSubkeyNames()) { + if(goodSIP.find(subkey) != goodSIP.end()) { + auto& entry{ goodSIP.at(subkey) }; + RegistryKey SIPType{ key, subkey }; + + for(auto GUID : SIPType.EnumerateSubkeyNames()) { + RegistryKey GUIDInfo{ SIPType, GUID }; + auto dll{ RegistryValue::Create(SIPType, L"Dll") }; + auto func{ RegistryValue::Create(SIPType, L"FuncName") }; + GUID = GUID.substr(1, GUID.length() - 2); + + if(entry.find(GUID) != entry.end()) { + auto& pair{ entry.at(GUID) }; + if(func && func->ToString() != pair.second) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *func, RegistryDetectionType::Configuration }); + } + + if(dll) { + if(files.find(dll->ToString()) == files.end()) { + files.emplace(dll->ToString(), + std::vector>{ + std::pair{ *dll, pair.first } }); + } else { + files.at(dll->ToString()) + .emplace_back(std::pair{ *dll, pair.first }); + } + } + } else { + auto message{ L"Nonstandard subject interface provider GUID " + GUID + L" (DLL: " + + dll->ToString() + L", Function: " + dll->ToString() + L")" }; + + if(func) { + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Strong, + RegistryDetectionData{ *func, RegistryDetectionType::Configuration }, + DetectionContext{ __name, std::nullopt, message }); + } + + if(dll) { + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Strong, + RegistryDetectionData{ *dll, RegistryDetectionType::FileReference }, + DetectionContext{ __name, std::nullopt, message }); + } + } + } + } + } + } + SUBSECTION_END(); + + // Verify trust providers + SUBSECTION_INIT(PROVIDERS, Intensive); + auto goodTrustProviders{ ParseResource(GoodTrustProviders) }; + for(auto keypath : { L"SOFTWARE\\Microsoft\\Cryptography\\Providers\\Trust", + L"SOFTWARE\\WoW6432Node\\Microsoft\\Cryptography\\Providers\\Trust" }) { + RegistryKey key{ HKEY_LOCAL_MACHINE, keypath }; + for(auto& subkey : key.EnumerateSubkeyNames()) { + if(goodTrustProviders.find(subkey) != goodTrustProviders.end()) { + auto& entry{ goodTrustProviders.at(subkey) }; + RegistryKey ProviderType{ key, subkey }; + + for(auto& GUID : ProviderType.EnumerateSubkeyNames()) { + RegistryKey GUIDInfo{ ProviderType, GUID }; + auto dll{ RegistryValue::Create(GUIDInfo, L"$DLL") }; + auto func{ RegistryValue::Create(GUIDInfo, L"$Function") }; + GUID = GUID.substr(1, GUID.length() - 2); + + if(entry.find(GUID) != entry.end()) { + auto& pair{ entry.at(GUID) }; + if(func && func->ToString() != pair.second) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *func, RegistryDetectionType::Configuration }); + } + + if(files.find(dll->ToString()) == files.end()) { + files.emplace(dll->ToString(), + std::vector>{ + std::pair{ *dll, pair.first } }); + } else { + files.at(dll->ToString()) + .emplace_back(std::pair{ *dll, pair.first }); + } + } else { + auto message{ L"Nonstandard trust provider GUID " + GUID + L" for " + subkey + L" (DLL: " + + dll->ToString() + L", Function: " + func->ToString() + L")" }; + if(func) { + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Strong, + RegistryDetectionData{ *func, RegistryDetectionType::Configuration }, + DetectionContext{ __name, std::nullopt, message }); + } + + if(dll) { + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Strong, + RegistryDetectionData{ *dll, RegistryDetectionType::FileReference }, + DetectionContext{ __name, std::nullopt, message }); + } + } + } + } + } + } + SUBSECTION_END(); + + // Verify collection of DLLs + for(auto& pair : files) { + auto dllpath{ FileSystem::SearchPathExecutable(pair.first) }; + if(!dllpath) { + auto message{ L"DLL " + pair.first + L" not found and may be a target for hijacking" }; + + // Assume the worst - if the DLL path isn't found, it's because there's a target process that WILL find it + for(auto& value : pair.second) { + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Weak, RegistryDetectionData{ value.first, RegistryDetectionType::FileReference }, + DetectionContext{ __name, std::nullopt, message }); + } + } else { + dllpath = ToLowerCaseW(*dllpath); + auto location{ dllpath->find(L"syswow64") }; + if(location != std::wstring::npos) { + dllpath->replace(dllpath->begin() + location, dllpath->begin() + location + 8, L"system32"); + } + for(auto& value : pair.second) { + if(dllpath != value.second && + (dllpath->length() >= value.second.length() && + dllpath->substr(dllpath->length() - value.second.length()) != value.second)) { + auto message{ L"Path for dll " + *dllpath + L" does not match " + value.second + + L" and may have been hijacked" }; + CREATE_DETECTION_WITH_CONTEXT( + Certainty::Weak, RegistryDetectionData{ value.first, RegistryDetectionType::FileReference }, + DetectionContext{ __name, std::nullopt, message }); + } + } + } + } + + // Ensure only Microsoft signed DLLs are used here + SUBSECTION_INIT(SIGNED, Intensive); + std::vector keypaths{ L"SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0", + L"SOFTWARE\\Microsoft\\Cryptography\\Providers\\Trust" }; + for(auto keypath : keypaths) { + for(auto key : CheckSubkeys(HKEY_LOCAL_MACHINE, keypath, true, false)) { + std::queue keys{}; + keys.emplace(key); + + while(keys.size()) { + auto check{ keys.front() }; + keys.pop(); + + for(auto val : check.EnumerateValues()) { + auto type{ check.GetValueType(val) }; + if(type == RegistryType::REG_SZ_T || type == RegistryType::REG_EXPAND_SZ_T) { + auto path{ FileSystem::SearchPathExecutable(*check.GetValue(val)) }; + if(path) { + if(!FileSystem::File(*path).IsMicrosoftSigned()) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *RegistryValue::Create(check, val), + RegistryDetectionType::FileReference }); + } + } else if(ToLowerCaseW(val).find(L"dll") != std::wstring::npos) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *RegistryValue::Create(check, val), + RegistryDetectionType::FileReference }); + } + } + } + for(auto subkey : check.EnumerateSubkeys()) { + keys.emplace(subkey); + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1553::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique003(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1553::GetMonitoringEvents() { + std::vector, Scope>> events; + + Registry::GetRegistryEvents(events, Scope::CreateSubhuntScope((1 << SIPS) | (1 << SIGNED)), HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0", true, false, true); + Registry::GetRegistryEvents(events, Scope::CreateSubhuntScope((1 << PROVIDERS) | (1 << SIGNED)), + HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography\\Providers\\Trust", true, + false, true); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1562.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1562.cpp new file mode 100644 index 00000000..e512b212 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1562.cpp @@ -0,0 +1,86 @@ +#include "hunt/hunts/HuntT1562.h" + +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "scan/FileScanner.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define REGISTRY_FIREWALL 0 + +namespace Hunts { + + HuntT1562::HuntT1562() : Hunt(L"T1562 - Impair Defenses") { + dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Network; + dwSourcesInvolved = (DWORD) DataSource::Registry; + dwTacticsUsed = (DWORD) Tactic::DefenseEvasion; + } + + void HuntT1562::Subtechnique004(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(004, Disable or Modify System Firewall); + + SUBSECTION_INIT(REGISTRY_FIREWALL, Normal); + RegistryKey DomainProfile{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters" + L"\\FirewallPolicy\\DomainProfile" }; + RegistryKey StandardProfile{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameter" + L"s\\FirewallPolicy\\StandardProfile" }; + RegistryKey PublicProfile{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters" + L"\\FirewallPolicy\\PublicProfile" }; + + for(auto key : { DomainProfile, StandardProfile, PublicProfile }) { + RegistryKey allowedapps{ key, L"AuthorizedApplications\\List" }; + if(allowedapps.Exists()) { + for(auto ProgramException : allowedapps.EnumerateValues()) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *RegistryValue::Create(allowedapps, ProgramException), + RegistryDetectionType::Configuration }); + if(FileScanner::PerformQuickScan(ProgramException)) { + CREATE_DETECTION(Certainty::Weak, FileDetectionData{ ProgramException }); + } + } + } + + auto ports = RegistryKey{ key, L"GloballyOpenPorts\\List" }; + if(ports.Exists()) { + for(auto PortsException : ports.EnumerateValues()) { + CREATE_DETECTION(Certainty::Strong, + RegistryDetectionData{ *RegistryValue::Create(ports, PortsException), + RegistryDetectionType::Configuration }); + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1562::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique004(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1562::GetMonitoringEvents() { + std::vector, Scope>> events; + + Registry::GetRegistryEvents(events, SCOPE(REGISTRY_FIREWALL), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\Do" + L"mainProfile", + false, false, true); + Registry::GetRegistryEvents(events, SCOPE(REGISTRY_FIREWALL), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\St" + L"andardProfile", + false, false, true); + Registry::GetRegistryEvents(events, SCOPE(REGISTRY_FIREWALL), HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\Pu" + L"blicProfile", + false, false, true); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-win-client/src/hunt/hunts/HuntT1569.cpp b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1569.cpp new file mode 100644 index 00000000..1dca6a76 --- /dev/null +++ b/BLUESPAWN-win-client/src/hunt/hunts/HuntT1569.cpp @@ -0,0 +1,72 @@ +#include "hunt/hunts/HuntT1569.h" + +#include "util/Utils.h" +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" +#include "util/processes/CheckLolbin.h" +#include "util/processes/ProcessUtils.h" + +#include "hunt/RegistryHunt.h" +#include "scan/FileScanner.h" +#include "scan/ProcessScanner.h" +#include "user/bluespawn.h" + +using namespace Registry; + +#define REGISTRY_SERVICES 0 + +namespace Hunts { + HuntT1569::HuntT1569() : Hunt(L"T1569 - Service Execution") { + dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Files | (DWORD) Category::Processes; + dwSourcesInvolved = (DWORD) DataSource::Registry | (DWORD) DataSource::FileSystem; + dwTacticsUsed = (DWORD) Tactic::Execution; + } + + void HuntT1569::Subtechnique002(IN CONST Scope& scope, OUT std::vector>& detections) { + SUBTECHNIQUE_INIT(2, Service Execution); + + SUBSECTION_INIT(REGISTRY_SERVICES, Normal); + auto services = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services" }; + + for(auto service : services.EnumerateSubkeys()) { + if(service.GetValue(L"Type") >= 0x10u) { + auto cmd{ Registry::RegistryValue::Create(service, L"ImagePath") }; + if(ProcessScanner::PerformQuickScan(cmd->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *cmd, RegistryDetectionType::CommandReference }); + } + + RegistryKey subkey = RegistryKey{ service, L"Parameters" }; + for(auto regkey : { service, subkey }) { + auto svcdll{ Registry::RegistryValue::Create(regkey, L"ServiceDll") }; + if(svcdll && FileScanner::PerformQuickScan(svcdll->ToString())) { + CREATE_DETECTION(Certainty::Moderate, + RegistryDetectionData{ *svcdll, RegistryDetectionType::FileReference }); + } + } + } + } + SUBSECTION_END(); + + SUBTECHNIQUE_END(); + } + + std::vector> HuntT1569::RunHunt(const Scope& scope) { + HUNT_INIT(); + + Subtechnique002(scope, detections); + + HUNT_END(); + } + + std::vector, Scope>> HuntT1569::GetMonitoringEvents() { + std::vector, Scope>> events; + + // Looks for T1569.002: Service Execution + GetRegistryEvents(events, SCOPE(REGISTRY_SERVICES), HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services", + false, false, true); + + return events; + } +} // namespace Hunts diff --git a/BLUESPAWN-client/src/mitigation/Mitigation.cpp b/BLUESPAWN-win-client/src/mitigation/Mitigation.cpp similarity index 94% rename from BLUESPAWN-client/src/mitigation/Mitigation.cpp rename to BLUESPAWN-win-client/src/mitigation/Mitigation.cpp index b9867fe9..84e939b4 100644 --- a/BLUESPAWN-client/src/mitigation/Mitigation.cpp +++ b/BLUESPAWN-win-client/src/mitigation/Mitigation.cpp @@ -1,6 +1,5 @@ #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "reaction/Reaction.h" Mitigation::Mitigation(const std::wstring& name, const std::wstring& description, const std::wstring& software, SoftwareAffected category, MitigationSeverity severity) : diff --git a/BLUESPAWN-client/src/mitigation/MitigationRegister.cpp b/BLUESPAWN-win-client/src/mitigation/MitigationRegister.cpp similarity index 76% rename from BLUESPAWN-client/src/mitigation/MitigationRegister.cpp rename to BLUESPAWN-win-client/src/mitigation/MitigationRegister.cpp index 2313e7db..6d42b503 100644 --- a/BLUESPAWN-client/src/mitigation/MitigationRegister.cpp +++ b/BLUESPAWN-win-client/src/mitigation/MitigationRegister.cpp @@ -15,7 +15,7 @@ void MitigationRegister::AuditMitigations(SecurityLevel securityLevel) { io.InformUser(vRegisteredMitigations[i]->getName() + L" is NOT configured."); } else { - LOG_INFO(vRegisteredMitigations[i]->getName() + L" is enabled."); + LOG_INFO(1, vRegisteredMitigations[i]->getName() + L" is enabled."); io.InformUser(vRegisteredMitigations[i]->getName() + L" is enabled."); } } @@ -29,15 +29,15 @@ void MitigationRegister::EnforceMitigations(SecurityLevel securityLevel, bool bF for(int i = 0; i < vRegisteredMitigations.size(); i++) { if (vRegisteredMitigations[i]->MitigationApplies()) { if (!vRegisteredMitigations[i]->MitigationIsEnforced(securityLevel)) { - LOG_WARNING(vRegisteredMitigations[i]->getName() + L" is NOT configured."); + LOG_INFO(2, vRegisteredMitigations[i]->getName() + L" is NOT configured."); if (bForceEnforce) { - LOG_WARNING(L"Enforcing mitigation for " + vRegisteredMitigations[i]->getName()); + LOG_INFO(1, L"Enforcing mitigation for " + vRegisteredMitigations[i]->getName()); io.InformUser(L"Enforcing mitigation for " + vRegisteredMitigations[i]->getName()); if (vRegisteredMitigations[i]->EnforceMitigation(securityLevel)) { iEnforcedCount++; } else { - LOG_WARNING(L"Unable to enforce mitigation for " + vRegisteredMitigations[i]->getName()); + LOG_ERROR(L"Unable to enforce mitigation for " + vRegisteredMitigations[i]->getName()); io.InformUser(L"Unable to enforce mitigation for " + vRegisteredMitigations[i]->getName()); } } @@ -48,18 +48,18 @@ void MitigationRegister::EnforceMitigations(SecurityLevel securityLevel, bool bF dwChoice = io.GetUserConfirm(L"Would you like to enforce this (y/n)"); } if (dwChoice > 0) { - LOG_INFO(L"Enforcing " + vRegisteredMitigations[i]->getName()); + LOG_INFO(1, L"Enforcing " + vRegisteredMitigations[i]->getName()); io.InformUser(L"Enforcing " + vRegisteredMitigations[i]->getName()); if (vRegisteredMitigations[i]->EnforceMitigation(securityLevel)) { iEnforcedCount++; } else { - LOG_WARNING(L"Unable to enforce mitigation for " + vRegisteredMitigations[i]->getName()); + LOG_ERROR(L"Unable to enforce mitigation for " + vRegisteredMitigations[i]->getName()); io.InformUser(L"Unable to enforce mitigation for " + vRegisteredMitigations[i]->getName()); } } else { - LOG_INFO(L"User chose not to enforce " + vRegisteredMitigations[i]->getName()); + LOG_INFO(2, L"User chose not to enforce " + vRegisteredMitigations[i]->getName()); iMitigationsIgnored++; } } @@ -67,11 +67,11 @@ void MitigationRegister::EnforceMitigations(SecurityLevel securityLevel, bool bF } } if (iMitigationsIgnored == 0) { - LOG_INFO(L"Enforced " + std::to_wstring(vRegisteredMitigations.size()) + L" Mitigations making " + std::to_wstring(iEnforcedCount) + L" changes."); + LOG_INFO(2, L"Enforced " + std::to_wstring(vRegisteredMitigations.size()) + L" Mitigations making " + std::to_wstring(iEnforcedCount) + L" changes."); io.InformUser(L"Enforced " + std::to_wstring(vRegisteredMitigations.size()) + L" Mitigations making " + std::to_wstring(iEnforcedCount) + L" changes."); } else { - LOG_INFO(L"Enforced " + std::to_wstring(vRegisteredMitigations.size() - iMitigationsIgnored) + L" Mitigations making " + std::to_wstring(iEnforcedCount) + + LOG_INFO(2, L"Enforced " + std::to_wstring(vRegisteredMitigations.size() - iMitigationsIgnored) + L" Mitigations making " + std::to_wstring(iEnforcedCount) + L" changes. Chose not to enforce " + std::to_wstring(iMitigationsIgnored) + L" Mitigations."); io.InformUser(L"Enforced " + std::to_wstring(vRegisteredMitigations.size() - iMitigationsIgnored) + L" Mitigations making " + std::to_wstring(iEnforcedCount) + L" changes. Chose not to enforce " + std::to_wstring(iMitigationsIgnored) + L" Mitigations."); diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1025.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1025.cpp similarity index 94% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1025.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1025.cpp index c8492d41..97e907a9 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1025.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1025.cpp @@ -37,13 +37,13 @@ namespace Mitigations{ } bool MitigateM1025::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); return CheckLSARunAsPPL(false); } bool MitigateM1025::EnforceMitigation(SecurityLevel level) { - LOG_INFO("Enforcing Mitigation for " << name); + LOG_INFO(1, "Enforcing Mitigation for " << name); return CheckLSARunAsPPL(level >= SecurityLevel::Medium); } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1028-WFW.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1028-WFW.cpp similarity index 98% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1028-WFW.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1028-WFW.cpp index a07222be..4539791b 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1028-WFW.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1028-WFW.cpp @@ -20,7 +20,7 @@ namespace Mitigations { ) {} bool MitigateM1028WFW::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto DomainProfile = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\DomainProfile" }; auto StandardProfile = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\StandardProfile" }; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1035-RDP.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1035-RDP.cpp similarity index 91% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1035-RDP.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1035-RDP.cpp index 239bac13..7669f792 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1035-RDP.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1035-RDP.cpp @@ -15,7 +15,7 @@ namespace Mitigations { ) {} bool MitigateM1035RDP::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO(L"Checking for presence of " << name); + LOG_INFO(1, L"Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp" }; if (key.ValueExists(L"UserAuthentication")) { @@ -30,7 +30,7 @@ namespace Mitigations { } bool MitigateM1035RDP::EnforceMitigation(SecurityLevel level) { - LOG_INFO(L"Enforcing mitigation " << name); + LOG_INFO(1, L"Enforcing mitigation " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp" }; if (key.SetValue(L"UserAuthentication", 1)) { LOG_VERBOSE(1, "NLA successfully enabled for RDP."); diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp index f7f2da8d..39c78e95 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp @@ -23,7 +23,7 @@ namespace Mitigations { ) {} bool MitigateM1042LLMNR::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient" }; std::wstring value = L"EnableMulticast"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp index f0ccad5a..74c01cad 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp @@ -23,7 +23,7 @@ namespace Mitigations { ) {} bool MitigateM1042NBT::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto base = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NetBT\\Parameters\\Interfaces" }; std::wstring value = L"NetbiosOptions"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp similarity index 96% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp index a17efb27..bbe51772 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp @@ -23,7 +23,7 @@ namespace Mitigations { ) {} bool MitigateM1042WSH::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); for (auto& detection : CheckValues(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows Script Host\\Settings", { { L"Enabled", 0, true, CheckDwordEqual }, diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1047.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1047.cpp similarity index 89% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1047.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1047.cpp index eb169ff3..9303676e 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1047.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1047.cpp @@ -2,7 +2,6 @@ #include "util/log/Log.h" #include "util/configurations/Registry.h" -#include "reaction/Log.h" #include "util/eventlogs/EventLogs.h" namespace Mitigations { @@ -43,8 +42,9 @@ namespace Mitigations { LOG_VERBOSE(1, L"Sysmon is not installed."); enforced = false; } - if (sysmon.Exists() && sysmon.GetValue(L"Start") >= static_cast(3) || - sysmon64.Exists() && sysmon64.GetValue(L"Start") >= static_cast(3)) { + if (sysmon.Exists() && *sysmon.GetValue(L"Start") >= 3UL || + sysmon64.Exists() && *sysmon64.GetValue(L"Start") >= 3UL) { + LOG_VERBOSE(1, L"Sysmon is set to manual or disabled."); enforced = false; } @@ -55,7 +55,7 @@ namespace Mitigations { LOG_VERBOSE(1, L"Windows Event Log Service is not installed."); enforced = false; } - else if (eventLogService.GetValue(L"Start") >= static_cast(3)) { + else if (eventLogService.GetValue(L"Start") >= 3u) { LOG_VERBOSE(1, L"Windows Event Log Service is set to manual or disabled."); enforced = false; } @@ -81,11 +81,11 @@ namespace Mitigations { LOG_VERBOSE(1, L"Sysmon is not installed."); enforced = false; } - if (sysmon.Exists() && sysmon.GetValue(L"Start") >= 3ul) { + if (sysmon.Exists() && *sysmon.GetValue(L"Start") >= 3UL) { LOG_VERBOSE(1, L"Attempting to set SYSTEM\\CurrentControlSet\\Services\\Sysmon\\Start to 2."); enforced &= sysmon.SetValue(L"Start", 2); } - if (sysmon64.Exists() && sysmon64.GetValue(L"Start") >= 3ul) { + if (sysmon64.Exists() && *sysmon64.GetValue(L"Start") >= 3UL) { LOG_VERBOSE(1, L"Attempting to set SYSTEM\\CurrentControlSet\\Services\\Sysmon64\\Start to 2."); enforced &= sysmon64.SetValue(L"Start", 2); } @@ -96,7 +96,7 @@ namespace Mitigations { LOG_VERBOSE(1, L"Windows Event Log Service is not installed."); enforced = false; } - else if (eventLogService.GetValue(L"Start") >= 3ul) { + else if (*eventLogService.GetValue(L"Start") >= 3UL) { LOG_VERBOSE(1, L"Attempting to set SYSTEM\\CurrentControlSet\\Services\\EventLog\\Start to 2."); enforced &= eventLogService.SetValue(L"Start", 2); } @@ -118,4 +118,4 @@ namespace Mitigations { bool MitigateM1047::MitigationApplies() { return true; } -} \ No newline at end of file +} diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1054-RDP.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1054-RDP.cpp similarity index 93% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1054-RDP.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1054-RDP.cpp index 8e165ab8..1fb8741e 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1054-RDP.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1054-RDP.cpp @@ -16,7 +16,7 @@ namespace Mitigations { ) {} bool MitigateM1054RDP::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services" }; std::wstring value = L"fDisableForcibleLogoff"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1054-WSC.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1054-WSC.cpp similarity index 98% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateM1054-WSC.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1054-WSC.cpp index 6e3ad0b5..108df917 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1054-WSC.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateM1054-WSC.cpp @@ -18,7 +18,7 @@ namespace Mitigations { ) {} bool MitigateM1054WSC::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto WindowsSecurityCenter = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Security Center" }; auto WindowsSecurityCenterWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Security Center" }; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV1093.cpp similarity index 93% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV1093.cpp index f68c3610..e9083f85 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV1093.cpp @@ -22,7 +22,7 @@ namespace Mitigations { auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa" }; auto data = key.GetValue(L"RestrictAnonymous"); if(!data || !*data){ - LOG_INFO(L"[" + name + L"] RestrictAnonymous value is not set to 1"); + LOG_INFO(1, L"[" + name + L"] RestrictAnonymous value is not set to 1"); return false; } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1153.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV1153.cpp similarity index 94% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV1153.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV1153.cpp index 06aa0378..6f812b34 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1153.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV1153.cpp @@ -37,13 +37,13 @@ namespace Mitigations{ } bool MitigateV1153::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); return CheckLMCompatibilityLevel(false); } bool MitigateV1153::EnforceMitigation(SecurityLevel level) { - LOG_INFO("Enforcing Mitigation for " << name); + LOG_INFO(1, "Enforcing Mitigation for " << name); return CheckLMCompatibilityLevel(level >= SecurityLevel::Medium); } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3338.cpp similarity index 95% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3338.cpp index 3680429d..a97d3d2f 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3338.cpp @@ -20,7 +20,7 @@ namespace Mitigations { ) {} bool MitigateV3338::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\LanManServer\\Parameters" }; if(key.ValueExists(L"NullSessionPipes")){ @@ -38,7 +38,7 @@ namespace Mitigations { } bool MitigateV3338::EnforceMitigation(SecurityLevel level) { - LOG_INFO("Enforcing Mitigation for " << name); + LOG_INFO(1, "Enforcing Mitigation for " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\LanManServer\\Parameters" }; if(key.ValueExists(L"NullSessionPipes")){ diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3340.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3340.cpp similarity index 96% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV3340.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3340.cpp index 18c55b81..8ad87e9e 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3340.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3340.cpp @@ -20,7 +20,7 @@ namespace Mitigations { ) {} bool MitigateV3340::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\LanManServer\\Parameters" }; std::wstring value = L"NullSessionShares"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3344.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3344.cpp similarity index 93% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV3344.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3344.cpp index c9066552..55b17d75 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3344.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3344.cpp @@ -35,13 +35,13 @@ namespace Mitigations{ } bool MitigateV3344::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); return LimitBlankPasswordUse(false); } bool MitigateV3344::EnforceMitigation(SecurityLevel level) { - LOG_INFO("Enforcing Mitigation for " << name); + LOG_INFO(1, "Enforcing Mitigation for " << name); return LimitBlankPasswordUse(level >= SecurityLevel::Medium); } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3379.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3379.cpp similarity index 93% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV3379.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3379.cpp index dee636f7..cf67d481 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3379.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3379.cpp @@ -35,13 +35,13 @@ namespace Mitigations{ } bool MitigateV3379::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); return CheckNoLMHash(false); } bool MitigateV3379::EnforceMitigation(SecurityLevel level) { - LOG_INFO("Enforcing Mitigation for " << name); + LOG_INFO(1, "Enforcing Mitigation for " << name); return CheckNoLMHash(level >= SecurityLevel::Medium); } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3479.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3479.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV3479.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3479.cpp index 1ca89155..85ac74df 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3479.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV3479.cpp @@ -22,7 +22,7 @@ namespace Mitigations { ) {} bool MitigateV3479::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager" }; std::wstring value = L"SafeDllSearchMode"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63597.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63597.cpp similarity index 96% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV63597.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63597.cpp index 67236308..ca071416 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63597.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63597.cpp @@ -22,7 +22,7 @@ namespace Mitigations { ) {} bool MitigateV63597::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; std::wstring value = L"LocalAccountTokenFilterPolicy"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63687.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63687.cpp similarity index 94% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV63687.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63687.cpp index 72fd312a..d60b727f 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63687.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63687.cpp @@ -23,7 +23,7 @@ namespace Mitigations { ) {} bool MitigateV63687::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" }; std::wstring value = L"CachedLogonsCount"; @@ -33,7 +33,7 @@ namespace Mitigations { return false; } - if(key.GetValue(value) > static_cast(1)){ + if(key.GetValue(value) > 1u){ LOG_VERBOSE(1, L"Value for " << value << L" is greater than 1."); return false; } @@ -54,4 +54,4 @@ namespace Mitigations { bool MitigateV63687::MitigationApplies(){ return true; } -} \ No newline at end of file +} diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63753.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63753.cpp similarity index 96% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV63753.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63753.cpp index c5a227e3..e5a1a49f 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63753.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63753.cpp @@ -20,7 +20,7 @@ namespace Mitigations { ) {} bool MitigateV63753::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; std::wstring value = L"DisableDomainCreds"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63817.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63817.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV63817.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63817.cpp index 871428d4..f9db5f26 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63817.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63817.cpp @@ -22,7 +22,7 @@ namespace Mitigations { ) {} bool MitigateV63817::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; std::wstring value = L"FilterAdministratorToken"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63825.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63825.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV63825.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63825.cpp index b95c1856..51d77b81 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63825.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63825.cpp @@ -22,7 +22,7 @@ namespace Mitigations { ) {} bool MitigateV63825::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; std::wstring value = L"EnableInstallerDetection"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63829.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63829.cpp similarity index 96% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV63829.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63829.cpp index 73bf918d..0615c6dd 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63829.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV63829.cpp @@ -21,7 +21,7 @@ namespace Mitigations { ) {} bool MitigateV63829::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; std::wstring value = L"EnableLUA"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV71769.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV71769.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV71769.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV71769.cpp index a7eb7bc5..eabb3123 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV71769.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV71769.cpp @@ -21,7 +21,7 @@ namespace Mitigations { ) {} bool MitigateV71769::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; std::wstring value = L"RestrictRemoteSAM"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV72753.cpp similarity index 91% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV72753.cpp index 20f2bcb0..22b26715 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV72753.cpp @@ -21,7 +21,7 @@ namespace Mitigations { ) {} bool MitigateV72753::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest" }; std::wstring value = L"UseLogonCredential"; @@ -36,7 +36,7 @@ namespace Mitigations { if(key.GetValue(value) == 1){ if(level == SecurityLevel::Low){ - LOG_INFO(L"[" + name + L"] Mitigation is not being enforced due to low security level."); + LOG_INFO(1, L"[" + name + L"] Mitigation is not being enforced due to low security level."); return true; } return false; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73511.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73511.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV73511.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73511.cpp index e426c744..a79e79b2 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73511.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73511.cpp @@ -21,7 +21,7 @@ namespace Mitigations { ) {} bool MitigateV73511::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit" }; std::wstring value = L"ProcessCreationIncludeCmdLine_Enabled"; diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73519.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73519.cpp similarity index 95% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV73519.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73519.cpp index 32ca8bf4..6d1530b7 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73519.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73519.cpp @@ -37,13 +37,13 @@ namespace Mitigations{ } bool MitigateV73519::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); return CheckSMBv1(false); } bool MitigateV73519::EnforceMitigation(SecurityLevel level) { - LOG_INFO("Enforcing Mitigation for " << name); + LOG_INFO(1, "Enforcing Mitigation for " << name); return CheckSMBv1(level >= SecurityLevel::Medium); } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73585.cpp b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73585.cpp similarity index 97% rename from BLUESPAWN-client/src/mitigation/mitigations/MitigateV73585.cpp rename to BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73585.cpp index 94d152de..278b2909 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73585.cpp +++ b/BLUESPAWN-win-client/src/mitigation/mitigations/MitigateV73585.cpp @@ -22,7 +22,7 @@ namespace Mitigations { ) {} bool MitigateV73585::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); + LOG_INFO(1, "Checking for presence of " << name); auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows\\Installer" }; std::wstring value = L"AlwaysInstallElevated"; diff --git a/BLUESPAWN-client/src/monitor/Event.cpp b/BLUESPAWN-win-client/src/monitor/Event.cpp similarity index 73% rename from BLUESPAWN-client/src/monitor/Event.cpp rename to BLUESPAWN-win-client/src/monitor/Event.cpp index ef7baea1..748fe18c 100644 --- a/BLUESPAWN-client/src/monitor/Event.cpp +++ b/BLUESPAWN-win-client/src/monitor/Event.cpp @@ -1,28 +1,18 @@ #include "monitor/Event.h" -#include "reaction/Log.h" #include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" #include "monitor/EventListener.h" #include "user/bluespawn.h" Event::Event(EventType type) : type(type) {} -void Event::AddCallback(const std::function& callback) { - callbacks.push_back(callback); -} - -std::wstring wsCallbackExceptionMessage{ L"Error occured while executing a callback for a monitor event" }; - -void RunCallback(const HuntEnd& callback){ - __try{ - callback(); - } __except(EXCEPTION_EXECUTE_HANDLER){ - Bluespawn::io.InformUser(wsCallbackExceptionMessage, ImportanceLevel::HIGH); - } +void Event::AddCallback(const std::function& callback, IN CONST Scope& scope) { + callbacks.push_back(std::make_pair(callback, scope)); } void Event::RunCallbacks() const { - for(auto& callback : callbacks){ - RunCallback(callback); + for(auto pair : callbacks){ + pair.first(pair.second); } } @@ -173,33 +163,34 @@ const FileSystem::Folder& FileEvent::GetFolder() const { } namespace Registry { - std::vector> GetRegistryEvents(HKEY hkHive, const std::wstring& path, bool WatchWow64, bool WatchUsers, bool WatchSubkeys){ - auto& base = std::make_shared(RegistryKey{ hkHive, path }, WatchSubkeys); - std::unordered_set> vKeys{}; - if(base->GetKey().Exists()){ - vKeys.emplace(base); - } + void GetRegistryEvents(OUT std::vector, Scope>>& dest, IN CONST Scope& scope, + IN HKEY hkHive, IN CONST std::wstring& path, IN bool WatchWow64 OPTIONAL, + IN bool WatchUsers OPTIONAL, IN bool WatchSubkeys OPTIONAL){ + std::unordered_set vKeys{ RegistryKey{ hkHive, path } }; if(WatchWow64){ - std::shared_ptr Wow64Key{ std::make_shared(RegistryKey{ HKEY(hkHive), path, true }, WatchSubkeys) }; - if(Wow64Key->GetKey().Exists() && Wow64Key->GetKey().GetName().find(L"WOW6432Node") != std::wstring::npos){ - vKeys.emplace(std::static_pointer_cast(Wow64Key)); + RegistryKey Wow64Key{ HKEY(hkHive), path, true }; + if(Wow64Key.Exists()){ + vKeys.emplace(Wow64Key); } } if(WatchUsers){ - std::vector hkUserHives{ RegistryKey{HKEY_USERS}.EnumerateSubkeys() }; + auto hkUserHives{ RegistryKey{ HKEY_USERS }.EnumerateSubkeys() }; for(auto& hive : hkUserHives){ - std::shared_ptr key{ std::make_shared(RegistryKey{ HKEY(hive), path, false }, WatchSubkeys) }; - if(key->GetKey().Exists()){ - vKeys.emplace(std::static_pointer_cast(key)); + RegistryKey key{ HKEY(hive), path, false }; + if(key.Exists()){ + vKeys.emplace(key); } if(WatchWow64){ - std::shared_ptr Wow64Key{ std::make_shared(RegistryKey{ HKEY(hive), path, true }, WatchSubkeys) }; - if(Wow64Key->GetKey().Exists() && Wow64Key->GetKey().GetName().find(L"WOW6432Node") != std::wstring::npos){ - vKeys.emplace(std::static_pointer_cast(Wow64Key)); + RegistryKey Wow64Key{ HKEY(hive), path, true }; + if(Wow64Key.Exists()){ + vKeys.emplace(Wow64Key); } } } } - return { vKeys.begin(), vKeys.end() }; + + for(auto& key : vKeys){ + dest.emplace_back(std::make_pair(std::make_unique(key), scope)); + } } -} \ No newline at end of file +} diff --git a/BLUESPAWN-client/src/monitor/EventListener.cpp b/BLUESPAWN-win-client/src/monitor/EventListener.cpp similarity index 95% rename from BLUESPAWN-client/src/monitor/EventListener.cpp rename to BLUESPAWN-win-client/src/monitor/EventListener.cpp index 1962782d..8cb7869f 100644 --- a/BLUESPAWN-client/src/monitor/EventListener.cpp +++ b/BLUESPAWN-win-client/src/monitor/EventListener.cpp @@ -1,14 +1,21 @@ #include "monitor/EventListener.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" #include "util/log/Log.h" +#include "util/ThreadPool.h" void EventListener::SubEventListener::HandleEventNotify(HANDLE hEvent){ + std::vector> functions{}; if(map.find(hEvent) != map.end()){ - for(auto& func : map.at(hEvent)){ - func(); + for(auto& f : map.at(hEvent)){ + functions.emplace_back(f); } } + if(functions.size()){ + ThreadPool::GetInstance().EnqueueTask([functions](){ + std::for_each(functions.begin(), functions.end(), [](auto& f){ f(); }); + }); + } } void EventListener::SubEventListener::ListenForEvents(){ @@ -111,6 +118,7 @@ bool EventListener::SubEventListener::TrySubscribe( // Set the manager event since we're writing to map SetEvent(hManager); + LeaveCriticalSection(hSection); auto status{ WaitForSingleObject(hManagerResponse, 1000) }; // Ensure the manager event has been processed before making changes @@ -120,11 +128,14 @@ bool EventListener::SubEventListener::TrySubscribe( // a single WaitForSingleObject with a timeout of INFINITE status = WaitForSingleObject(hManagerResponse, 1000); } else { + EnterCriticalSection(hSection); + // An error occured; return failure SetLastError(status); return false; } } + EnterCriticalSection(hSection); events.emplace_back(hEvent); map.emplace(std::move(std::pair>>{ hEvent, callbacks })); diff --git a/BLUESPAWN-client/src/monitor/EventManager.cpp b/BLUESPAWN-win-client/src/monitor/EventManager.cpp similarity index 51% rename from BLUESPAWN-client/src/monitor/EventManager.cpp rename to BLUESPAWN-win-client/src/monitor/EventManager.cpp index cd8e3dd2..76b3908e 100644 --- a/BLUESPAWN-client/src/monitor/EventManager.cpp +++ b/BLUESPAWN-win-client/src/monitor/EventManager.cpp @@ -11,21 +11,21 @@ EventManager& EventManager::GetInstance(){ return manager; } -DWORD EventManager::SubscribeToEvent(const std::shared_ptr& e, const std::function& callback) { +DWORD EventManager::SubscribeToEvent(std::unique_ptr&& e, const std::function& callback, + IN CONST Scope& scope){ DWORD status = ERROR_SUCCESS; - for(auto evt : vEventList){ + for(auto& evt : vEventList){ if(*evt == *e){ - evt->AddCallback(callback); + evt->AddCallback(callback, scope); return status; } } - std::shared_ptr evt = e; - evt->AddCallback(callback); - evt->Subscribe(); + e->AddCallback(callback, scope); + e->Subscribe(); - vEventList.push_back(evt); + vEventList.push_back(std::move(e)); return status; } \ No newline at end of file diff --git a/BLUESPAWN-client/src/monitor/etw/ETW_Wrapper.cpp b/BLUESPAWN-win-client/src/monitor/etw/ETW_Wrapper.cpp similarity index 100% rename from BLUESPAWN-client/src/monitor/etw/ETW_Wrapper.cpp rename to BLUESPAWN-win-client/src/monitor/etw/ETW_Wrapper.cpp diff --git a/BLUESPAWN-win-client/src/reaction/CarveMemory.cpp b/BLUESPAWN-win-client/src/reaction/CarveMemory.cpp new file mode 100644 index 00000000..cd034ca5 --- /dev/null +++ b/BLUESPAWN-win-client/src/reaction/CarveMemory.cpp @@ -0,0 +1,52 @@ +#include "reaction/CarveMemory.h" + +#include +#include + +#include "util/log/Log.h" +#include "util/processes/PERemover.h" + +#include "user/bluespawn.h" + +namespace Reactions { + + void CarveMemoryReaction::React(IN Detection& detection) { + auto& data{ std::get(detection.data) }; + if(!data.PID) { + return; + } + + HandleWrapper process{ OpenProcess(PROCESS_SUSPEND_RESUME, false, *data.PID) }; + if(process) { + if(Bluespawn::io.GetUserConfirm(L"`" + (data.ProcessCommand ? *data.ProcessCommand : *data.ProcessName) + + L"` (PID " + std::to_wstring(*data.PID) + + L") appears to be infected. " + "Carve out infected memory?") == 1) { + if(data.ImageName) { + if(!PERemover{ *data.PID, *data.ImageName }.RemoveImage()) { + LOG_ERROR(L"Failed to carve image " << *data.ImageName << L" from process with PID " + << *data.PID); + } else { + LOG_INFO(1, L"Successfully carved image " << *data.ImageName << L" from process with PID " + << *data.PID); + } + } else { + if(!PERemover{ *data.PID, *data.BaseAddress, *data.MemorySize }.RemoveImage()) { + LOG_ERROR(L"Failed to carve memory at " << *data.BaseAddress << L" from process with PID " + << *data.PID); + } else { + LOG_INFO(1, L"Successfully carved memory at " << *data.BaseAddress << L" from process with PID " + << *data.PID); + } + } + } + } else { + LOG_ERROR("Unable to open potentially infected process " << *data.PID); + } + } + + bool CarveMemoryReaction::Applies(IN CONST Detection& detection) { + return !detection.DetectionStale && detection.type == DetectionType::ProcessDetection && + std::get(detection.data).type != ProcessDetectionType::MaliciousProcess; + } +} // namespace Reactions diff --git a/BLUESPAWN-win-client/src/reaction/DeleteFile.cpp b/BLUESPAWN-win-client/src/reaction/DeleteFile.cpp new file mode 100644 index 00000000..8dee07ce --- /dev/null +++ b/BLUESPAWN-win-client/src/reaction/DeleteFile.cpp @@ -0,0 +1,42 @@ +#include "reaction/DeleteFile.h" + +#include +#include + +#include "util/wrappers.hpp" + +#include "util/log/Log.h" + +#include "user/bluespawn.h" + +namespace Reactions { + + void DeleteFileReaction::React(IN Detection& detection) { + auto data{ std::get(detection.data) }; + if(Bluespawn::io.GetUserConfirm(L"File " + data.FilePath + L" appears to be malicious. Delete file?") == 1) { + if(!data.FileHandle->TakeOwnership()) { + LOG_ERROR("Unable to take ownership of file, still attempting to delete. " << SYSTEM_ERROR); + } + ACCESS_MASK del{ 0 }; + Permissions::AccessAddDelete(del); + std::optional BluespawnOwner = Permissions::GetProcessOwner(); + if(BluespawnOwner == std::nullopt) { + LOG_ERROR("Unable to get process owner, still attempting to delete. " << SYSTEM_ERROR); + } else { + if(!data.FileHandle->GrantPermissions(*BluespawnOwner, del)) { + LOG_ERROR("Unable to grant delete permission, still attempting to delete. (Error: " + << GetLastError() << ")"); + } + } + if(!data.FileHandle->Delete()) { + LOG_ERROR("Unable to delete file " << data.FilePath << ". " << SYSTEM_ERROR); + } else{ + detection.DetectionStale = true; + } + } + } + + bool DeleteFileReaction::Applies(IN CONST Detection& detection){ + return !detection.DetectionStale && detection.type == DetectionType::FileDetection; + } +} // namespace Reactions diff --git a/BLUESPAWN-win-client/src/reaction/QuarantineFile.cpp b/BLUESPAWN-win-client/src/reaction/QuarantineFile.cpp new file mode 100644 index 00000000..aaeb679c --- /dev/null +++ b/BLUESPAWN-win-client/src/reaction/QuarantineFile.cpp @@ -0,0 +1,27 @@ +#include "reaction/QuarantineFile.h" + +#include +#include + +#include "util/wrappers.hpp" + +#include "util/log/Log.h" + +#include "user/bluespawn.h" + +namespace Reactions { + void QuarantineFileReaction::React(IN Detection& detection) { + auto data{ std::get(detection.data) }; + if(Bluespawn::io.GetUserConfirm(L"File " + data.FilePath + L" appears to be malicious. Delete file?") == 1) { + if(!data.FileHandle->Quarantine()) { + LOG_ERROR("Unable to quarantine file " << data.FilePath << ". " << SYSTEM_ERROR); + } else { + detection.DetectionStale = true; + } + } + } + + bool QuarantineFileReaction::Applies(IN CONST Detection& detection) { + return !detection.DetectionStale && detection.type == DetectionType::FileDetection; + } +} // namespace Reactions diff --git a/BLUESPAWN-win-client/src/reaction/ReactionManager.cpp b/BLUESPAWN-win-client/src/reaction/ReactionManager.cpp new file mode 100644 index 00000000..5a384bab --- /dev/null +++ b/BLUESPAWN-win-client/src/reaction/ReactionManager.cpp @@ -0,0 +1,20 @@ +#include "reaction/ReactionManager.h" + +#include + +void ReactionManager::React(IN Detection& detection) CONST { + EnterCriticalSection(detection.hGuard); + if(detection.remediator){ + (*detection.remediator)(); + } + std::for_each(reactions.begin(), reactions.end(), [&detection](const auto& f){ + if(f->Applies(detection) && (!detection.remediator || f->IgnoreRemediator)){ + f->React(detection); + } + }); + LeaveCriticalSection(detection.hGuard); +} + +void ReactionManager::AddHandler(IN std::unique_ptr&& reaction){ + reactions.emplace_back(std::move(reaction)); +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/src/reaction/RemoveValue.cpp b/BLUESPAWN-win-client/src/reaction/RemoveValue.cpp new file mode 100644 index 00000000..6a3e3117 --- /dev/null +++ b/BLUESPAWN-win-client/src/reaction/RemoveValue.cpp @@ -0,0 +1,53 @@ +#include +#include + +#include "reaction/RemoveValue.h" +#include "util/configurations/Registry.h" +#include "util/wrappers.hpp" +#include "user/bluespawn.h" +#include "util/log/Log.h" + +namespace Reactions{ + + void RemoveValueReaction::React(IN Detection& detection){ + auto& data{ std::get(detection.data) }; + if(data.value){ + if(Bluespawn::io.GetUserConfirm(L"Registry key `" + data.key.ToString() + L"` contains potentially " + "malicious value `" + data.value->wValueName + L"` with data `" + + data.value->ToString() + L"`. Remove this value?") == 1){ + auto type{ data.key.GetValueType(data.value->wValueName) }; + if(data.key.GetValueType(data.value->wValueName) == data.value->GetType()){ + if(!data.key.RemoveValue(data.value->wValueName)){ + LOG_ERROR("Unable to remove registry value `" << data.value->ToString() << "`: `" << + data.value->wValueName << "` (Error " << GetLastError() << ")"); + } else{ + detection.DetectionStale = true; + } + } else{ + if(type == RegistryType::REG_MULTI_SZ_T){ + auto val{ *data.key.GetValue>(data.value->wValueName) }; + for(size_t idx{ 0 }; idx < val.size(); idx++){ + if(val[idx] == std::get(data.value->data)){ + val.erase(val.begin() + idx); + idx--; + } + } + if(!data.key.SetValue>(data.value->wValueName, val)){ + LOG_ERROR("Unable to remove registry value `" << data.value->ToString() << "`: `" << + data.value->wValueName << "` (Error " << GetLastError() << ")"); + } else{ + detection.DetectionStale = true; + } + } else{ + LOG_ERROR("Unable to remove registry value `" << data.value->ToString() << "` from `" << + data.value->wValueName << "` (Error " << GetLastError() << ")"); + } + } + } + } + } + + bool RemoveValueReaction::Applies(IN CONST Detection& detection){ + return !detection.DetectionStale && detection.type == DetectionType::RegistryDetection; + } +} diff --git a/BLUESPAWN-win-client/src/reaction/SuspendProcess.cpp b/BLUESPAWN-win-client/src/reaction/SuspendProcess.cpp new file mode 100644 index 00000000..17f291ce --- /dev/null +++ b/BLUESPAWN-win-client/src/reaction/SuspendProcess.cpp @@ -0,0 +1,34 @@ +#include +#include + +#include "reaction/SuspendProcess.h" +#include "util/wrappers.hpp" +#include "util/log/Log.h" +#include "user/bluespawn.h" + +#include + +LINK_FUNCTION(NtSuspendProcess, NTDLL.DLL) + +namespace Reactions{ + + void SuspendProcessReaction::React(IN Detection& detection){ + auto& data{ std::get(detection.data) }; + if(data.PID){ + HandleWrapper process{ OpenProcess(PROCESS_SUSPEND_RESUME, false, *data.PID) }; + if(process){ + if(Bluespawn::io.GetUserConfirm(L"`" + (data.ProcessCommand ? *data.ProcessCommand : *data.ProcessName) + + L"` (PID " + std::to_wstring(*data.PID) + L") appears to be infected. " + "Suspend process?") == 1){ + Linker::NtSuspendProcess(process); + } + } else{ + LOG_ERROR("Unable to open potentially infected process " << *data.PID); + } + } + } + + bool SuspendProcessReaction::Applies(IN CONST Detection& detection){ + return !detection.DetectionStale && detection.type == DetectionType::ProcessDetection; + } +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/src/scan/Detection.cpp b/BLUESPAWN-win-client/src/scan/Detection.cpp new file mode 100644 index 00000000..6544f5ee --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/Detection.cpp @@ -0,0 +1,551 @@ +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" + +#include "util/processes/ProcessUtils.h" + +#include "scan/Detections.h" + +size_t ComputeHash(IN CONST std::map& map) { + size_t hash{ 0 }; + + std::hash hasher{}; + for(auto& pair : map) { + auto first{ hasher(pair.first) }; + auto second{ hasher(pair.second) }; +#ifdef _WIN64 + hash = ((hash << 35) | (hash >> 29)) ^ ((first >> 32) | ((first << 32) >> 32)) ^ + ((second << 32) | ((second >> 32) << 32)); +#else + hash = ((hash << 19) | (hash >> 13)) ^ ((first >> 16) | ((first << 16) >> 16)) ^ + ((second << 16) | ((second >> 16) << 16)); +#endif + } + + return hash; +} + +size_t ComputeHash(IN CONST std::vector& values){ + size_t hash{ 0 }; + + std::hash hasher{}; + for(auto& val : values){ + auto first{ hasher(val) }; +#ifdef _WIN64 + hash = ((hash << 35) | (hash >> 29)) ^ ((first >> 32) | ((first << 32) >> 32)); +#else + hash = ((hash << 19) | (hash >> 14)) ^ ((first >> 16) | ((first << 16) >> 16)); +#endif + } + + return hash; +} + +ProcessDetectionData +ProcessDetectionData::CreateImageDetectionData(IN DWORD PID, + IN CONST std::wstring& ProcessName, + IN CONST std::wstring& ImageName, + IN CONST std::optional& BaseAddress OPTIONAL, + IN CONST std::optional& MemorySize OPTIONAL, + IN CONST std::optional& ProcessPath OPTIONAL, + IN CONST std::optional& ProcessCommand OPTIONAL, + IN std::unique_ptr&& ParentProcess OPTIONAL) { + HandleWrapper hProcess{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, PID) }; + if(hProcess) { + return CreateImageDetectionData(hProcess, ProcessName, ImageName, BaseAddress, MemorySize, ProcessPath, + ProcessCommand, std::move(ParentProcess)); + } else { + return ProcessDetectionData{ + ProcessDetectionType::MaliciousMemory, + PID, // PID + std::nullopt, // TID + std::nullopt, // ProcessHandle + ProcessName, // ProcessName + ProcessPath, // ProcessPath + ProcessCommand, // ProcessCommand + std::move(ParentProcess), // ParentProcess + BaseAddress, // BaseAddress + MemorySize, // MemorySize + ImageName // ImageName + }; + } +} + +ProcessDetectionData +ProcessDetectionData::CreateImageDetectionData(IN CONST HandleWrapper& ProcessHandle, + IN CONST std::wstring& ProcessName, + IN CONST std::wstring& ImageName, + IN CONST std::optional& BaseAddress OPTIONAL, + IN CONST std::optional& MemorySize OPTIONAL, + IN CONST std::optional& ProcessPath OPTIONAL, + IN CONST std::optional& ProcessCommand OPTIONAL, + IN std::unique_ptr&& ParentProcess OPTIONAL) { + auto addr{ BaseAddress ? *BaseAddress : GetModuleAddress(ProcessHandle, ImageName) }; + + return ProcessDetectionData{ + ProcessDetectionType::MaliciousImage, // type + GetProcessId(ProcessHandle), // PID + std::nullopt, // TID + ProcessHandle, // ProcessHandle + ProcessName, // ProcessName + ProcessPath ? ProcessPath : GetProcessImage(ProcessHandle), // ProcessPath + ProcessCommand ? ProcessCommand : GetProcessCommandline(ProcessHandle), // ProcessCommand + std::move(ParentProcess), // ParentProcess + addr, // BaseAddress + MemorySize ? MemorySize : GetRegionSize(ProcessHandle, addr), // MemorySize + ImageName // ImageName + }; +} + +ProcessDetectionData +ProcessDetectionData::CreateProcessDetectionData(IN DWORD PID, + IN CONST std::wstring& ProcessName, + IN CONST std::optional& ProcessPath OPTIONAL, + IN CONST std::optional& ProcessCommand OPTIONAL, + IN std::unique_ptr&& ParentProcess OPTIONAL) { + HandleWrapper hProcess{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, PID) }; + if(hProcess) { + return CreateProcessDetectionData(hProcess, ProcessName, ProcessPath, ProcessCommand, std::move(ParentProcess)); + } else { + return ProcessDetectionData{ + ProcessDetectionType::MaliciousProcess, + PID, // PID + std::nullopt, // TID + std::nullopt, // ProcessHandle + ProcessName, // ProcessName + ProcessPath, // ProcessPath + ProcessCommand, // ProcessCommand + std::move(ParentProcess), // ParentProcess + std::nullopt, // BaseAddress + std::nullopt, // MemorySize + std::nullopt // ImageName + }; + } +} + +ProcessDetectionData ProcessDetectionData::CreateCommandDetectionData(IN CONST std::wstring& ProcessCommand) { + return ProcessDetectionData{ + ProcessDetectionType::MaliciousCommand, + std::nullopt, // PID + std::nullopt, // TID + std::nullopt, // ProcessHandle + std::nullopt, // ProcessName + std::nullopt, // ProcessPath + ProcessCommand, // ProcessCommand + nullptr, // ParentProcess + std::nullopt, // MemorySize + std::nullopt, // BaseAddress + std::nullopt // ImageName + }; +} + +ProcessDetectionData +ProcessDetectionData::CreateProcessDetectionData(IN CONST HandleWrapper& ProcessHandle, + IN CONST std::wstring& ProcessName, + IN CONST std::optional& ProcessPath OPTIONAL, + IN CONST std::optional& ProcessCommand OPTIONAL, + IN std::unique_ptr&& ParentProcess OPTIONAL) { + return ProcessDetectionData{ + ProcessDetectionType::MaliciousProcess, // type + GetProcessId(ProcessHandle), // PID + std::nullopt, // TID + ProcessHandle, // ProcessHandle + ProcessName, // ProcessName + ProcessPath ? ProcessPath : GetProcessImage(ProcessHandle), // ProcessPath + ProcessCommand ? ProcessCommand : GetProcessCommandline(ProcessHandle), // ProcessCommand + std::move(ParentProcess), // ParentProcess + std::nullopt, // BaseAddress + std::nullopt, // MemorySize + std::nullopt // ImageName + }; +} + +ProcessDetectionData +ProcessDetectionData::CreateMemoryDetectionData(IN DWORD PID, + IN CONST std::wstring& ProcessName, + IN PVOID64 BaseAddress, + IN DWORD MemorySize, + IN CONST std::optional& ImageName OPTIONAL, + IN CONST std::optional& ProcessPath OPTIONAL, + IN CONST std::optional& ProcessCommand OPTIONAL, + IN std::unique_ptr&& ParentProcess OPTIONAL) { + HandleWrapper hProcess{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, PID) }; + if(hProcess) { + return CreateMemoryDetectionData(hProcess, ProcessName, BaseAddress, MemorySize, ImageName, ProcessPath, + ProcessCommand, std::move(ParentProcess)); + } else { + auto image{ ImageName }; + if(image && !image->length()){ + image = std::nullopt; + } + return ProcessDetectionData{ + ProcessDetectionType::MaliciousMemory, + PID, // PID + std::nullopt, // TID + std::nullopt, // ProcessHandle + ProcessName, // ProcessName + ProcessPath, // ProcessPath + ProcessCommand, // ProcessCommand + std::move(ParentProcess), // ParentProcess + BaseAddress, // BaseAddress + MemorySize, // MemorySize + image // ImageName + }; + } +} + +ProcessDetectionData +ProcessDetectionData::CreateMemoryDetectionData(IN CONST HandleWrapper& ProcessHandle, + IN CONST std::wstring& ProcessName, + IN PVOID64 BaseAddress, + IN DWORD MemorySize, + IN CONST std::optional& ImageName OPTIONAL, + IN CONST std::optional& ProcessPath OPTIONAL, + IN CONST std::optional& ProcessCommand OPTIONAL, + IN std::unique_ptr&& ParentProcess OPTIONAL) { + std::optional image{ ImageName }; + if(!image) { + auto mapped{ GetMappedFile(ProcessHandle, BaseAddress) }; + if(mapped) { image = mapped->GetFilePath(); } + } + if(image && !image->length()){ + image = std::nullopt; + } + + return ProcessDetectionData{ + ProcessDetectionType::MaliciousMemory, // type + GetProcessId(ProcessHandle), // PID + std::nullopt, // TID + ProcessHandle, // ProcessHandle + ProcessName, // ProcessName + ProcessPath ? ProcessPath : GetProcessImage(ProcessHandle), // ProcessPath + ProcessCommand ? ProcessCommand : GetProcessCommandline(ProcessHandle), // ProcessCommand + std::move(ParentProcess), // ParentProcess + BaseAddress, // BaseAddress + MemorySize, // MemorySize + image // ImageName + }; +} + +ProcessDetectionData::ProcessDetectionData(IN ProcessDetectionType type, + IN CONST std::optional PID, + IN CONST std::optional& TID, + IN CONST std::optional& ProcessHandle, + IN CONST std::optional& ProcessName, + IN CONST std::optional& ProcessPath, + IN CONST std::optional& ProcessCommand, + IN std::unique_ptr&& ParentProcess, + IN CONST std::optional& BaseAddress, + IN CONST std::optional& MemorySize, + IN CONST std::optional& ImageName) : + type{ type }, + PID{ PID }, TID{ TID }, ProcessHandle{ ProcessHandle }, ProcessName{ ProcessName }, ProcessPath{ ProcessPath }, + ProcessCommand{ ProcessCommand }, ParentProcess{ std::move(ParentProcess) }, BaseAddress{ BaseAddress }, + MemorySize{ MemorySize }, ImageName{ ImageName } { + auto tied{ std::tie(type, PID, TID, ProcessHandle, ProcessName, ProcessPath, ProcessCommand, ParentProcess, + BaseAddress, MemorySize, ImageName) }; + + serialization = std::map{ + { L"Type", type == ProcessDetectionType::MaliciousImage ? + L"Image" : + type == ProcessDetectionType::MaliciousMemory ? + L"Memory" : + type == ProcessDetectionType::MaliciousProcess ? L"Process" : L"Command" }, + }; + if(PID) serialization.emplace(L"PID", std::to_wstring(*PID)); + if(TID) serialization.emplace(L"TID", std::to_wstring(*TID)); + if(ProcessName) serialization.emplace(L"Process Name", *ProcessName); + if(ProcessPath) serialization.emplace(L"Process Path", *ProcessPath); + if(ProcessCommand) serialization.emplace(L"Process Command", *ProcessCommand); + if(ParentProcess && ParentProcess->PID) serialization.emplace(L"Parent PID", std::to_wstring(*ParentProcess->PID)); + if(BaseAddress) { + std::wstringstream wss{}; + wss << std::hex << *BaseAddress; + serialization.emplace(L"Base Address", wss.str()); + } + if(MemorySize) serialization.emplace(L"Memory Size", std::to_wstring(*MemorySize)); + if(ImageName) serialization.emplace(L"Image Name", *ImageName); + + hash = ComputeHash(serialization); +} + +const std::map& ProcessDetectionData::Serialize() CONST { + return serialization; +} + +size_t ProcessDetectionData::Hash() CONST { + return hash; +} + +FileDetectionData::FileDetectionData(IN CONST FileSystem::File& file, + IN CONST std::optional& scan OPTIONAL) : + FileFound{ file.GetFileExists() }, + FilePath{ file.GetFilePath() }, FileName{ FilePath.find(L"\\/") == std::wstring::npos ? + FilePath : + FilePath.substr(FilePath.find_last_of(L"\\/")) }, + FileExtension{ file.GetFileAttribs().extension }, + FileHandle{ file }, MD5{ file.GetMD5Hash() }, SHA1{ file.GetSHA1Hash() }, SHA256{ file.GetSHA256Hash() }, + LastOpened{ file.GetAccessTime() }, FileCreated{ file.GetCreationTime() }, + yara{ scan ? + scan : + (FileFound ? std::optional(YaraScanner::GetInstance().ScanFile(file)) : std::nullopt) }, + FileSigned{ FileFound ? std::optional(file.GetFileSigned()) : std::nullopt }, Signer{ + FileSigned && *FileSigned ? file.GetCertificateIssuer() : std::nullopt + } { + if(FileExtension) { + Registry::RegistryKey FileExtClass{ HKEY_CLASSES_ROOT, *FileExtension }; + if(FileExtClass.Exists() && FileExtClass.ValueExists(L"")) { + FileType = FileExtClass.GetValue(L""); + if(FileType) { + Registry::RegistryKey FileClass{ HKEY_CLASSES_ROOT, *FileType }; + if(FileClass.Exists()) { + Registry::RegistryKey shell{ FileClass, L"shell\\open\\command" }; + auto command{ shell.GetValue(L"") }; + if(command) { + Executor = StringReplaceW(StringReplaceW(*command, L"%1", FilePath), L"%*", L""); + } + } + } + } + } + + serialization = std::map{ + { L"Path", FilePath }, + { L"Name", FileName }, + { L"Exists", FileFound ? L"true" : L"false" }, + }; + if(FileExtension) serialization.emplace(L"Extension", *FileExtension); + if(FileType) serialization.emplace(L"File Type", *FileType); + if(Executor) serialization.emplace(L"File Executor", *Executor); + if(MD5) serialization.emplace(L"MD5 Hash", *MD5); + if(SHA1) serialization.emplace(L"SHA1 Hash", *SHA1); + if(SHA256) serialization.emplace(L"SHA256 Hash", *SHA256); + if(LastOpened) serialization.emplace(L"Last Opened", FormatWindowsTime(*LastOpened)); + if(FileCreated) serialization.emplace(L"Date Created", FormatWindowsTime(*FileCreated)); + if(yara) { + std::wstring malicious{}; + for(auto& mal : yara->vKnownBadRules) { + if(malicious.length()) malicious += L", "; + malicious += mal; + } + std::wstring identifier{}; + for(auto& id : yara->vIndicatorRules) { + if(identifier.length()) identifier += L", "; + identifier += id; + } + serialization.emplace(L"Malicious Yara Rules", malicious); + serialization.emplace(L"Other Yara Rules", identifier); + } + if(FileSigned) serialization.emplace(L"Signed", *FileSigned ? L"true" : L"false"); + if(Signer) serialization.emplace(L"Signer", *Signer); + + hash = ComputeHash(std::vector{ FilePath, SHA256 ? *SHA256 : L"" }); +} + +FileDetectionData::FileDetectionData(IN CONST std::wstring& path) : + FileDetectionData(FileSystem::File{ path }, std::nullopt) {} + +const std::map& FileDetectionData::Serialize() CONST { + return serialization; +} + +size_t FileDetectionData::Hash() CONST { + return hash; +} + +bool FileDetectionData::operator==(IN CONST FileDetectionData& data) const { + if(data.SHA256 != SHA256) { return false; } + return FilePath == data.FilePath; +} + +RegistryDetectionData::RegistryDetectionData(IN CONST Registry::RegistryKey& key, + IN CONST std::optional& value OPTIONAL, + IN RegistryDetectionType type OPTIONAL, + IN CONST std::optional& data OPTIONAL) : + KeyPath{ key.GetName() }, + key{ key }, value{ value }, type{ type }, data{ data } { + serialization = std::map{ + { L"Key Path", key.GetName() }, + { L"Registry Entry Type", type == RegistryDetectionType::CommandReference ? + L"Command" : + type == RegistryDetectionType::Configuration ? + L"Configuration" : + type == RegistryDetectionType::FileReference ? + L"File" : + type == RegistryDetectionType::FolderReference ? + L"Folder" : + type == RegistryDetectionType::PipeReference ? + L"Pipe" : + type == RegistryDetectionType::ShareReference ? + L"Share" : + type == RegistryDetectionType::UserReference ? L"User" : L"Unknown" } + }; + if(value) { + serialization.emplace(L"Key Value Name", value->wValueName); + serialization.emplace(L"Key Value Data", value->ToString()); + } + + hash = ComputeHash(std::vector{ KeyPath, value ? value->wValueName : L"" }); +} + +RegistryDetectionData::RegistryDetectionData(IN CONST Registry::RegistryValue& value, + IN RegistryDetectionType type OPTIONAL) : + RegistryDetectionData{ value.key, value, type, value.key.GetRawValue(value.wValueName) } {} + +const std::map& RegistryDetectionData::Serialize() CONST { + return serialization; +} + +size_t RegistryDetectionData::Hash() CONST { + return hash; +} + +bool RegistryDetectionData::operator==(IN CONST RegistryDetectionData& data) CONST { + return KeyPath == data.KeyPath && value->wValueName == data.value->wValueName; +} + +ServiceDetectionData::ServiceDetectionData(IN CONST std::optional& ServiceName OPTIONAL, + IN CONST std::optional& DisplayName OPTIONAL, + IN CONST std::optional& FilePath OPTIONAL, + IN CONST std::optional& Description OPTIONAL) : + ServiceName{ ServiceName }, + DisplayName{ DisplayName }, FilePath{ FilePath }, Description{ Description } { + serialization = std::map{}; + if(ServiceName) { serialization.emplace(L"Service Name", *ServiceName); } + if(FilePath) { serialization.emplace(L"Service Executable", *FilePath); } + if(DisplayName) { serialization.emplace(L"Display Name", *DisplayName); } + if(Description) { serialization.emplace(L"Description", *Description); } + + hash = ComputeHash(serialization); +} + +const std::map& ServiceDetectionData::Serialize() CONST { + return serialization; +} + +size_t ServiceDetectionData::Hash() CONST { + return hash; +} + +OtherDetectionData::OtherDetectionData(IN CONST std::wstring& DetectionType, + IN CONST std::map& DetectionProperties) : + DetectionType{ DetectionType }, + DetectionProperties{ DetectionProperties }, serialization(DetectionProperties.begin(), DetectionProperties.end()) { + serialization.emplace(L"Detection Type", DetectionType); + hash = ComputeHash(serialization); +} + +const std::map& OtherDetectionData::Serialize() CONST { + return serialization; +} + +size_t OtherDetectionData::Hash() CONST { + return hash; +} + +DetectionContext::DetectionContext(IN CONST std::optional& hunt OPTIONAL, + IN CONST std::optional& FirstEvidenceTime OPTIONAL, + IN CONST std::optional& note OPTIONAL) : + FirstEvidenceTime{ FirstEvidenceTime }, + note{ note } { + if(hunt) hunts.emplace(*hunt); + + GetSystemTimeAsFileTime(&DetectionCreatedTime); +} + +decltype(Detection::serializer) Detection::serializer{}; +decltype(Detection::hasher) Detection::hasher{}; + +Detection::Detection(IN CONST DetectionData& data, + IN CONST std::optional& context OPTIONAL, + IN CONST std::optional>& remediator OPTIONAL, + IN bool DetectionStale OPTIONAL) : + data{ data }, + DetectionStale{ DetectionStale }, type{ DetectionType::OtherDetection }, dwID{ IDCounter++ }, + remediator{ remediator }, context{ context ? *context : DetectionContext{} } { + if(data.index() == 0) { + type = DetectionType::ProcessDetection; + } else if(data.index() == 1) { + type = DetectionType::FileDetection; + } else if(data.index() == 2) { + type = DetectionType::RegistryDetection; + } else if(data.index() == 3) { + type = DetectionType::ServiceDetection; + } + + hash = std::visit(hasher, data); + auto tmp{ std::visit(serializer, data) }; + serialization = std::map(tmp.begin(), tmp.end()); +} + +Detection::Detection(IN CONST Detection& detection) : + data{ detection.data }, DetectionStale{ detection.DetectionStale }, type{ detection.type }, dwID{ detection.dwID }, + remediator{ detection.remediator }, context{ detection.context }, hash{ detection.hash }, + serialization{ detection.serialization } { + info.associations = std::make_unique, Association>>( + *detection.info.associations); + info.bAssociativeStale = detection.info.bAssociativeStale; + info.cAssociativeCertainty = detection.info.cAssociativeCertainty; + info.certainty = detection.info.certainty; +} + +Detection& Detection::operator=(IN CONST Detection& detection) { + data = detection.data; + DetectionStale = detection.DetectionStale; + type = detection.type; + dwID = detection.dwID; + remediator = detection.remediator; + context = detection.context; + hash = detection.hash; + serialization = detection.serialization; + info = {}; + info.associations = std::make_unique, Association>>( + *detection.info.associations); + info.bAssociativeStale = detection.info.bAssociativeStale; + info.cAssociativeCertainty = detection.info.cAssociativeCertainty; + info.certainty = detection.info.certainty; + return *this; +} + +bool Detection::operator==(IN CONST Detection& detection) CONST { + if(type != detection.type) { return false; } + + if(type == DetectionType::ProcessDetection) { + return std::get(data) == std::get(detection.data); + } else if(type == DetectionType::ServiceDetection) { + return std::get(data) == std::get(detection.data); + } else if(type == DetectionType::RegistryDetection) { + return std::get(data) == std::get(detection.data); + } else if(type == DetectionType::FileDetection) { + return std::get(data) == std::get(detection.data); + } else { + return std::get(data) == std::get(detection.data); + } +} + +Detection::operator PCRITICAL_SECTION() { + return hGuard; +} + +Detection::operator CriticalSection(){ + return hGuard; +} + +const std::map& Detection::Serialize() CONST { + return serialization; +}; + +size_t std::hash::operator()(IN CONST Detection& detection) CONST { + return detection.hash; +} + +size_t +std::hash>::operator()(IN CONST std::shared_ptr& detection) CONST { + return detection->hash; +} + +bool std::equal_to>::operator()( + IN CONST std::shared_ptr& _Left, IN CONST std::shared_ptr& _Right) CONST { + return *_Left == *_Right; +} diff --git a/BLUESPAWN-win-client/src/scan/DetectionRegister.cpp b/BLUESPAWN-win-client/src/scan/DetectionRegister.cpp new file mode 100644 index 00000000..6d091d73 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/DetectionRegister.cpp @@ -0,0 +1,223 @@ +#include "scan/DetectionRegister.h" + +#include + +#include "util/ThreadPool.h" + +#include "scan/Scanner.h" +#include "user/bluespawn.h" + +DetectionRegister::DetectionRegister(IN CONST Certainty& threshold) : + threshold{ threshold }, hEvent{ CreateEventW(nullptr, true, true, nullptr) } {} + +void DetectionRegister::AddDetectionAsync(IN CONST std::shared_ptr& detection, + IN CONST Certainty& certainty) { + EnterCriticalSection(*detection); + detection->info.SetCertainty(certainty); + for(auto& scan : Scanner::scanners) { + detection->info.AddCertainty(scan->ScanDetection(*detection)); + } + + auto associations{ detection->info.GetAssociations() }; + LeaveCriticalSection(*detection); + for(auto& pair : associations){ + pair.first->info.bAssociativeStale = true; + for(auto& sink : Bluespawn::detectionSinks){ + sink->UpdateCertainty(pair.first); + } + } + EnterCriticalSection(*detection); + + for(auto& scan : Scanner::scanners) { + for(auto& pair : scan->GetAssociatedDetections(*detection)){ + detection->info.AddAssociation(pair.first, pair.second); + pair.first->info.AddAssociation(detection, pair.second); + + auto first{ detection->dwID < pair.first->dwID ? detection : pair.first }; + auto second{ detection->dwID < pair.first->dwID ? pair.first : detection }; + + LeaveCriticalSection(*detection); + for(auto& sink : Bluespawn::detectionSinks){ + sink->RecordAssociation(first, second, pair.second); + } + EnterCriticalSection(*detection); + } + } + + EnterCriticalSection(hScannedGuard); + scanned.emplace(detection); + LeaveCriticalSection(hScannedGuard); + + EnterCriticalSection(hQueueGuard); + queue.erase(detection); + if(queue.size() == 0) { + SetEvent(hEvent); + } + LeaveCriticalSection(hQueueGuard); + + if(detection->info.GetCertainty() >= threshold) { + LeaveCriticalSection(*detection); + for(auto& sink : Bluespawn::detectionSinks) { + sink->RecordDetection(detection, RecordType::PostScan); + } + + EnterCriticalSection(*detection); + Bluespawn::reaction.React(*detection); + } + LeaveCriticalSection(*detection); +} + +void DetectionRegister::UpdateDetectionCertainty(IN CONST std::shared_ptr& detection, + IN CONST Certainty& certainty) { + EnterCriticalSection(*detection); + + // if the detection is queued and we can enter its critical section, it hasn't been scanned yet + bool queued{ false }; + EnterCriticalSection(hQueueGuard); + if(queue.find(detection) != queue.end()) { + detection->info.AddCertainty(certainty); + queued = true; + } + LeaveCriticalSection(hQueueGuard); + + // The detection has been scanned + if(!queued) { + bool below{ !(detection->info.GetCertainty() >= threshold) }; + + detection->info.AddCertainty(certainty); + + // This update caused it to pass the threshold, so scan for assocations + if(below && detection->info.GetCertainty() >= threshold) { + for(auto& scan : Scanner::scanners) { + for(auto& pair : scan->GetAssociatedDetections(*detection)) { + detection->info.AddAssociation(pair.first, pair.second); + pair.first->info.AddAssociation(detection, pair.second); + + auto first{ detection->dwID < pair.first->dwID ? detection : pair.first }; + auto second{ detection->dwID < pair.first->dwID ? pair.first : detection }; + for(auto& sink : Bluespawn::detectionSinks) { + sink->RecordAssociation(first, second, pair.second); + } + } + } + + LeaveCriticalSection(*detection); + for(auto& sink : Bluespawn::detectionSinks){ + sink->RecordDetection(detection, RecordType::PostScan); + } + + EnterCriticalSection(*detection); + Bluespawn::reaction.React(*detection); + } else if(!below && detection->info.GetCertainty() >= threshold){ + LeaveCriticalSection(*detection); + for(auto& sink : Bluespawn::detectionSinks){ + sink->UpdateCertainty(detection); + } + EnterCriticalSection(*detection); + } + + // Existing associations' associativity scores are now stale + for(auto& pair : detection->info.GetAssociations()) { + pair.first->info.bAssociativeStale = true; + for(auto& sink : Bluespawn::detectionSinks){ + sink->UpdateCertainty(pair.first); + } + } + } + + LeaveCriticalSection(*detection); +} + +std::shared_ptr DetectionRegister::AddDetection(IN Detection&& raw, IN CONST Certainty& certainty) { + auto detection{ std::make_shared(raw) }; + for(auto& sink : Bluespawn::detectionSinks){ + sink->RecordDetection(detection, RecordType::PreScan); + } + + EnterCriticalSection(hScannedGuard); + auto itr{ scanned.find(detection) }; + if(itr != scanned.end()) { + LeaveCriticalSection(hScannedGuard); + auto ref{ *itr }; + for(auto& hunt : raw.context.hunts){ + if(ref->context.hunts.find(hunt) == ref->context.hunts.end()){ + ThreadPool::GetInstance().EnqueueTask( + std::bind(&DetectionRegister::UpdateDetectionCertainty, this, ref, certainty)); + return ref; + } + } + return ref; + } + LeaveCriticalSection(hScannedGuard); + + EnterCriticalSection(hQueueGuard); + itr = queue.find(detection); + if(itr != queue.end()) { + LeaveCriticalSection(hQueueGuard); + auto ref{ *itr }; + for(auto& hunt : raw.context.hunts){ + if(ref->context.hunts.find(hunt) == ref->context.hunts.end()){ + ThreadPool::GetInstance().EnqueueTask( + std::bind(&DetectionRegister::UpdateDetectionCertainty, this, ref, certainty)); + return ref; + } + } + return ref; + } + LeaveCriticalSection(hQueueGuard); + + EnterCriticalSection(hGuard); + detections.emplace_back(detection); + std::shared_ptr ref{ detections[detections.size() - 1] }; + ids.emplace(detection->dwID, ref); + LeaveCriticalSection(hGuard); + + for(auto& sink : Bluespawn::detectionSinks) { + sink->RecordDetection(ref, RecordType::PreScan); + } + + ResetEvent(hEvent); + + EnterCriticalSection(hQueueGuard); + queue.emplace(ref); + LeaveCriticalSection(hQueueGuard); + + ThreadPool::GetInstance().EnqueueTask(std::bind(&DetectionRegister::AddDetectionAsync, this, ref, certainty)); + + return ref; +} + +void DetectionRegister::Wait() CONST { + auto status{ WaitForSingleObject(hEvent, INFINITE) }; + if(status != ERROR_SUCCESS) { + BeginCriticalSection _{ hQueueGuard }; + if(queue.size() != 0) { + throw std::exception{ "Failed to wait for detection register to finish scans!" }; + } + } +} + +DetectionRegister::operator HANDLE() CONST { + return hEvent; +} + +std::vector> DetectionRegister::GetAllDetections(IN CONST Certainty& level OPTIONAL) CONST { + Wait(); + BeginCriticalSection _{ hScannedGuard }; + std::vector> found{}; + for(auto detection : scanned) { + if(detection->info.certainty >= level) { + found.emplace_back(detection); + } + } + return found; +} + +std::shared_ptr DetectionRegister::GetByID(IN DWORD ID) CONST{ + BeginCriticalSection _{ hGuard }; + if(ids.find(ID) != ids.end()){ + return ids.at(ID); + } else{ + return nullptr; + } +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/src/scan/FileScanner.cpp b/BLUESPAWN-win-client/src/scan/FileScanner.cpp new file mode 100644 index 00000000..108fe944 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/FileScanner.cpp @@ -0,0 +1,256 @@ +#include "scan/FileScanner.h" + +#include + +#include + +#include "util/StringUtils.h" +#include "util/wrappers.hpp" + +#include "util/filesystem/FileSystem.h" +#include "util/processes/ProcessUtils.h" + +#include "scan/ProcessScanner.h" +#include "scan/RegistryScanner.h" +#include "scan/ScanInfo.h" +#include "scan/YaraScanner.h" +#include "user/bluespawn.h" + +bool GetFilesSimilar(const AllocationWrapper& lpFile1, const AllocationWrapper& lpFile2) { + return lpFile1.GetSize() == lpFile2.GetSize() && + lpFile1.GetSize() == RtlCompareMemory(lpFile1, lpFile2, lpFile1.GetSize()); +} + +std::vector FileScanner::ExtractStrings(IN CONST AllocationWrapper& data, IN DWORD dwMinLength OPTIONAL) { + std::vector strings{}; + + DWORD dwStringStart{}; + for(DWORD idx = 0; idx < data.GetSize(); idx++) { + if(!(data[idx] >= 0x20 && data[idx] <= 0x7F)) { + DWORD dwStringLength = idx - dwStringStart; + if(dwStringLength >= dwMinLength) { + strings.emplace_back( + StringToWidestring(std::string{ PCHAR(LPVOID(data)) + dwStringStart, dwStringLength })); + } + + dwStringStart = idx + 1; + } + } + + auto dwStringLength = data.GetSize() - dwStringStart; + if(dwStringLength >= dwMinLength) { + strings.emplace_back(StringToWidestring(std::string{ PCHAR(LPVOID(data)) + dwStringStart, dwStringLength })); + } + + dwStringStart = 0; + auto mem{ reinterpret_cast(LPVOID(data)) }; + for(DWORD idx = 0; 2 * idx < data.GetSize(); idx++) { + if(!(mem[idx] >= 0x20 && mem[idx] < 0x7E)) { + dwStringLength = idx - dwStringStart; + if(dwStringLength >= dwMinLength) { + strings.emplace_back(std::wstring{ PWCHAR(LPVOID(data)) + dwStringStart, dwStringLength }); + } + + dwStringStart = idx + 1; + } + } + + dwStringLength = data.GetSize() / 2 - dwStringStart; + if(dwStringLength >= dwMinLength && data.GetSize() / 2 > dwStringStart) { + strings.emplace_back(std::wstring{ mem + dwStringStart, dwStringLength }); + } + + return strings; +} + +std::vector FileScanner::ExtractFilePaths(IN CONST std::vector& strings) { + std::vector filepaths{}; + std::wregex regex{ L"[a-zA-Z]:([/\\\\][a-zA-Z0-9(). @_-]+)+" }; + for(auto& string : strings) { + std::wsmatch match{}; + if(std::regex_search(string, match, regex)) { + for(auto& filename : match) { + if(FileSystem::CheckFileExists(filename.str())) { + filepaths.emplace_back(filename.str()); + } + } + } + } + return filepaths; +} + +void FileScanner::UpdateModules() { + BeginCriticalSection _{ hGuard }; + + FILETIME time{}; + GetSystemTimeAsFileTime(&time); + + uint64_t tdiff{ (static_cast(time.dwHighDateTime - ModuleLastUpdateTime.dwHighDateTime) << 32) + + time.dwLowDateTime - ModuleLastUpdateTime.dwLowDateTime }; + DWORD dwSecondsElapsed = tdiff / 10000; + if(dwSecondsElapsed >= MODULE_UPDATE_INTERVAL) { + modules.clear(); + hashes.clear(); + + std::vector processes(1024); + DWORD dwBytesNeeded{}; + auto success{ EnumProcesses(processes.data(), 1024 * sizeof(DWORD), &dwBytesNeeded) }; + if(dwBytesNeeded > 1024 * sizeof(DWORD)) { + processes.resize(dwBytesNeeded / sizeof(DWORD)); + success = EnumProcesses(processes.data(), dwBytesNeeded, &dwBytesNeeded); + } + + auto dwProcCount{ dwBytesNeeded / sizeof(DWORD) }; + for(int i = 0; i < dwProcCount; i++) { + auto modules{ EnumModules(processes[i]) }; + for(auto& mod : modules) { + auto name{ ToLowerCaseW(mod) }; + if(FileScanner::modules.find(name) == FileScanner::modules.end()) { + FileScanner::modules.emplace(name, std::unordered_set{ processes[i] }); + } else + FileScanner::modules.at(name).emplace(processes[i]); + } + } + + for(auto& mod : modules) { + auto path{ FileSystem::SearchPathExecutable(mod.first) }; + if(path) { + auto hash{ FileSystem::File{ *path }.GetSHA256Hash() }; + if(hash) { + if(hashes.count(*hash)) { + hashes.at(*hash).emplace(mod.first); + } else { + hashes.emplace(*hash, std::unordered_set{ mod.first }); + } + } + } + } + + ModuleLastUpdateTime = time; + } +} + +bool IsPEFile(IN CONST FileSystem::File& file){ + if(file.GetFileExists()){ + if(!file.HasReadAccess()){ + LOG_WARNING(L"Unable to properly scan " << file << L" due to lack of read access."); + return false; + } + + auto headers{ file.Read(0x400) }; + MemoryWrapper<> memory{ static_cast(headers), headers.GetSize() }; + if(*memory.Convert() != 0x5A4D){ + return false; + } + + DWORD offset{ *memory.GetOffset(0x3C).Convert() }; + if(offset + 4 >= 0x400){ + LOG_INFO(2, "File " << file << " may conform to the PE32+ standard, but it is not normal PE."); + return false; + } + + return *memory.GetOffset(offset).Convert() == 0x4550UL; + } else{ + return false; + } +} + +bool FileScanner::PerformQuickScan(IN CONST std::wstring& string) { + if(FileSystem::CheckFileExists(string)) { + return !FileSystem::File{ string }.GetFileSigned(); + } else if(auto path{ FileSystem::SearchPathExecutable(string) }){ + return !FileSystem::File{ *path }.GetFileSigned(); + } else{ + return false; + } +} + +std::unordered_map, Association> +FileScanner::GetAssociatedDetections(IN CONST Detection& detection) { + if(detection.type != DetectionType::FileDetection || detection.info.certainty < Certainty::Moderate) { + return {}; + } + + std::unordered_map, Association> detections{}; + + auto data{ std::get(detection.data) }; + + if(data.Executor && !IsPEFile(*data.FileHandle) && ProcessScanner::PerformQuickScan(*data.Executor)) { + detections.emplace(Bluespawn::detections.AddDetection( + Detection{ ProcessDetectionData::CreateCommandDetectionData(*data.Executor) }), Association::Strong); + } + + if(data.FileFound) { + UpdateModules(); + + EnterCriticalSection(hGuard); + auto hashes{ this->hashes }; + auto modules{ this->modules }; + LeaveCriticalSection(hGuard); + + if(data.SHA256) { + if(hashes.count(*data.SHA256)) { + auto loaded{ hashes.at(*data.SHA256) }; + for(auto& lib : loaded) { + for(auto pid : modules.at(lib)) { + auto alloc{ GetModuleAddress(pid, lib) }; + if(alloc) { + auto dwAllocSize{ GetRegionSize(pid, alloc) }; + detections.emplace( + Bluespawn::detections.AddDetection(Detection{ + ProcessDetectionData::CreateImageDetectionData(pid, GetProcessImage(pid), lib) }), + Association::Certain); + } + } + } + } + } + + if(!detection.DetectionStale && data.FileHandle && Bluespawn::aggressiveness == Aggressiveness::Intensive) { + auto strings = ExtractStrings(data.FileHandle->Read(), 8); + auto filenames = ExtractFilePaths(strings); + for(auto& filename : filenames) { + if(FileScanner::PerformQuickScan(filename)){ + detections.emplace(Bluespawn::detections.AddDetection(Detection{ FileDetectionData{ filename } }), + Association::Weak); + } + } + } + } + + return detections; +} + +Certainty FileScanner::ScanDetection(IN CONST Detection& detection) { + Certainty certainty{ Certainty::None }; + if(detection.type == DetectionType::FileDetection) { + auto& file{ std::get(detection.data) }; + if(!file.FileFound) { + return Certainty::None; + } + + if(IsPEFile(*file.FileHandle)){ + if(file.FileHandle->IsMicrosoftSigned()){ + return Certainty::None; + } + if(!*file.FileSigned){ + certainty = certainty + Certainty::Moderate; + } + if(file.yara){ + for(auto& rule : file.yara->vKnownBadRules){ + // Tune this! + certainty = certainty + Certainty::Moderate; + } + } + } else{ + if(file.Executor){ + if(ProcessScanner::PerformQuickScan(*file.Executor)){ + certainty = certainty + Certainty::Moderate; + } + } + } + + LOG_INFO(2, L"Scanned file " << file.FilePath << ". Certainty: " << static_cast(certainty)); + } + return certainty; +} diff --git a/BLUESPAWN-win-client/src/scan/MemoryScanner.cpp b/BLUESPAWN-win-client/src/scan/MemoryScanner.cpp new file mode 100644 index 00000000..ff4504ea --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/MemoryScanner.cpp @@ -0,0 +1,121 @@ +#include "scan/MemoryScanner.h" + +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "util/processes/ProcessUtils.h" +#include "util/wrappers.hpp" + +#include "hunt/Hunt.h" +#include "scan/FileScanner.h" +#include "scan/RegistryScanner.h" +#include "scan/YaraScanner.h" +#include "user/bluespawn.h" + +std::unordered_map, Association> +MemoryScanner::GetAssociatedDetections(IN CONST Detection& detection) { + if(detection.type != DetectionType::ProcessDetection || detection.DetectionStale) { + return {}; + } + + ProcessDetectionData data{ std::get(detection.data) }; + if(!data.BaseAddress || !data.MemorySize) { + return {}; + } + + std::unordered_map, Association> detections{}; + + if(data.ImageName) { + if(data.type == ProcessDetectionType::MaliciousImage) { + detections.emplace(Bluespawn::detections.AddDetection(Detection{ FileDetectionData{ *data.ImageName } }), + Association::Certain); + } else { + detections.emplace(Bluespawn::detections.AddDetection( + Detection{ FileDetectionData{ *data.ImageName }, + DetectionContext{ std::nullopt, detection.context.FirstEvidenceTime, + L"This image appears to have been modified to behave " + "maliciously, so while it's possible this file is " + "malicious, this detection was created to serve as an " + "IoC" } }), + Association::Moderate); + } + } + + if(Bluespawn::aggressiveness > Aggressiveness::Normal && data.ProcessHandle && + detection.info.certainty >= Certainty::Moderate) { + auto memory{ Utils::Process::ReadProcessMemory(*data.ProcessHandle, *data.BaseAddress, *data.MemorySize) }; + if(memory) { + auto strings = FileScanner::ExtractStrings(memory, 8); + auto filenames = FileScanner::ExtractFilePaths(strings); + for(auto& filename : filenames) { + if(FileScanner::PerformQuickScan(filename)) { + detections.emplace(Bluespawn::detections.AddDetection(Detection{ FileDetectionData{ filename } }), + Association::Weak); + } + } + } + } + + return detections; +} + +bool MemoryScanner::PerformQuickScan(IN CONST std::wstring& in) { + return false; +} + +#define IS_PAGE_EXECUTABLE(prot) (prot & 0xF0) +#define IS_PAGE_WRITECOPY(prot) (prot == 0x80 || prot == 0x08) +#define IS_PAGE_WRITEABLE(prot) (prot & 0xCC) + +Certainty MemoryScanner::ScanDetection(IN CONST Detection& detection) { + if(detection.type != DetectionType::ProcessDetection || detection.DetectionStale) { + return Certainty::None; + } + + ProcessDetectionData data{ std::get(detection.data) }; + if(!data.BaseAddress || !data.MemorySize || !data.ProcessHandle) { + return Certainty::None; + } + + Certainty certainty{ Certainty::None }; + + if(Bluespawn::aggressiveness == Aggressiveness::Intensive) { + auto& scanner{ YaraScanner::GetInstance() }; + auto scan{ scanner.ScanMemory(MemoryWrapper<>{ *data.BaseAddress, *data.MemorySize, *data.ProcessHandle }) }; + for(auto& rule : scan.vKnownBadRules) { + LOG_INFO(2, L"Memory at " << *data.BaseAddress << L" in process with PID " + << GetProcessId(*data.ProcessHandle) << L" violates yara rule " << rule); + certainty = certainty + Certainty::Moderate; + } + } + + MEMORY_BASIC_INFORMATION info{}; + if(VirtualQueryEx(*data.ProcessHandle, *data.BaseAddress, &info, sizeof(info))) { + bool wc{ IS_PAGE_WRITECOPY(info.AllocationProtect) }; + if(IS_PAGE_EXECUTABLE(info.AllocationProtect) && !wc) { + certainty = certainty + Certainty::Moderate; + LOG_INFO(3, L"Allocation at " << *data.BaseAddress << L" in process with PID " + << GetProcessId(*data.ProcessHandle) << L" has suspicious permissions " + << std::hex << info.Protect); + } + + auto addr{ reinterpret_cast(*data.BaseAddress) }; + while(addr < reinterpret_cast(*data.BaseAddress) + *data.MemorySize) { + if(VirtualQueryEx(*data.ProcessHandle, addr, &info, sizeof(info))) { + if(IS_PAGE_EXECUTABLE(info.Protect) && (IS_PAGE_WRITEABLE(info.Protect) || !wc)) { + LOG_INFO(3, L"Page at " << reinterpret_cast(addr) << L" in process with PID " + << GetProcessId(*data.ProcessHandle) << L" has suspicious permissions " + << std::hex << info.Protect); + certainty = certainty + Certainty::Moderate; + } + addr += info.RegionSize; + } else + return certainty; + } + } else { + LOG_WARNING(L"Failed to analyze memory protections for memory at " + << *data.BaseAddress << L" in process with PID " << GetProcessId(*data.ProcessHandle) + << L" with error 0x" << std::hex << GetLastError()); + } + + return certainty; +} diff --git a/BLUESPAWN-win-client/src/scan/ProcessScanner.cpp b/BLUESPAWN-win-client/src/scan/ProcessScanner.cpp new file mode 100644 index 00000000..2b7966e1 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/ProcessScanner.cpp @@ -0,0 +1,112 @@ +#include "scan/ProcessScanner.h" + +#include + +#include "util/configurations/Registry.h" +#include "util/filesystem/FileSystem.h" +#include "util/processes/CheckLolbin.h" +#include "util/processes/ProcessUtils.h" +#include "util/wrappers.hpp" + +#include "scan/FileScanner.h" +#include "user/bluespawn.h" + +std::unordered_map, Association> +ProcessScanner::SearchCommand(IN CONST std::wstring& ProcessCommand) { + // TODO: Better handling for parsing commands + //LOG_ERROR(L"Unable to properly scan command `" << ProcessCommand << L"`; function not implemented"); + std::unordered_map, Association> detections{}; + + auto image{ GetImagePathFromCommand(ProcessCommand) }; + if(FileScanner::PerformQuickScan(image)) { + detections.emplace(Bluespawn::detections.AddDetection(Detection{ FileDetectionData{ image } }), + Association::Strong); + } + + return detections; +} + +std::unordered_map, Association> +ProcessScanner::GetAssociatedDetections(IN CONST Detection& detection) { + if(detection.type != DetectionType::ProcessDetection) { + return {}; + } + + std::unordered_map, Association> detections{}; + ProcessDetectionData data{ std::get(detection.data) }; + + if((data.type == ProcessDetectionType::MaliciousProcess || data.type == ProcessDetectionType::MaliciousCommand) && + data.ProcessCommand) { + auto associated{ SearchCommand(*data.ProcessCommand) }; + for(auto& pair : associated) { + detections.emplace(pair.first, pair.second); + } + } + + if(data.type == ProcessDetectionType::MaliciousProcess) { + HandleWrapper snapshot{ CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; + + PROCESSENTRY32W entry{}; + entry.dwSize = sizeof(entry); + + if(Process32FirstW(snapshot, &entry)) { + do { + if(entry.th32ParentProcessID == data.PID) { + detections.emplace( + Bluespawn::detections.AddDetection(Detection{ + ProcessDetectionData::CreateProcessDetectionData(entry.th32ProcessID, entry.szExeFile) }), + Association::Moderate); + } else if(entry.th32ProcessID == data.PID) { + HandleWrapper parent{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, + entry.th32ParentProcessID) }; + if(parent) { + detections.emplace(Bluespawn::detections.AddDetection( + Detection{ ProcessDetectionData::CreateProcessDetectionData( + entry.th32ParentProcessID, GetProcessImage(parent)) }), + Association::Weak); + } else { + detections.emplace( + Bluespawn::detections.AddDetection(Detection{ + ProcessDetectionData::CreateProcessDetectionData(entry.th32ParentProcessID, L"Unknow" + L"n") }), + Association::Weak); + } + } + } while(Process32NextW(snapshot, &entry)); + } + } + + return detections; +} + +bool ProcessScanner::PerformQuickScan(IN CONST std::wstring& in) { + // `in` is a command. Start by finding the associated executable + auto file{ GetImagePathFromCommand(in) }; + + // Check if the file appears malicious + if(FileScanner::PerformQuickScan(file)) { + return true; + } + + // Check if the file appears to use a "lolbin" to obfuscate its execution + bool lolbin{ IsLolbinMalicious(in) }; + + return lolbin; +} + +Certainty ProcessScanner::ScanDetection(IN CONST Detection& detection) { + /// TODO: Implement check for LOLBins + + if(detection.type != DetectionType::ProcessDetection) { + return Certainty::None; + } + + ProcessDetectionData data{ std::get(detection.data) }; + if(data.type == ProcessDetectionType::MaliciousProcess && data.ProcessCommand) { + if(IsLolbinMalicious(*data.ProcessCommand)) { + return Certainty::Moderate; + } + } + + return Certainty::None; +} diff --git a/BLUESPAWN-win-client/src/scan/RegistryScanner.cpp b/BLUESPAWN-win-client/src/scan/RegistryScanner.cpp new file mode 100644 index 00000000..9e27ff93 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/RegistryScanner.cpp @@ -0,0 +1,101 @@ +#include "scan/RegistryScanner.h" + +#include "util/wrappers.hpp" +#include "util/StringUtils.h" +#include "util/configurations/RegistryValue.h" +#include "util/processes/ProcessUtils.h" +#include "scan/YaraScanner.h" +#include "scan/ProcessScanner.h" +#include "scan/FileScanner.h" +#include "user/bluespawn.h" + +#include + +std::vector RegistryScanner::ExtractRegistryKeys(IN CONST std::vector& strings){ + std::vector keys{}; + std::wregex regex{ L"(system|software)([/\\\\][a-zA-Z0-9\\. @_-]+)+" }; + for(auto& string : strings){ + std::wsmatch match{}; + auto lower{ ToLowerCaseW(string) }; + if(std::regex_search(lower, match, regex)){ + for(auto& keyname : match){ + for(auto hive : Registry::vHives){ + if(Registry::RegistryKey::CheckKeyExists(hive.first, keyname.str())){ + keys.emplace_back(hive.second + L"\\" + keyname.str()); + } + } + } + } + } + return keys; +} + +std::unordered_map, Association> RegistryScanner::GetAssociatedDetections( + IN CONST Detection& detection){ + if(detection.type != DetectionType::RegistryDetection || detection.DetectionStale){ + return {}; + } + + auto data{ std::get(detection.data) }; + if(!data.key.Exists()){ + return {}; + } + + std::unordered_map, Association> detections{}; + + if(!data.value){ + if(Bluespawn::aggressiveness == Aggressiveness::Intensive){ + for(auto val : data.key.EnumerateValues()){ + detections.emplace(Bluespawn::detections.AddDetection(Detection{ + RegistryDetectionData{ data.key, Registry::RegistryValue::Create(data.key, val) } + }), Association::Moderate); + } + } + + return detections; + } else if(!data.key.ValueExists(data.value->wValueName)){ + return {}; + } + + /// TODO: Add more of these + if(data.type == RegistryDetectionType::CommandReference){ + if(ProcessScanner::PerformQuickScan(std::get(data.value->data))){ + detections.emplace(Bluespawn::detections.AddDetection(Detection{ + ProcessDetectionData::CreateCommandDetectionData(std::get(data.value->data)) + }), Association::Certain); + } + } else if(data.type == RegistryDetectionType::FileReference){ + auto name{ std::get(data.value->data) }; + auto path{ FileSystem::SearchPathExecutable(name) }; + if(path && FileScanner::PerformQuickScan(*path)){ + detections.emplace(Bluespawn::detections.AddDetection(Detection{ + FileDetectionData{ *path } + }), Association::Certain); + } + } + + return detections; +} + +Certainty RegistryScanner::ScanDetection(IN CONST Detection& detection){ + if(Bluespawn::aggressiveness != Aggressiveness::Intensive || detection.type != DetectionType::RegistryDetection || + detection.DetectionStale){ + + return Certainty::None; + } + + Certainty certainty{ Certainty::None }; + auto data{ std::get(detection.data) }; + if(data.value){ + auto mem{ data.key.GetRawValue(data.value->wValueName) }; + if(mem.GetSize() > 0x10){ + auto result{ YaraScanner::GetInstance().ScanMemory(mem) }; + for(auto& rule : result.vKnownBadRules){ + // Tune this! + certainty = certainty + Certainty::Moderate; + } + } + } + + return certainty; +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/src/scan/ScanInfo.cpp b/BLUESPAWN-win-client/src/scan/ScanInfo.cpp new file mode 100644 index 00000000..56f46e4a --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/ScanInfo.cpp @@ -0,0 +1,85 @@ +#include "scan/ScanInfo.h" + +#include +#include +#include + +#include "scan/Detections.h" + +const Certainty Certainty::Certain = 1.00; +const Certainty Certainty::Strong = 0.75; +const Certainty Certainty::Moderate = 0.50; +const Certainty Certainty::Weak = 0.25; +const Certainty Certainty::None = 0.00; +Certainty::Certainty(double certainty) : confidence{ certainty }{} +Certainty::operator double() const { return confidence; } +Certainty Certainty::operator*(Certainty c) const { return confidence * c.confidence; } +Certainty Certainty::operator+(Certainty c) const { return 1 - (1 - confidence) * (1 - c.confidence); } +bool Certainty::operator==(Certainty c) const { + return c.confidence > confidence ? c.confidence - confidence <= 0.125 : confidence - c.confidence <= 0.125; +} +bool Certainty::operator!=(Certainty c) const { + return c.confidence > confidence ? c.confidence - confidence > 0.125 : confidence - c.confidence > 0.125; +} +bool Certainty::operator>=(Certainty c) const { return *this > c || *this == c; } +bool Certainty::operator<=(Certainty c) const { return *this > c || *this == c; } +bool Certainty::operator>(Certainty c) const { return confidence > c.confidence; } +bool Certainty::operator<(Certainty c) const { return confidence < c.confidence; } + +volatile std::atomic Detection::IDCounter{ 1 }; + +ScanInfo::ScanInfo() : + certainty{ Certainty::None }, + cAssociativeCertainty{ Certainty::None }, + associations{ std::make_unique, Association>>() }, + bAssociativeStale{ true }{} + +std::unordered_map, Association> ScanInfo::GetAssociations(){ + BeginCriticalSection _{ hGuard }; + return *associations; +} + +Certainty ScanInfo::GetCertainty(){ + BeginCriticalSection _{ hGuard }; + if(bAssociativeStale){ + cAssociativeCertainty = Certainty::None; + + for(auto& pair : *associations){ + LeaveCriticalSection(hGuard); + auto raw{ pair.first->info.GetIntrinsicCertainty() }; + EnterCriticalSection(hGuard); + cAssociativeCertainty = cAssociativeCertainty + (raw * pair.second); + } + + bAssociativeStale = false; + } + return certainty + cAssociativeCertainty; +}; + +Certainty ScanInfo::GetIntrinsicCertainty(){ + BeginCriticalSection _{ hGuard }; + return certainty; +}; + +void ScanInfo::AddAssociation(IN CONST std::shared_ptr& node, IN CONST Association& a){ + BeginCriticalSection _{ hGuard }; + bAssociativeStale = true; + if(associations->find(node) == associations->end()){ + associations->emplace(node, a); + } else{ + auto& assoc{ associations->at(node) }; + assoc = assoc + a; + } +} + +void ScanInfo::SetCertainty(IN CONST Certainty& certainty){ + this->certainty = certainty; +} + +void ScanInfo::AddCertainty(IN CONST Certainty& certainty){ + this->certainty = this->certainty + certainty; +} + +ScanInfo::operator LPCRITICAL_SECTION() const { + return hGuard; +} \ No newline at end of file diff --git a/BLUESPAWN-win-client/src/scan/Scanner.cpp b/BLUESPAWN-win-client/src/scan/Scanner.cpp new file mode 100644 index 00000000..229863e7 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/Scanner.cpp @@ -0,0 +1,22 @@ +#include "scan/Scanner.h" + +#include "scan/FileScanner.h" +#include "scan/MemoryScanner.h" +#include "scan/ProcessScanner.h" +#include "scan/RegistryScanner.h" + +std::vector> Scanner::scanners{ + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), +}; + +std::unordered_map, Association> +Scanner::GetAssociatedDetections(IN CONST Detection& detection) { + return {}; +} + +Certainty Scanner::ScanDetection(IN CONST Detection& detection) { + return Certainty::None; +} diff --git a/BLUESPAWN-win-client/src/scan/ServiceScanner.cpp b/BLUESPAWN-win-client/src/scan/ServiceScanner.cpp new file mode 100644 index 00000000..71ad36d3 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/ServiceScanner.cpp @@ -0,0 +1,125 @@ +#include "scan/ServiceScanner.h" + +#include + +#include "util/StringUtils.h" + +#include "scan/ProcessScanner.h" +#include "user/bluespawn.h" + +std::unordered_map, Association> +ServiceScanner::GetAssociatedDetections(IN CONST Detection& detection) { + if(detection.type != DetectionType::ServiceDetection) { + return {}; + } + + std::unordered_map, Association> detections{}; + ServiceDetectionData data{ std::get(detection.data) }; + + if(data.FilePath) { + detections.emplace(Bluespawn::detections.AddDetection(Detection{ FileDetectionData{ *data.FilePath } }), + Association::Certain); + } + + if(!detection.DetectionStale) { + std::wstring name{}; + if(!data.ServiceName && data.DisplayName) { + GenericWrapper ServiceManager{ OpenSCManagerW(nullptr, nullptr, GENERIC_READ), + CloseServiceHandle }; + std::vector KeyName(256); + DWORD size{}; + if(!GetServiceKeyNameW(ServiceManager, data.DisplayName->c_str(), KeyName.data(), &size)) { + KeyName.resize(size); + if(GetServiceKeyNameW(ServiceManager, data.DisplayName->c_str(), KeyName.data(), &size)) { + name = KeyName.data(); + } + } else { + name = KeyName.data(); + } + } + + if(name.length() || data.ServiceName) { + if(data.ServiceName) { + name = *data.ServiceName; + } + + Registry::RegistryKey ServicesKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services" }; + + std::queue bfsQueue{}; + bfsQueue.emplace(Registry::RegistryKey{ ServicesKey, name }); + std::set visited{}; // To avoid symlink loops + while(!bfsQueue.empty()) { + auto key{ bfsQueue.front() }; + bfsQueue.pop(); + + if(visited.find(key) == visited.end()) { + visited.emplace(key); + + for(auto name : key.EnumerateValues()) { + auto val{ Registry::RegistryValue::Create(key, name) }; + + // TODO: Add support for exes and dlls in REG_MULTI_SZ values + if(val && val->GetType() == RegistryType::REG_SZ_T || + val->GetType() == RegistryType::REG_EXPAND_SZ_T) { + auto str{ ToLowerCaseW(std::get(val->data)) }; + if(str.find(L".exe") || str.find(L".dll")) { + detections.emplace(Bluespawn::detections.AddDetection(Detection{ RegistryDetectionData{ + key, val, RegistryDetectionType::FileReference } }), + Association::Strong); + } + } + } + + for(auto subkey : key.EnumerateSubkeys()) { + bfsQueue.emplace(subkey); + } + } + } + } + } + + return detections; +} + +bool StringContainsKeywords(IN CONST std::wstring& str) { + auto name{ ToLowerCaseW(str) }; + return name.find(L"psexecsvc") != std::wstring::npos || name.find(L"mimi") != std::wstring::npos; +} + +bool ServiceScanner::PerformQuickScan(IN CONST std::optional& ServiceName, + IN CONST std::optional& ServiceDisplayName, + IN CONST std::optional& ServicePath OPTIONAL) { + if(ServicePath) { + if(ProcessScanner::PerformQuickScan(*ServicePath)) { + return true; + } + + if(ServicePath->find(L"mimidrv.sys") != std::wstring::npos) { + return true; + } + } + + if(ServiceName && StringContainsKeywords(*ServiceName)) { + return true; + } + + if(ServiceDisplayName && StringContainsKeywords(*ServiceDisplayName)) { + return true; + } + + return false; +} + +Certainty ServiceScanner::ScanDetection(IN CONST Detection& detection) { + if(detection.type == DetectionType::ServiceDetection) { + ServiceDetectionData data{ std::get(detection.data) }; + if(data.ServiceName && StringContainsKeywords(*data.ServiceName)) + return Certainty::Strong; + if(data.DisplayName && StringContainsKeywords(*data.DisplayName)) + return Certainty::Strong; + if(data.FilePath && StringContainsKeywords(*data.FilePath)) + return Certainty::Strong; + } + + return Certainty::None; +} diff --git a/BLUESPAWN-win-client/src/scan/YaraScanner.cpp b/BLUESPAWN-win-client/src/scan/YaraScanner.cpp new file mode 100644 index 00000000..2d2b3dc3 --- /dev/null +++ b/BLUESPAWN-win-client/src/scan/YaraScanner.cpp @@ -0,0 +1,235 @@ +#include "scan/YaraScanner.h" + +#include + +#include "util/StringUtils.h" +#include "util/log/Log.h" +#include "util/wrappers.hpp" + +#include "../resources/resource.h" +#include "yara/libyara.h" +#include "yara/rules.h" + +const YaraScanner YaraScanner::instance{}; + +AllocationWrapper GetResourceRule(DWORD identifier) { + auto hRsrcInfo = FindResourceW(nullptr, MAKEINTRESOURCE(identifier), L"yararule"); + if(!hRsrcInfo) { + return { nullptr, 0 }; + } + + auto hRsrc = LoadResource(nullptr, hRsrcInfo); + if(!hRsrc) { + return { nullptr, 0 }; + } + + zip_error_t err{}; + auto lpZipSource = zip_source_buffer_create(LockResource(hRsrc), SizeofResource(nullptr, hRsrcInfo), 0, &err); + if(lpZipSource) { + auto zip = zip_open_from_source(lpZipSource, 0, &err); + if(zip) { + auto fdRules = zip_fopen(zip, "data", 0); + if(fdRules) { + zip_stat_t stats{}; + if(-1 != zip_stat(zip, "data", ZIP_STAT_SIZE, &stats)) { + if(-1 != stats.size) { + AllocationWrapper data{ HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + static_cast(stats.size)), + static_cast(stats.size), AllocationWrapper::HEAP_ALLOC }; + if(-1 != zip_fread(fdRules, data, stats.size)) { + zip_fclose(fdRules); + zip_close(zip); + zip_source_close(lpZipSource); + + return data; + } + } + } + + zip_fclose(fdRules); + } + zip_close(zip); + } + zip_source_close(lpZipSource); + } + + return { nullptr, 0 }; +} + +struct AllocationWrapperStream { + AllocationWrapper wrapper; + size_t offset; +}; + +size_t ReadAllocationWrapper(LPVOID dest, size_t size, size_t count, AllocationWrapperStream* data) { + size_t desired_amnt = size * count; + size_t actual_amnt = min(desired_amnt, data->wrapper.GetSize() - data->offset); + + CopyMemory(dest, reinterpret_cast((LPVOID) data->wrapper) + data->offset, actual_amnt); + + data->offset += actual_amnt; + return actual_amnt / size; +} + +YR_RULES* LoadRules(const AllocationWrapper& memory) { + AllocationWrapperStream stream_data = { memory, 0 }; + YR_STREAM stream = { &stream_data, YR_STREAM_READ_FUNC(ReadAllocationWrapper) }; + YR_RULES* rules; + auto status = yr_rules_load_stream(&stream, &rules); + if(status != ERROR_SUCCESS) { + return nullptr; + } + return rules; +} + +YaraScanner::YaraScanner() : status{ YaraStatus::Success } { + yr_initialize(); + + auto hSevereYara = GetResourceRule(YaraSevere); + if(!hSevereYara) { + status = YaraStatus::RulesMissing; + return; + } + KnownBad = LoadRules(hSevereYara); + if(!KnownBad) { + status = YaraStatus::RulesMissing; + return; + } + + auto hSevereYara2 = GetResourceRule(YaraSevere2); + if(!hSevereYara2) { + status = YaraStatus::RulesMissing; + return; + } + KnownBad2 = LoadRules(hSevereYara2); + if(!KnownBad2) { + status = YaraStatus::RulesMissing; + return; + } + + auto hIndicatorsYara = GetResourceRule(YaraIndicators); + if(!hIndicatorsYara) { + status = YaraStatus::RulesInvalid; + return; + } + Indicators = LoadRules(hIndicatorsYara); + if(!Indicators) { + status = YaraStatus::RulesInvalid; + return; + } +} + +YaraScanner::~YaraScanner() { + if(KnownBad) { + yr_rules_destroy(KnownBad); + KnownBad = nullptr; + } + + if(KnownBad2) { + yr_rules_destroy(KnownBad2); + KnownBad2 = nullptr; + } + + if(Indicators) { + yr_rules_destroy(Indicators); + Indicators = nullptr; + } + yr_finalize(); +} + +const YaraScanner& YaraScanner::GetInstance() { + return instance; +} + +struct YaraScanArg { + YaraScanResult result; + enum { Severe, Indicator } type; +}; + +int YaraCallbackFunction(int message, LPVOID lpMessageData, YaraScanArg* arg) { + if(message == CALLBACK_MSG_RULE_MATCHING) { + auto rule = reinterpret_cast(lpMessageData); + if(arg->type == arg->Severe) { + arg->result.AddBadRule(StringToWidestring(rule->identifier)); + } else if(arg->type == arg->Indicator) { + arg->result.AddIndicatorRule(StringToWidestring(rule->identifier)); + } + } + return CALLBACK_CONTINUE; +} + +YaraScanResult YaraScanner::ScanFile(const FileSystem::File& file) const { + if(status != YaraStatus::Success) { + YaraScanResult res = {}; + res.status = status; + return res; + } + + auto memory = file.Read(); + if(!memory) { + YaraScanResult result{}; + result.status = YaraStatus::Failure; + return result; + } + auto result{ ScanMemory(memory) }; + + for(auto identifier : result.vKnownBadRules) { + LOG_INFO(1, file.GetFilePath() << L" matches known malicious identifier " << identifier); + } + for(auto identifier : result.vIndicatorRules) { + LOG_INFO(2, file.GetFilePath() << L" matches known indicator identifier " << identifier); + } + + return result; +} + +YaraScanResult YaraScanner::ScanMemory(const AllocationWrapper& memory) const { + YaraScanArg arg{}; + arg.result.status = YaraStatus::Success; + arg.type = arg.Severe; + auto status = yr_rules_scan_mem(KnownBad, reinterpret_cast((LPVOID) memory), memory.GetSize(), 0, + YR_CALLBACK_FUNC(YaraCallbackFunction), &arg, 0); + if(status != ERROR_SUCCESS) { + arg.result.status = YaraStatus::Failure; + } + + arg.type = arg.Severe; + status = yr_rules_scan_mem(KnownBad2, reinterpret_cast((LPVOID) memory), memory.GetSize(), 0, + YR_CALLBACK_FUNC(YaraCallbackFunction), &arg, 0); + if(status != ERROR_SUCCESS) { + arg.result.status = YaraStatus::Failure; + } + + arg.type = arg.Indicator; + status = yr_rules_scan_mem(Indicators, reinterpret_cast((LPVOID) memory), memory.GetSize(), 0, + YR_CALLBACK_FUNC(YaraCallbackFunction), &arg, 0); + if(status != ERROR_SUCCESS) { + arg.result.status = YaraStatus::Failure; + } + + return arg.result; +} + +YaraScanResult YaraScanner::ScanMemory(LPVOID memory, DWORD dwSize) const { + return ScanMemory(AllocationWrapper{ memory, dwSize }); +} + +YaraScanResult YaraScanner::ScanMemory(const MemoryWrapper<>& memory) const { + return ScanMemory(memory.ToAllocationWrapper()); +} + +void YaraScanResult::AddBadRule(const std::wstring& identifier) { + vKnownBadRules.emplace_back(identifier); +} + +void YaraScanResult::AddIndicatorRule(const std::wstring& identifier) { + vIndicatorRules.emplace_back(identifier); +} + +YaraScanResult::operator bool() { + return vKnownBadRules.empty() && status == YaraStatus::Success; +} + +bool YaraScanResult::operator!() { + return !(vKnownBadRules.empty() && status == YaraStatus::Success); +} diff --git a/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp b/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp new file mode 100644 index 00000000..b3c38456 --- /dev/null +++ b/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp @@ -0,0 +1,459 @@ +#include "user/bluespawn.h" + +#include +#include + +#include "util/DynamicLinker.h" +#include "util/StringUtils.h" +#include "util/ThreadPool.h" +#include "util/eventlogs/EventLogs.h" +#include "util/log/CLISink.h" +#include "util/log/DebugSink.h" +#include "util/log/XMLSink.h" + +#include "hunt/hunts/HuntT1036.h" +#include "hunt/hunts/HuntT1037.h" +#include "hunt/hunts/HuntT1053.h" +#include "hunt/hunts/HuntT1055.h" +#include "hunt/hunts/HuntT1068.h" +#include "hunt/hunts/HuntT1070.h" +#include "hunt/hunts/HuntT1136.h" +#include "hunt/hunts/HuntT1484.h" +#include "hunt/hunts/HuntT1505.h" +#include "hunt/hunts/HuntT1543.h" +#include "hunt/hunts/HuntT1546.h" +#include "hunt/hunts/HuntT1547.h" +#include "hunt/hunts/HuntT1553.h" +#include "hunt/hunts/HuntT1562.h" +#include "hunt/hunts/HuntT1569.h" +#include "mitigation/mitigations/MitigateM1025.h" +#include "mitigation/mitigations/MitigateM1028-WFW.h" +#include "mitigation/mitigations/MitigateM1035-RDP.h" +#include "mitigation/mitigations/MitigateM1042-LLMNR.h" +#include "mitigation/mitigations/MitigateM1042-NBT.h" +#include "mitigation/mitigations/MitigateM1042-WSH.h" +#include "mitigation/mitigations/MitigateM1047.h" +#include "mitigation/mitigations/MitigateM1054-RDP.h" +#include "mitigation/mitigations/MitigateM1054-WSC.h" +#include "mitigation/mitigations/MitigateV1093.h" +#include "mitigation/mitigations/MitigateV1153.h" +#include "mitigation/mitigations/MitigateV3338.h" +#include "mitigation/mitigations/MitigateV3340.h" +#include "mitigation/mitigations/MitigateV3344.h" +#include "mitigation/mitigations/MitigateV3379.h" +#include "mitigation/mitigations/MitigateV3479.h" +#include "mitigation/mitigations/MitigateV63597.h" +#include "mitigation/mitigations/MitigateV63687.h" +#include "mitigation/mitigations/MitigateV63753.h" +#include "mitigation/mitigations/MitigateV63817.h" +#include "mitigation/mitigations/MitigateV63825.h" +#include "mitigation/mitigations/MitigateV63829.h" +#include "mitigation/mitigations/MitigateV71769.h" +#include "mitigation/mitigations/MitigateV72753.h" +#include "mitigation/mitigations/MitigateV73511.h" +#include "mitigation/mitigations/MitigateV73519.h" +#include "mitigation/mitigations/MitigateV73585.h" +#include "reaction/CarveMemory.h" +#include "reaction/DeleteFile.h" +#include "reaction/QuarantineFile.h" +#include "reaction/RemoveValue.h" +#include "reaction/SuspendProcess.h" +#include "user/CLI.h" + +#pragma warning(push) + +#pragma warning(disable : 26451) +#pragma warning(disable : 26444) + +#include "cxxopts.hpp" + +#pragma warning(pop) + +#include + +#include + +DEFINE_FUNCTION(BOOL, IsWow64Process2, NTAPI, HANDLE hProcess, USHORT* pProcessMachine, USHORT* pNativeMachine); +LINK_FUNCTION(IsWow64Process2, KERNEL32.DLL); + +const IOBase& Bluespawn::io = CLI::GetInstance(); +HuntRegister Bluespawn::huntRecord{}; +MitigationRegister Bluespawn::mitigationRecord{ io }; +Aggressiveness Bluespawn::aggressiveness{ Aggressiveness::Normal }; +DetectionRegister Bluespawn::detections{ Certainty::Moderate }; +ReactionManager Bluespawn::reaction{}; +std::vector> Bluespawn::detectionSinks{}; +bool Bluespawn::EnablePreScanDetections{ false }; + +std::map> reactions{}; + +Bluespawn::Bluespawn() { + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + huntRecord.RegisterHunt(std::make_unique()); + + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + mitigationRecord.RegisterMitigation(std::make_shared()); + + reactions.emplace("carve-memory", std::make_unique()); + reactions.emplace("delete-file", std::make_unique()); + reactions.emplace("quarantine-file", std::make_unique()); + reactions.emplace("remove-value", std::make_unique()); + reactions.emplace("suspend", std::make_unique()); +} + +void Bluespawn::RunHunts() { + Bluespawn::io.InformUser(L"Starting a Hunt"); + DWORD tactics = UINT_MAX; + DWORD dataSources = UINT_MAX; + DWORD affectedThings = UINT_MAX; + Scope scope{}; + + huntRecord.RunHunts(vIncludedHunts, vExcludedHunts, scope); +} + +void Bluespawn::RunMitigations(bool enforce, bool force) { + if(enforce) { + Bluespawn::io.InformUser(L"Enforcing Mitigations"); + mitigationRecord.EnforceMitigations(SecurityLevel::High, force); + } else { + Bluespawn::io.InformUser(L"Auditing Mitigations"); + mitigationRecord.AuditMitigations(SecurityLevel::High); + } +} + +void Bluespawn::RunMonitor() { + DWORD tactics = UINT_MAX; + DWORD dataSources = UINT_MAX; + DWORD affectedThings = UINT_MAX; + Scope scope{}; + + Bluespawn::io.InformUser(L"Monitoring the system"); + huntRecord.SetupMonitoring(vIncludedHunts, vExcludedHunts); + + HandleWrapper hRecordEvent{ CreateEventW(nullptr, false, false, L"Local\\FlushLogs") }; + while(true) { + SetEvent(hRecordEvent); + Sleep(5000); + } +} + +void Bluespawn::AddReaction(std::unique_ptr&& reaction) { + Bluespawn::reaction.AddHandler(std::move(reaction)); +} + +void Bluespawn::EnableMode(BluespawnMode mode, int option) { + modes.emplace(mode, option); +} + +void Bluespawn::SetIncludedHunts(std::vector includedHunts) { + for(auto& id : includedHunts) { + Bluespawn::vIncludedHunts.emplace_back(StringToWidestring(id)); + } +} + +void Bluespawn::SetExcludedHunts(std::vector excludedHunts) { + for(auto& id : excludedHunts) { + Bluespawn::vExcludedHunts.emplace_back(StringToWidestring(id)); + } +} + +void Bluespawn::Run() { + if(modes.find(BluespawnMode::SCAN) != modes.end()) { + aggressiveness = static_cast(modes.at(BluespawnMode::SCAN)); + } else { + aggressiveness = Aggressiveness::Normal; + } + if(modes.find(BluespawnMode::MITIGATE) != modes.end()) { + RunMitigations(modes[BluespawnMode::MITIGATE] & 0x01, modes[BluespawnMode::MITIGATE] & 0x02); + } + if(modes.find(BluespawnMode::HUNT) != modes.end()) { + RunHunts(); + } + if(modes.find(BluespawnMode::MONITOR) != modes.end()) { + RunMonitor(); + } + + ThreadPool::GetInstance().Wait(); + Bluespawn::detections.Wait(); +} + +void print_help(cxxopts::ParseResult result, cxxopts::Options options) { + std::string help_category = result["help"].as(); + + std::string output = ""; + if(CompareIgnoreCase(help_category, std::string{ "hunt" })) { + output = options.help({ "hunt" }); + } else if(CompareIgnoreCase(help_category, std::string{ "monitor" })) { + output = std::regex_replace(options.help({ "hunt" }), std::regex("hunt options"), "monitor options"); + } else if(CompareIgnoreCase(help_category, std::string{ "mitigate" })) { + output = options.help({ "mitigate" }); + } else { + output = std::regex_replace(options.help(), std::regex("hunt options"), "hunt/monitor options"); + } + Bluespawn::io.InformUser(StringToWidestring(output)); +} + +void Bluespawn::check_correct_arch() { + BOOL bIsWow64 = FALSE; + if(IsWindows10OrGreater() && Linker::IsWow64Process2) { + USHORT ProcessMachine; + USHORT NativeMachine; + Linker::IsWow64Process2(GetCurrentProcess(), &ProcessMachine, &NativeMachine); + if(ProcessMachine != IMAGE_FILE_MACHINE_UNKNOWN) { + bIsWow64 = TRUE; + } + } else { + IsWow64Process(GetCurrentProcess(), &bIsWow64); + } + if(bIsWow64) { + Bluespawn::io.AlertUser(L"Running the x86 version of BLUESPAWN on an x64 system! This configuration is not " + L"fully supported, so we recommend downloading the x64 version.", + 5000, ImportanceLevel::MEDIUM); + LOG_WARNING("Running the x86 version of BLUESPAWN on an x64 system! This configuration is not fully supported, " + "so we recommend downloading the x64 version."); + } +} + +void ParseLogSinks(const std::string& sinks) { + std::set sink_set; + for(unsigned startIdx = 0; startIdx < sinks.size();) { + auto endIdx{ sinks.find(',', startIdx) }; + auto sink{ sinks.substr(startIdx, endIdx - startIdx) }; + sink_set.emplace(sink); + startIdx = endIdx + 1; + if(endIdx == std::string::npos) { + break; + } + } + + std::vector> levels{ + Log::LogLevel::LogError, Log::LogLevel::LogWarn, Log::LogLevel::LogInfo1, Log::LogLevel::LogInfo2, + Log::LogLevel::LogInfo3, Log::LogLevel::LogVerbose1, Log::LogLevel::LogVerbose2, Log::LogLevel::LogVerbose3, + }; + + for(auto sink : sink_set) { + if(sink == "console") { + auto console = std::make_shared(); + Log::AddSink(console, levels); + Bluespawn::detectionSinks.emplace_back(console); + } else if(sink == "xml") { + auto XML = std::make_shared(); + Log::AddSink(XML, levels); + Bluespawn::detectionSinks.emplace_back(XML); + } else if(sink == "debug") { + auto debug = std::make_shared(); + Log::AddSink(debug, levels); + Bluespawn::detectionSinks.emplace_back(debug); + } else { + Bluespawn::io.AlertUser(L"Unknown log sink \"" + StringToWidestring(sink) + L"\"", INFINITY, + ImportanceLevel::MEDIUM); + } + } +} + +Aggressiveness GetAggressiveness(const cxxopts::OptionValue& value) { + Aggressiveness aHuntLevel{}; + auto level{ value.as() }; + + if(CompareIgnoreCase(level, "Cursory")) { + aHuntLevel = Aggressiveness::Cursory; + } else if(CompareIgnoreCase(level, "Normal")) { + aHuntLevel = Aggressiveness::Normal; + } else if(CompareIgnoreCase(level, "Intensive")) { + aHuntLevel = Aggressiveness::Intensive; + } else { + LOG_ERROR("Error " << StringToWidestring(level) + << " - Unknown level. Please specify either Cursory, Normal, or Intensive"); + LOG_ERROR("Will default to Normal for this run."); + Bluespawn::io.InformUser(L"Error " + StringToWidestring(level) + + L" - Unknown level. Please specify either Cursory, Normal, or Intensive"); + Bluespawn::io.InformUser(L"Will default to Normal."); + aHuntLevel = Aggressiveness::Normal; + } + + return aHuntLevel; +} +int main(int argc, char* argv[]) { + Log::LogLevel::LogError.Enable(); + Log::LogLevel::LogWarn.Enable(); + ThreadPool::GetInstance().AddExceptionHandler([](const auto& e) { LOG_ERROR(e.what()); }); + + Bluespawn bluespawn{}; + + print_banner(); + + bluespawn.check_correct_arch(); + + if(argc == 1) { + Bluespawn::io.AlertUser(L"Please launch BLUESPAWN from a CLI and specify what you want it to do. You can use " + L"the --help flag to see what options are available.", + INFINITE, ImportanceLevel::MEDIUM); + } + + cxxopts::Options options("BLUESPAWN.exe", "BLUESPAWN: An Active Defense and EDR software to empower Blue Teams"); + + // clang-format off + options.add_options() + ("h,hunt", "Hunt for malicious activity on the system", cxxopts::value()) + ("n,monitor", "Monitor the system for malicious activity, dispatching hunts as changes are detected.", + cxxopts::value()) + ("m,mitigate", "Mitigate vulnerabilities by applying security settings.", + cxxopts::value()) + ("log", "Specify how BLUESPAWN should log events. Options are console, xml, and debug.", + cxxopts::value()->default_value("console")) + ("help", "Help Information. You can also specify a category for help on a specific module such as hunt.", + cxxopts::value()->implicit_value("general")) + ("v,verbose", "Verbosity", cxxopts::value()->default_value("1")) + ("debug", "Enable Debug Output", cxxopts::value()->default_value("0")) + ; + + options.add_options("hunt") + ("a,aggressiveness", "Sets the aggressiveness of BLUESPAWN. Options are cursory, normal, and intensive.", + cxxopts::value()->default_value("Normal")) + ("hunts", "Only run the hunts specified. Provide as a comma separated list of Mitre ATT&CK Technique IDs.", + cxxopts::value>()) + ("exclude-hunts", "Run all hunts except those specified. Provide as a comma separated list of Mitre ATT&CK Technique IDs.", + cxxopts::value>()) + ("r,react", "Specifies how BLUESPAWN should react to potential threats dicovered during hunts. Available reactions are remove-value, carve-memory, suspend, delete-file, and quarantine-file", + cxxopts::value()->default_value("")) + ; + + options.add_options("mitigate") + ("action", "Selects whether to audit or enforce each mitigations.", + cxxopts::value()->default_value("audit")->implicit_value("audit")) + ("force", "Use this option to forcibly apply mitigations with no prompt", + cxxopts::value()) + ; + // clang-format on + + try { + auto result = options.parse(argc, argv); + + if(result.count("help")) { + print_help(result, options); + return 0; + } + + if(result["verbose"].as() >= 1) { + Log::LogLevel::LogInfo1.Enable(); + } + if(result["verbose"].as() >= 2) { + Log::LogLevel::LogInfo2.Enable(); + } + if(result["verbose"].as() >= 3) { + Log::LogLevel::LogInfo3.Enable(); + } + + if(result.count("debug")) { + if(result["debug"].as() >= 1) { + Log::LogLevel::LogVerbose1.Enable(); + } + if(result["debug"].as() >= 2) { + Log::LogLevel::LogVerbose2.Enable(); + } + if(result["debug"].as() >= 3) { + Log::LogLevel::LogVerbose3.Enable(); + } + } + + ParseLogSinks(result["log"].as()); + + if(result.count("hunt") || result.count("monitor")) { + if(result.count("hunt")) { + bluespawn.EnableMode(BluespawnMode::HUNT); + } + if(result.count("monitor")) { + bluespawn.EnableMode(BluespawnMode::MONITOR); + } + + if(result.count("aggressiveness")) { + bluespawn.EnableMode(BluespawnMode::SCAN, static_cast(GetAggressiveness(result["aggressivenes" + "s"]))); + } + + if(result.count("hunts")) { + bluespawn.SetIncludedHunts(result["hunts"].as>()); + } else if(result.count("exclude-hunts")) { + bluespawn.SetExcludedHunts(result["exclude-hunts"].as>()); + } + + auto UserReactions = result["react"].as(); + std::set reaction_set; + for(unsigned startIdx = 0; startIdx < UserReactions.size();) { + auto endIdx{ UserReactions.find(',', startIdx) }; + auto sink{ UserReactions.substr(startIdx, endIdx - startIdx) }; + reaction_set.emplace(sink); + startIdx = endIdx + 1; + if(endIdx == std::string::npos) { + break; + } + } + + for(auto reaction : reaction_set) { + if(reactions.find(reaction) != reactions.end()) { + bluespawn.AddReaction(std::move(reactions[reaction])); + } else { + bluespawn.io.AlertUser(L"Unknown reaction \"" + StringToWidestring(reaction) + L"\"", INFINITY, + ImportanceLevel::MEDIUM); + } + } + } + + if(result.count("mitigate")) { + bool bForceEnforce = false; + if(result.count("force")) + bForceEnforce = true; + + MitigationMode mode = MitigationMode::Audit; + if(result["action"].as() == "e" || result["action"].as() == "enforce") + mode = MitigationMode::Enforce; + + bluespawn.EnableMode(BluespawnMode::MITIGATE, + (static_cast(bForceEnforce) << 1) | (static_cast(mode) << 0)); + } + + bluespawn.Run(); + } catch(cxxopts::OptionParseException e1) { + Bluespawn::io.InformUser(StringToWidestring(options.help())); + LOG_ERROR(e1.what()); + } + return 0; +} diff --git a/BLUESPAWN-client/src/user/CLI.cpp b/BLUESPAWN-win-client/src/user/CLI.cpp similarity index 99% rename from BLUESPAWN-client/src/user/CLI.cpp rename to BLUESPAWN-win-client/src/user/CLI.cpp index 96028abb..cbb62437 100644 --- a/BLUESPAWN-client/src/user/CLI.cpp +++ b/BLUESPAWN-win-client/src/user/CLI.cpp @@ -3,7 +3,7 @@ #include #include #include -#include "common/stringutils.h" +#include "util/stringutils.h" #undef max diff --git a/BLUESPAWN-client/src/user/banners.cpp b/BLUESPAWN-win-client/src/user/banners.cpp similarity index 100% rename from BLUESPAWN-client/src/user/banners.cpp rename to BLUESPAWN-win-client/src/user/banners.cpp diff --git a/BLUESPAWN-common/src/DynamicLinker.cpp b/BLUESPAWN-win-client/src/util/DynamicLinker.cpp similarity index 89% rename from BLUESPAWN-common/src/DynamicLinker.cpp rename to BLUESPAWN-win-client/src/util/DynamicLinker.cpp index 1c617c91..612196d5 100644 --- a/BLUESPAWN-common/src/DynamicLinker.cpp +++ b/BLUESPAWN-win-client/src/util/DynamicLinker.cpp @@ -1,4 +1,4 @@ -#include "common/DynamicLinker.h" +#include "util/DynamicLinker.h" #include #include diff --git a/BLUESPAWN-common/src/StringUtils.cpp b/BLUESPAWN-win-client/src/util/StringUtils.cpp similarity index 78% rename from BLUESPAWN-common/src/StringUtils.cpp rename to BLUESPAWN-win-client/src/util/StringUtils.cpp index 4b6bdee7..ca61ed04 100644 --- a/BLUESPAWN-common/src/StringUtils.cpp +++ b/BLUESPAWN-win-client/src/util/StringUtils.cpp @@ -1,4 +1,4 @@ -#include "common/StringUtils.h" +#include "util/StringUtils.h" #include @@ -50,7 +50,7 @@ std::wstring ExpandEnvStringsW(const std::wstring& in){ return str; } -std::string ExpandEnvStringsA(const std::string& in){ +std::string ExpandEnvStringsW(const std::string& in){ CHAR* expanded = new CHAR[MAX_PATH]; auto result = ExpandEnvironmentStringsA(in.c_str(), expanded, MAX_PATH); if(result > MAX_PATH){ @@ -94,6 +94,19 @@ bool CompareIgnoreCase(const T& in1, const T& in2){ template bool CompareIgnoreCase(const std::wstring& in1, const std::wstring& in2); template bool CompareIgnoreCase(const std::string& in, const std::string& in2); +template +T StringReplace(const T& string, const T& search, const T& replacement){ + auto copy{ string }; + for(auto find{ copy.find(search) }; find != std::string::npos; find = copy.find(search, find + replacement.size())){ + copy.replace(copy.begin() + find, copy.begin() + find + search.length(), replacement); + } + return copy; +} + +template std::wstring StringReplace(const std::wstring& string, const std::wstring& search, const std::wstring& replacement); +template std::string StringReplace(const std::string& string, const std::string& search, const std::string& replacement); +template bool CompareIgnoreCase(const std::string& in, const std::string& in2); + template std::vector> SplitString(const std::basic_string& in, const std::basic_string& delimiter){ std::vector> substrs{}; @@ -109,4 +122,4 @@ std::vector> SplitString(const std::basic_string& in, co } template std::vector SplitString(const std::wstring& in, const std::wstring& delimiter); -template std::vector SplitString(const std::string& in, const std::string& delimiter); \ No newline at end of file +template std::vector SplitString(const std::string& in, const std::string& delimiter); diff --git a/BLUESPAWN-win-client/src/util/ThreadPool.cpp b/BLUESPAWN-win-client/src/util/ThreadPool.cpp new file mode 100644 index 00000000..4282f201 --- /dev/null +++ b/BLUESPAWN-win-client/src/util/ThreadPool.cpp @@ -0,0 +1,122 @@ +#include "util/ThreadPool.h" + +#include + +ThreadPool ThreadPool::instance{}; + +void ThreadPool::ThreadFunction(){ + while(active){ + auto result{ WaitForSingleObject(hSemaphore, INFINITE) }; + if(result == WAIT_OBJECT_0){ + if(!active){ + return; + } + + EnterCriticalSection(hGuard); + auto function{ tasks.front() }; + tasks.pop(); + LeaveCriticalSection(hGuard); + + try{ + function(); + + EnterCriticalSection(hGuard); + count--; + if(count == 0){ + SetEvent(hEvent); + } + LeaveCriticalSection(hGuard); + + } catch(std::exception e){ + EnterCriticalSection(hGuard); + count--; + if(count == 0){ + SetEvent(hEvent); + } + LeaveCriticalSection(hGuard); + + auto functions{ vExceptionHandlers }; + + // Defer handling the exceptions until later + EnqueueTask([functions, e](){ + for(auto& function : functions){ + function(e); + } + }); + } + } else{ + if(WaitForSingleObject(hSemaphore, 0) == WAIT_FAILED){ + // hSemaphore has become invalidated somehow. Recreate it + hSemaphore = CreateSemaphoreW(nullptr, 0, static_cast(-1), nullptr); + } + } + } +} + +ThreadPool::ThreadPool() : + hSemaphore{ CreateSemaphoreW(nullptr, 0, LONG_MAX, nullptr) }, + hEvent{ CreateEventW(nullptr, true, true, nullptr) }, + active{ true }{ + + auto error{ GetLastError() }; + // https://stackoverflow.com/questions/457577/catching-access-violation-exceptions + _set_se_translator([](unsigned int u, EXCEPTION_POINTERS* pExp){ + std::string error = "Structured Exception: "; + char result[11]; + sprintf_s(result, 11, "0x%08X", u); + error += result; + throw std::exception(error.c_str()); + }); + + auto count{ std::thread::hardware_concurrency() }; + + for(unsigned int idx = 0; idx < count; idx++){ + threads.emplace_back(std::thread{ &ThreadPool::ThreadFunction, this }); + } +} + +ThreadPool::~ThreadPool(){ + active = false; + + ReleaseSemaphore(hSemaphore, threads.size(), nullptr); + + for(auto& thread : threads){ + thread.join(); + } +} + +void ThreadPool::EnqueueTask(IN CONST std::function& function){ + ResetEvent(hEvent); + + auto lock{ BeginCriticalSection(hGuard) }; + + tasks.emplace(function); + count++; + + ReleaseSemaphore(hSemaphore, 1, nullptr); +} + +ThreadPool& ThreadPool::GetInstance(){ + return instance; +} + +void ThreadPool::AddExceptionHandler( + IN CONST std::function& function){ + vExceptionHandlers.emplace_back(function); +} + +void ThreadPool::Wait() const { + while(true){ + auto status{ WaitForSingleObject(hEvent, INFINITE) }; + if(status == WAIT_OBJECT_0){ + EnterCriticalSection(hGuard); + if(count == 0){ + LeaveCriticalSection(hGuard); + return; + } + LeaveCriticalSection(hGuard); + } else{ + throw std::exception{ "Error waiting for threadpool to finish" }; + } + } +} \ No newline at end of file diff --git a/BLUESPAWN-common/src/Utils.cpp b/BLUESPAWN-win-client/src/util/Utils.cpp similarity index 72% rename from BLUESPAWN-common/src/Utils.cpp rename to BLUESPAWN-win-client/src/util/Utils.cpp index c5966fb5..65cf3ede 100644 --- a/BLUESPAWN-common/src/Utils.cpp +++ b/BLUESPAWN-win-client/src/util/Utils.cpp @@ -1,10 +1,10 @@ -#include "common/Utils.h" +#include "util/Utils.h" #include #include #include #include -int64_t SystemTimeToInteger(const SYSTEMTIME st) { +int64_t SystemTimeToInteger(const SYSTEMTIME& st){ FILETIME ft; SystemTimeToFileTime(&st, &ft); @@ -16,16 +16,16 @@ int64_t SystemTimeToInteger(const SYSTEMTIME st) { return lv_Large.QuadPart; } -std::wstring FormatWindowsTime(const SYSTEMTIME st) { +std::wstring FormatWindowsTime(const SYSTEMTIME& st){ std::wostringstream w; - w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " << - std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." << + w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " << + std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." << ((st.wMilliseconds % 10000000) * 100) << "Z"; return w.str(); } -std::wstring FormatWindowsTime(const FILETIME ft) { +std::wstring FormatWindowsTime(const FILETIME& ft){ SYSTEMTIME st; FileTimeToSystemTime(&ft, &st); @@ -37,15 +37,15 @@ std::wstring FormatWindowsTime(const FILETIME ft) { } -std::wstring FormatWindowsTime(const std::wstring& windowsTime) { +std::wstring FormatWindowsTime(const std::wstring& windowsTime){ SYSTEMTIME st; FILETIME ft; - ULONGLONG time = (ULONGLONG)stoull(windowsTime); + ULONGLONG time = (ULONGLONG) stoull(windowsTime); ULONGLONG nano = 0; - ft.dwHighDateTime = (DWORD)((time >> 32) & 0xFFFFFFFF); - ft.dwLowDateTime = (DWORD)(time & 0xFFFFFFFF); + ft.dwHighDateTime = (DWORD) ((time >> 32) & 0xFFFFFFFF); + ft.dwLowDateTime = (DWORD) (time & 0xFFFFFFFF); FileTimeToSystemTime(&ft, &st); nano = (time % 10000000) * 100; // Display nanoseconds instead of milliseconds for higher resolution @@ -55,4 +55,4 @@ std::wstring FormatWindowsTime(const std::wstring& windowsTime) { std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." << nano << "Z"; return w.str(); -} \ No newline at end of file +} diff --git a/BLUESPAWN-client/src/util/configurations/CollectInfo.cpp b/BLUESPAWN-win-client/src/util/configurations/CollectInfo.cpp similarity index 84% rename from BLUESPAWN-client/src/util/configurations/CollectInfo.cpp rename to BLUESPAWN-win-client/src/util/configurations/CollectInfo.cpp index 32e1040f..7c9b02c5 100644 --- a/BLUESPAWN-client/src/util/configurations/CollectInfo.cpp +++ b/BLUESPAWN-win-client/src/util/configurations/CollectInfo.cpp @@ -8,12 +8,12 @@ #include "util/log/Log.h" void OutputComputerInformation() { - LOG_INFO("Computer Information\n"); - LOG_INFO("DNS FQDN: " << GetFQDN()); - LOG_INFO("Computer DNS Name: " << GetComputerDNSName()); - LOG_INFO("Active Directory Domain: " << GetDomain()); - LOG_INFO("Operating System: " << GetOSVersion()); - LOG_INFO("Current User: " << GetCurrentUser()); + LOG_INFO(1, L"Computer Information\n"); + LOG_INFO(1, L"DNS FQDN: " << GetFQDN()); + LOG_INFO(1, L"Computer DNS Name: " << GetComputerDNSName()); + LOG_INFO(1, L"Active Directory Domain: " << GetDomain()); + LOG_INFO(1, L"Operating System: " << GetOSVersion()); + LOG_INFO(1, L"Current User: " << GetCurrentUser()); } std::wstring GetOSVersion() { diff --git a/BLUESPAWN-client/src/util/configurations/RegistryKey.cpp b/BLUESPAWN-win-client/src/util/configurations/RegistryKey.cpp similarity index 88% rename from BLUESPAWN-client/src/util/configurations/RegistryKey.cpp rename to BLUESPAWN-win-client/src/util/configurations/RegistryKey.cpp index 6885601d..04312504 100644 --- a/BLUESPAWN-client/src/util/configurations/RegistryKey.cpp +++ b/BLUESPAWN-win-client/src/util/configurations/RegistryKey.cpp @@ -3,9 +3,10 @@ #include #include +#include "util/StringUtils.h" +#include "util/Internals.h" + #include "util/configurations/Registry.h" -#include "common/StringUtils.h" -#include "common/Internals.h" LINK_FUNCTION(NtQueryKey, ntdll.dll); LINK_FUNCTION(NtQueryValueKey, ntdll.dll); @@ -37,18 +38,48 @@ namespace Registry { {HKEY_CURRENT_CONFIG, L"HKEY_CURRENT_CONFIG"}, }; - std::map RegistryKey::_ReferenceCounts = {}; - + RegistryKey::Tracker::Tracker(){} + + void RegistryKey::Tracker::Increment(IN HKEY hKey){ + BeginCriticalSection _{ hGuard }; + if(hKey){ + if(counts.find(hKey) == counts.end()){ + counts[hKey] = 1; + } else{ + counts[hKey]++; + } + } + } + + void RegistryKey::Tracker::Decrement(IN HKEY hKey){ + BeginCriticalSection _{ hGuard }; + if(hKey){ + if(counts.find(hKey) != counts.end()){ + if(!--counts[hKey] && !(ULONG_PTR(hKey) & 0xFFFFFFFF80000000)){ + CloseHandle(hKey); + } + } + } + } + + int RegistryKey::Tracker::Get(IN HKEY hKey){ + BeginCriticalSection _{ hGuard }; + if(counts.find(hKey) != counts.end()){ + return counts[hKey]; + } else{ + return 0; + } + } + + std::shared_ptr RegistryKey::__tracker{ std::make_shared() }; + RegistryKey::RegistryKey(const RegistryKey& key) noexcept : bKeyExists{ key.bKeyExists }, bWow64{ key.bWow64 }, - hkBackingKey{ key.hkBackingKey }{ + hkBackingKey{ key.hkBackingKey }, + tracker{ __tracker }{ - if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ - _ReferenceCounts[hkBackingKey] = 1; - } else { - _ReferenceCounts[hkBackingKey]++; - } + tracker->Increment(hkBackingKey); } RegistryKey& RegistryKey::operator=(const RegistryKey& key) noexcept { @@ -56,11 +87,7 @@ namespace Registry { this->bWow64 = key.bWow64; this->hkBackingKey = key.hkBackingKey; - if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ - _ReferenceCounts[hkBackingKey] = 1; - } else { - _ReferenceCounts[hkBackingKey]++; - } + tracker->Increment(hkBackingKey); return *this; } @@ -68,7 +95,8 @@ namespace Registry { RegistryKey::RegistryKey(RegistryKey&& key) noexcept : bKeyExists{ key.bKeyExists }, bWow64{ key.bWow64 }, - hkBackingKey{ key.hkBackingKey }{ + hkBackingKey{ key.hkBackingKey }, + tracker{ __tracker }{ key.bKeyExists = false; key.bWow64 = false; @@ -95,17 +123,14 @@ namespace Registry { RegistryKey::RegistryKey(HKEY key) : bKeyExists{ true }, bWow64{ false }, - hkBackingKey{ key }{ + hkBackingKey{ key }, + tracker{ __tracker }{ - - if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ - _ReferenceCounts[hkBackingKey] = 1; - } else { - _ReferenceCounts[hkBackingKey]++; - } + tracker->Increment(hkBackingKey); } - RegistryKey::RegistryKey(HKEY hive, std::wstring path, bool WoW64){ + RegistryKey::RegistryKey(HKEY hive, std::wstring path, bool WoW64) : + tracker{ __tracker }{ auto wLowerPath = ToLowerCase(path); bWow64 = WoW64 || wLowerPath.find(L"wow6432node") != std::wstring::npos; @@ -122,15 +147,12 @@ namespace Registry { } else { bKeyExists = true; - if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ - _ReferenceCounts[hkBackingKey] = 1; - } else { - _ReferenceCounts[hkBackingKey]++; - } + tracker->Increment(hkBackingKey); } } - RegistryKey::RegistryKey(std::wstring name, bool WoW64){ + RegistryKey::RegistryKey(std::wstring name, bool WoW64) : + tracker{ __tracker }{ name = ToUpperCase(name); SIZE_T slash = name.find_first_of(L"/\\"); @@ -163,11 +185,7 @@ namespace Registry { } if(status == ERROR_SUCCESS){ - if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ - _ReferenceCounts[hkBackingKey] = 1; - } else { - _ReferenceCounts[hkBackingKey]++; - } + tracker->Increment(hkBackingKey); bKeyExists = true; } else { @@ -178,11 +196,24 @@ namespace Registry { } RegistryKey::~RegistryKey(){ - if(_ReferenceCounts.find(hkBackingKey) != _ReferenceCounts.end()){ - if(!--_ReferenceCounts[hkBackingKey] && !(ULONG_PTR(hkBackingKey) & 0xFFFFFFFF80000000)){ - CloseHandle(hkBackingKey); - } + tracker->Decrement(hkBackingKey); + } + + bool RegistryKey::CheckKeyExists(HKEY hive, const std::wstring& name, bool WoW64){ + auto wLowerPath = ToLowerCase(name); + + HKEY key{}; + WoW64 = WoW64 || wLowerPath.find(L"wow6432node") != std::wstring::npos; + LSTATUS status = RegOpenKeyExW(hive, name.c_str(), 0, + KEY_READ | KEY_NOTIFY | (WoW64 ? KEY_WOW64_32KEY : KEY_WOW64_64KEY), &key); + if(status == ERROR_ACCESS_DENIED){ return true; } + + if(status == ERROR_SUCCESS){ + if(!__tracker->Get(key)){ RegCloseKey(key); } + return true; } + + return false; } bool RegistryKey::Exists() const { @@ -220,11 +251,7 @@ namespace Registry { if(status == ERROR_SUCCESS){ bKeyExists = true; - if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ - _ReferenceCounts[hkBackingKey] = 1; - } else { - _ReferenceCounts[hkBackingKey]++; - } + tracker->Increment(hkBackingKey); return true; } @@ -286,15 +313,15 @@ namespace Registry { std::vector data(size); status = Linker::NtQueryValueKey(hkBackingKey, &RegistryKeyName, 0, data.data(), size, &size); - auto KeyValueInfo{ reinterpret_cast(data.data()) }; - - DWORD dwType = KeyValueInfo->Type; - - if(!NT_SUCCESS(status)) { + if(!NT_SUCCESS(status)){ SetLastError(status); return std::nullopt; } + auto KeyValueInfo{ reinterpret_cast(data.data()) }; + + DWORD dwType = KeyValueInfo->Type; + if(dwType == REG_SZ){ return RegistryType::REG_SZ_T; } else if(dwType == REG_EXPAND_SZ){ @@ -596,4 +623,8 @@ namespace Registry { RegistryKey::operator HKEY() const { return hkBackingKey; } -} \ No newline at end of file +} + +size_t std::hash::operator()(IN CONST Registry::RegistryKey& key) const{ + return reinterpret_cast(static_cast(key)); +} diff --git a/BLUESPAWN-client/src/util/configurations/RegistryValue.cpp b/BLUESPAWN-win-client/src/util/configurations/RegistryValue.cpp similarity index 68% rename from BLUESPAWN-client/src/util/configurations/RegistryValue.cpp rename to BLUESPAWN-win-client/src/util/configurations/RegistryValue.cpp index d8be9750..a7fafae5 100644 --- a/BLUESPAWN-client/src/util/configurations/RegistryValue.cpp +++ b/BLUESPAWN-win-client/src/util/configurations/RegistryValue.cpp @@ -31,6 +31,23 @@ namespace Registry { type{ RegistryType::REG_MULTI_SZ_T }, data{ std::forward>(vData) }{} + + std::optional RegistryValue::Create(IN CONST RegistryKey& key, + IN CONST std::wstring& wsValueName){ + if(key.ValueExists(wsValueName)){ + auto type{ key.GetValueType(wsValueName) }; + if(type == RegistryType::REG_DWORD_T){ + return RegistryValue{ key, wsValueName, *key.GetValue(wsValueName) }; + } else if(type == RegistryType::REG_MULTI_SZ_T){ + return RegistryValue{ key, wsValueName, *key.GetValue>(wsValueName) }; + } else if(type == RegistryType::REG_EXPAND_SZ_T || type == RegistryType::REG_SZ_T){ + return RegistryValue{ key, wsValueName, *key.GetValue(wsValueName) }; + } else return RegistryValue{ key, wsValueName, key.GetRawValue(wsValueName) }; + } + + return std::nullopt; + } + RegistryType RegistryValue::GetType() const { return type; } @@ -75,4 +92,13 @@ namespace Registry { return string; } } + + bool RegistryValue::operator==(const RegistryValue& value) const{ + return value.key == key && value.wValueName == wValueName && value.data == data; + } + + bool RegistryValue::operator<(const RegistryValue& value) const{ + return value.key < key || + (value.key == key && value.wValueName < wValueName); + } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/eventlogs/EventLogItem.cpp b/BLUESPAWN-win-client/src/util/eventlogs/EventLogItem.cpp similarity index 100% rename from BLUESPAWN-client/src/util/eventlogs/EventLogItem.cpp rename to BLUESPAWN-win-client/src/util/eventlogs/EventLogItem.cpp diff --git a/BLUESPAWN-win-client/src/util/eventlogs/EventLogs.cpp b/BLUESPAWN-win-client/src/util/eventlogs/EventLogs.cpp new file mode 100644 index 00000000..4bcaceae --- /dev/null +++ b/BLUESPAWN-win-client/src/util/eventlogs/EventLogs.cpp @@ -0,0 +1,295 @@ +#include "util/eventlogs/EventLogs.h" + +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" +#include "util/log/Log.h" + +const int SIZE_DATA = 4096; +const int ARRAY_SIZE = 10; + +namespace EventLogs { + + /** + * The callback function directly called by event subscriptions. + * In turn it calls the EventSubscription::SubscriptionCallback of a specific class instance. + */ + DWORD WINAPI CallbackWrapper(EVT_SUBSCRIBE_NOTIFY_ACTION Action, PVOID UserContext, EVT_HANDLE Event) { + return reinterpret_cast(UserContext)->SubscriptionCallback(Action, Event); + } + + std::optional EventLogs::GetEventParam(const EventWrapper& hEvent, const std::wstring& param) { + auto queryParam = param.c_str(); + EventWrapper hContext = EvtCreateRenderContext(1, &queryParam, EvtRenderContextValues); + if(!hContext) { + LOG_ERROR(L"EventLogs::GetEventParam: EvtCreateRenderContext failed with " + + std::to_wstring(GetLastError())); + return std::nullopt; + } + + DWORD dwBufferSize{}; + if(!EvtRender(hContext, hEvent, EvtRenderEventValues, dwBufferSize, nullptr, &dwBufferSize, nullptr)) { + if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) { + auto pRenderedValues = AllocationWrapper{ HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize), + dwBufferSize, AllocationWrapper::HEAP_ALLOC }; + if(pRenderedValues) { + if(EvtRender(hContext, hEvent, EvtRenderEventValues, dwBufferSize, pRenderedValues, &dwBufferSize, + nullptr)) { + /* + Table of variant members found here: https://docs.microsoft.com/en-us/windows/win32/api/winevt/ns-winevt-evt_variant + Table of type values found here: https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_variant_type + */ + PEVT_VARIANT result = reinterpret_cast((LPVOID) pRenderedValues); + if(result->Type == EvtVarTypeString) + return std::wstring(result->StringVal); + else if(result->Type == EvtVarTypeFileTime) { + wchar_t ar[30]; + _ui64tow(result->FileTimeVal, ar, 10); + return ar; + } else if(result->Type == EvtVarTypeUInt16) { + return std::to_wstring(result->UInt16Val); + } else if(result->Type == EvtVarTypeUInt32) { + return std::to_wstring(result->UInt32Val); + } else if(result->Type == EvtVarTypeUInt64) { + return std::to_wstring(result->UInt64Val); + } else if(result->Type == EvtVarTypeNull) + return L"NULL"; + else { + return L"Unknown VARIANT: " + std::to_wstring(result->Type); + } + } + } + } + } + return std::nullopt; + } + + std::optional EventLogs::GetEventXML(const EventWrapper& hEvent) { + DWORD dwBufferSize = 0; + if(!EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, nullptr, &dwBufferSize, nullptr)) { + if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) { + auto pRenderedContent = AllocationWrapper{ HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize), + dwBufferSize, AllocationWrapper::HEAP_ALLOC }; + if(pRenderedContent) { + if(EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, pRenderedContent, &dwBufferSize, + nullptr)) { + return reinterpret_cast((LPVOID) pRenderedContent); + } + } + } + } + + return std::nullopt; + } + + // Enumerate all the events in the result set. + std::vector EventLogs::ProcessResults(const EventWrapper& hResults, + const std::vector& filters) { + EVT_HANDLE hEvents[ARRAY_SIZE]; + + std::vector results; + std::vector params; + for(auto query : filters) { + if(!query.SearchesByValue()) { + params.push_back(query.ToString()); + } + } + + DWORD dwReturned{}; + while(EvtNext(hResults, ARRAY_SIZE, hEvents, INFINITE, 0, &dwReturned)) { + for(DWORD i = 0; i < dwReturned; i++) { + auto item = EventToEventLogItem(hEvents[i], params); + if(item) { + results.push_back(*item); + } + + EvtClose(hEvents[i]); + hEvents[i] = NULL; + } + } + + for(unsigned i = 0; i < ARRAY_SIZE; i++) { + if(hEvents[i]) { + EvtClose(hEvents[i]); + } + } + + if(GetLastError() != ERROR_NO_MORE_ITEMS) { + LOG_VERBOSE(1, "EventLogs::ProcessResults: EvtNext failed with " << GetLastError()); + } + + return results; + } + + std::optional EventToEventLogItem(const EventWrapper& hEvent, + const std::vector& params) { + + std::optional eventIDStr, eventRecordIDStr, timeCreated, channel, rawXML; + + if(std::nullopt == (eventIDStr = GetEventParam(hEvent, L"Event/System/EventID"))) + return std::nullopt; + if(std::nullopt == (eventRecordIDStr = GetEventParam(hEvent, L"Event/System/EventRecordID"))) + return std::nullopt; + if(std::nullopt == (timeCreated = GetEventParam(hEvent, L"Event/System/TimeCreated/@SystemTime"))) + return std::nullopt; + if(std::nullopt == (channel = GetEventParam(hEvent, L"Event/System/Channel"))) + return std::nullopt; + if(std::nullopt == (rawXML = GetEventXML(hEvent))) + return std::nullopt; + + EventLogItem pItem{}; + + // Provide values for filtered parameters + for(std::wstring key : params) { + std::optional val = GetEventParam(hEvent, key); + if(!val) { + return std::nullopt; + } + + pItem.SetProperty(key, *val); + } + + pItem.SetEventID(std::stoul(*eventIDStr)); + pItem.SetEventRecordID(std::stoul(*eventRecordIDStr)); + pItem.SetTimeCreated(*timeCreated); + pItem.SetChannel(*channel); + pItem.SetXML(*rawXML); + + return pItem; + } + + std::vector + EventLogs::QueryEvents(const std::wstring& channel, unsigned int id, const std::vector& filters) { + std::vector items; + + auto query = std::wstring(L"Event/System[EventID=") + std::to_wstring(id) + std::wstring(L"]"); + for(auto param : filters) + query += L" and " + param.ToString(); + + EventWrapper hResults = + EvtQuery(NULL, channel.c_str(), query.c_str(), EvtQueryChannelPath | EvtQueryReverseDirection); + if(!hResults) { + if(ERROR_EVT_CHANNEL_NOT_FOUND == GetLastError()) + LOG_WARNING("EventLogs::QueryEvents: Unable to find channel " << channel); + else if(ERROR_EVT_INVALID_QUERY == GetLastError()) + LOG_ERROR(L"EventLogs::QueryEvents: The query " << query << L" is not valid."); + else + LOG_ERROR("EventLogs::QueryEvents: EvtQuery failed with " << SYSTEM_ERROR); + } else { + items = ProcessResults(hResults, filters); + } + + return items; + } + + std::vector subscriptions = {}; + + std::optional> + EventLogs::SubscribeToEvent(const std::wstring& pwsPath, + unsigned int id, + const std::function& callback, + const std::vector& filters) { + auto query = std::wstring(L"Event/System[EventID=") + std::to_wstring(id) + std::wstring(L"]"); + for(auto param : filters) + query += L" and " + param.ToString(); + + subscriptions.emplace_back(EventSubscription{ callback }); + auto& eventSub = subscriptions[subscriptions.size() - 1]; + + EventWrapper hSubscription = EvtSubscribe(NULL, NULL, pwsPath.c_str(), query.c_str(), NULL, + &subscriptions[subscriptions.size() - 1], CallbackWrapper, + EvtSubscribeToFutureEvents); + eventSub.setSubHandle(hSubscription); + + if(!hSubscription) { + if(ERROR_EVT_CHANNEL_NOT_FOUND == GetLastError()) + LOG_WARNING("EventLogs::QueryEvents: Unable to find channel " << pwsPath); + else if(ERROR_EVT_INVALID_QUERY == GetLastError()) + LOG_ERROR(L"EventLogs::SubscribeToEvent: query " << query << L" is not valid."); + else + LOG_ERROR("EventLogs::SubscribeToEvent: EvtSubscribe failed with " << GetLastError()); + + return std::nullopt; + } + + return eventSub; + } + + bool IsChannelOpen(const std::wstring& channel) { + PEVT_VARIANT pProperty = NULL; + PEVT_VARIANT pTemp = NULL; + DWORD dwBufferSize = 0; + DWORD dwBufferUsed = 0; + + // Open the channel config + EventWrapper hChannel{ EvtOpenChannelConfig(NULL, channel.c_str(), 0) }; + if(NULL == hChannel) { + LOG_ERROR(L"EventLogs::IsChannelOpen: EvtOpenChannelConfig failed with " + std::to_wstring(GetLastError()) + + L" for channel " + channel); + return false; + } + + // Attempt to get the channel property + if(!EvtGetChannelConfigProperty(hChannel, EvtChannelConfigEnabled, 0, dwBufferSize, pProperty, &dwBufferUsed)) { + auto status{ GetLastError() }; + if(ERROR_INSUFFICIENT_BUFFER == status) { + dwBufferSize = dwBufferUsed; + pTemp = (PEVT_VARIANT) realloc(pProperty, dwBufferSize); + + if(pTemp) { + pProperty = pTemp; + pTemp = NULL; + EvtGetChannelConfigProperty(hChannel, EvtChannelConfigEnabled, 0, dwBufferSize, pProperty, + &dwBufferUsed); + } else { + if(pProperty) + free(pProperty); + + LOG_ERROR(L"EventLogs::IsChannelOpen: realloc failed for channel " + channel); + return false; + } + } + + if(ERROR_SUCCESS != (status = GetLastError())) { + LOG_ERROR(L"EventLogs::IsChannelOpen: EvtGetChannelConfigProperty failed with " + + std::to_wstring(GetLastError()) + L" for channel " + channel); + return false; + } + } + if(pProperty) + free(pProperty); + + return pProperty->BooleanVal; + } + + bool OpenChannel(const std::wstring& channel) { + EVT_HANDLE hChannel = NULL; + EVT_VARIANT ChannelProperty; + DWORD dwBufferSize = sizeof(EVT_VARIANT); + hChannel = EvtOpenChannelConfig(NULL, channel.c_str(), 0); + if(NULL == hChannel) { + LOG_ERROR(L"EventLogs::OpenChannel: EvtOpenChannelConfig failed with " + std::to_wstring(GetLastError()) + + L" for channel " + channel); + return false; + } + RtlZeroMemory(&ChannelProperty, dwBufferSize); + + ChannelProperty.Type = EvtVarTypeBoolean; + ChannelProperty.BooleanVal = TRUE; + + if(!EvtSetChannelConfigProperty(hChannel, EvtChannelConfigEnabled, 0, &ChannelProperty)) { + LOG_ERROR(L"EventLogs::OpenChannel: EvtSetChannelConfigProperty failed with " + + std::to_wstring(GetLastError()) + L" for channel " + channel); + return false; + } + if(!EvtSaveChannelConfig(hChannel, 0)) { + LOG_ERROR(L"EventLogs::OpenChannel: EvtSaveChannelConfig failed with " + std::to_wstring(GetLastError()) + + L" for channel " + channel); + return false; + } + + return true; + } + +} // namespace EventLogs diff --git a/BLUESPAWN-client/src/util/eventlogs/EventSubscription.cpp b/BLUESPAWN-win-client/src/util/eventlogs/EventSubscription.cpp similarity index 96% rename from BLUESPAWN-client/src/util/eventlogs/EventSubscription.cpp rename to BLUESPAWN-win-client/src/util/eventlogs/EventSubscription.cpp index c2925c11..d4381aaa 100644 --- a/BLUESPAWN-client/src/util/eventlogs/EventSubscription.cpp +++ b/BLUESPAWN-win-client/src/util/eventlogs/EventSubscription.cpp @@ -1,5 +1,4 @@ #include "util/eventlogs/EventSubscription.h" -#include "reaction/Detections.h" #include "util/log/Log.h" #include "util/eventlogs/EventLogs.h" diff --git a/BLUESPAWN-client/src/util/eventlogs/XpathQuery.cpp b/BLUESPAWN-win-client/src/util/eventlogs/XpathQuery.cpp similarity index 100% rename from BLUESPAWN-client/src/util/eventlogs/XpathQuery.cpp rename to BLUESPAWN-win-client/src/util/eventlogs/XpathQuery.cpp diff --git a/BLUESPAWN-win-client/src/util/filesystem/FileSystem.cpp b/BLUESPAWN-win-client/src/util/filesystem/FileSystem.cpp new file mode 100644 index 00000000..4fd47853 --- /dev/null +++ b/BLUESPAWN-win-client/src/util/filesystem/FileSystem.cpp @@ -0,0 +1,991 @@ +#include "util/filesystem/FileSystem.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "util/StringUtils.h" +#include "util/log/Log.h" +#include "util/wrappers.hpp" + +#include "aclapi.h" + +LINK_FUNCTION(NtCreateFile, ntdll.dll) + +namespace FileSystem { + bool CheckFileExists(const std::wstring& path) { + auto attribs = GetFileAttributesW(path.c_str()); + if(INVALID_FILE_ATTRIBUTES == attribs && GetLastError() == ERROR_FILE_NOT_FOUND) { + LOG_VERBOSE(3, "File " << path << " does not exist."); + return false; + } + + if(attribs & FILE_ATTRIBUTE_DIRECTORY) { + LOG_VERBOSE(3, "File " << path << " is a directory."); + return false; + } + LOG_VERBOSE(3, "File " << path << " exists"); + return true; + } + + std::optional SearchPathExecutable(const std::wstring& name) { + std::wstring fullname = ExpandEnvStringsW(name); + + auto size = SearchPathW(nullptr, fullname.c_str(), L".exe", 0, nullptr, nullptr); + if(!size) { + return std::nullopt; + } + + std::vector buffer(static_cast(size) + 1); + WCHAR* filename{}; + if(!SearchPathW(nullptr, fullname.c_str(), L".exe", size + 1, buffer.data(), &filename)) { + return std::nullopt; + } + + return buffer.data(); + } + + DWORD File::SetFilePointer(DWORD64 val) const { + //Calculate the offset into format needed for SetFilePointer + long lowerMask = 0xFFFFFFFF; + DWORD64 upperMask = static_cast(0xFFFFFFFF) << 32; + auto lowerVal = static_cast(val & lowerMask); + auto upperVal = static_cast((val & upperMask) >> 32); + return ::SetFilePointer(hFile, lowerVal, reinterpret_cast(&upperVal), 0); + } + + std::vector GetCatalog(const HandleWrapper& hFile) { + if(!hFile) { + return {}; + } + + HCATADMIN admin{}; + GUID gAction = DRIVER_ACTION_VERIFY; + if(!CryptCATAdminAcquireContext(&admin, &gAction, 0)) { + LOG_ERROR("Error acquiring catalog admin context " << SYSTEM_ERROR); + return {}; + } + GenericWrapper hCatAdmin{ admin, [](auto val){ CryptCATAdminReleaseContext(&val, 0); }, + INVALID_HANDLE_VALUE }; + + DWORD dwHashLength{ 0 }; + if(!CryptCATAdminCalcHashFromFileHandle(hFile, &dwHashLength, NULL, 0)) { + LOG_ERROR("Error getting hash size " << SYSTEM_ERROR); + return {}; + } + + std::vector pbHash(dwHashLength); + if(!CryptCATAdminCalcHashFromFileHandle(hFile, &dwHashLength, pbHash.data(), 0)) { + LOG_VERBOSE(2, "Error getting file hash for " << static_cast(hFile) << " " << SYSTEM_ERROR); + return {}; + } + + std::vector catalogfiles{}; + GenericWrapper hCatInfo{ + CryptCATAdminEnumCatalogFromHash(hCatAdmin, pbHash.data(), dwHashLength, 0, nullptr), + [&hCatAdmin](auto val){ CryptCATAdminReleaseCatalogContext(&hCatAdmin, &val, 0); }, + INVALID_HANDLE_VALUE + }; + while(hCatInfo){ + CATALOG_INFO ciCatalogInfo = {}; + ciCatalogInfo.cbStruct = sizeof(ciCatalogInfo); + + if(!CryptCATCatalogInfoFromContext(hCatInfo, &ciCatalogInfo, 0)) { + LOG_ERROR("Couldn't get catalog info for catalog containing hash of file"); + break; + } + + LOG_VERBOSE(3, "Hash for file found in catalog " << ciCatalogInfo.wszCatalogFile); + catalogfiles.emplace_back(ciCatalogInfo.wszCatalogFile); + hCatInfo.reassign(CryptCATAdminEnumCatalogFromHash(hCatAdmin, pbHash.data(), dwHashLength, 0, &hCatInfo)); + } + + return catalogfiles; + } + + bool File::GetFileInSystemCatalogs() const { return GetCatalog(hFile).size(); } + + std::optional File::CalculateHashType(HashType hashType) const { + // Adapted largely from https://stackoverflow.com/a/13259720/4815264 + auto data{ Read() }; + if(!data) { + LOG_VERBOSE(2, "File " << FilePath << " cannot be hashed."); + } + + HCRYPTPROV prov{}; + if(!CryptAcquireContextW(&prov, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { + LOG_ERROR("Unable to create cryptography provider"); + return std::nullopt; + } + GenericWrapper hProv{ prov, std::bind(CryptReleaseContext, std::placeholders::_1, 0) }; + + bool success{ false }; + HCRYPTPROV hash{}; + switch(hashType) { + case HashType::SHA1_HASH: success = CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hash); break; + case HashType::MD5_HASH: success = CryptCreateHash(hProv, CALG_MD5, 0, 0, &hash); break; + case HashType::SHA256_HASH: success = CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hash); break; + } + + GenericWrapper hHash{ hash, CryptDestroyHash }; + + if(!success) { + LOG_ERROR("Unable to create hash providers"); + return std::nullopt; + } + + if(!CryptHashData(hHash, data.GetAsPointer(), data.GetSize(), 0)) { + LOG_ERROR("Failed to hash file " << FilePath << " (" << SYSTEM_ERROR << ")"); + return std::nullopt; + } + + DWORD cbHashSize = 0, dwCount = sizeof(DWORD); + if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*) &cbHashSize, &dwCount, 0)) { + LOG_ERROR("Failed to read hash size for " << FilePath << " (" << SYSTEM_ERROR << ")"); + return std::nullopt; + } + + std::vector buffer(cbHashSize); + if(!CryptGetHashParam(hHash, HP_HASHVAL, reinterpret_cast(buffer.data()), &cbHashSize, 0)) { + LOG_ERROR("Failed to read hash for " << FilePath << " (" << SYSTEM_ERROR << ")"); + return std::nullopt; + } + + std::wstringstream oss{}; + for(auto& byte : buffer) { + oss.fill('0'); + oss.width(2); + oss << std::hex << static_cast(byte); + } + + return oss.str(); + } + + File::File(IN const std::wstring& path) : hFile{ nullptr } { + if(!path.length()) { + bFileExists = false; + bWriteAccess = false; + bReadAccess = false; + return; + } + FilePath = ExpandEnvStringsW(path); + LOG_VERBOSE(2, "Attempting to open file: " << FilePath << "."); + if(FilePath.at(0) == L'\\') { + UNICODE_STRING UnicodeName{ static_cast(FilePath.length() * 2), + static_cast(FilePath.length() * 2), + const_cast(FilePath.c_str()) }; + OBJECT_ATTRIBUTES attributes{}; + IO_STATUS_BLOCK IoStatus{}; + InitializeObjectAttributes(&attributes, &UnicodeName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr, + nullptr); + NTSTATUS Status{ Linker::NtCreateFile(&hFile, GENERIC_READ | GENERIC_WRITE, &attributes, &IoStatus, nullptr, + FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, + FILE_SEQUENTIAL_ONLY, nullptr, 0) }; + if(NT_SUCCESS(Status)) { + this->hFile = hFile; + bFileExists = true; + bWriteAccess = true; + bReadAccess = true; + } else if(Status == 0xC0000022 || Status == 0xC0000043) { //STATUS_ACCESS_DENIED, STATUS_SHARING_VIOLATION + Status = Linker::NtCreateFile(&hFile, GENERIC_READ, &attributes, &IoStatus, nullptr, + FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SEQUENTIAL_ONLY, + nullptr, 0); + if(NT_SUCCESS(Status)) { + this->hFile = hFile; + bFileExists = true; + bWriteAccess = false; + bReadAccess = true; + } else { + LOG_ERROR("Unable to create a file handle for file " << FilePath << " (NTSTATUS " << Status << ")"); + bFileExists = true; + bWriteAccess = false; + bReadAccess = false; + } + } else { + LOG_VERBOSE(2, "Couldn't open file since file doesn't exist (" << FilePath << ")."); + bFileExists = false; + bWriteAccess = false; + bReadAccess = false; + } + } else { + hFile = CreateFileW(FilePath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, nullptr); + if(!hFile && GetLastError() == ERROR_FILE_NOT_FOUND) { + LOG_VERBOSE(2, "Couldn't open file, file doesn't exist " << FilePath << "."); + bFileExists = false; + bWriteAccess = false; + bReadAccess = false; + } else if(!hFile && (GetLastError() == ERROR_ACCESS_DENIED || GetLastError() == ERROR_SHARING_VIOLATION)) { + bWriteAccess = false; + hFile = CreateFileW(FilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, nullptr); + if(!hFile && GetLastError() == ERROR_SHARING_VIOLATION) { + LOG_VERBOSE(2, "Couldn't open file, sharing violation " << FilePath << "."); + bFileExists = true; + bReadAccess = false; + } else if(!hFile && GetLastError() == ERROR_ACCESS_DENIED) { + LOG_VERBOSE(2, "Couldn't open file, Access Denied" << FilePath << "."); + bFileExists = true; + bReadAccess = false; + } else if(GetLastError() != ERROR_SUCCESS) { + LOG_VERBOSE(2, "Couldn't open file " << FilePath << ". " << SYSTEM_ERROR); + bFileExists = true; + bReadAccess = false; + } else { + LOG_VERBOSE(2, "File " << FilePath << " opened."); + bFileExists = true; + bReadAccess = true; + } + } else if(ERROR_SUCCESS == GetLastError()) { + LOG_VERBOSE(2, "File " << FilePath << " opened."); + bFileExists = true; + bWriteAccess = true; + bReadAccess = true; + } else { + LOG_VERBOSE(2, "File " << FilePath << " failed to open with error " << GetLastError()); + bFileExists = false; + bWriteAccess = false; + bReadAccess = false; + } + } + Attribs.extension = PathFindExtensionW(FilePath.c_str()); + } + + std::wstring File::GetFilePath() const { return FilePath; } + + FileAttribs File::GetFileAttribs() const { return Attribs; } + + bool File::GetFileExists() const { return bFileExists; } + + bool File::HasWriteAccess() const { return bWriteAccess; } + + bool File::HasReadAccess() const { return bReadAccess; } + + bool File::Write(IN const LPVOID value, + IN const long offset, + IN const unsigned long length, + IN const bool truncate OPTIONAL, + IN const bool insert OPTIONAL) const { + SCOPE_LOCK(SetFilePointer(0), ResetFilePointer); + LOG_VERBOSE(2, "Writing to file " << FilePath << " at " << offset << ". Insert = " << insert); + + DWORD dwBytesIO{}; + + if(!bFileExists) { + LOG_VERBOSE(2, "Can't write to file " << FilePath << ". File doesn't exist"); + SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + + if(!bWriteAccess) { + LOG_VERBOSE(2, "Can't write to file " << FilePath << ". Insufficient permissions."); + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + + //Insert value into file at specified offset + if(insert && !truncate) { + DWORD64 dwFileSize = GetFileSize(); + for(DWORD64 dwCopyOffset = 0; dwCopyOffset < length; dwCopyOffset += min(1 << 20, length - dwCopyOffset)) { + DWORD dwCopySize = min(1 << 20, length - dwCopyOffset); + AllocationWrapper buffer = { new char[dwCopySize], dwCopySize, AllocationWrapper::CPP_ARRAY_ALLOC }; + if(SetFilePointer(dwFileSize - dwCopyOffset - dwCopySize) == INVALID_SET_FILE_POINTER) { + LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); + return false; + } + if(!ReadFile(hFile, buffer, dwCopySize, &dwBytesIO, nullptr)) { + LOG_ERROR("Unable to read " << FilePath << " at offset " << dwFileSize - dwCopyOffset - dwCopySize + << " " << SYSTEM_ERROR); + return false; + } + if(SetFilePointer(dwFileSize - dwCopyOffset) == INVALID_SET_FILE_POINTER) { + LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); + return false; + } + if(!WriteFile(hFile, buffer, dwCopySize, &dwBytesIO, nullptr)) { + LOG_ERROR("Unable to write to " << FilePath << " at offset " << dwFileSize - dwCopyOffset << " " + << SYSTEM_ERROR); + return false; + } + } + } + //Write value over file at specified offset + if(SetFilePointer(offset) == INVALID_SET_FILE_POINTER) { + LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); + return false; + } + + if(!WriteFile(hFile, value, length, &dwBytesIO, nullptr)) { + LOG_ERROR("Failed to write to " << FilePath << " at offset " << offset << " with " << SYSTEM_ERROR); + return false; + } + + if(truncate) { + if(!SetEndOfFile(hFile)) { + LOG_ERROR("Couldn't truncate file " << FilePath); + return false; + } + } + LOG_VERBOSE(1, "Successfule wrote to " << FilePath << "at offset" << offset); + return true; + } + + bool File::Read(OUT LPVOID buffer, + IN const unsigned long amount OPTIONAL, + IN const long offset OPTIONAL, + OUT PDWORD amountRead OPTIONAL) const { + SCOPE_LOCK(SetFilePointer(0), ResetFilePointer); + LOG_VERBOSE(2, "Attempting to read " << amount << " bytes from " << FilePath << " at offset " << offset); + if(!bFileExists) { + LOG_VERBOSE(2, "Can't read from " << FilePath << ". File doesn't exist."); + SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + + if(!bReadAccess) { + LOG_VERBOSE(2, "Can't read from " << FilePath << ". Insufficient permissions."); + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + + if(SetFilePointer(offset) == INVALID_SET_FILE_POINTER) { + LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); + return false; + } + + DWORD dwBytesRead{}; + if(!amountRead) { + amountRead = &dwBytesRead; + } + + if(!ReadFile(hFile, buffer, amount, amountRead, NULL)) { + LOG_ERROR("Failed to read from " << FilePath << " at offset " << offset << " with error " + << GetLastError()); + return false; + } + LOG_VERBOSE(1, "Successfully wrote " << amount << " bytes to " << FilePath); + return true; + } + + AllocationWrapper + File::Read(__in_opt unsigned long amount, __in_opt long offset, __out_opt PDWORD amountRead) const { + if(amount == -1) { + amount = GetFileSize(); + } + AllocationWrapper memory = { HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, amount + 1), amount + 1, + AllocationWrapper::HEAP_ALLOC }; + bool success = Read(memory, amount, offset, amountRead); + return success ? memory : AllocationWrapper{ nullptr, 0 }; + } + + bool File::MatchesAttributes(IN const FileSearchAttribs& searchAttribs) const { + if(searchAttribs.extensions.size() > 0) { + std::wstring ext = ToLowerCaseW(GetFileAttribs().extension); + if(std::count(searchAttribs.extensions.begin(), searchAttribs.extensions.end(), ext) == 0) { + return false; + } + } + + return true; + } + + bool File::GetFileSigned() const { + if(!bFileExists) { + LOG_VERBOSE(2, "Can't check file signature for " << FilePath << ". File doesn't exist."); + SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + if(!bReadAccess) { + LOG_VERBOSE(2, "Can't check file signature for " << FilePath << ". Insufficient permissions."); + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + WINTRUST_FILE_INFO FileData{}; + FileData.cbStruct = sizeof(WINTRUST_FILE_INFO); + FileData.pcwszFilePath = FilePath.c_str(); + FileData.hFile = hFile; + FileData.pgKnownSubject = NULL; + + GUID verification = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + WINTRUST_DATA WinTrustData{}; + + WinTrustData.cbStruct = sizeof(WinTrustData); + WinTrustData.pPolicyCallbackData = NULL; + WinTrustData.pSIPClientData = NULL; + WinTrustData.dwUIChoice = WTD_UI_NONE; + WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; + WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; + WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; + WinTrustData.hWVTStateData = NULL; + WinTrustData.pwszURLReference = NULL; + WinTrustData.dwUIContext = 0; + WinTrustData.pFile = &FileData; + + LONG result = WinVerifyTrust((HWND) INVALID_HANDLE_VALUE, &verification, &WinTrustData); + WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(NULL, &verification, &WinTrustData); + if(result == ERROR_SUCCESS) { + LOG_VERBOSE(1, FilePath << " is signed."); + return true; + } else { + //Verify signature in system catalog + bool bInCatalog = File::GetFileInSystemCatalogs(); + if(bInCatalog) { + LOG_VERBOSE(1, FilePath << " signed in system catalogs."); + return true; + } + } + LOG_VERBOSE(1, FilePath << " not signed or located in system catalogs."); + return false; + } + + std::optional File::GetCertificateIssuer() const { + if(!bFileExists) { + LOG_VERBOSE(2, "Can't check file signature for " << FilePath << ". File doesn't exist."); + SetLastError(ERROR_FILE_NOT_FOUND); + return std::nullopt; + } + + if(!bReadAccess) { + LOG_VERBOSE(2, "Can't check file signature for " << FilePath << ". Insufficient permissions."); + SetLastError(ERROR_ACCESS_DENIED); + return std::nullopt; + } + + if(!GetFileSigned()) { + return std::nullopt; + } + + if(File::GetFileInSystemCatalogs()) { + auto catalogs{ GetCatalog(hFile) }; + if(catalogs.size()) { + return FileSystem::File{ catalogs[0] }.GetCertificateIssuer(); + } else { + LOG_ERROR("Unable to get the catalog for " << FilePath << ": " << SYSTEM_ERROR); + } + } else { + DWORD dwEncoding{}; + DWORD dwContentType{}; + DWORD dwFormatType{}; + HCERTSTORE store; + HCRYPTMSG msg; + auto status{ CryptQueryObject(CERT_QUERY_OBJECT_FILE, FilePath.c_str(), + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY, 0, + &dwEncoding, &dwContentType, &dwFormatType, &store, &msg, nullptr) }; + if(!status) { + LOG_ERROR("Failed to query signature for " << FilePath << ": " << SYSTEM_ERROR); + return std::nullopt; + } + GenericWrapper hStore{ store, [](HCERTSTORE store){ CertCloseStore(store, 0); }, + INVALID_HANDLE_VALUE }; + GenericWrapper hMsg{ msg, CryptMsgClose, INVALID_HANDLE_VALUE }; + + DWORD dwSignerInfoSize{}; + status = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr, &dwSignerInfoSize); + if(!status) { + LOG_ERROR("Failed to query signer information size for " << FilePath << ": " << SYSTEM_ERROR); + return std::nullopt; + } + + std::vector info(dwSignerInfoSize); + status = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, info.data(), &dwSignerInfoSize); + if(!status) { + LOG_ERROR("Failed to query signer information for " << FilePath << ": " << SYSTEM_ERROR); + return std::nullopt; + } + + auto signer{ reinterpret_cast(info.data())->Issuer }; + DWORD dwSize = CertNameToStrW(X509_ASN_ENCODING, &signer, CERT_SIMPLE_NAME_STR, nullptr, 0); + + std::vector buffer(dwSize); + CertNameToStrW(X509_ASN_ENCODING, &signer, CERT_SIMPLE_NAME_STR, buffer.data(), dwSize); + + return std::wstring{ buffer.data(), dwSize }; + } + + return std::nullopt; + } + + bool File::IsMicrosoftSigned() const { + auto issuer{ GetCertificateIssuer() }; + return issuer && ToLowerCaseW(*issuer).find(L"microsoft") != std::wstring::npos; + } + + std::optional File::GetMD5Hash() const { + LOG_VERBOSE(3, "Attempting to get MD5 hash of " << FilePath); + return CalculateHashType(HashType::MD5_HASH); + } + + std::optional File::GetSHA1Hash() const { + LOG_VERBOSE(3, "Attempting to get SHA1 hash of " << FilePath); + return CalculateHashType(HashType::SHA1_HASH); + } + + std::optional File::GetSHA256Hash() const { + LOG_VERBOSE(3, "Attempting to get SHA256 hash of " << FilePath); + return CalculateHashType(HashType::SHA256_HASH); + } + + bool File::Create() { + LOG_VERBOSE(1, "Attempting to create file: " << FilePath); + if(bFileExists) { + LOG_ERROR("Can't create " << FilePath << ". File already exists."); + return false; + } + hFile = CreateFileW(FilePath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, NULL); + if(INVALID_HANDLE_VALUE == hFile) { + LOG_ERROR("Error creating file " << FilePath << ". " << SYSTEM_ERROR); + bFileExists = false; + return false; + } + LOG_VERBOSE(1, FilePath << " successfully created."); + bFileExists = true; + bReadAccess = true; + bWriteAccess = true; + return true; + } + + bool File::Delete() { + LOG_VERBOSE(1, "Attempting to delete file " << FilePath); + if(!bFileExists) { + LOG_VERBOSE(2, "Can't delete file " << FilePath << ". File doesn't exist"); + return false; + } + CloseHandle(hFile); + if(!DeleteFileW(FilePath.c_str())) { + LOG_ERROR("Deleting file " << FilePath << " failed with " << SYSTEM_ERROR); + hFile = CreateFileW(FilePath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, NULL); + if(INVALID_HANDLE_VALUE == hFile) { + DWORD dwStatus = GetLastError(); + LOG_ERROR("Couldn't reopen " << FilePath << ". Error = " << dwStatus); + bFileExists = false; + return false; + } + bFileExists = true; + return false; + } + LOG_VERBOSE(1, FilePath << "deleted."); + bFileExists = false; + return true; + } + + bool FileSystem::File::ChangeFileLength(IN const long length) const { + SCOPE_LOCK(SetFilePointer(0), ResetFilePointer); + if(bFileExists) { + LOG_VERBOSE(2, "Attempting to change length of " << FilePath << " to " << length); + + if(!SetFilePointer(length)) { + LOG_ERROR("Couldn't change file pointer to " << length << " in file " << FilePath); + return false; + } + + if(!SetEndOfFile(hFile)) { + LOG_ERROR("Couldn't change the length of file " << FilePath); + return false; + } + LOG_VERBOSE(2, "Changed length of " << FilePath << " to " << length); + return true; + } else + return false; + } + + DWORD64 File::GetFileSize() const { + DWORD high = {}; + auto size = ::GetFileSize(hFile, &high); + return (static_cast(high) << 32) + size; + } + + std::wstring File::ToString() const { return FilePath; } + + std::optional File::GetFileOwner() const { + if(!bFileExists) { + LOG_VERBOSE(2, "Can't get owner of nonexistent file " << FilePath); + return std::nullopt; + } + PSID psOwnerSID = NULL; + PISECURITY_DESCRIPTOR pDesc = NULL; + if(GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &psOwnerSID, nullptr, nullptr, nullptr, + reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { + LOG_ERROR("Error getting file owner for file " << FilePath << ". " << SYSTEM_ERROR); + return std::nullopt; + } + pDesc->Owner = psOwnerSID; + + Permissions::SecurityDescriptor secDesc(pDesc); + return Permissions::Owner(secDesc); + } + + bool File::SetFileOwner(const Permissions::Owner& owner) { + if(!bFileExists) { + LOG_VERBOSE(2, "Can't get owner of nonexistent file " << FilePath); + SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + if(!this->bWriteAccess) { + LOG_WARNING("Can't write owner of file " << FilePath << ". Lack permissions"); + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + if(SetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, owner.GetSID(), nullptr, nullptr, + nullptr) != ERROR_SUCCESS) { + LOG_ERROR("Error setting the file owner for file " << FilePath << " to " << owner << ". " << SYSTEM_ERROR); + return false; + } + LOG_VERBOSE(3, "Set the owner for file " << FilePath << " to " << owner << "."); + return true; + } + + ACCESS_MASK File::GetAccessPermissions(const Permissions::Owner& owner) { + if(!bFileExists) { + LOG_VERBOSE(2, "Can't get permissions of nonexistent file " << FilePath); + SetLastError(ERROR_FILE_NOT_FOUND); + return 0; + } + PACL paDACL = NULL; + PISECURITY_DESCRIPTOR pDesc = NULL; + if(GetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &paDACL, nullptr, + reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { + LOG_ERROR("Error getting permissions on file " << FilePath << " for owner " << owner << ". " + << SYSTEM_ERROR); + return 0; + } + + //Correct positional memory of the ACL is weird and doesn't naturally work with the SecurityDescriptor class. + //This gets the right data to the right place + Permissions::SecurityDescriptor secDesc = Permissions::SecurityDescriptor::CreateDACL(paDACL->AclSize); + memcpy(secDesc.GetDACL(), paDACL, paDACL->AclSize); + LocalFree(pDesc); + return Permissions::GetOwnerRightsFromACL(owner, secDesc); + } + + ACCESS_MASK File::GetEveryonePermissions() { + Permissions::Owner everyone(L"Everyone"); + return this->GetAccessPermissions(everyone); + } + + bool File::TakeOwnership() { + std::optional BluespawnOwner = Permissions::GetProcessOwner(); + if(BluespawnOwner == std::nullopt) { + return false; + } + return this->SetFileOwner(*BluespawnOwner); + } + + bool File::GrantPermissions(const Permissions::Owner& owner, const ACCESS_MASK& amAccess) { + return Permissions::UpdateObjectACL(FilePath, SE_FILE_OBJECT, owner, amAccess); + } + + bool File::DenyPermissions(const Permissions::Owner& owner, const ACCESS_MASK& amAccess) { + return Permissions::UpdateObjectACL(FilePath, SE_FILE_OBJECT, owner, amAccess, true); + } + + bool File::Quarantine() { + if(!bFileExists) { + LOG_VERBOSE(2, "Can't quarantine file " << FilePath << ". File doesn't exist"); + SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + ACCESS_MASK amEveryoneDeniedAccess{ 0 }; + Permissions::AccessAddAll(amEveryoneDeniedAccess); + return DenyPermissions(Permissions::Owner(L"Everyone"), amEveryoneDeniedAccess); + } + + std::optional File::GetCreationTime() const { + if(!bFileExists || !bReadAccess) { + LOG_VERBOSE(2, "Can't get creation time of " << FilePath << ", file doesn't exist or no read access"); + SetLastError(ERROR_FILE_NOT_FOUND); + return std::nullopt; + } + FILETIME fReturnInfo; + if(GetFileTime(hFile, &fReturnInfo, nullptr, nullptr)) { + return fReturnInfo; + } else { + LOG_ERROR("Error getting creation time of " << FilePath << ". (Error: " << GetLastError() << ")"); + return std::nullopt; + } + } + + std::optional File::GetModifiedTime() const { + if(!bFileExists || !bReadAccess) { + LOG_VERBOSE(2, "Can't get last modified time of " << FilePath << ", file doesn't exist or no read access"); + SetLastError(ERROR_FILE_NOT_FOUND); + return std::nullopt; + } + FILETIME fReturnInfo; + if(GetFileTime(hFile, nullptr, nullptr, &fReturnInfo)) { + return fReturnInfo; + } else { + LOG_ERROR("Error getting last modified time of " << FilePath << ". " << SYSTEM_ERROR); + return std::nullopt; + } + } + + std::optional File::GetAccessTime() const { + if(!bFileExists || !bReadAccess) { + LOG_VERBOSE(2, "Can't get last access time of " << FilePath << ", file doesn't exist or no read access"); + SetLastError(ERROR_FILE_NOT_FOUND); + return std::nullopt; + } + FILETIME fReturnInfo; + if(GetFileTime(hFile, nullptr, &fReturnInfo, nullptr)) { + return fReturnInfo; + } else { + LOG_ERROR("Error getting last access time of " << FilePath << ". " << SYSTEM_ERROR); + return std::nullopt; + } + } + + Folder::Folder(const std::wstring& path) : hCurFile{ nullptr } { + FolderPath = ExpandEnvStringsW(path); + std::wstring searchName = FolderPath; + searchName += L"\\*"; + bFolderExists = true; + auto f = FindFirstFileW(searchName.c_str(), &ffd); + hCurFile = { f }; + if(hCurFile == INVALID_HANDLE_VALUE) { + LOG_VERBOSE(2, "Couldn't find folder " << FolderPath); + bFolderExists = false; + } + if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + bIsFile = false; + } else { + bIsFile = true; + } + } + + std::wstring Folder::GetFolderPath() const { return FolderPath; } + + bool Folder::MoveToNextFile() { + if(FindNextFileW(hCurFile, &ffd) != 0) { + if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + bIsFile = false; + } else { + bIsFile = true; + } + return true; + } + return false; + } + + bool Folder::MoveToBeginning() { + std::wstring searchName = FolderPath; + searchName += L"\\*"; + hCurFile = FindFirstFileW(searchName.c_str(), &ffd); + if(hCurFile == INVALID_HANDLE_VALUE) { + LOG_VERBOSE(3, "Cannot move to beginning of nonexistent folder " << FolderPath); + return false; + } + if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + bIsFile = false; + } else { + bIsFile = true; + } + return true; + } + + bool Folder::GetFolderExists() const { return bFolderExists; } + + bool Folder::GetCurIsFile() const { return bIsFile; } + + std::optional Folder::Open() const { + if(bIsFile) { + std::wstring fileName(ffd.cFileName); + std::wstring filePath(FolderPath); + filePath += std::wstring(L"\\") + fileName; + std::wstring out = filePath.c_str(); + File file = FileSystem::File(out); + if(file.GetFileExists()) { + return std::optional{ file }; + } + } + return std::nullopt; + } + + std::optional Folder::EnterDir() { + if(!bIsFile) { + std::wstring folderName = FolderPath; + folderName += L"\\"; + folderName += ffd.cFileName; + Folder folder = Folder(folderName.c_str()); + if(folder.GetFolderExists()) + return folder; + } + return std::nullopt; + } + + std::optional Folder::AddFile(IN const std::wstring& fileName) const { + std::wstring filePath = FolderPath; + std::wstring fName = fileName; + filePath += L"\\" + fName; + File file = File(filePath.c_str()); + if(file.GetFileExists()) { + return file; + } + if(file.Create()) { + return file; + } + return std::nullopt; + } + + bool FileSystem::Folder::RemoveFile() const { + if(GetCurIsFile()) { + std::optional f = Open(); + if(f && f->GetFileExists()) { + if(f->Delete()) { + return true; + } + } + } + return false; + } + + std::vector Folder::GetFiles(__in_opt std::optional attribs, __in_opt int recurDepth) { + if(MoveToBeginning() == 0) { + LOG_VERBOSE(2, "Couldn't iterate subfolders of nonexistent folder " << FolderPath); + return std::vector(); + } + std::vector toRet = std::vector(); + do { + if(GetCurIsFile()) { + std::optional f = Open(); + if(f) { + if(!attribs) { + toRet.emplace_back(*f); + } else { + if(f->MatchesAttributes(attribs.value())) { + toRet.emplace_back(*f); + } + } + } + } else if(recurDepth != 0 && ffd.cFileName != std::wstring{ L"." } && + ffd.cFileName != std::wstring{ L".." }) { + std::vector temp; + std::optional f = EnterDir(); + if(f) { + if(recurDepth == -1) { + temp = f->GetFiles(attribs, recurDepth); + } else { + temp = f->GetFiles(attribs, recurDepth - 1); + } + while(!temp.empty()) { + File file = temp.at(temp.size() - 1); + temp.pop_back(); + toRet.emplace_back(file); + } + } + } + } while(MoveToNextFile()); + return toRet; + } + + std::vector Folder::GetSubdirectories(__in_opt int recurDepth) { + if(MoveToBeginning() == 0) { + LOG_VERBOSE(2, "Couldn't iterate subfolders of nonexistent folder " << FolderPath); + return {}; + } + std::vector toRet = {}; + do { + if(!GetCurIsFile() && recurDepth != 0 && ffd.cFileName != std::wstring{ L"." } && + ffd.cFileName != std::wstring{ L".." }) { + std::vector temp; + std::optional f = EnterDir(); + if(f.has_value()) { + toRet.emplace_back(f.value()); + if(recurDepth == -1) { + temp = f->GetSubdirectories(recurDepth); + } else { + temp = f->GetSubdirectories(recurDepth - 1); + } + while(!temp.empty()) { + auto folder = temp.at(temp.size() - 1); + temp.pop_back(); + toRet.emplace_back(folder); + } + } + } + } while(MoveToNextFile()); + return toRet; + } + + std::optional Folder::GetFolderOwner() const { + if(!bFolderExists) { + LOG_VERBOSE(2, "Couldn't find owner of nonexistent folder " << FolderPath); + return std::nullopt; + } + PSID psOwnerSID = NULL; + PISECURITY_DESCRIPTOR pDesc = NULL; + if(GetNamedSecurityInfoW((LPWSTR) FolderPath.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &psOwnerSID, + nullptr, nullptr, nullptr, + reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { + LOG_WARNING("Error getting file owner for folder " << FolderPath << ". " << SYSTEM_ERROR); + return std::nullopt; + } + pDesc->Owner = psOwnerSID; + + Permissions::SecurityDescriptor secDesc(pDesc); + return Permissions::Owner(secDesc); + } + + bool Folder::SetFolderOwner(const Permissions::Owner& owner) { + if(!bFolderExists) { + LOG_VERBOSE(2, "Couldn't set owner of nonexistent folder " << FolderPath); + return false; + } + if(SetNamedSecurityInfoW((LPWSTR) FolderPath.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + owner.GetSID(), nullptr, nullptr, nullptr) != ERROR_SUCCESS) { + LOG_WARNING("Error setting the folder owner for folder " << FolderPath << " to " << owner << ". " + << SYSTEM_ERROR); + return false; + } + LOG_VERBOSE(3, "Set the owner for folder " << FolderPath << " to " << owner << "."); + return true; + } + + ACCESS_MASK Folder::GetAccessPermissions(const Permissions::Owner& owner) { + if(!bFolderExists) { + LOG_VERBOSE(2, "Couldn't find permissions for nonexistent folder " << FolderPath); + return 0; + } + PACL paDACL = NULL; + PISECURITY_DESCRIPTOR pDesc = NULL; + if(GetNamedSecurityInfoW((LPWSTR) FolderPath.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, + nullptr, &paDACL, nullptr, + reinterpret_cast(&pDesc)) != ERROR_SUCCESS) { + LOG_WARNING("Error getting permissions on file " << FolderPath << " for owner " << owner + << ". Error: " << SYSTEM_ERROR); + return 0; + } + //Correct positional memory of the ACL is weird and doesn't naturally work with the SecurityDescriptor class. + //This gets the right data to the right place + Permissions::SecurityDescriptor secDesc = Permissions::SecurityDescriptor::CreateDACL(paDACL->AclSize); + memcpy(secDesc.GetDACL(), paDACL, paDACL->AclSize); + LocalFree(pDesc); + return Permissions::GetOwnerRightsFromACL(owner, secDesc); + } + + ACCESS_MASK Folder::GetEveryonePermissions() { + Permissions::Owner everyone(L"Everyone"); + return this->GetAccessPermissions(everyone); + } + + bool Folder::TakeOwnership() { + std::optional BluespawnOwner = Permissions::GetProcessOwner(); + if(BluespawnOwner == std::nullopt) { + return false; + } + return this->SetFolderOwner(*BluespawnOwner); + } +} // namespace FileSystem diff --git a/BLUESPAWN-win-client/src/util/log/CLISink.cpp b/BLUESPAWN-win-client/src/util/log/CLISink.cpp new file mode 100644 index 00000000..c6e2dae1 --- /dev/null +++ b/BLUESPAWN-win-client/src/util/log/CLISink.cpp @@ -0,0 +1,116 @@ +#include "util/log/CLISink.h" + +#include + +#include + +#include "util/Utils.h" + +#include "user/bluespawn.h" + +namespace Log { + + void CLISink::SetConsoleColor(CLISink::MessageColor color) { + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(hConsole, static_cast(color)); + } + + CLISink::CLISink() : hMutex{ CreateMutexW(nullptr, false, L"Local\\CLI-Mutex") } {} + + void CLISink::LogMessage(IN CONST LogLevel& level, IN CONST std::wstring& message) { + AcquireMutex mutex{ hMutex }; + if(level.Enabled()) { + SetConsoleColor(CLISink::PrependColors[static_cast(level.severity)]); + std::wcout << CLISink::MessagePrepends[static_cast(level.severity)] << " "; + SetConsoleColor(CLISink::MessageColor::LIGHTGREY); + std::wcout << message << std::endl; + } + } + + bool CLISink::operator==(IN CONST LogSink& sink) const { return (bool) dynamic_cast(&sink); } + + void CLISink::RecordDetection(IN CONST std::shared_ptr& detection, IN RecordType type) { + if(type == RecordType::PreScan && Bluespawn::EnablePreScanDetections || type == RecordType::PostScan) { + BeginCriticalSection _{ *detection }; + + AcquireMutex mutex{ hMutex }; + + SetConsoleColor(CLISink::PrependColors[4]); + std::wcout << CLISink::MessagePrepends[4] << (type == RecordType::PreScan ? L"[Pre-Scan] " : L" "); + SetConsoleColor(CLISink::MessageColor::LIGHTGREY); + + std::wcout << L"Detection ID: " << detection->dwID << std::endl; + + std::wcout << L"\tDetection Recorded at " << FormatWindowsTime(detection->context.DetectionCreatedTime) + << std::endl; + if(detection->context.note) { + std::wcout << L"\tNote: " << *detection->context.note << std::endl; + } + if(detection->context.FirstEvidenceTime) { + std::wcout << L"\tFirst Evidence at " << FormatWindowsTime(*detection->context.FirstEvidenceTime) + << std::endl; + } + + if(detection->context.hunts.size()) { + std::wcout << L"\tDetected by: "; + short cnt = detection->context.hunts.size(); + for(auto& hunt : detection->context.hunts) { + cnt--; + std::wcout << hunt; + if(cnt > 0) { + std::wcout << L", "; + } + } + std::wcout << std::endl; + } + + if(detection->DetectionStale) { + std::wcout << L"\tDetection is stale" << std::endl; + } + + std::wcout << L"\tDetection Type: " + << (detection->type == DetectionType::FileDetection ? + L"File" : + detection->type == DetectionType::ProcessDetection ? + L"Process" : + detection->type == DetectionType::RegistryDetection ? + L"Registry" : + detection->type == DetectionType::ServiceDetection ? + L"Service" : + std::get(detection->data).DetectionType) + << std::endl; + + std::wcout << L"\tDetection Certainty: " << static_cast(detection->info.GetCertainty()) + << std::endl; + std::wcout << L"\tDetection Data: " << std::endl; + + auto properties{ detection->Serialize() }; + for(auto& pair : properties) { + std::wcout << L"\t\t" << pair.first << ": " << pair.second << std::endl; + } + } + } + + void CLISink::RecordAssociation(IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& a) { + AcquireMutex mutex{ hMutex }; + + SetConsoleColor(CLISink::PrependColors[2]); + std::wcout << CLISink::MessagePrepends[2]; + SetConsoleColor(CLISink::MessageColor::LIGHTGREY); + std::wcout << L" Detections with IDs " << first->dwID << L" and " << second->dwID << L" now are associated" + << L" with strength " << static_cast(a) << std::endl; + } + + void CLISink::UpdateCertainty(IN CONST std::shared_ptr& detection) { + BeginCriticalSection _{ *detection }; + AcquireMutex mutex{ hMutex }; + + SetConsoleColor(CLISink::PrependColors[2]); + std::wcout << CLISink::MessagePrepends[2]; + SetConsoleColor(CLISink::MessageColor::LIGHTGREY); + std::wcout << L" Detection with ID " << detection->dwID << L" now has certainty " + << static_cast(detection->info.GetCertainty()) << std::endl; + } +} // namespace Log diff --git a/BLUESPAWN-win-client/src/util/log/DebugSink.cpp b/BLUESPAWN-win-client/src/util/log/DebugSink.cpp new file mode 100644 index 00000000..ec87662b --- /dev/null +++ b/BLUESPAWN-win-client/src/util/log/DebugSink.cpp @@ -0,0 +1,88 @@ +#include + +#include + +#include "util/log/DebugSink.h" +#include "user/bluespawn.h" +#include "util/Utils.h" + +#define DEBUG_STREAM(...) \ + OutputDebugStringW((std::wstringstream{} << __VA_ARGS__).str().c_str()) + +#define DETECTION_DEBUG_STREAM(...) \ + DEBUG_STREAM((type == RecordType::PreScan ? L"[Pre-Scan Detection]" : L"[Detection]") << L"[ID " << detection->dwID << \ + L"]" << __VA_ARGS__); + +namespace Log{ + + void DebugSink::LogMessage(IN CONST LogLevel& level, IN CONST std::wstring& message){ + BeginCriticalSection _{ hGuard }; + + if(level.Enabled()){ + DEBUG_STREAM(DebugSink::MessagePrepends[static_cast(level.severity)] << L" " << message); + } + } + + bool DebugSink::operator==(IN CONST LogSink& sink) const{ + return (bool) dynamic_cast(&sink); + } + + void DebugSink::RecordDetection(IN CONST std::shared_ptr& detection, IN RecordType type){ + + if(type == RecordType::PreScan && Bluespawn::EnablePreScanDetections || type == RecordType::PostScan){ + + BeginCriticalSection __{ *detection }; + BeginCriticalSection _{ hGuard }; + + DETECTION_DEBUG_STREAM(L" Detection Logged at " << FormatWindowsTime(detection->context.DetectionCreatedTime)); + if(detection->context.note){ + DETECTION_DEBUG_STREAM(L" Note: " << *detection->context.note); + } + if(detection->context.FirstEvidenceTime){ + DETECTION_DEBUG_STREAM(L" First Evidence: " << FormatWindowsTime(*detection->context.FirstEvidenceTime)); + } + + if(detection->context.hunts.size()){ + std::wstringstream hunts{}; + for(auto& hunt : detection->context.hunts){ + hunts << hunt << L", "; + } + DETECTION_DEBUG_STREAM(L" Associated Hunts: " << hunts.str()); + } + + if(detection->DetectionStale){ + DETECTION_DEBUG_STREAM(L" Detection is Stale"); + } + + DETECTION_DEBUG_STREAM(L" Detection Type: " << + (detection->type == DetectionType::FileDetection ? L"File" : + detection->type == DetectionType::ProcessDetection ? L"Process" : + detection->type == DetectionType::RegistryDetection ? L"Registry" : + detection->type == DetectionType::ServiceDetection ? L"Service" : + std::get(detection->data).DetectionType)); + + DETECTION_DEBUG_STREAM(L" Detection Certainty: " << static_cast(detection->info.GetCertainty())); + + auto properties{ detection->Serialize() }; + for(auto& pair : properties){ + DETECTION_DEBUG_STREAM(L"[Data] " << pair.first << L": " << pair.second); + } + } + } + + void DebugSink::RecordAssociation(IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, IN CONST Association& a){ + BeginCriticalSection _{ hGuard }; + + DEBUG_STREAM(L"[Detection][ID " << first->dwID << L"]" << L" Associated with " << second->dwID << + L" with strength " << static_cast(a)); + } + + void DebugSink::UpdateCertainty(IN CONST std::shared_ptr& detection){ + BeginCriticalSection __{ *detection }; + BeginCriticalSection _{ hGuard }; + + DEBUG_STREAM(L"[Detection][ID " << detection->dwID << L"]" << L" now has certainty " + << static_cast(detection->info.GetCertainty())); + } +} diff --git a/BLUESPAWN-win-client/src/util/log/Log.cpp b/BLUESPAWN-win-client/src/util/log/Log.cpp new file mode 100644 index 00000000..ab9f23bb --- /dev/null +++ b/BLUESPAWN-win-client/src/util/log/Log.cpp @@ -0,0 +1,77 @@ +#include "util/log/Log.h" + +#include + +#include "util/StringUtils.h" + +namespace Log { + std::vector> _LogSinks; + LogTerminator endlog{}; + + LogMessage& LogMessage::operator<<(IN CONST LogTerminator& terminator) { + auto message{ stream.str() }; + + stream = std::wstringstream{}; + level.LogMessage(message); + + return *this; + } + + LogMessage& LogMessage::InnerLog(IN CONST Loggable& loggable, IN CONST std::true_type&) { + return operator<<(loggable.ToString()); + } + + template<> + LogMessage& LogMessage::InnerLog(IN CONST LPCSTR& data, IN CONST std::false_type&) { + stream << StringToWidestring(data); + return *this; + } + + template<> + LogMessage& LogMessage::InnerLog(IN CONST std::string& data, IN CONST std::false_type&) { + stream << StringToWidestring(data); + return *this; + } + + LogMessage::LogMessage(IN CONST LogLevel& level) : level{ level } {} + LogMessage::LogMessage(IN CONST LogLevel& level, IN CONST std::wstringstream& message) : level{ level }, stream{} { + stream << message.str(); + } + + void AddSink(IN CONST std::shared_ptr& sink, + IN CONST std::vector>& levels) { + LogSink* pointer{ sink.get() }; + bool exists{ false }; + + for(auto& existing : _LogSinks) { + if(*existing == *sink) { + pointer = existing.get(); + exists = true; + } + } + + if(!exists) { + _LogSinks.emplace_back(sink); + } + + for(auto level : levels) { + level.get().AddSink(pointer); + } + } + + std::wstring FormatErrorMessage(DWORD dwErrorCode) { + //https://stackoverflow.com/a/45565001/3302799 + LPWSTR psz{ nullptr }; + auto cchMsg = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, + dwErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&psz), 0, nullptr); + if(cchMsg) { + auto delfunc{ [](void* p) { ::LocalFree(p); } }; + std::unique_ptr ptrBuffer(psz, delfunc); + return std::wstring(ptrBuffer.get(), cchMsg); + } else { + auto error_code{ ::GetLastError() }; + return L"Unable to format error message!"; + } + } +} // namespace Log diff --git a/BLUESPAWN-win-client/src/util/log/LogLevel.cpp b/BLUESPAWN-win-client/src/util/log/LogLevel.cpp new file mode 100644 index 00000000..0c76d76a --- /dev/null +++ b/BLUESPAWN-win-client/src/util/log/LogLevel.cpp @@ -0,0 +1,56 @@ +#include "util/log/LogLevel.h" +#include "util/log/LogSink.h" + +namespace Log { + + LogLevel::LogLevel(IN Severity severity, + IN CONST std::optional& detail OPTIONAL) : + enabled{ true }, + severity{ severity }, + detail{ detail }{} + LogLevel::LogLevel(IN Severity severity, + IN bool DefaultState, + IN CONST std::optional& detail OPTIONAL) : + enabled{ enabled }, + severity{ severity }, + detail{ detail }{} + + LogLevel LogLevel::LogError{Severity::LogError, true }; + + LogLevel LogLevel::LogWarn{Severity::LogWarn, true }; + + LogLevel LogLevel::LogInfo1{ Severity::LogInfo, true, Detail::Low }; + + LogLevel LogLevel::LogInfo2{ Severity::LogInfo, false, Detail::Moderate }; + + LogLevel LogLevel::LogInfo3{ Severity::LogInfo, false, Detail::High }; + + LogLevel LogLevel::LogVerbose1{ Severity::LogVerbose, false, Detail::Low }; + + LogLevel LogLevel::LogVerbose2{ Severity::LogVerbose, false, Detail::Moderate }; + + LogLevel LogLevel::LogVerbose3{ Severity::LogVerbose, false, Detail::High }; + + void LogLevel::Enable(){ enabled = true; } + void LogLevel::Disable(){ enabled = false; } + bool LogLevel::Toggle(){ return enabled = !enabled; } + bool LogLevel::Enabled() const { return enabled; } + + void LogLevel::AddSink(IN LogSink* sink){ + for(auto existing : sinks){ + if(*existing == *sink){ + return; + } + } + + sinks.emplace_back(sink); + } + + void LogLevel::LogMessage(IN CONST std::wstring& message){ + if(enabled){ + for(auto sink : sinks){ + sink->LogMessage(*this, message); + } + } + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/ServerSink.cpp b/BLUESPAWN-win-client/src/util/log/ServerSink.cpp similarity index 88% rename from BLUESPAWN-client/src/util/log/ServerSink.cpp rename to BLUESPAWN-win-client/src/util/log/ServerSink.cpp index 24c7c0f9..0f40936e 100644 --- a/BLUESPAWN-client/src/util/log/ServerSink.cpp +++ b/BLUESPAWN-win-client/src/util/log/ServerSink.cpp @@ -3,7 +3,7 @@ #include #include "util/log/ServerSink.h" -#include "common/StringUtils.h" +#include "util/StringUtils.h" #ifndef GRPC_BROKEN @@ -48,7 +48,7 @@ namespace Log { return gpbInfo; } - std::vector ServerSink::GetFileReactions(const std::vector>& detections) { + std::vector ServerSink::GetFileReactions(const std::vector>& detections) { std::vector fileDetections; for (auto& detection : detections) { @@ -65,20 +65,20 @@ namespace Log { return fileDetections; } - std::vector ServerSink::GetRegistryReactions(const std::vector>& detections) { + std::vector ServerSink::GetRegistryReactions(const std::vector>& detections) { return std::vector(); } - std::vector ServerSink::GetProcessReactions(const std::vector>& detections) { + std::vector ServerSink::GetProcessReactions(const std::vector>& detections) { return std::vector(); } - std::vector ServerSink::GetServiceReactions(const std::vector>& detections) { + std::vector ServerSink::GetServiceReactions(const std::vector>& detections) { return std::vector(); } void ServerSink::LogMessage(const LogLevel& level, const std::string& message, const std::optional info, - const std::vector>& detections){ + const std::vector>& detections){ if (!level.Enabled()) return; diff --git a/BLUESPAWN-win-client/src/util/log/XMLSink.cpp b/BLUESPAWN-win-client/src/util/log/XMLSink.cpp new file mode 100644 index 00000000..049227e8 --- /dev/null +++ b/BLUESPAWN-win-client/src/util/log/XMLSink.cpp @@ -0,0 +1,230 @@ +#include "util/log/XMLSink.h" + +#include +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" + +#include "user/bluespawn.h" + +namespace Log { + + std::wstring ToWstringPad(DWORD value, size_t length = 2) { + wchar_t* buf = new wchar_t[length + 1]; + swprintf(buf, (L"%0" + std::to_wstring(length) + L"d").c_str(), value); + std::wstring str = buf; + delete[] buf; + return str; + } + + void UpdateLog(XMLSink* sink) { + HandleWrapper hRecordEvent{ CreateEventW(nullptr, false, false, L"Local\\FlushLogs") }; + while(true) { + WaitForSingleObject(hRecordEvent, INFINITE); + sink->Flush(); + } + } + + XMLSink::XMLSink() : + Root{ XMLDoc.NewElement("bluespawn") }, LogRoot{ XMLDoc.NewElement("log-messages") }, thread{ + CreateThread(nullptr, 0, PTHREAD_START_ROUTINE(UpdateLog), this, CREATE_SUSPENDED, nullptr) + } { + SYSTEMTIME time{}; + GetLocalTime(&time); + wFileName = L"bluespawn-" + ToWstringPad(time.wMonth) + L"-" + ToWstringPad(time.wDay) + L"-" + + ToWstringPad(time.wYear, 4) + L"-" + ToWstringPad(time.wHour) + ToWstringPad(time.wMinute) + L"-" + + ToWstringPad(time.wSecond) + L".xml"; + XMLDoc.InsertEndChild(Root); + Root->InsertEndChild(LogRoot); + ResumeThread(thread); + } + + XMLSink::XMLSink(const std::wstring& wFileName) : + Root{ XMLDoc.NewElement("bluespawn") }, wFileName{ wFileName }, LogRoot{ XMLDoc.NewElement("log-messages") }, + thread{ CreateThread(nullptr, 0, PTHREAD_START_ROUTINE(UpdateLog), this, 0, nullptr) } { + XMLDoc.InsertEndChild(Root); + } + + XMLSink::~XMLSink() { + XMLDoc.SaveFile(WidestringToString(wFileName).c_str()); + TerminateThread(thread, 0); + } + + void InsertElement(IN tinyxml2::XMLDocument& XMLDoc, + IN tinyxml2::XMLElement* parent, + IN CONST std::string& name, + IN CONST std::wstring& value) { + auto elem{ XMLDoc.NewElement(name.c_str()) }; + elem->SetText(WidestringToString(value).c_str()); + parent->InsertEndChild(elem); + } + + void XMLSink::UpdateCertainty(IN CONST std::shared_ptr& detection) { + BeginCriticalSection __{ *detection }; + BeginCriticalSection _{ hGuard }; + if(detections.find(detection->dwID) != detections.end()) { + for(auto child{ detections.at(detection->dwID)->FirstChildElement() }; child; + child = child->NextSiblingElement()) { + if(child->Name() == std::string{ "certainty" }) { + child->SetText(detection->info.GetCertainty()); + } + if(child->Name() == std::string{ "raw-certainty" }) { + child->SetText(detection->info.GetIntrinsicCertainty()); + } + } + } + } + + void AddAssociation(IN tinyxml2::XMLDocument& doc, IN tinyxml2::XMLElement* to, IN DWORD id, IN double strength) { + for(auto child{ to->FirstChildElement() }; child; child = child->NextSiblingElement()) { + if(child->Name() == std::string{ "associated-detections" }) { + for(auto elem{ child->FirstChildElement() }; elem; elem = elem->NextSiblingElement()) { + if(elem->GetText() == std::to_string(id)) { + double existing{ 0 }; + elem->FindAttribute("strength")->QueryDoubleValue(&existing); + elem->SetAttribute("strength", 1.0 - (1 - existing) * (1 - strength)); + return; + } + } + + auto elem{ doc.NewElement("association") }; + elem->SetAttribute("strength", strength); + elem->SetText(std::to_string(id).c_str()); + child->InsertEndChild(elem); + return; + } + } + + auto assocations{ doc.NewElement("associated-detections") }; + auto elem{ doc.NewElement("association") }; + elem->SetAttribute("strength", strength); + elem->SetText(std::to_string(id).c_str()); + assocations->InsertEndChild(elem); + to->InsertEndChild(assocations); + } + + void XMLSink::RecordDetection(IN CONST std::shared_ptr& detection, IN RecordType type) { + if(type == RecordType::PreScan && !Bluespawn::EnablePreScanDetections) { + return; + } + + BeginCriticalSection __{ *detection }; + BeginCriticalSection _{ hGuard }; + + tinyxml2::XMLElement* detect{ nullptr }; + if(detections.find(detection->dwID) != detections.end()) { + detect = detections.at(detection->dwID); + detect->DeleteChildren(); + } else { + detect = XMLDoc.NewElement("detection"); + detections.emplace(detection->dwID, detect); + Root->InsertEndChild(detect); + } + + if(type == RecordType::PreScan) { + detect->SetAttribute("prescan", true); + } + + detect->SetAttribute("type", + (detection->type == DetectionType::FileDetection ? + "File" : + detection->type == DetectionType::ProcessDetection ? + "Process" : + detection->type == DetectionType::RegistryDetection ? + "Registry" : + detection->type == DetectionType::ServiceDetection ? + "Service" : + WidestringToString(std::get(detection->data).DetectionType)) + .c_str()); + + detect->SetAttribute("id", std::to_string(detection->dwID).c_str()); + detect->SetAttribute("time", + WidestringToString(FormatWindowsTime(detection->context.DetectionCreatedTime)).c_str()); + + if(detection->context.FirstEvidenceTime) { + InsertElement(XMLDoc, detect, "first-evidence-time", + FormatWindowsTime(*detection->context.FirstEvidenceTime)); + } + + if(detection->context.note) { + InsertElement(XMLDoc, detect, "note", *detection->context.note); + } + + InsertElement(XMLDoc, detect, "certainty", std::to_wstring(detection->info.GetCertainty())); + InsertElement(XMLDoc, detect, "raw-certainty", std::to_wstring(detection->info.GetIntrinsicCertainty())); + + if(detection->context.hunts.size()) { + auto hunts{ XMLDoc.NewElement("associated-hunts") }; + for(const auto& hunt : detection->context.hunts) { + InsertElement(XMLDoc, hunts, "hunt", hunt); + } + detect->InsertEndChild(hunts); + } + + auto data{ XMLDoc.NewElement("associated-data") }; + for(const auto& entry : detection->Serialize()) { + auto elem{ XMLDoc.NewElement("property") }; + elem->SetAttribute("name", WidestringToString(entry.first).c_str()); + elem->SetText(WidestringToString(entry.second).c_str()); + data->InsertEndChild(elem); + } + detect->InsertEndChild(data); + + auto assoc{ detection->info.GetAssociations() }; + if(assoc.size()) { + auto assocations{ XMLDoc.NewElement("associated-detections") }; + for(const auto& det : assoc) { + auto elem{ XMLDoc.NewElement("association") }; + elem->SetAttribute("strength", static_cast(det.second)); + elem->SetText(std::to_string(det.first->dwID).c_str()); + assocations->InsertEndChild(elem); + } + detect->InsertEndChild(assocations); + } + } + + void XMLSink::RecordAssociation(IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength) { + UpdateCertainty(first); + UpdateCertainty(second); + + BeginCriticalSection _{ hGuard }; + + if(detections.find(first->dwID) != detections.end()) { + AddAssociation(XMLDoc, detections.at(first->dwID), second->dwID, strength); + } + + if(detections.find(second->dwID) != detections.end()) { + AddAssociation(XMLDoc, detections.at(second->dwID), first->dwID, strength); + } + } + + void XMLSink::LogMessage(const LogLevel& level, const std::wstring& message) { + BeginCriticalSection _{ hGuard }; + + if(level.Enabled()) { + auto msg = XMLDoc.NewElement(MessageTags[static_cast(level.severity)].c_str()); + if(level.detail) { + msg->SetAttribute("detail", *level.detail == Detail::High ? + "high" : + *level.detail == Detail::Moderate ? "moderate" : "low"); + } + + SYSTEMTIME time{}; + GetLocalTime(&time); + msg->SetAttribute("time", WidestringToString(FormatWindowsTime(time)).c_str()); + + msg->SetText(WidestringToString(message).c_str()); + LogRoot->InsertEndChild(msg); + } + } + + bool XMLSink::operator==(const LogSink& sink) const { + return (bool) dynamic_cast(&sink) && + dynamic_cast(&sink)->wFileName == wFileName; + } + + void XMLSink::Flush() { XMLDoc.SaveFile(WidestringToString(wFileName).c_str()); } +}; // namespace Log diff --git a/BLUESPAWN-client/src/util/pe/Export_Section.cpp b/BLUESPAWN-win-client/src/util/pe/Export_Section.cpp similarity index 98% rename from BLUESPAWN-client/src/util/pe/Export_Section.cpp rename to BLUESPAWN-win-client/src/util/pe/Export_Section.cpp index a4170d83..b0889155 100644 --- a/BLUESPAWN-client/src/util/pe/Export_Section.cpp +++ b/BLUESPAWN-win-client/src/util/pe/Export_Section.cpp @@ -2,8 +2,8 @@ #include "util/pe/PE_Image.h" #include "util/pe/Image_Loader.h" -#include "common/DynamicLinker.h" -#include "common/StringUtils.h" +#include "util/DynamicLinker.h" +#include "util/StringUtils.h" #include "util/log/Log.h" #include diff --git a/BLUESPAWN-client/src/util/pe/Image_Loader.cpp b/BLUESPAWN-win-client/src/util/pe/Image_Loader.cpp similarity index 100% rename from BLUESPAWN-client/src/util/pe/Image_Loader.cpp rename to BLUESPAWN-win-client/src/util/pe/Image_Loader.cpp diff --git a/BLUESPAWN-client/src/util/pe/Import_Section.cpp b/BLUESPAWN-win-client/src/util/pe/Import_Section.cpp similarity index 99% rename from BLUESPAWN-client/src/util/pe/Import_Section.cpp rename to BLUESPAWN-win-client/src/util/pe/Import_Section.cpp index 60d0ba7b..4ece2ca5 100644 --- a/BLUESPAWN-client/src/util/pe/Import_Section.cpp +++ b/BLUESPAWN-win-client/src/util/pe/Import_Section.cpp @@ -1,7 +1,7 @@ #include "util/pe/Import_Section.h" #include "util/pe/PE_Image.h" #include "util/pe/Image_Loader.h" -#include "common/StringUtils.h" +#include "util/StringUtils.h" #include diff --git a/BLUESPAWN-client/src/util/pe/PE_Image.cpp b/BLUESPAWN-win-client/src/util/pe/PE_Image.cpp similarity index 99% rename from BLUESPAWN-client/src/util/pe/PE_Image.cpp rename to BLUESPAWN-win-client/src/util/pe/PE_Image.cpp index c394cbac..732aba69 100644 --- a/BLUESPAWN-client/src/util/pe/PE_Image.cpp +++ b/BLUESPAWN-win-client/src/util/pe/PE_Image.cpp @@ -1,6 +1,6 @@ #include "util/pe/PE_Image.h" #include "util/pe/PE_Section.h" -#include "common/wrappers.hpp" +#include "util/wrappers.hpp" bool PE_Image::ValidatePE() const { MemoryWrapper<> PESignature = { new BYTE[2]{0x4D, 0x5A}, 2 }; diff --git a/BLUESPAWN-client/src/util/pe/PE_Section.cpp b/BLUESPAWN-win-client/src/util/pe/PE_Section.cpp similarity index 87% rename from BLUESPAWN-client/src/util/pe/PE_Section.cpp rename to BLUESPAWN-win-client/src/util/pe/PE_Section.cpp index 7b55de2d..11e46c46 100644 --- a/BLUESPAWN-client/src/util/pe/PE_Section.cpp +++ b/BLUESPAWN-win-client/src/util/pe/PE_Section.cpp @@ -20,11 +20,11 @@ PE_Section::PE_Section(const PE_Image& image, IMAGE_SECTION_HEADER SectionHeader } PE_Section::PE_Section(const PE_Section& copy) : - SectionHeader{ copy.SectionHeader }, - SectionContent{ copy.SectionContent }, - Signature{ copy.Signature }, - AssociatedImage{ copy.AssociatedImage }, - expanded{ copy.expanded }{} + SectionHeader{ detection->SectionHeader }, + SectionContent{ detection->SectionContent }, + Signature{ detection->Signature }, + AssociatedImage{ detection->AssociatedImage }, + expanded{ detection->expanded }{} bool PE_Section::ContainsOffset(DWORD offset) const { return offset >= SectionHeader.PointerToRawData && offset < SectionHeader.PointerToRawData + SectionHeader.SizeOfRawData; diff --git a/BLUESPAWN-client/src/util/pe/Relocation_Section.cpp b/BLUESPAWN-win-client/src/util/pe/Relocation_Section.cpp similarity index 100% rename from BLUESPAWN-client/src/util/pe/Relocation_Section.cpp rename to BLUESPAWN-win-client/src/util/pe/Relocation_Section.cpp diff --git a/BLUESPAWN-win-client/src/util/pe/Resource_Section.cpp b/BLUESPAWN-win-client/src/util/pe/Resource_Section.cpp new file mode 100644 index 00000000..e69de29b diff --git a/BLUESPAWN-client/src/util/permissions/permissions.cpp b/BLUESPAWN-win-client/src/util/permissions/permissions.cpp similarity index 71% rename from BLUESPAWN-client/src/util/permissions/permissions.cpp rename to BLUESPAWN-win-client/src/util/permissions/permissions.cpp index e6e460d5..6449aa74 100644 --- a/BLUESPAWN-client/src/util/permissions/permissions.cpp +++ b/BLUESPAWN-win-client/src/util/permissions/permissions.cpp @@ -1,85 +1,85 @@ #include "util/permissions/permissions.h" #include "util/log/Log.h" -namespace Permissions { +namespace Permissions{ - bool AccessIncludesAll(const ACCESS_MASK& access) { + bool AccessIncludesAll(const ACCESS_MASK& access){ return ((access & GENERIC_ALL) == GENERIC_ALL) || ((access & FILE_ALL_ACCESS) == FILE_ALL_ACCESS); } - bool AccessIncludesWrite(const ACCESS_MASK& access) { - return AccessIncludesAll(access) || + bool AccessIncludesWrite(const ACCESS_MASK& access){ + return AccessIncludesAll(access) || ((access & GENERIC_WRITE) == GENERIC_WRITE) || ((access & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE); } - - bool AccessIncludesRead(const ACCESS_MASK& access) { - return AccessIncludesAll(access) || - ((access & GENERIC_READ) == GENERIC_READ) || + + bool AccessIncludesRead(const ACCESS_MASK& access){ + return AccessIncludesAll(access) || + ((access & GENERIC_READ) == GENERIC_READ) || ((access & FILE_GENERIC_READ) == FILE_GENERIC_READ); } - - bool AccessIncludesExecute(const ACCESS_MASK& access) { - return AccessIncludesAll(access) || + + bool AccessIncludesExecute(const ACCESS_MASK& access){ + return AccessIncludesAll(access) || ((access & GENERIC_EXECUTE) == GENERIC_EXECUTE) || ((access & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE); } - bool AccessIncludesWriteOwner(const ACCESS_MASK& access) { - return AccessIncludesAll(access) || + bool AccessIncludesWriteOwner(const ACCESS_MASK& access){ + return AccessIncludesAll(access) || ((access & WRITE_OWNER) == WRITE_OWNER); } - bool AccessContainsDelete(const ACCESS_MASK& access) { + bool AccessContainsDelete(const ACCESS_MASK& access){ return AccessIncludesAll(access) || ((access & DELETE) == DELETE); } - void AccessAddAll(ACCESS_MASK& access) { + void AccessAddAll(ACCESS_MASK& access){ access |= GENERIC_ALL; } - void AccessAddWrite(ACCESS_MASK& access) { + void AccessAddWrite(ACCESS_MASK& access){ access |= GENERIC_WRITE; } - void AccessAddRead(ACCESS_MASK& access) { + void AccessAddRead(ACCESS_MASK& access){ access |= GENERIC_READ; } - void AccessAddExecute(ACCESS_MASK& access) { + void AccessAddExecute(ACCESS_MASK& access){ access |= GENERIC_EXECUTE; } - void AccessAddWriteOwner(ACCESS_MASK& access) { + void AccessAddWriteOwner(ACCESS_MASK& access){ access |= WRITE_OWNER; } - void AccessAddDelete(ACCESS_MASK& access) { + void AccessAddDelete(ACCESS_MASK& access){ access |= DELETE; } - ACCESS_MASK GetOwnerRightsFromACL(const Owner& owner, const SecurityDescriptor& acl) { + ACCESS_MASK GetOwnerRightsFromACL(const Owner& owner, const SecurityDescriptor& acl){ TRUSTEE_W tOwnerTrustee; BuildTrusteeWithSidW(&tOwnerTrustee, owner.GetSID()); ACCESS_MASK amAccess{ 0 }; auto dacl = acl.GetDACL(); auto x{ GetLastError() }; HRESULT hr = GetEffectiveRightsFromAclW(dacl, &tOwnerTrustee, &amAccess); - if (hr != ERROR_SUCCESS) { - LOG_ERROR("Error getting rights from acl with owner " << owner << ". ERROR: " << hr ); + if(hr != ERROR_SUCCESS){ + LOG_ERROR("Error getting rights from acl with owner " << owner << ". ERROR: " << hr); return 0; } return amAccess; } - bool UpdateObjectACL(const std::wstring& wsObjectName, const SE_OBJECT_TYPE& seObjectType, const Owner& oOwner, const ACCESS_MASK& amDesiredAccess, const bool& bDeny) { + bool UpdateObjectACL(const std::wstring& wsObjectName, const SE_OBJECT_TYPE& seObjectType, const Owner& oOwner, const ACCESS_MASK& amDesiredAccess, const bool& bDeny){ PACL pOldDacl; PSECURITY_DESCRIPTOR pDesc{ nullptr }; HRESULT hr = GetNamedSecurityInfoW(reinterpret_cast(wsObjectName.c_str()), seObjectType, DACL_SECURITY_INFORMATION, nullptr, nullptr, &pOldDacl, nullptr, &pDesc); AllocationWrapper awDesc{ pDesc, 0, AllocationWrapper::LOCAL_ALLOC }; - if (hr != ERROR_SUCCESS) { + if(hr != ERROR_SUCCESS){ LOG_ERROR("Couldn't read current DACL for object " << wsObjectName << ". (Error " << hr << ")"); SetLastError(hr); return false; @@ -95,16 +95,16 @@ namespace Permissions { PACL pNewDacl{ nullptr }; hr = SetEntriesInAcl(1, &ea, pOldDacl, &pNewDacl); AllocationWrapper awNewDacl{ pNewDacl, 0, AllocationWrapper::LOCAL_ALLOC }; - if (hr != ERROR_SUCCESS) { + if(hr != ERROR_SUCCESS){ LOG_ERROR("Couldn't update DACL for object " << wsObjectName << ". (Error " << hr << ")"); SetLastError(hr); return false; } hr = SetNamedSecurityInfoW(const_cast(wsObjectName.c_str()), seObjectType, - DACL_SECURITY_INFORMATION, - NULL, NULL, pNewDacl, NULL); - if (hr != ERROR_SUCCESS) { + DACL_SECURITY_INFORMATION, + NULL, NULL, pNewDacl, NULL); + if(hr != ERROR_SUCCESS){ LOG_ERROR("Couldn't set new DACL for object " << wsObjectName << ". (Error " << hr << ")"); SetLastError(hr); return false; @@ -115,26 +115,26 @@ namespace Permissions { SecurityDescriptor::SecurityDescriptor(DWORD dwSize, SecurityDescriptor::SecurityDataType type) : GenericWrapper(reinterpret_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize)), - [](LPVOID memory) { HeapFree(GetProcessHeap(), 0, memory); }, nullptr) { - switch (type) { + [](LPVOID memory){ HeapFree(GetProcessHeap(), 0, memory); }, nullptr){ + switch(type){ case SecurityDescriptor::SecurityDataType::USER_SID: - lpUserSID = reinterpret_cast(WrappedObject); + lpUserSID = reinterpret_cast(*ReferenceCounter); break; case SecurityDescriptor::SecurityDataType::GROUP_SID: - lpGroupSID = reinterpret_cast(WrappedObject); + lpGroupSID = reinterpret_cast(*ReferenceCounter); break; case SecurityDescriptor::SecurityDataType::DACL: - dacl = reinterpret_cast(WrappedObject); + dacl = reinterpret_cast(*ReferenceCounter); break; case SecurityDescriptor::SecurityDataType::SACL: - sacl = reinterpret_cast(WrappedObject); + sacl = reinterpret_cast(*ReferenceCounter); break; } } SecurityDescriptor::SecurityDescriptor(PISECURITY_DESCRIPTOR lpSecurity) : - GenericWrapper(lpSecurity, LocalFree, nullptr) { - if (lpSecurity) { + GenericWrapper(lpSecurity, LocalFree, nullptr){ + if(lpSecurity){ lpUserSID = lpSecurity->Owner; lpGroupSID = lpSecurity->Group; dacl = lpSecurity->Dacl; @@ -142,26 +142,26 @@ namespace Permissions { } } - SecurityDescriptor SecurityDescriptor::CreateUserSID(DWORD dwSize) { + SecurityDescriptor SecurityDescriptor::CreateUserSID(DWORD dwSize){ return SecurityDescriptor(dwSize, SecurityDescriptor::SecurityDataType::USER_SID); } - SecurityDescriptor SecurityDescriptor::CreateGroupSID(DWORD dwSize) { + SecurityDescriptor SecurityDescriptor::CreateGroupSID(DWORD dwSize){ return SecurityDescriptor(dwSize, SecurityDescriptor::SecurityDataType::GROUP_SID); } - SecurityDescriptor SecurityDescriptor::CreateDACL(DWORD dwSize) { + SecurityDescriptor SecurityDescriptor::CreateDACL(DWORD dwSize){ return SecurityDescriptor(dwSize, SecurityDescriptor::SecurityDataType::DACL); } - SecurityDescriptor SecurityDescriptor::CreateSACL(DWORD dwSize) { + SecurityDescriptor SecurityDescriptor::CreateSACL(DWORD dwSize){ return SecurityDescriptor(dwSize, SecurityDescriptor::SecurityDataType::SACL); } - PACL SecurityDescriptor::GetDACL() const { return this->dacl; } - PACL SecurityDescriptor::GetSACL() const { return this->sacl; } - PSID SecurityDescriptor::GetUserSID() const { return this->lpUserSID; } - PSID SecurityDescriptor::GetGroupSID() const { return this->lpGroupSID; } + PACL SecurityDescriptor::GetDACL() const{ return this->dacl; } + PACL SecurityDescriptor::GetSACL() const{ return this->sacl; } + PSID SecurityDescriptor::GetUserSID() const{ return this->lpUserSID; } + PSID SecurityDescriptor::GetGroupSID() const{ return this->lpGroupSID; } Owner::Owner(IN const std::wstring& name) : wName{ name }, bExists{ true } { DWORD dwSIDLen{}; @@ -174,48 +174,41 @@ namespace Permissions { DWORD dwTempSIDLen = dwSIDLen; LookupAccountNameW(nullptr, wName.c_str(), tempSID.GetUserSID(), &dwTempSIDLen, Domain.data(), &dwTempDomainLen, &SIDType); - if (SIDType == SidTypeUser || SIDType == SidTypeDeletedAccount) { + if(SIDType == SidTypeUser || SIDType == SidTypeDeletedAccount){ otType = OwnerType::USER; LOG_VERBOSE(3, "Owner with name " << wName << " is a user."); sdSID = SecurityDescriptor::CreateUserSID(dwSIDLen); - if (!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetUserSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetUserSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting user with name " << wName << " " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); - if (SIDType == SidTypeDeletedAccount) { + if(SIDType == SidTypeDeletedAccount){ LOG_VERBOSE(2, "User with name " << wName << " has been deleted."); bExists = false; - } - else if (SIDType != SidTypeUser) { + } else if(SIDType != SidTypeUser){ LOG_VERBOSE(2, "User with name " << wName << " does not exist."); bExists = false; - } - else { + } else{ LOG_VERBOSE(3, "User with name " << wName << " found."); } } - } - else if (SIDType == SidTypeGroup || SIDType == SidTypeWellKnownGroup || SIDType == SidTypeAlias) { + } else if(SIDType == SidTypeGroup || SIDType == SidTypeWellKnownGroup || SIDType == SidTypeAlias){ otType = OwnerType::GROUP; - if (SIDType == SidTypeWellKnownGroup) { + if(SIDType == SidTypeWellKnownGroup){ LOG_VERBOSE(3, "Owner with name " << wName << " is a well known group."); - } - else { + } else{ LOG_VERBOSE(3, "Owner with name " << wName << " is a group."); } sdSID = SecurityDescriptor::CreateGroupSID(dwSIDLen); - if (!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetGroupSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetGroupSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting group with name " << wName << " " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); LOG_VERBOSE(3, "Group with name " << wName << " exists."); } - } - else { + } else{ otType = OwnerType::NONE; LOG_ERROR("Name " << wName << " does not correspond to a known owner type."); bExists = false; @@ -231,27 +224,23 @@ namespace Permissions { std::vector Domain(dwDomainLen); std::vector Name(dwNameLen); - if (!LookupAccountSid(nullptr, sdSID.GetUserSID(), Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountSid(nullptr, sdSID.GetUserSID(), Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting owner " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); wName = std::wstring(Name.data()); - if (SIDType == SidTypeDeletedAccount) { + if(SIDType == SidTypeDeletedAccount){ otType = OwnerType::USER; LOG_VERBOSE(2, "User " << wName << " has been deleted."); bExists = false; - } - else if (SIDType == SidTypeGroup || SIDType == SidTypeWellKnownGroup || SIDType == SidTypeAlias) { + } else if(SIDType == SidTypeGroup || SIDType == SidTypeWellKnownGroup || SIDType == SidTypeAlias){ LOG_VERBOSE(2, "Group " << wName << " exists."); otType = OwnerType::GROUP; - } - else if (SIDType == SidTypeUser) { + } else if(SIDType == SidTypeUser){ LOG_VERBOSE(3, "User " << wName << " Exists."); otType = OwnerType::USER; - } - else { + } else{ otType = OwnerType::NONE; LOG_ERROR("Unknown owner type."); bExists = false; @@ -266,32 +255,32 @@ namespace Permissions { Owner::Owner(IN const std::wstring& name, IN const std::wstring& domain, IN const SecurityDescriptor& sid, IN const bool& exists, IN const OwnerType& type) : wName{ name }, wDomainName{ domain }, sdSID{ sid }, bExists{ exists }, otType{ type } {} - bool Owner::Exists() const { + bool Owner::Exists() const{ return bExists; } - std::wstring Owner::GetName() const { + std::wstring Owner::GetName() const{ return wName; } - std::wstring Owner::GetDomainName() const { + std::wstring Owner::GetDomainName() const{ return wDomainName; } - PSID Owner::GetSID() const { - if (otType == OwnerType::USER) return sdSID.GetUserSID(); + PSID Owner::GetSID() const{ + if(otType == OwnerType::USER) return sdSID.GetUserSID(); return sdSID.GetGroupSID(); } - OwnerType Owner::GetOwnerType() const { + OwnerType Owner::GetOwnerType() const{ return otType; } - std::wstring Owner::ToString() const { + std::wstring Owner::ToString() const{ return wName; } - User::User(IN const std::wstring& uName) : Owner{ uName , true, OwnerType::USER} { + User::User(IN const std::wstring& uName) : Owner{ uName , true, OwnerType::USER }{ DWORD dwSIDLen{}; DWORD dwDomainLen{}; SID_NAME_USE SIDType{}; @@ -299,27 +288,24 @@ namespace Permissions { sdSID = SecurityDescriptor::CreateUserSID(dwSIDLen); std::vector Domain(dwDomainLen); - if (!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetUserSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetUserSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting user with name " << wName << " " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); - if (SIDType == SidTypeDeletedAccount) { + if(SIDType == SidTypeDeletedAccount){ LOG_VERBOSE(2, "User with name " << wName << " has been deleted."); bExists = false; - } - else if (SIDType != SidTypeUser) { + } else if(SIDType != SidTypeUser){ LOG_VERBOSE(2, "User with name " << wName << " does not exist."); bExists = false; - } - else { + } else{ LOG_VERBOSE(3, "User with name " << wName << " found."); } } } - User::User(IN const SecurityDescriptor& sid) : Owner{ sid , true, OwnerType::USER } { + User::User(IN const SecurityDescriptor& sid) : Owner{ sid , true, OwnerType::USER }{ DWORD dwDomainLen{}; DWORD dwNameLen{}; SID_NAME_USE SIDType{ SidTypeUnknown }; @@ -328,28 +314,25 @@ namespace Permissions { std::vector Domain(dwDomainLen); std::vector Name(dwNameLen); - if (!LookupAccountSid(nullptr, sdSID.GetUserSID(), Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountSid(nullptr, sdSID.GetUserSID(), Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting user " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); wName = std::wstring(Name.data()); - if (SIDType == SidTypeDeletedAccount) { + if(SIDType == SidTypeDeletedAccount){ LOG_VERBOSE(2, "User " << wName << " has been deleted."); bExists = false; - } - else if (SIDType != SidTypeUser) { + } else if(SIDType != SidTypeUser){ LOG_VERBOSE(2, "User doesn't exist."); bExists = false; - } - else { + } else{ LOG_VERBOSE(3, "User " << wName << " Exists."); } } } - Group::Group(IN const std::wstring& name) : Owner{ name, true, OwnerType::GROUP } { + Group::Group(IN const std::wstring& name) : Owner{ name, true, OwnerType::GROUP }{ DWORD dwSIDLen{}; DWORD dwDomainLen{}; SID_NAME_USE SIDType{}; @@ -357,26 +340,23 @@ namespace Permissions { sdSID = SecurityDescriptor::CreateUserSID(dwSIDLen); std::vector Domain(dwDomainLen); - if (!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetUserSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountNameW(nullptr, wName.c_str(), sdSID.GetUserSID(), &dwSIDLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting user with name " << wName << " " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); - if (SIDType == SidTypeWellKnownGroup) { + if(SIDType == SidTypeWellKnownGroup){ LOG_VERBOSE(2, "Group with name " << wName << " is a well known group."); - } - else if (SIDType == SidTypeGroup || SIDType == SidTypeAlias) { + } else if(SIDType == SidTypeGroup || SIDType == SidTypeAlias){ LOG_VERBOSE(2, "Group with name " << wName << " found."); - } - else { + } else{ LOG_VERBOSE(3, "Group with name " << wName << " does not exist."); bExists = false; } } } - Group::Group(IN const SecurityDescriptor& sid) : Owner{ sid, true, OwnerType::GROUP } { + Group::Group(IN const SecurityDescriptor& sid) : Owner{ sid, true, OwnerType::GROUP }{ DWORD dwDomainLen{}; DWORD dwNameLen{}; SID_NAME_USE SIDType{ SidTypeUnknown }; @@ -385,46 +365,43 @@ namespace Permissions { std::vector Domain(dwDomainLen); std::vector Name(dwNameLen); - if (!LookupAccountSid(nullptr, sdSID.GetUserSID(), Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountSid(nullptr, sdSID.GetUserSID(), Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting user " << GetLastError()); bExists = false; - } - else { + } else{ wDomainName = std::wstring(Domain.data()); wName = std::wstring(Name.data()); - if (SIDType == SidTypeWellKnownGroup) { + if(SIDType == SidTypeWellKnownGroup){ LOG_VERBOSE(2, "Group with name " << wName << " is a well known group."); - } - else if (SIDType == SidTypeGroup || SIDType == SidTypeAlias) { + } else if(SIDType == SidTypeGroup || SIDType == SidTypeAlias){ LOG_VERBOSE(2, "Group with name " << wName << " found."); - } - else { + } else{ LOG_VERBOSE(3, "Group with name " << wName << " does not exist."); bExists = false; } } } - std::optional GetProcessOwner() { + std::optional GetProcessOwner(){ HANDLE hToken; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)){ LOG_ERROR("Couldn't access process token. Error " << GetLastError()); return std::nullopt; } DWORD dwSize{ 0 }; GetTokenInformation(hToken, TokenOwner, nullptr, dwSize, &dwSize); - PTOKEN_OWNER owner = (PTOKEN_OWNER)GlobalAlloc(GPTR, dwSize); + PTOKEN_OWNER owner = (PTOKEN_OWNER) GlobalAlloc(GPTR, dwSize); DWORD dwDomainLen{}; DWORD dwNameLen{}; SID_NAME_USE SIDType{ SidTypeUnknown }; std::vector Domain(dwDomainLen); std::vector Name(dwNameLen); - if (owner == nullptr) { + if(owner == nullptr){ LOG_ERROR("Unable to allocate space for owner token."); goto fail; } - if (!GetTokenInformation(hToken, TokenOwner, owner, dwSize, &dwSize)) { + if(!GetTokenInformation(hToken, TokenOwner, owner, dwSize, &dwSize)){ LOG_ERROR("Couldn't get owner from token. Error " << GetLastError()); goto fail; } @@ -432,10 +409,10 @@ namespace Permissions { Domain = std::vector(dwDomainLen); Name = std::vector(dwNameLen); - if (!LookupAccountSid(nullptr, owner->Owner, Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)) { + if(!LookupAccountSid(nullptr, owner->Owner, Name.data(), &dwNameLen, Domain.data(), &dwDomainLen, &SIDType)){ LOG_ERROR("Error getting owner " << GetLastError()); } - if (owner != nullptr) GlobalFree(owner); + if(owner != nullptr) GlobalFree(owner); CloseHandle(hToken); return Owner(Name.data()); fail: diff --git a/BLUESPAWN-client/src/util/processes/Analyzer.cpp b/BLUESPAWN-win-client/src/util/processes/Analyzer.cpp similarity index 100% rename from BLUESPAWN-client/src/util/processes/Analyzer.cpp rename to BLUESPAWN-win-client/src/util/processes/Analyzer.cpp diff --git a/BLUESPAWN-win-client/src/util/processes/CheckLolbin.cpp b/BLUESPAWN-win-client/src/util/processes/CheckLolbin.cpp new file mode 100644 index 00000000..0ad8a715 --- /dev/null +++ b/BLUESPAWN-win-client/src/util/processes/CheckLolbin.cpp @@ -0,0 +1,213 @@ + + +#include +#include +#include +#include + +#include "util/StringUtils.h" + +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" +#include "util/processes/CommandParser.h" +#include "util/processes/ProcessUtils.h" + +std::vector lolbins{ L"cmd.exe", + L"powershell.exe", + L"explorer.exe", + L"net.exe", + L"net1.exe", + L"At.exe", + L"Atbroker.exe", + L"Bash.exe", + L"Bitsadmin.exe", + L"Cmstp.exe", + L"Diskshadow.exe", + L"Dnscmd.exe", + L"Extexport.exe", + L"Forfiles.exe", + L"Ftp.exe", + L"Gpscript.exe", + L"Hh.exe", + L"Ie4uinit.exe", + L"Ieexec.exe", + L"Infdefaultinstall.exe", + L"Installutil.exe", + L"Mavinject.exe", + L"Microsoft.Workflow.Compiler.exe", + L"Mmc.exe", + L"Msbuild.exe", + L"Msconfig.exe", + L"Msdt.exe", + L"Mshta.exe", + L"Msiexec.exe", + L"Netsh.exe", + L"Odbcconf.exe", + L"Pcalua.exe", + L"Pcwrun.exe", + L"Presentationhost.exe", + L"Rasautou.exe", + L"Regasm.exe", + L"Register-cimprovider.exe", + L"Regsvcs.exe", + L"Regsvr32.exe", + L"Rundll32.exe", + L"Runonce.exe", + L"Runscripthelper.exe", + L"Schtasks.exe", + L"Scriptrunner.exe", + L"SyncAppvPublishingServer.exe", + L"Tttracer.exe", + L"Verclsid.exe", + L"Wab.exe", + L"Wmic.exe", + L"Xwizard.exe", + L"Appvlp.exe", + L"Bginfo.exe", + L"Cdb.exe", + L"csi.exe", + L"Devtoolslauncher.exe", + L"dnx.exe", + L"Dotnet.exe", + L"Dxcap.exe", + L"Mftrace.exe", + L"Msdeploy.exe", + L"msxsl.exe", + L"rcsi.exe", + L"Sqlps.exe", + L"SQLToolsPS.exe", + L"Squirrel.exe", + L"te.exe", + L"Tracker.exe", + L"Update.exe", + L"vsjitdebugger.exe", + L"Wsl.exe", + L"Advpack.dll", + L"Ieadvpack.dll", + L"Ieaframe.dll", + L"Mshtml.dll", + L"Pcwutl.dll", + L"Setupapi.dll", + L"Shdocvw.dll", + L"Shell32.dll", + L"Syssetup.dll", + L"Url.dll", + L"Zipfldr.dll" }; + +std::set LolbinHashes{}; + +std::map hashmap{}; + +bool IsLolbin(const FileSystem::File& file) { + if(!file.GetFileExists()) { + return false; + } + + if(!LolbinHashes.size()) { + for(auto name : lolbins) { + auto path{ FileSystem::SearchPathExecutable(name) }; + if(path) { + auto hash{ FileSystem::File{ *path }.GetSHA256Hash() }; + if(hash) { + LolbinHashes.emplace(*hash); + hashmap.emplace(name, *hash); + } + } + } + } + + auto hash{ file.GetSHA256Hash() }; + if(hash && LolbinHashes.count(*hash)) { + return true; + } + + return false; +} + +bool IsLolbinMalicious(const std::wstring& command) { + std::wstring executable{ GetImagePathFromCommand(command) }; + + LOG_VERBOSE(1, "Checking if " << command << " will execute a lolbin maliciously"); + + if(!IsLolbin(executable)) { + return false; + } + + auto args{ GetArgumentTokens(command) }; + + LOG_VERBOSE(3, "Getting hash of " << executable); + auto hash{ FileSystem::File(executable).GetSHA256Hash() }; + + LOG_VERBOSE(3, "Checking if " << executable << " is rundll32"); + if(hashmap.count(L"Rundll32.exe") && hashmap.at(L"Rundll32.exe") == hash) { + if(args.size() && args[0] != L"/sta") { + auto arg{ args[0] }; + auto br{ arg.find_first_of(L" \t,") }; + auto dll{ arg.substr(0, br) }; + auto dllpath{ FileSystem::SearchPathExecutable(dll) }; + + FileSystem::File dllfile{ *dllpath }; + if(!dllpath || !dllfile.GetFileSigned()) { + LOG_INFO(2, "rundll32 found to be executing " << dll); + return true; + } + + if(hashmap.count(L"Shell32.dll") && hashmap.at(L"Shell32.dll") == dllfile.GetSHA256Hash() && + br != std::wstring::npos) { + auto start{ arg.find_first_not_of(L" ,\t", br) }; + auto func{ arg.substr(start, arg.find_first_of(L" ,\t", start)) }; + LOG_INFO(3, "rundll32 found to be executing shell32"); + return !CompareIgnoreCaseW(func, L"SHCreateLocalServerRunDll"); + } else { + LOG_INFO(2, "rundll32 found to be executing " << dll); + return true; + } + } + return false; + } + + LOG_VERBOSE(3, "Checking if " << executable << " is mmc.exe"); + if(hashmap.count(L"Mmc.exe") && hashmap.at(L"Mmc.exe") == hash) { + for(auto& arg : args) { + if(FileSystem::SearchPathExecutable(arg)) { + LOG_INFO(3, "mmc found to be executing " << arg); + return true; + } + } + return false; + } + + LOG_VERBOSE(3, "Checking if " << executable << " is presentationhost"); + if(hashmap.count(L"Presentationhost.exe") && hashmap.at(L"Presentationhost.exe") == hash) { + for(auto& arg : args) { + if(FileSystem::SearchPathExecutable(arg)) { + LOG_INFO(3, "PresentationHost found to be executing " << arg); + return true; + } + } + return false; + } + + LOG_VERBOSE(3, "Checking if " << executable << " is Mshta.exe"); + if(hashmap.count(L"Mshta.exe") && hashmap.at(L"Mshta.exe") == hash) { + return args.size(); + } + + LOG_VERBOSE(3, "Checking if " << executable << " is Msiexec.exe"); + if(hashmap.count(L"Msiexec.exe") && hashmap.at(L"Msiexec.exe") == hash){ + return args.size() && args[0] != L"/V"; + } + + LOG_VERBOSE(3, "Checking if " << executable << " is explorer.exe"); + if(hashmap.count(L"explorer.exe") && hashmap.at(L"explorer.exe") == hash) { + for(auto& arg : args) { + if(FileSystem::SearchPathExecutable(arg)) { + LOG_INFO(2, "explorer found to be executing " << arg); + return true; + } + } + return false; + } else{ + return true; + } +} diff --git a/BLUESPAWN-client/src/util/processes/CommandParser.cpp b/BLUESPAWN-win-client/src/util/processes/CommandParser.cpp similarity index 99% rename from BLUESPAWN-client/src/util/processes/CommandParser.cpp rename to BLUESPAWN-win-client/src/util/processes/CommandParser.cpp index ba13db1d..d0490aae 100644 --- a/BLUESPAWN-client/src/util/processes/CommandParser.cpp +++ b/BLUESPAWN-win-client/src/util/processes/CommandParser.cpp @@ -3,7 +3,7 @@ #include -#include "common/StringUtils.h" +#include "util/StringUtils.h" #include "util/filesystem/FileSystem.h" #include "util/log/Log.h" diff --git a/BLUESPAWN-client/src/util/processes/PERemover.cpp b/BLUESPAWN-win-client/src/util/processes/PERemover.cpp similarity index 58% rename from BLUESPAWN-client/src/util/processes/PERemover.cpp rename to BLUESPAWN-win-client/src/util/processes/PERemover.cpp index b3cce211..d2e0dbb3 100644 --- a/BLUESPAWN-client/src/util/processes/PERemover.cpp +++ b/BLUESPAWN-win-client/src/util/processes/PERemover.cpp @@ -1,40 +1,43 @@ -#include -#include -#include +#include "util/processes/PERemover.h" + #include #include +#include +#include +#include -#include "util/processes/PERemover.h" -#include "reaction/SuspendProcess.h" #include "util/log/Log.h" #include "util/processes/ProcessUtils.h" +#include "reaction/SuspendProcess.h" + LINK_FUNCTION(NtResumeProcess, NTDLL.DLL) -PERemover::PERemover(const HandleWrapper& hProcess, LPVOID lpBaseAddress, DWORD dwImageSize) : - hProcess{ hProcess }, - lpBaseAddress{ lpBaseAddress }, - dwImageSize{ dwImageSize == -1 ? GetRegionSize(hProcess, lpBaseAddress) : dwImageSize }{ - if(!hProcess){ +PERemover::PERemover(const HandleWrapper& hProcess, LPVOID lpBaseAddress, DWORD dwImageSize) : + hProcess{ hProcess }, lpBaseAddress{ lpBaseAddress }, dwImageSize{ dwImageSize == -1 ? + GetRegionSize(hProcess, lpBaseAddress) : + dwImageSize } { + if(!hProcess) { auto x = GetLastError(); - LOG_ERROR(x); + LOG_ERROR(L"Failed to retrieve process handle"); } } PERemover::PERemover(DWORD dwPID, LPVOID lpBaseAddress, DWORD dwImageSize) : - PERemover(OpenProcess(PROCESS_ALL_ACCESS, false, dwPID), lpBaseAddress, dwImageSize){} + PERemover(OpenProcess(PROCESS_ALL_ACCESS, false, dwPID), lpBaseAddress, dwImageSize) {} PERemover::PERemover(const HandleWrapper& hProcess, const std::wstring& wsImageName) : - PERemover(hProcess, GetModuleHandleW(wsImageName.c_str()), -1){} + PERemover(hProcess, GetModuleAddress(hProcess, wsImageName.c_str()), -1) {} PERemover::PERemover(DWORD dwPID, const std::wstring& wsImageName) : - PERemover(OpenProcess(PROCESS_ALL_ACCESS, false, dwPID), GetModuleHandleW(wsImageName.c_str()), -1){} + PERemover(OpenProcess(PROCESS_ALL_ACCESS, false, dwPID), GetModuleAddress(dwPID, wsImageName.c_str()), -1) {} -bool PERemover::RemoveImage(){ +bool PERemover::RemoveImage() { Linker::NtSuspendProcess(hProcess); - LOG_VERBOSE(1, "Suspended process with PID " << GetProcessId(hProcess) << " to remove image at " << lpBaseAddress); + LOG_VERBOSE(2, "Suspended process with PID " << GetProcessId(hProcess) << " to remove image at " << lpBaseAddress); SCOPE_LOCK(Linker::NtResumeProcess(hProcess), RESUME_PROCESS); - if(CheckThreads() && AdjustPointers() && WipeMemory()){ - LOG_INFO("Successfully removed image at " << lpBaseAddress << " from process with PID " << GetProcessId(hProcess)); + if(CheckThreads() && AdjustPointers() && WipeMemory()) { + LOG_INFO(2, "Successfully removed image at " << lpBaseAddress << " from process with PID " + << GetProcessId(hProcess)); return true; } @@ -42,49 +45,56 @@ bool PERemover::RemoveImage(){ return false; } -bool PERemover::AddressIsInRegion(LPVOID lpAddress){ +bool PERemover::AddressIsInRegion(LPVOID lpAddress) { return reinterpret_cast(lpAddress) >= reinterpret_cast(lpBaseAddress) && - reinterpret_cast(lpAddress) < reinterpret_cast(lpBaseAddress) + dwImageSize; + reinterpret_cast(lpAddress) < reinterpret_cast(lpBaseAddress) + dwImageSize; } -bool PERemover::CheckThreads(){ +bool PERemover::CheckThreads() { HandleWrapper hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if(!hThreadSnapshot){ - LOG_ERROR("Unable to open Tool Help Snapshot entry to scan threads" << " (Error " << GetLastError() << ")"); + if(!hThreadSnapshot) { + LOG_ERROR("Unable to open Tool Help Snapshot entry to scan threads" + << " (Error " << GetLastError() << ")"); return false; } int PID = GetProcessId(hProcess); THREADENTRY32 ThreadEntry = { sizeof(THREADENTRY32), 0 }; - if(!Thread32First(hThreadSnapshot, &ThreadEntry)){ - LOG_ERROR("Unable to open thread entry to scan threads" << " (Error " << GetLastError() << ")"); + if(!Thread32First(hThreadSnapshot, &ThreadEntry)) { + LOG_ERROR("Unable to open thread entry to scan threads" + << " (Error " << GetLastError() << ")"); return false; } - do if(ThreadEntry.th32OwnerProcessID == PID){ - HandleWrapper hThread = OpenThread(THREAD_ALL_ACCESS, false, ThreadEntry.th32ThreadID); - if(!hThread){ - LOG_ERROR("Unable to open thread with TID " << ThreadEntry.th32ThreadID << " to scan for infected memory (Error " << GetLastError() << ")"); - return false; - } + do + if(ThreadEntry.th32OwnerProcessID == PID) { + HandleWrapper hThread = OpenThread(THREAD_ALL_ACCESS, false, ThreadEntry.th32ThreadID); + if(!hThread) { + LOG_ERROR("Unable to open thread with TID " << ThreadEntry.th32ThreadID + << " to scan for infected memory (Error " << GetLastError() + << ")"); + return false; + } - LOG_VERBOSE(2, "Thread with TID " << ThreadEntry.th32ThreadID << " detected in target process. Scanning stack now"); - if(!WalkThreadBack(hThread, ThreadEntry.th32ThreadID)){ - LOG_ERROR("Unable to remove malicious threads from infected process"); - return false; + LOG_VERBOSE(2, "Thread with TID " << ThreadEntry.th32ThreadID + << " detected in target process. Scanning stack now"); + if(!WalkThreadBack(hThread, ThreadEntry.th32ThreadID)) { + LOG_ERROR("Unable to remove malicious threads from infected process"); + return false; + } } - } while(Thread32Next(hThreadSnapshot, &ThreadEntry)); + while(Thread32Next(hThreadSnapshot, &ThreadEntry)); return true; } /// TODO: Actually walk the thread back instead of terminating it -bool PERemover::WalkThreadBack(const HandleWrapper& hThread, DWORD dwTID){ +bool PERemover::WalkThreadBack(const HandleWrapper& hThread, DWORD dwTID) { CONTEXT context{}; ZeroMemory(&context, sizeof(CONTEXT)); context.ContextFlags = CONTEXT_FULL; - if(!GetThreadContext(hThread, &context)){ + if(!GetThreadContext(hThread, &context)) { LOG_ERROR("Unable to get the context of thread in infected process"); return false; } @@ -102,16 +112,19 @@ bool PERemover::WalkThreadBack(const HandleWrapper& hThread, DWORD dwTID){ DWORD dwMachineType = IMAGE_FILE_MACHINE_AMD64; BOOL wow64 = false; IsWow64Process(hProcess, &wow64); - if(wow64){ + if(wow64) { dwMachineType = IMAGE_FILE_MACHINE_I386; } - if(AddressIsInRegion(reinterpret_cast(context.Rip))){ - if(TerminateThread(hThread, 0)){ - LOG_INFO("Thread with TID " << dwTID << " was executing malicious code at " << reinterpret_cast(context.Rip) << " and was terminated"); + if(AddressIsInRegion(reinterpret_cast(context.Rip))) { + if(TerminateThread(hThread, 0)) { + LOG_INFO(1, "Thread with TID " << dwTID << " was executing malicious code at " + << reinterpret_cast(context.Rip) << " and was terminated"); return true; } else { - LOG_ERROR("Thread with TID " << dwTID << " was executing malicious code at " << reinterpret_cast(context.Rip) << " but couldn't terminated" << " (Error " << GetLastError() << ")"); + LOG_ERROR("Thread with TID " << dwTID << " was executing malicious code at " + << reinterpret_cast(context.Rip) << " but couldn't terminated" + << " (Error " << GetLastError() << ")"); return false; } } @@ -122,24 +135,32 @@ bool PERemover::WalkThreadBack(const HandleWrapper& hThread, DWORD dwTID){ DWORD dwMachineType = IMAGE_FILE_MACHINE_I386; - if(AddressIsInRegion(reinterpret_cast(context.Eip))){ - if(TerminateThread(hThread, 0)){ - LOG_INFO("Thread with TID " << dwTID << " was executing malicious code at " << reinterpret_cast(context.Eip) << " and was terminated"); + if(AddressIsInRegion(reinterpret_cast(context.Eip))) { + if(TerminateThread(hThread, 0)) { + LOG_INFO(1, "Thread with TID " << dwTID << " was executing malicious code at " + << reinterpret_cast(context.Eip) << " and was terminated"); return true; } else { - LOG_ERROR("Thread with TID " << dwTID << " was executing malicious code at " << reinterpret_cast(context.Eip) << " but couldn't terminated"); + LOG_ERROR("Thread with TID " << dwTID << " was executing malicious code at " + << reinterpret_cast(context.Eip) << " but couldn't terminated"); return false; } } #endif SymInitialize(hProcess, nullptr, true); - while(StackWalk64(dwMachineType, hProcess , hThread, &stack, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)){ - if(AddressIsInRegion(reinterpret_cast(stack.AddrPC.Offset))){ - if(TerminateThread(hThread, 0)){ - LOG_INFO("Thread with TID " << dwTID << " was executing malicious code at " << reinterpret_cast(stack.AddrPC.Offset) << " and was terminated"); + while(StackWalk64(dwMachineType, hProcess, hThread, &stack, &context, nullptr, SymFunctionTableAccess64, + SymGetModuleBase64, nullptr)) { + if(AddressIsInRegion(reinterpret_cast(stack.AddrPC.Offset))) { + if(TerminateThread(hThread, 0)) { + LOG_INFO(1, "Thread with TID " << dwTID << " was executing malicious code at " + << reinterpret_cast(stack.AddrPC.Offset) + << " and was terminated"); return true; } else { - LOG_ERROR("Thread with TID " << dwTID << " was executing malicious code at " << reinterpret_cast(stack.AddrPC.Offset) << " but couldn't terminated" << " (Error " << GetLastError() << ")"); + LOG_ERROR("Thread with TID " << dwTID << " was executing malicious code at " + << reinterpret_cast(stack.AddrPC.Offset) + << " but couldn't terminated" + << " (Error " << GetLastError() << ")"); return false; } } @@ -149,7 +170,7 @@ bool PERemover::WalkThreadBack(const HandleWrapper& hThread, DWORD dwTID){ return true; } -DWORD GetFunctionStackSize(LPVOID lpFunction, const HandleWrapper& hProcess, const HandleWrapper& hThread){ +DWORD GetFunctionStackSize(LPVOID lpFunction, const HandleWrapper& hProcess, const HandleWrapper& hThread) { CONTEXT context{}; STACKFRAME64 stack{}; @@ -169,7 +190,7 @@ DWORD GetFunctionStackSize(LPVOID lpFunction, const HandleWrapper& hProcess, con DWORD dwMachineType = IMAGE_FILE_MACHINE_AMD64; BOOL wow64 = false; IsWow64Process(hProcess, &wow64); - if(wow64){ + if(wow64) { dwMachineType = IMAGE_FILE_MACHINE_I386; } @@ -184,9 +205,11 @@ DWORD GetFunctionStackSize(LPVOID lpFunction, const HandleWrapper& hProcess, con SetThreadContext(hThread, &context); SymInitialize(hProcess, nullptr, true); - if(StackWalk64(dwMachineType, hProcess, hThread, &stack, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr) && - StackWalk64(dwMachineType, hProcess, hThread, &stack, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)){ - if(stack.AddrStack.Offset == 0x10000){ + if(StackWalk64(dwMachineType, hProcess, hThread, &stack, &context, nullptr, SymFunctionTableAccess64, + SymGetModuleBase64, nullptr) && + StackWalk64(dwMachineType, hProcess, hThread, &stack, &context, nullptr, SymFunctionTableAccess64, + SymGetModuleBase64, nullptr)) { + if(stack.AddrStack.Offset == 0x10000) { return 0; } @@ -197,15 +220,15 @@ DWORD GetFunctionStackSize(LPVOID lpFunction, const HandleWrapper& hProcess, con return 0; } -bool PERemover::AdjustPointer(LPVOID lpAddress){ +bool PERemover::AdjustPointer(LPVOID lpAddress) { LOG_VERBOSE(2, "Updating address " << lpAddress << " to prevent calls to image"); MEMORY_BASIC_INFORMATION memory{}; - if(!VirtualQueryEx(hProcess, lpAddress, &memory, sizeof(memory))){ + if(!VirtualQueryEx(hProcess, lpAddress, &memory, sizeof(memory))) { LOG_ERROR("Unable to read memory protections at " << lpAddress << " (Error " << GetLastError() << ")"); return false; } else { - if(memory.Protect & 0xF0){ + if(memory.Protect & 0xF0) { LOG_VERBOSE(3, "Address " << lpAddress << " is executable memory; patching with a return"); // DWORD dwStackChange = GetFunctionStackSize(lpAddress, hProcess, hThread); @@ -217,7 +240,7 @@ bool PERemover::AdjustPointer(LPVOID lpAddress){ bool x64 = true; BOOL wow64 = false; IsWow64Process(hProcess, &wow64); - if(wow64){ + if(wow64) { x64 = false; } #else @@ -229,16 +252,19 @@ bool PERemover::AdjustPointer(LPVOID lpAddress){ unsigned char instruction[4]{ 0x6a, 0x00, 0x58, 0xc3 }; DWORD dwOldProtections{}; - if(!VirtualProtectEx(hProcess, lpAddress, 4, PAGE_READWRITE, &dwOldProtections)){ - LOG_ERROR("Unable to adjust memory protections at " << lpAddress << " (Error " << GetLastError() << ")"); + if(!VirtualProtectEx(hProcess, lpAddress, 4, PAGE_READWRITE, &dwOldProtections)) { + LOG_ERROR("Unable to adjust memory protections at " << lpAddress << " (Error " << GetLastError() + << ")"); return false; } - if(!WriteProcessMemory(hProcess, lpAddress, instruction, 4, nullptr)){ - LOG_ERROR("Unable to adjust memory protections at " << lpAddress << " (Error " << GetLastError() << ")"); + if(!WriteProcessMemory(hProcess, lpAddress, instruction, 4, nullptr)) { + LOG_ERROR("Unable to adjust memory protections at " << lpAddress << " (Error " << GetLastError() + << ")"); return false; } - if(!VirtualProtectEx(hProcess, lpAddress, 4, dwOldProtections, &dwOldProtections)){ - LOG_ERROR("Unable to repair memory protections at " << lpAddress << " (Error " << GetLastError() << ")"); + if(!VirtualProtectEx(hProcess, lpAddress, 4, dwOldProtections, &dwOldProtections)) { + LOG_ERROR("Unable to repair memory protections at " << lpAddress << " (Error " << GetLastError() + << ")"); return false; } } else { @@ -249,23 +275,25 @@ bool PERemover::AdjustPointer(LPVOID lpAddress){ return true; } -bool PERemover::AdjustPointers(){ - +bool PERemover::AdjustPointers() { ULONG_PTR address = 0; - while(address < (1LL << 48)){ + while(address < (1LL << 48)) { MEMORY_BASIC_INFORMATION memory{}; - if(!VirtualQueryEx(hProcess, reinterpret_cast(address), &memory, sizeof(memory))){ + if(!VirtualQueryEx(hProcess, reinterpret_cast(address), &memory, sizeof(memory))) { return true; } else { address += memory.RegionSize; - AllocationWrapper buffer = { VirtualAlloc(nullptr, memory.RegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), memory.RegionSize, AllocationWrapper::VIRTUAL_ALLOC }; - if(ReadProcessMemory(hProcess, memory.BaseAddress, buffer, memory.RegionSize, nullptr)){ - for(int i = 0; i < memory.RegionSize - sizeof(ULONG_PTR); i++){ + AllocationWrapper buffer = { VirtualAlloc(nullptr, memory.RegionSize, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE), + memory.RegionSize, AllocationWrapper::VIRTUAL_ALLOC }; + if(ReadProcessMemory(hProcess, memory.BaseAddress, buffer, memory.RegionSize, nullptr)) { + for(int i = 0; i < memory.RegionSize - sizeof(ULONG_PTR); i++) { auto lpCheckAddress = *reinterpret_cast(reinterpret_cast(LPVOID(buffer)) + i); - if(AddressIsInRegion(lpCheckAddress)){ - if(!AdjustPointer(lpCheckAddress)){ - LOG_ERROR("Unable to adjust pointer to bad memory at " << address + i << " (Error " << GetLastError() << ")"); + if(AddressIsInRegion(lpCheckAddress)) { + if(!AdjustPointer(lpCheckAddress)) { + LOG_ERROR("Unable to adjust pointer to bad memory at " << address + i << " (Error " + << GetLastError() << ")"); return false; } } @@ -276,25 +304,27 @@ bool PERemover::AdjustPointers(){ return true; } -bool PERemover::WipeMemory(){ +bool PERemover::WipeMemory() { // For now, this will only wipe exports and the entrypoint. - AllocationWrapper headers = { VirtualAlloc(nullptr, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), 0x1000, AllocationWrapper::VIRTUAL_ALLOC }; - if(!ReadProcessMemory(hProcess, lpBaseAddress, headers, 0x1000, nullptr)){ + AllocationWrapper headers = { VirtualAlloc(nullptr, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), 0x1000, + AllocationWrapper::VIRTUAL_ALLOC }; + if(!ReadProcessMemory(hProcess, lpBaseAddress, headers, 0x1000, nullptr)) { LOG_ERROR("Unable to read memory to wipe at " << lpBaseAddress << " (Error " << GetLastError() << ")"); return false; } - if(headers[0] == 'M' && headers[1] == 'Z'){ + if(headers[0] == 'M' && headers[1] == 'Z') { DWORD dwNTHeaderOffset = reinterpret_cast(LPVOID(headers))->e_lfanew; DWORD dwEntrypointRVA{}; DWORD dwExportsRVA{}; DWORD dwExportsSize{}; - if(dwNTHeaderOffset + sizeof(IMAGE_NT_HEADERS64) < 0x1000){ - auto lpNTHeaders = reinterpret_cast(reinterpret_cast(LPVOID(headers)) + dwNTHeaderOffset); - if(lpNTHeaders->Signature != 0x00004550){ + if(dwNTHeaderOffset + sizeof(IMAGE_NT_HEADERS64) < 0x1000) { + auto lpNTHeaders = + reinterpret_cast(reinterpret_cast(LPVOID(headers)) + dwNTHeaderOffset); + if(lpNTHeaders->Signature != 0x00004550) { return true; } @@ -302,14 +332,16 @@ bool PERemover::WipeMemory(){ dwExportsRVA = lpNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; dwExportsSize = lpNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; } else { - AllocationWrapper NTHeaders = { VirtualAlloc(nullptr, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), 0x1000, AllocationWrapper::VIRTUAL_ALLOC }; - if(!ReadProcessMemory(hProcess, reinterpret_cast(lpBaseAddress) + dwNTHeaderOffset, NTHeaders, 0x1000, nullptr)){ + AllocationWrapper NTHeaders = { VirtualAlloc(nullptr, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), + 0x1000, AllocationWrapper::VIRTUAL_ALLOC }; + if(!ReadProcessMemory(hProcess, reinterpret_cast(lpBaseAddress) + dwNTHeaderOffset, NTHeaders, + 0x1000, nullptr)) { LOG_ERROR("Unable to read memory to wipe at " << lpBaseAddress << " (Error " << GetLastError() << ")"); return false; } auto lpNTHeaders = reinterpret_cast(LPVOID(NTHeaders)); - if(lpNTHeaders->Signature != 0x00004550){ + if(lpNTHeaders->Signature != 0x00004550) { return true; } @@ -318,24 +350,28 @@ bool PERemover::WipeMemory(){ dwExportsSize = lpNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; } - if(dwEntrypointRVA && !AdjustPointer(reinterpret_cast(lpBaseAddress) + dwEntrypointRVA)){ + if(dwEntrypointRVA && !AdjustPointer(reinterpret_cast(lpBaseAddress) + dwEntrypointRVA)) { LOG_ERROR("Failed to adjust the entrypoint of image to remove"); return false; } - if(dwExportsRVA && dwExportsSize){ - AllocationWrapper Exports = { VirtualAlloc(nullptr, dwExportsSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), dwExportsSize, AllocationWrapper::VIRTUAL_ALLOC }; - if(!ReadProcessMemory(hProcess, reinterpret_cast(lpBaseAddress) + dwExportsRVA, Exports, dwExportsSize, nullptr)){ + if(dwExportsRVA && dwExportsSize) { + AllocationWrapper Exports = { VirtualAlloc(nullptr, dwExportsSize, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE), + dwExportsSize, AllocationWrapper::VIRTUAL_ALLOC }; + if(!ReadProcessMemory(hProcess, reinterpret_cast(lpBaseAddress) + dwExportsRVA, Exports, + dwExportsSize, nullptr)) { LOG_ERROR("Unable to read memory to wipe at " << lpBaseAddress << " (Error " << GetLastError() << ")"); return false; } auto lpExports = reinterpret_cast(LPVOID(Exports)); auto dwFunctions = lpExports->NumberOfFunctions; - auto lpFuncionsPtr = reinterpret_cast(reinterpret_cast(LPVOID(Exports)) + lpExports->AddressOfFunctions - dwExportsRVA); + auto lpFuncionsPtr = reinterpret_cast(reinterpret_cast(LPVOID(Exports)) + + lpExports->AddressOfFunctions - dwExportsRVA); - for(DWORD i = 0; i < dwFunctions; i++){ - if(lpFuncionsPtr[i] && !AdjustPointer(reinterpret_cast(lpBaseAddress) + lpFuncionsPtr[i])){ + for(DWORD i = 0; i < dwFunctions; i++) { + if(lpFuncionsPtr[i] && !AdjustPointer(reinterpret_cast(lpBaseAddress) + lpFuncionsPtr[i])) { LOG_ERROR("Failed to adjust an export of image to remove"); return false; } diff --git a/BLUESPAWN-client/src/util/processes/ParseCobalt.cpp b/BLUESPAWN-win-client/src/util/processes/ParseCobalt.cpp similarity index 99% rename from BLUESPAWN-client/src/util/processes/ParseCobalt.cpp rename to BLUESPAWN-win-client/src/util/processes/ParseCobalt.cpp index 2c640594..58d4ded1 100644 --- a/BLUESPAWN-client/src/util/processes/ParseCobalt.cpp +++ b/BLUESPAWN-win-client/src/util/processes/ParseCobalt.cpp @@ -8,8 +8,8 @@ #include "util/filesystem/FileSystem.h" #include "util/log/Log.h" -#include "common/wrappers.hpp" -#include "common/StringUtils.h" +#include "util/wrappers.hpp" +#include "util/StringUtils.h" std::optional FindBeaconInfoQuick(const MemoryWrapper<>& memory){ IMAGE_DOS_HEADER hdr{}; diff --git a/BLUESPAWN-win-client/src/util/processes/ProcessUtils.cpp b/BLUESPAWN-win-client/src/util/processes/ProcessUtils.cpp new file mode 100644 index 00000000..b847627f --- /dev/null +++ b/BLUESPAWN-win-client/src/util/processes/ProcessUtils.cpp @@ -0,0 +1,317 @@ +#include "util/processes/ProcessUtils.h" + +#include + +#include "util/StringUtils.h" +#include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" + +#include "shlwapi.h" + +typedef struct _CURDIR { + UNICODE_STRING DosPath; + PVOID Handle; +} CURDIR, *PCURDIR; + +typedef struct _RTL_DRIVE_LETTER_CURDIR { + WORD Flags; + WORD Length; + ULONG TimeStamp; + STRING DosPath; +} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; + +typedef struct _RTL_USER_PROCESS_PARAMETERS_ { + ULONG MaximumLength; + ULONG Length; + ULONG Flags; + ULONG DebugFlags; + PVOID ConsoleHandle; + ULONG ConsoleFlags; + PVOID StandardInput; + PVOID StandardOutput; + PVOID StandardError; + CURDIR CurrentDirectory; + UNICODE_STRING DllPath; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + PVOID Environment; + ULONG StartingX; + ULONG StartingY; + ULONG CountX; + ULONG CountY; + ULONG CountCharsX; + ULONG CountCharsY; + ULONG FillAttribute; + ULONG WindowFlags; + ULONG ShowWindowFlags; + UNICODE_STRING WindowTitle; + UNICODE_STRING DesktopInfo; + UNICODE_STRING ShellInfo; + UNICODE_STRING RuntimeData; + RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32]; + ULONG EnvironmentSize; +} RTL_USER_PROCESS_PARAMETERS_, *PRTL_USER_PROCESS_PARAMETERS_; + +bool HookIsOkay(const Hook& hook) { + // Once Detours is set up, this will become significantly more complicated... + return false; +} + +std::vector GetExecutableNonImageSections(DWORD pid) { + // Make use of APIs in PE Sieve... + return {}; +} + +std::wstring GetProcessCommandline(const HandleWrapper& process) { + if(process) { + PROCESS_BASIC_INFORMATION information{}; + NTSTATUS status = Linker::NtQueryInformationProcess(process, ProcessBasicInformation, &information, + sizeof(information), nullptr); + if(NT_SUCCESS(status)) { + auto peb = information.PebBaseAddress; + + ULONG_PTR pointer{}; + if(!ReadProcessMemory(process, &peb->ProcessParameters, &pointer, sizeof(pointer), nullptr)) { + LOG_WARNING("Unable to read memory from process with PID " + << GetProcessId(process) << " to find its command line (error " << GetLastError() << ")"); + return {}; + } + RTL_USER_PROCESS_PARAMETERS_ params{}; + if(!ReadProcessMemory(process, LPVOID(pointer), ¶ms, sizeof(params), nullptr)) { + LOG_WARNING("Unable to read memory from process with PID " + << GetProcessId(process) << " to find its command line (error " << GetLastError() << ")"); + return {}; + } + + DWORD dwLength = params.CommandLine.Length; + auto cmdline = + AllocationWrapper{ new WCHAR[dwLength / 2 + 1], dwLength + 2, AllocationWrapper::CPP_ARRAY_ALLOC }; + if(!ReadProcessMemory(process, params.CommandLine.Buffer, cmdline, dwLength, nullptr)) { + LOG_WARNING("Unable to read memory from process with PID " + << GetProcessId(process) << " to find its command line (error " << GetLastError() << ")"); + return {}; + } + cmdline.SetByte(dwLength, 0); + cmdline.SetByte(dwLength + 1, 0); + + return std::wstring{ reinterpret_cast(LPVOID(cmdline)) }; + } else { + LOG_WARNING("Unable to query information from process with PID " + << GetProcessId(process) << " to find its command line (error " << status << ")"); + return {}; + } + } else { + LOG_WARNING("Unable to get command line of invalid process"); + return {}; + } +} + +std::wstring GetProcessCommandline(DWORD dwPID) { + HandleWrapper process{ OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, dwPID) }; + if(process) { + return GetProcessCommandline(process); + } else { + LOG_WARNING("Unable to open process with PID " << dwPID << " to find its command line (error " << GetLastError() + << ")"); + return {}; + } +} + +std::wstring GetProcessImage(const HandleWrapper& process) { + if(process) { + std::vector name{}; + DWORD dwSize{ 0 }; + QueryFullProcessImageNameW(process, 0, name.data(), &dwSize); + dwSize += 1; + name.resize(dwSize); + if(QueryFullProcessImageNameW(process, 0, name.data(), &dwSize)) { + return name.data(); + } else { + LOG_WARNING("Unable to get image path of process - " << SYSTEM_ERROR); + return {}; + } + } else { + LOG_WARNING("Unable to get image path of invalid process"); + return {}; + } +} + +std::wstring GetProcessImage(DWORD dwPID) { + HandleWrapper process{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, dwPID) }; + if(process) { + return GetProcessImage(process); + } else { + LOG_WARNING("Unable to open process with PID " << dwPID << " to find its command line (error " << GetLastError() + << ")"); + return {}; + } +} + +std::vector EnumModules(DWORD dwPID) { + HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); + if(hProcess) { + return EnumModules(hProcess); + } else { + LOG_INFO(2, "Unable to open process with PID " << dwPID << " to enumerate its modules (error " << GetLastError() + << ")"); + return {}; + } +} + +std::vector EnumModules(const HandleWrapper& hProcess) { + std::vector modules(1024); + DWORD dwBytesNeeded{}; + auto status{ EnumProcessModules(hProcess, modules.data(), 1024 * sizeof(HMODULE), &dwBytesNeeded) }; + while(dwBytesNeeded > modules.size() * sizeof(HMODULE)) { + modules.resize(dwBytesNeeded / sizeof(HMODULE)); + status = EnumProcessModules(hProcess, modules.data(), 1024 * sizeof(HMODULE), &dwBytesNeeded); + } + + std::vector vModules{}; + + if(status){ + for(auto mod : modules) { + WCHAR path[MAX_PATH]; + if(GetModuleFileNameExW(hProcess, mod, path, MAX_PATH)) { + vModules.emplace_back(path); + } else { + LOG_WARNING("Unable to get name of module at " << mod << " in process with PID " + << GetProcessId(hProcess)); + } + } + } else { + LOG_WARNING("Unable to enumerate modules in process with PID " << GetProcessId(hProcess) << " (Error " + << GetLastError() << ")"); + } + + return vModules; +} + +LPVOID GetModuleAddress(DWORD dwPID, const std::wstring& wsModuleName) { + HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); + if(hProcess) { + return GetModuleAddress(hProcess, wsModuleName); + } else { + LOG_INFO(2, "Unable to open process with PID " << dwPID << " to enumerate its modules (error " << GetLastError() + << ")"); + return {}; + } +} + +LPVOID GetModuleAddress(const HandleWrapper& hProcess, const std::wstring& wsModuleName) { + std::vector modules(1024); + DWORD dwBytesNeeded{}; + auto status{ EnumProcessModules(hProcess, modules.data(), 1024 * sizeof(HMODULE), &dwBytesNeeded) }; + while(dwBytesNeeded > modules.size() * sizeof(HMODULE)){ + modules.resize(dwBytesNeeded / sizeof(HMODULE)); + status = EnumProcessModules(hProcess, modules.data(), 1024 * sizeof(HMODULE), &dwBytesNeeded); + } + + if(status){ + for(auto mod : modules) { + WCHAR path[MAX_PATH]; + if(GetModuleFileNameExW(hProcess, mod, path, MAX_PATH)) { + if(CompareIgnoreCaseW(path, wsModuleName)) { + return mod; + } + } else { + LOG_WARNING("Unable to get name of module at " << mod << " in process with PID " + << GetProcessId(hProcess)); + } + } + } else { + LOG_WARNING("Unable to enumerate modules in process with PID " << GetProcessId(hProcess) << " (Error " + << GetLastError() << ")"); + } + + LOG_WARNING("Unable to find address of module " << wsModuleName << " in process with PID " + << GetProcessId(hProcess)); + return nullptr; +} + +DWORD GetRegionSize(const HandleWrapper& hProcess, LPVOID lpBaseAddress) { + DWORD dwImageSize = 0; + ULONG_PTR address = reinterpret_cast(lpBaseAddress); + + while(true) { + MEMORY_BASIC_INFORMATION memory{}; + if(VirtualQueryEx(hProcess, reinterpret_cast(address), &memory, sizeof(memory))) { + if(memory.AllocationBase == lpBaseAddress) { + dwImageSize += memory.RegionSize; + address += memory.RegionSize; + } else + break; + } else + break; + } + + LOG_VERBOSE(2, "Determined the size of the region to remove is " << dwImageSize); + return dwImageSize; +} + +DWORD GetRegionSize(DWORD dwPID, LPVOID lpBaseAddress) { + HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); + if(hProcess) { + return GetRegionSize(hProcess, lpBaseAddress); + } else { + LOG_WARNING("Unable to open process with PID " << dwPID << " to determine size of region at " << lpBaseAddress + << " (error " << GetLastError() << ")"); + return {}; + } +} + +std::optional GetMappedFile(DWORD dwPID, LPVOID lpAllocationBase) { + HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); + if(hProcess) { + return GetMappedFile(hProcess, lpAllocationBase); + } else { + LOG_WARNING("Unable to open process with PID " << dwPID << " to determine size of region at " + << lpAllocationBase << " (error " << GetLastError() << ")"); + return {}; + } +} + +std::optional GetMappedFile(const HandleWrapper& hProcess, LPVOID lpAllocationBase) { + std::vector filename(MAX_PATH); + auto len = GetMappedFileNameW(hProcess, lpAllocationBase, filename.data(), MAX_PATH); + if(!len) { + return std::nullopt; + } + + return FileSystem::File(std::wstring{ filename.data(), len }); +} + +namespace Utils::Process { + AllocationWrapper ReadProcessMemory(const HandleWrapper& hProcess, LPVOID lpBaseAddress, DWORD dwSize) { + if(hProcess) { + if(dwSize == -1) { + dwSize = GetRegionSize(hProcess, lpBaseAddress); + } + + AllocationWrapper wrapper{ VirtualAlloc(nullptr, dwSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE), + dwSize }; + + if(::ReadProcessMemory(hProcess, lpBaseAddress, wrapper, dwSize, nullptr)) { + return wrapper; + } else { + LOG_WARNING("Unable to read memory at " << lpBaseAddress << " in process with PID " + << GetProcessId(hProcess) << " (error " << GetLastError() + << ")"); + } + } else { + LOG_WARNING("Unable to read memory from invalid process!"); + } + return { nullptr, 0 }; + } + + AllocationWrapper ReadProcessMemory(DWORD dwPID, LPVOID lpBaseAddress, DWORD dwSize) { + HandleWrapper hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, dwPID); + if(hProcess) { + return ReadProcessMemory(hProcess, lpBaseAddress, dwSize); + } else { + LOG_WARNING("Unable to open process with PID " << dwPID << " to read memory at " << lpBaseAddress + << " (error " << GetLastError() << ")"); + return { nullptr, 0 }; + } + } +} // namespace Utils::Process diff --git a/BLUESPAWN-win-client/src/yara/args.c b/BLUESPAWN-win-client/src/yara/args.c new file mode 100644 index 00000000..a865d61b --- /dev/null +++ b/BLUESPAWN-win-client/src/yara/args.c @@ -0,0 +1,262 @@ +/* +Copyright (c) 2014. The YARA Authors. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include + +#include "args.h" + +#define args_is_long_arg(arg) \ + (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0') + + +#define args_is_short_arg(arg) \ + (arg[0] == '-' && arg[1] != '-' && arg[1] != '\0') + + +args_option_t* args_get_short_option( + args_option_t* options, + const char opt){ + while(options->type != ARGS_OPT_END){ + if(opt == options->short_name) + return options; + + options++; + } + + return NULL; +} + + +args_option_t* args_get_long_option( + args_option_t* options, + const char* arg){ + arg += 2; // skip starting -- + + while(options->type != ARGS_OPT_END){ + if(options->long_name != NULL){ + size_t l = strlen(options->long_name); + + if((arg[l] == '\0' || arg[l] == '=') && + strstr(arg, options->long_name) == arg){ + return options; + } + } + + options++; + } + + return NULL; +} + + +args_error_type_t args_parse_option( + args_option_t* opt, + const char* opt_arg, + int* opt_arg_was_used){ + char* endptr = NULL; + + if(opt_arg_was_used != NULL) + *opt_arg_was_used = 0; + + if(opt->count == opt->max_count) + return ARGS_ERROR_TOO_MANY; + + switch(opt->type){ + case ARGS_OPT_BOOLEAN: + *(bool*) opt->value = true; + break; + + case ARGS_OPT_INTEGER: + + if(opt_arg == NULL) + return ARGS_ERROR_REQUIRED_INTEGER_ARG; + + *(int*) opt->value = strtol(opt_arg, &endptr, 0); + + if(*endptr != '\0') + return ARGS_ERROR_REQUIRED_INTEGER_ARG; + + if(opt_arg_was_used != NULL) + *opt_arg_was_used = 1; + + break; + + case ARGS_OPT_STRING: + + if(opt_arg == NULL) + return ARGS_ERROR_REQUIRED_STRING_ARG; + + if(opt->max_count > 1) + ((const char**) opt->value)[opt->count] = opt_arg; + else + *(const char**) opt->value = opt_arg; + + if(opt_arg_was_used != NULL) + *opt_arg_was_used = 1; + + break; + + default: + assert(0); + } + + opt->count++; + + return ARGS_ERROR_OK; +} + + +void args_print_error( + args_error_type_t error, + const char* option){ + switch(error){ + case ARGS_ERROR_UNKNOWN_OPT: + fprintf(stderr, "unknown option `%s`\n", option); + break; + case ARGS_ERROR_TOO_MANY: + fprintf(stderr, "too many `%s` options\n", option); + break; + case ARGS_ERROR_REQUIRED_INTEGER_ARG: + fprintf(stderr, "option `%s` requires an integer argument\n", option); + break; + case ARGS_ERROR_REQUIRED_STRING_ARG: + fprintf(stderr, "option `%s` requires a string argument\n", option); + break; + case ARGS_ERROR_UNEXPECTED_ARG: + fprintf(stderr, "option `%s` doesn't expect an argument\n", option); + break; + default: + return; + } +} + + +int args_parse( + args_option_t* options, + int argc, + const char** argv){ + args_error_type_t error = ARGS_ERROR_OK; + + int i = 1; // start with i = 1, argv[0] is the program name + int o = 0; + + while(i < argc){ + const char* arg = argv[i]; + + if(args_is_long_arg(arg)){ + args_option_t* opt = args_get_long_option(options, arg); + + if(opt != NULL){ + const char* equal = strchr(arg, '='); + + if(equal) + error = args_parse_option(opt, equal + 1, NULL); + else + error = args_parse_option(opt, NULL, NULL); + } else{ + error = ARGS_ERROR_UNKNOWN_OPT; + } + } else if(args_is_short_arg(arg)){ + for(int j = 1; arg[j] != '\0'; j++){ + args_option_t* opt = args_get_short_option(options, arg[j]); + + if(opt != NULL){ + if(arg[j + 1] == '\0'){ + int arg_used; + + // short option followed by a space, argv[i + 1] could be + // an argument for the option (i.e: -a ) + error = args_parse_option(opt, argv[i + 1], &arg_used); + + // argv[i + 1] was actually an argument to the option, skip it. + if(arg_used) + i++; + } else{ + // short option followed by another option (i.e: -ab), no + // argument for this option + error = args_parse_option(opt, NULL, NULL); + } + } else{ + error = ARGS_ERROR_UNKNOWN_OPT; + } + + if(error != ARGS_ERROR_OK) + break; + } + } else{ + argv[o++] = arg; + } + + if(error != ARGS_ERROR_OK){ + args_print_error(error, arg); + exit(1); + } + + i++; + } + + return o; +} + + +void args_print_usage( + args_option_t* options, + int help_alignment){ + char buffer[128]; + + for(; options->type != ARGS_OPT_END; options++){ + int len = sprintf(buffer, " "); + + if(options->short_name != '\0') + len += sprintf(buffer + len, "-%c", options->short_name); + else + len += sprintf(buffer + len, " "); + + if(options->short_name != '\0' && options->long_name != NULL) + len += sprintf(buffer + len, ", "); + + if(options->long_name != NULL) + len += sprintf(buffer + len, "--%s", options->long_name); + + if(options->type == ARGS_OPT_STRING || + options->type == ARGS_OPT_INTEGER){ + len += sprintf( + buffer + len, + "%s%s", + (options->long_name != NULL) ? "=" : " ", + options->type_help); + } + + printf("%-*s%s\n", help_alignment, buffer, options->help); + } +} diff --git a/BLUESPAWN-client/src/yara/args.h b/BLUESPAWN-win-client/src/yara/args.h similarity index 68% rename from BLUESPAWN-client/src/yara/args.h rename to BLUESPAWN-win-client/src/yara/args.h index 1f9d267e..1ddbe7ac 100644 --- a/BLUESPAWN-client/src/yara/args.h +++ b/BLUESPAWN-win-client/src/yara/args.h @@ -38,38 +38,38 @@ extern "C" { #endif -typedef enum _args_error_type { - ARGS_ERROR_OK, - ARGS_ERROR_UNKNOWN_OPT, - ARGS_ERROR_TOO_MANY, - ARGS_ERROR_REQUIRED_INTEGER_ARG, - ARGS_ERROR_REQUIRED_STRING_ARG, - ARGS_ERROR_UNEXPECTED_ARG, -} args_error_type_t; - - -typedef enum _args_option_type { - // special - ARGS_OPT_END, - ARGS_OPT_GROUP, - // options with no arguments - ARGS_OPT_BOOLEAN, - // options with arguments (optional or required) - ARGS_OPT_INTEGER, - ARGS_OPT_STRING, -} args_option_type_t; - - -typedef struct _args_option { - args_option_type_t type; - const char short_name; - const char *long_name; - void *value; - int max_count; - const char *help; - const char *type_help; - int count; -} args_option_t; + typedef enum _args_error_type { + ARGS_ERROR_OK, + ARGS_ERROR_UNKNOWN_OPT, + ARGS_ERROR_TOO_MANY, + ARGS_ERROR_REQUIRED_INTEGER_ARG, + ARGS_ERROR_REQUIRED_STRING_ARG, + ARGS_ERROR_UNEXPECTED_ARG, + } args_error_type_t; + + + typedef enum _args_option_type { + // special + ARGS_OPT_END, + ARGS_OPT_GROUP, + // options with no arguments + ARGS_OPT_BOOLEAN, + // options with arguments (optional or required) + ARGS_OPT_INTEGER, + ARGS_OPT_STRING, + } args_option_type_t; + + + typedef struct _args_option { + args_option_type_t type; + const char short_name; + const char* long_name; + void* value; + int max_count; + const char* help; + const char* type_help; + int count; + } args_option_t; #define OPT_BOOLEAN(short_name, long_name, value, ...) \ @@ -87,15 +87,15 @@ typedef struct _args_option { #define OPT_END() { ARGS_OPT_END, 0 } -int args_parse( - args_option_t *options, - int argc, - const char **argv); + int args_parse( + args_option_t* options, + int argc, + const char** argv); -void args_print_usage( - args_option_t *options, - int alignment); + void args_print_usage( + args_option_t* options, + int alignment); #ifdef __cplusplus diff --git a/BLUESPAWN-win-client/src/yara/common.h b/BLUESPAWN-win-client/src/yara/common.h new file mode 100644 index 00000000..550560cd --- /dev/null +++ b/BLUESPAWN-win-client/src/yara/common.h @@ -0,0 +1,141 @@ +/* +Copyright (c) 2017. The YARA Authors. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef COMMON_H +#define COMMON_H + +#include + +#ifdef _WIN32 +#include +#define access _access_s +#else +#include +#endif + +#define exit_with_code(code) { result = code; goto _exit; } + + +bool compile_files( + YR_COMPILER* compiler, + int argc, + const char** argv){ + for(int i = 0; i < argc - 1; i++){ + FILE* rule_file; + const char* ns; + const char* file_name; + char* colon = NULL; + int errors; + + if(access(argv[i], 0) != 0){ + // A file with the name specified by the command-line argument wasn't + // found, it may be because the name is prefixed with a namespace, so + // lets try to find the colon that separates the namespace from the + /// actual file name. + colon = (char*) strchr(argv[i], ':'); + } + + // The namespace delimiter must be a colon not followed by a slash or + // backslash. + if(colon && *(colon + 1) != '\\' && *(colon + 1) != '/'){ + + file_name = colon + 1; + *colon = '\0'; + ns = argv[i]; + } else{ + file_name = argv[i]; + ns = NULL; + } + + if(strcmp(file_name, "-") == 0) + rule_file = stdin; + else + rule_file = fopen(file_name, "r"); + + if(rule_file == NULL){ + fprintf(stderr, "error: could not open file: %s\n", file_name); + return false; + } + + errors = yr_compiler_add_file(compiler, rule_file, ns, file_name); + + fclose(rule_file); + + if(errors > 0) + return false; + } + + return true; +} + + +bool is_integer(const char* str){ + if(*str == '-') + str++; + + if(*str == '\0') + return false; + + while(*str){ + if(!isdigit(*str)) + return false; + str++; + } + + return true; +} + + +bool is_float(const char* str){ + bool has_dot = false; + + if(*str == '-') // skip the minus sign if present + str++; + + if(*str == '.') // float can't start with a dot + return false; + + while(*str){ + if(*str == '.'){ + if(has_dot) // two dots, not a float + return false; + + has_dot = true; + } else if(!isdigit(*str)){ + return false; + } + + str++; + } + + return has_dot; // to be float must contain a dot +} + +#endif diff --git a/BLUESPAWN-win-client/src/yara/yarac.c b/BLUESPAWN-win-client/src/yara/yarac.c new file mode 100644 index 00000000..747c8e74 --- /dev/null +++ b/BLUESPAWN-win-client/src/yara/yarac.c @@ -0,0 +1,319 @@ +/* +Copyright (c) 2013. The YARA Authors. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _WIN32 + +#include +#include +#include +#include + +#else + +#include + +#endif + +#include +#include +#include +#include +#include + +#include "args.h" +#include "common.h" + +/// BEGIN MODIFICATIONS +#include +#include +/// END MODITIFICATIONS + +#ifndef MAX_PATH +#define MAX_PATH 256 +#endif + +#define MAX_ARGS_EXT_VAR 32 + + +typedef struct COMPILER_RESULTS +{ + int errors; + int warnings; + +} COMPILER_RESULTS; + + +static char* atom_quality_table; +static char* ext_vars[MAX_ARGS_EXT_VAR + 1]; +static bool ignore_warnings = false; +static bool show_version = false; +static bool show_help = false; +static bool fail_on_warnings = false; +static int max_strings_per_rule = 10000; + + +#define USAGE_STRING \ + "Usage: yarac [OPTION]... [NAMESPACE:]SOURCE_FILE... OUTPUT_FILE" + +args_option_t options[] = +{ + OPT_STRING(0, "atom-quality-table", &atom_quality_table, + "path to a file with the atom quality table", "FILE"), + + OPT_STRING_MULTI('d', "define", &ext_vars, MAX_ARGS_EXT_VAR, + "define external variable", "VAR=VALUE"), + + OPT_BOOLEAN(0, "fail-on-warnings", &fail_on_warnings, + "fail on warnings"), + + OPT_BOOLEAN('h', "help", &show_help, + "show this help and exit"), + + OPT_INTEGER(0, "max-strings-per-rule", &max_strings_per_rule, + "set maximum number of strings per rule (default=10000)", "NUMBER"), + + OPT_BOOLEAN('w', "no-warnings", &ignore_warnings, + "disable warnings"), + + OPT_BOOLEAN('v', "version", &show_version, + "show version information"), + + OPT_END() +}; + + +static void report_error( + int error_level, + const char* file_name, + int line_number, + const YR_RULE* rule, + const char* message, + void* user_data){ + char* msg_type; + + if(error_level == YARA_ERROR_LEVEL_ERROR){ + msg_type = "error"; + } else if(!ignore_warnings){ + COMPILER_RESULTS* compiler_results = (COMPILER_RESULTS*) user_data; + compiler_results->warnings++; + msg_type = "warning"; + } else{ + return; + } + + if(rule != NULL){ + fprintf( + stderr, + "%s(%d): %s in rule \"%s\": %s\n", + file_name, + line_number, + msg_type, + rule->identifier, + message); + } else{ + fprintf( + stderr, + "%s(%d): %s: %s\n", + file_name, + line_number, + msg_type, + message); + } +} + + +static bool define_external_variables( + YR_COMPILER* compiler){ + for(int i = 0; ext_vars[i] != NULL; i++){ + char* equal_sign = strchr(ext_vars[i], '='); + + if(!equal_sign){ + fprintf(stderr, "error: wrong syntax for `-d` option.\n"); + return false; + } + + // Replace the equal sign with null character to split the external + // variable definition (i.e: myvar=somevalue) in two strings: identifier + // and value. + + *equal_sign = '\0'; + + char* identifier = ext_vars[i]; + char* value = equal_sign + 1; + + if(is_float(value)){ + yr_compiler_define_float_variable( + compiler, + identifier, + atof(value)); + } else if(is_integer(value)){ + yr_compiler_define_integer_variable( + compiler, + identifier, + atoi(value)); + } else if(strcmp(value, "true") == 0 || strcmp(value, "false") == 0){ + yr_compiler_define_boolean_variable( + compiler, + identifier, + strcmp(value, "true") == 0); + } else{ + yr_compiler_define_string_variable( + compiler, + identifier, + value); + } + } + + return true; +} + + +int main( + int argc, + const char** argv){ + COMPILER_RESULTS cr; + + YR_COMPILER* compiler = NULL; + YR_RULES* rules = NULL; + + int result; + + argc = args_parse(options, argc, argv); + + if(show_version){ + printf("%s\n", YR_VERSION); + return EXIT_SUCCESS; + } + + if(show_help){ + printf("%s\n\n", USAGE_STRING); + + args_print_usage(options, 40); + printf("\nSend bug reports and suggestions to: vmalvarez@virustotal.com\n"); + + return EXIT_SUCCESS; + } + + if(argc < 2){ + fprintf(stderr, "yarac: wrong number of arguments\n"); + fprintf(stderr, "%s\n\n", USAGE_STRING); + fprintf(stderr, "Try `--help` for more options\n"); + + exit_with_code(EXIT_FAILURE); + } + + result = yr_initialize(); + + if(result != ERROR_SUCCESS) + exit_with_code(EXIT_FAILURE); + + if(yr_compiler_create(&compiler) != ERROR_SUCCESS) + exit_with_code(EXIT_FAILURE); + + if(!define_external_variables(compiler)) + exit_with_code(EXIT_FAILURE); + + if(atom_quality_table != NULL){ + result = yr_compiler_load_atom_quality_table( + compiler, atom_quality_table, 0); + + if(result != ERROR_SUCCESS){ + fprintf(stderr, "error loading atom quality table\n"); + exit_with_code(EXIT_FAILURE); + } + } + + cr.errors = 0; + cr.warnings = 0; + + yr_set_configuration(YR_CONFIG_MAX_STRINGS_PER_RULE, &max_strings_per_rule); + + if(!compile_files(compiler, argc, argv)) + exit_with_code(EXIT_FAILURE); + + if(cr.errors > 0) + exit_with_code(EXIT_FAILURE); + + if(fail_on_warnings && cr.warnings > 0) + exit_with_code(EXIT_FAILURE); + + result = yr_compiler_get_rules(compiler, &rules); + + if(result != ERROR_SUCCESS){ + fprintf(stderr, "error: %d\n", result); + exit_with_code(EXIT_FAILURE); + } + + result = yr_rules_save(rules, argv[argc - 1]); + + if(result != ERROR_SUCCESS){ + fprintf(stderr, "error: %d\n", result); + exit_with_code(EXIT_FAILURE); + } + /// BEGIN MODIFICATIONS + else{ + std::string name = std::string{ argv[argc - 1] } + ".z"; + int err{}; + auto zip = zip_open(name.c_str(), ZIP_CREATE, &err); + if(zip){ + auto source = zip_source_file(zip, argv[argc - 1], 0, 0); + if(source){ + if(-1 == zip_file_add(zip, "data", source, ZIP_FL_OVERWRITE)){ + zip_close(zip); + zip_source_close(source); + goto _exit; + } + zip_source_close(source); + zip_close(zip); + MoveFileExA(name.c_str(), argv[argc - 1], MOVEFILE_REPLACE_EXISTING); + } + if(!source){ + zip_close(zip); + goto _exit; + } + } else{ + goto _exit; + } + } + /// END MODIFICATIONS + + result = EXIT_SUCCESS; + +_exit: + + if(compiler != NULL) + yr_compiler_destroy(compiler); + + if(rules != NULL) + yr_rules_destroy(rules); + + yr_finalize(); + + return result; +} diff --git a/BLUESPAWN-client/yarac.vcxproj b/BLUESPAWN-win-client/yarac.vcxproj similarity index 100% rename from BLUESPAWN-client/yarac.vcxproj rename to BLUESPAWN-win-client/yarac.vcxproj diff --git a/BLUESPAWN.sln b/BLUESPAWN.sln index 00caae78..3f9a348b 100644 --- a/BLUESPAWN.sln +++ b/BLUESPAWN.sln @@ -3,18 +3,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28922.388 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "BLUESPAWN-common\CommonLib.vcxproj", "{25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BLUESPAWN-client", "BLUESPAWN-client\BLUESPAWN-client.vcxproj", "{159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BLUESPAWN-win-client", "BLUESPAWN-win-client\BLUESPAWN-client.vcxproj", "{159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}" ProjectSection(ProjectDependencies) = postProject {7C72350B-AA5B-41AD-8957-CE3924A7F11B} = {7C72350B-AA5B-41AD-8957-CE3924A7F11B} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pe-sieve", "BLUESPAWN-client\pe-sieve.vcxproj", "{BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pe-sieve", "BLUESPAWN-win-client\pe-sieve.vcxproj", "{BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpeconv", "BLUESPAWN-client\libpeconv.vcxproj", "{C9D09618-1DE6-3323-AED8-9B885AC8D9F3}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpeconv", "BLUESPAWN-win-client\libpeconv.vcxproj", "{C9D09618-1DE6-3323-AED8-9B885AC8D9F3}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "yarac", "BLUESPAWN-client\yarac.vcxproj", "{7C72350B-AA5B-41AD-8957-CE3924A7F11B}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "yarac", "BLUESPAWN-win-client\yarac.vcxproj", "{7C72350B-AA5B-41AD-8957-CE3924A7F11B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -24,14 +22,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Debug|x64.ActiveCfg = Debug|x64 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Debug|x64.Build.0 = Debug|x64 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Debug|x86.ActiveCfg = Debug|Win32 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Debug|x86.Build.0 = Debug|Win32 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Release|x64.ActiveCfg = Release|x64 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Release|x64.Build.0 = Release|x64 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Release|x86.ActiveCfg = Release|Win32 - {25AE1D80-3E17-4E1D-BFB4-8AFB375EBAF1}.Release|x86.Build.0 = Release|Win32 {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}.Debug|x64.ActiveCfg = Debug|x64 {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}.Debug|x64.Build.0 = Debug|x64 {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}.Debug|x86.ActiveCfg = Debug|Win32 diff --git a/NuGet.Config b/NuGet.Config deleted file mode 100644 index acd31d4e..00000000 --- a/NuGet.Config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/README.md b/README.md index 1a1fe31e..c2203241 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ +![BLUESPAWN-logo2-temp](https://user-images.githubusercontent.com/3931697/89133344-0e439500-d4e9-11ea-992f-6ae8ebe66177.png) + # BLUESPAWN -[![Client build](https://github.com/ION28/BLUESPAWN/workflows/BLUESPAWN%20client%20build/badge.svg)](https://github.com/ION28/BLUESPAWN/actions) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d070613d09404e14b47f69147a99064e)](https://www.codacy.com/manual/ION28/BLUESPAWN?utm_source=github.com&utm_medium=referral&utm_content=ION28/BLUESPAWN&utm_campaign=Badge_Grade) ![Version](https://img.shields.io/github/v/release/ION28/BLUESPAWN?include_prereleases) ![License](https://img.shields.io/github/license/ION28/BLUESPAWN?color=yellow) ![Platform](https://img.shields.io/badge/platform-x86%20%7C%20x64-lightgrey) ![Operating System](https://img.shields.io/badge/os-Windows%207%2F08%2B-blue) ![Discord](https://img.shields.io/discord/713926524167913544?color=blueviolet&label=Discord&logo=Discord&logoColor=white) +![Version](https://img.shields.io/github/v/release/ION28/BLUESPAWN?include_prereleases) ![License](https://img.shields.io/github/license/ION28/BLUESPAWN?color=yellow) ![Platform](https://img.shields.io/badge/platform-x86%20%7C%20x64-lightgrey) ![Operating System](https://img.shields.io/badge/os-Windows%207%2F08%2B-blue) [![Discord](https://img.shields.io/discord/713926524167913544?color=blueviolet&label=Discord&logo=Discord&logoColor=white)](https://discord.gg/JMxPPfZ) + +#### Code Status + +[![Win Client build](https://github.com/ION28/BLUESPAWN/workflows/BLUESPAWN-win-client%20build/badge.svg)](https://github.com/ION28/BLUESPAWN/actions) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d070613d09404e14b47f69147a99064e)](https://www.codacy.com/manual/ION28/BLUESPAWN?utm_source=github.com&utm_medium=referral&utm_content=ION28/BLUESPAWN&utm_campaign=Badge_Grade) ![Last Commit](https://img.shields.io/github/last-commit/ION28/BLUESPAWN/develop) ## Our Mission BLUESPAWN helps blue teams monitor systems in real-time against active attackers by detecting anomalous activity @@ -8,9 +14,11 @@ BLUESPAWN helps blue teams monitor systems in real-time against active attackers ## What is BLUESPAWN BLUESPAWN is an **active defense** and **endpoint detection and response tool** which means it can be used by defenders to quickly **detect**, **identify**, and **eliminate** malicious activity and malware across a network. -## Get Involved +## Get Involved & Contribute to the project Want to help make BLUESPAWN even more effective at locating and stopping malware? Join us on [the BLUESPAWN Discord Server](https://discord.gg/JMxPPfZ) and help with development or even just suggest a feature or report a bug. No experience required - there's no better way to learn about development or security than by just jumping right in! +If you'd like to help contribute code, you can get started by checking out our wiki page on [setting up your development environment](https://github.com/ION28/BLUESPAWN/wiki/Setting-up-your-Development-Environment). Please feel free to reach out to us in Discord if you run into any problems getting set up! We generally track bugs and new features through Issues and coordinate in chat when doing any development work. + ## Why we made BLUESPAWN We've created and open-sourced this for a number of reasons which include the following: @@ -29,6 +37,7 @@ Visit [this map](https://bluespawn.cloud/coverage/) to see current coverage capa > Note 2: BLUESPAWN is meant to be run by a security professional in most cases and as such, will detect on non-malicious activity sometimes. While BLUESPAWN helps to quickly surface potentially bad things, it expects the user to use the available information to make the final determination. +0. Check out the [Wiki pages](https://github.com/ION28/BLUESPAWN/wiki) to learn more about the available [command line options](https://github.com/ION28/BLUESPAWN/wiki/Getting-Started), [examples](https://github.com/ION28/BLUESPAWN/wiki/Examples), and more. 1. Download the latest release from [this page](https://github.com/ION28/BLUESPAWN/releases) 2. Open an Administrative Command Prompt 3. Run the following command to see the available options @@ -39,16 +48,24 @@ Visit [this map](https://bluespawn.cloud/coverage/) to see current coverage capa ### Mitigate Mode 4. Run the following from your Administrative Command Prompt to audit your system for the presence of many security settings ```cmd -.\BLUESPAWN-client-x64.exe --mitigate=audit --log=console +.\BLUESPAWN-client-x64.exe --mitigate --action=audit ``` -![BLUESPAWN in Action-Mitigate](https://user-images.githubusercontent.com/3931697/77474842-2f370380-6dee-11ea-9d31-9392daa0a5da.png) +![BLUESPAWN in Action-Mitigate](https://user-images.githubusercontent.com/3931697/89669848-25e69900-d8ae-11ea-836d-1618d7377211.png) ### Hunt Mode 5. Run BLUESPAWN from the Administrative Command Prompt to hunt for malicious activity on the system ```cmd -.\BLUESPAWN-client-x64.exe --hunt -l Cursory --log=console,xml --reaction=log +.\BLUESPAWN-client-x64.exe --hunt -a Cursory --log=console,xml +``` +![BLUESPAWN in Action-Hunt](https://user-images.githubusercontent.com/3931697/89669912-4878b200-d8ae-11ea-967b-03318468d711.png) + +### Monitor Mode +6. Run BLUESPAWN from the Administrative Command Prompt to monitor for malicious activity on the system +```cmd +.\BLUESPAWN-client-x64.exe --monitor -a Cursory --log=console,xml ``` -![BLUESPAWN in Action-Hunt](https://user-images.githubusercontent.com/3931697/77475483-4a564300-6def-11ea-8faf-151508af73cb.png) +![BLUESPAWN in Action-Monitor](https://user-images.githubusercontent.com/3931697/89670008-752cc980-d8ae-11ea-8490-1e0473d5f3c6.png) + ## Lines of Effort BLUESPAWN consists of 3 major modes as listed below. Several of these modules have submodules (which may not be created in the codebase yet) as listed below and all are in varying stages of planning, research, and development. Additionally, they are supported by a number of other modules. @@ -71,13 +88,25 @@ BLUESPAWN consists of 3 major modes as listed below. Several of these modules ha * PEs * Processes +## Talks, Publications, and Other Mentions + +Here are some of the places you may have heard about the project :) + +[![DEFCON 28 Blue Team Village](https://user-images.githubusercontent.com/3931697/89669226-11ee6780-d8ad-11ea-9361-fba4cb92c97c.png)](https://github.com/ION28/BLUESPAWN/blob/master/docs/media/Defcon28-BlueTeamVillage-BLUESPAWN-Presentation.pdf) + +DEFCON 28 Blue Team Village - [Overview](https://cfc.blueteamvillage.org/call-for-content-2020/talk/NCWJFG/), [Slides](https://github.com/ION28/BLUESPAWN/blob/master/docs/media/Defcon28-BlueTeamVillage-BLUESPAWN-Presentation.pdf) + +National Collegiate Cyber Defense Competition, 2020 Red Team Debrief - [Youtube](https://youtu.be/UsZhMRMGLMA?t=3582) + +BLUESPAWN Research Paper at UVA - [Paper](https://libraetd.lib.virginia.edu/downloads/1j92g810n?filename=Smith_Jacob_Technical_Report.pdf), DOI 10.18130/v3-b1n6-ef83 + ## Contact Us If you have any questions, comments, or suggestions, please feel free to send us an email at or message us in [the BLUESPAWN Discord Server](https://discord.gg/JMxPPfZ). ## Licensing & Compliance The core BLUESPAWN code is licensed under [GNU General Public License (GPL) v3.0](https://github.com/ION28/BLUESPAWN/blob/master/LICENSE). -Note that the project integrates several other libraries to provide additional features/detections. One of these is Florian Roth's [signature-base](https://github.com/Neo23x0/signature-base) which is licensed under the [Creative Commons Attribution-NonCommercial 4.0 International License](http://creativecommons.org/licenses/by-nc/4.0/). YARA rules from this project are integrated into the standard build without any changes. In order to use BLUESPAWN for any commercial purposes, you must remove everything under the "Non-Commercial Only" line in [this file](https://github.com/ION28/BLUESPAWN/blob/master/BLUESPAWN-client/resources/severe2.yar) and recompile the project. +Note that the project integrates several other libraries to provide additional features/detections. One of these is Florian Roth's [signature-base](https://github.com/Neo23x0/signature-base) which is licensed under the [Creative Commons Attribution-NonCommercial 4.0 International License](http://creativecommons.org/licenses/by-nc/4.0/). YARA rules from this project are integrated into the standard build without any changes. In order to use BLUESPAWN for any commercial purposes, you must remove everything under the "Non-Commercial Only" line in [this file](https://github.com/ION28/BLUESPAWN/blob/master/BLUESPAWN-win-client/resources/severe2.yar) and recompile the project. ## Project Authors Made with :heart: by the UVA Cyber Defense Team and the other awesome people in the core dev team listed below @@ -87,12 +116,13 @@ Made with :heart: by the UVA Cyber Defense Team and the other awesome people in * Calvin Krist ([Github](https://github.com/CalvinKrist), [Twitter](https://twitter.com/CalvinKrist)) * Will Mayes ([Github](https://github.com/wtm99), [Twitter](https://twitter.com/will_mayes99)) * David Smith ([Github](https://github.com/DavidSmith166)) +* Aaron Gdanski ([Github](https://github.com/agski331)) * Grant Matteo ([Github](https://github.com/GrantMatteo)) ## Contributors Thanks to all of the folks listed below for their contributions to BLUESPAWN! -* Alexander Kluth ([Github](https://github.com/alexclooze)) +* Alexander Kluth ([Github](https://github.com/akluth)) Want to help? Take a look at the current issues, add ideas for new features, write some code, and create a pull request! diff --git a/config/GRPC.props b/config/GRPC.props deleted file mode 100644 index 551df15a..00000000 --- a/config/GRPC.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - _WIN32_WINNT=0x601;%(PreprocessorDefinitions) - $(ProjectDir)grpc\include;$(ProjectDir)grpc\generated;%(AdditionalIncludeDirectories) - - - \ No newline at end of file diff --git a/config/buildsettings.props b/config/buildsettings.props index deabf95b..d3d01f60 100644 --- a/config/buildsettings.props +++ b/config/buildsettings.props @@ -10,19 +10,19 @@ true Unicode + + <_PropertySheetDisplayName>buildsettings + $(ProjectDir)headers;%(AdditionalIncludeDirectories) Level1 - stdcpp17 + stdcpplatest Disabled MaxSpeed - %(AdditionalIncludeDirectories) DELAYLOAD_IMPORTS_DEFINED;_UNICODE;UNICODE;_ITERATOR_DEBUG_LEVEL=0;_WIN32_WINNT=0x601;%(PreprocessorDefinitions) MultiThreaded - true - true - false + false Console diff --git a/docs/BLUESPAWN/templates/BLUESPAWN/base.html b/docs/BLUESPAWN/templates/BLUESPAWN/base.html index 432f4480..a45f39c3 100644 --- a/docs/BLUESPAWN/templates/BLUESPAWN/base.html +++ b/docs/BLUESPAWN/templates/BLUESPAWN/base.html @@ -42,10 +42,10 @@   MITRE ATT&CK Coverage -
  • Open an Administrative Command Prompt
  • Run the following command to see the available options -
    .\BLUESPAWN.exe --help
    +            
    .\BLUESPAWN-client-x64.exe --help
     
  • Mitigate Mode

    1. Run the following from your Administrative Command Prompt to audit your system for the presence of many security settings -
      .\BLUESPAWN-client-x64.exe --mitigate=audit --log=console
      +            
      .\BLUESPAWN-client-x64.exe --mitigate --action=audit
       
      BLUESPAWN in Action-Mitigate + src="https://user-images.githubusercontent.com/3931697/89669848-25e69900-d8ae-11ea-836d-1618d7377211.png">

    Hunt Mode

    1. Run BLUESPAWN from the Administrative Command Prompt to hunt for malicious activity on the system -
      .\BLUESPAWN-client-x64.exe --hunt -l Cursory --log=console,xml --reaction=log
      +            
      .\BLUESPAWN-client-x64.exe --hunt -a Cursory --log=console,xml
       
      BLUESPAWN in Action-Hunt + src="https://user-images.githubusercontent.com/3931697/89669912-4878b200-d8ae-11ea-967b-03318468d711.png"> +
    2. +
    +

    Monitor Mode

    +
      +
    1. Run BLUESPAWN from the Administrative Command Prompt to monitor for malicious activity on the system +
      .\BLUESPAWN-client-x64.exe --monitor -a Cursory --log=console,xml
      +
      BLUESPAWN in Action-Monitor
    diff --git a/testing/attack/hunt-t1183-001.bat b/testing/attack/hunt-t1546-012-test001.bat similarity index 100% rename from testing/attack/hunt-t1183-001.bat rename to testing/attack/hunt-t1546-012-test001.bat diff --git a/testing/attack/hunt-t1183-002.bat b/testing/attack/hunt-t1546-012-test002.bat similarity index 100% rename from testing/attack/hunt-t1183-002.bat rename to testing/attack/hunt-t1546-012-test002.bat diff --git a/testing/clean/hunt-t1183-001-clean.bat b/testing/clean/hunt-t1546-012-test001-clean.bat similarity index 100% rename from testing/clean/hunt-t1183-001-clean.bat rename to testing/clean/hunt-t1546-012-test001-clean.bat diff --git a/testing/clean/hunt-t1183-002-clean.bat b/testing/clean/hunt-t1546-012-test002-clean.bat similarity index 100% rename from testing/clean/hunt-t1183-002-clean.bat rename to testing/clean/hunt-t1546-012-test002-clean.bat diff --git a/testing/run-atomic-prep.ps1 b/testing/run-atomic-prep.ps1 index 413ee672..58f7b944 100644 --- a/testing/run-atomic-prep.ps1 +++ b/testing/run-atomic-prep.ps1 @@ -1,2 +1,2 @@ -# Needed for T1100 +# Needed for T1505.003 # mkdir C:\inetpub\wwwroot diff --git a/testing/run-atomic-tests.ps1 b/testing/run-atomic-tests.ps1 index 3ab5af21..3f100076 100644 --- a/testing/run-atomic-tests.ps1 +++ b/testing/run-atomic-tests.ps1 @@ -5,19 +5,25 @@ Install-AtomicRedTeam -getAtomics -verbose Import-Module "C:\AtomicRedTeam\invoke-atomicredteam\Invoke-AtomicRedTeam.psd1" -Force # Test Parameters -$T1103Args = @{ "registry_file" = "C:\AtomicRedTeam\atomics\T1103\T1103.reg" } +$T1136001Args = @{ "password" = "Chiapet1" } +$T1505003Args = @{ "web_shell_path" = "C:\inetpub\wwwroot"; "web_shells" = "C:\AtomicRedTeam\atomics\T1505.003\src" } +$T1546007Args = @{ "helper_file" = "C:\AtomicRedTeam\atomics\T1134.004\bin\calc.dll" } +$T1546010Args = @{ "registry_file" = "C:\AtomicRedTeam\atomics\T1546.010\src\T1546.010.reg" } -Invoke-AtomicTest T1004 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1015 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1037 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1050 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1053 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1037.001 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1053.005 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' Invoke-AtomicTest T1055 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1060 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1099 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1100 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1101 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1103 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -InputArgs $T1103Args -Invoke-AtomicTest T1136 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1138 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -Invoke-AtomicTest T1183 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1136.001 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -InputArgs $T1136001Args +Invoke-AtomicTest T1505.003 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -InputArgs $T1505003Args +Invoke-AtomicTest T1543.003 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1546.007 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -InputArgs $T1546007Args +Invoke-AtomicTest T1546.008 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1546.010 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' -InputArgs $T1546010Args +Invoke-AtomicTest T1546.011 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1546.012 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1546.015 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1547.001 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1547.004 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1547.005 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1562.004 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' +Invoke-AtomicTest T1569.002 -ExecutionLogPath 'd:\a\BLUESPAWN\BLUESPAWN\AtomicTestsResults.csv' diff --git a/testing/run-hunt-results-comparison.ps1 b/testing/run-hunt-results-comparison.ps1 index 4c155fdd..52667438 100644 --- a/testing/run-hunt-results-comparison.ps1 +++ b/testing/run-hunt-results-comparison.ps1 @@ -9,9 +9,13 @@ ForEach($Technique in ($tests | ForEach-Object { $_.Technique } | get-unique)) { $TechniqueTests = $tests | Where-Object { $_.Technique -eq $Technique } $TechniqueTestCount = (($TechniqueTests | Measure).count) + + $TechniqueMajor = $Technique.split(".")[0] + $TechniqueMinor = $Technique.split(".")[1] - $TechniqueResults = $results.bluespawn.hunt | Where-Object { $_.name -like "$Technique*" } - $TechniqueDetectionCount = (($TechniqueResults.detection | Measure).count) + $TechniqueResults = $results.bluespawn.detection."associated-hunts" | Where-Object { $_.hunt -like "$TechniqueMajor*" -and $_.hunt -like [string]"*$TechniqueMinor*" } + + $TechniqueDetectionCount = (($TechniqueResults | Measure).count) if($TechniqueDetectionCount -ge $TechniqueTestCount) { Write-Host "${TechniqueTestCount}/${TechniqueTestCount} Tests for Technique ${Technique}: PASSED" diff --git a/vcpkg_response_file.txt b/vcpkg_response_file.txt new file mode 100644 index 00000000..200643be --- /dev/null +++ b/vcpkg_response_file.txt @@ -0,0 +1,6 @@ +cxxopts:x64-windows-static +cxxopts:x86-windows-static +yara:x64-windows-static +yara:x86-windows-static +libzip:x64-windows-static +libzip:x86-windows-static