diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..12abf86a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.vcxproj.filters merge=union diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37a95f2f..9ea1067d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,7 @@ jobs: strategy: matrix: buildtype: [Release, Debug] + buildarch: [x64, x86] runs-on: windows-2019 @@ -22,14 +23,14 @@ jobs: - uses: actions/checkout@v1 - name: Update submodules - run: git submodule update --init --recursive + run: git submodule update --init --recursive --remote - name: Build BLUESPAWN-client run: | - "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" BLUESPAWN.sln /p:Configuration=${{ matrix.buildtype }} + "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" BLUESPAWN.sln /p:Configuration=${{ matrix.buildtype }} /p:Platform=${{ matrix.buildarch }} shell: cmd - uses: actions/upload-artifact@master with: - name: BLUESPAWN-client-${{ matrix.buildtype }} - path: artifacts\x64\${{ matrix.buildtype }}\BLUESPAWN-client.exe + name: BLUESPAWN-client-${{ matrix.buildarch }}-${{ matrix.buildtype }} + path: artifacts\${{ matrix.buildarch }}\${{ matrix.buildtype }}\BLUESPAWN-client.exe diff --git a/.gitignore b/.gitignore index 991c29c5..ea560c6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ .vs/ build/ artifacts/ -BLUESPAWN-client/external/ \ No newline at end of file +BLUESPAWN-client/external/ +*.user +*.filters +*.cache diff --git a/.gitmodules b/.gitmodules index b949b0db..376ba492 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [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 diff --git a/BLUESPAWN-client/BLUESPAWN-client.vcxproj b/BLUESPAWN-client/BLUESPAWN-client.vcxproj index 72c69455..48f47b48 100644 --- a/BLUESPAWN-client/BLUESPAWN-client.vcxproj +++ b/BLUESPAWN-client/BLUESPAWN-client.vcxproj @@ -1,4 +1,4 @@ - + @@ -19,12 +19,14 @@ + + @@ -32,13 +34,27 @@ - + + + + + + + + + + + + + + + true true @@ -51,8 +67,10 @@ + + @@ -68,12 +86,7 @@ true true - - true - true - true - true - + true true @@ -382,6 +395,8 @@ + + @@ -413,6 +428,7 @@ + @@ -420,12 +436,27 @@ + + + + + + + + + + + + + + + true true @@ -436,8 +467,10 @@ - + + + @@ -451,12 +484,7 @@ true true - - true - true - true - true - + true true @@ -490,21 +518,25 @@ + {25ae1d80-3e17-4e1d-bfb4-8afb375ebaf1} + + {bec01f8e-5892-3f6f-a741-5bbd1d0f4ef9} + $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log - $(SolutionDir)BLUESPAWN-client\external\krabsetw\krabs;$(SolutionDir)BLUESPAWN-client\external\cxxopts\include;$(SolutionDir)BLUESPAWN-client\external\boost;%(AdditionalIncludeDirectories) + $(SolutionDir)BLUESPAWN-client\external\pe-sieve\include;$(SolutionDir)BLUESPAWN-client\external\krabsetw\krabs;$(SolutionDir)BLUESPAWN-client\external\cxxopts\include;$(SolutionDir)BLUESPAWN-client\external\boost;%(AdditionalIncludeDirectories) - Secur32.lib;DbgHelp.lib;Wintrust.lib;%(AdditionalDependencies) + Secur32.lib;DbgHelp.lib;Wintrust.lib;Shlwapi.lib;%(AdditionalDependencies) diff --git a/BLUESPAWN-client/BLUESPAWN-client.vcxproj.filters b/BLUESPAWN-client/BLUESPAWN-client.vcxproj.filters index 98dcd917..374f96a4 100644 --- a/BLUESPAWN-client/BLUESPAWN-client.vcxproj.filters +++ b/BLUESPAWN-client/BLUESPAWN-client.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -58,9 +58,6 @@ {6f3b3ba6-87f7-461f-a9ca-95e5d976dc3a} - - {bb865992-8401-4892-837f-29746f11a9f7} - {d7e04114-31a5-49e8-ae0e-7f41d50aff4a} @@ -97,11 +94,8 @@ {9387c772-fdc7-45c9-a31f-d13558ffe430} - - {23120957-b2c7-4957-9951-0eabd2e5edf2} - - - {7e223556-6f7d-4752-ae74-120962505e82} + + {bb865992-8401-4892-837f-29746f11a9f7} @@ -1059,20 +1053,80 @@ Header Files\mitigation\mitigations - - Header Files\util\eventlogs + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files - Header Files\mitigation\mitigations + Header Files - - Header Files\user + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files - Header Files\user + Header Files - - Header Files\hunt\hunts + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files @@ -1119,10 +1173,10 @@ Source Files\mitigation - Source Files\user + Source Files\monitor\user - Source Files\user + Source Files\monitor\user Source Files\util\configurations @@ -1202,17 +1256,74 @@ Source Files\mitigation\mitigations - - Source Files\util\eventlogs + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files - Source Files\mitigation\mitigations + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files - Source Files\user + Source Files - - Source Files\hunt\hunt + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files \ No newline at end of file diff --git a/BLUESPAWN-client/BLUESPAWN-client.vcxproj.user b/BLUESPAWN-client/BLUESPAWN-client.vcxproj.user deleted file mode 100644 index ed20ac5a..00000000 --- a/BLUESPAWN-client/BLUESPAWN-client.vcxproj.user +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - WindowsLocalDebugger - - - - - WindowsLocalDebugger - - diff --git a/BLUESPAWN-client/external/krabsetw b/BLUESPAWN-client/external/krabsetw deleted file mode 160000 index 5b110cad..00000000 --- a/BLUESPAWN-client/external/krabsetw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5b110cadf039c5fb4136672614c93e53545ce667 diff --git a/BLUESPAWN-client/external/pe-sieve b/BLUESPAWN-client/external/pe-sieve new file mode 160000 index 00000000..e0dc1287 --- /dev/null +++ b/BLUESPAWN-client/external/pe-sieve @@ -0,0 +1 @@ +Subproject commit e0dc12876dd61d42edd765cb4d9aa90f55c3a77c diff --git a/BLUESPAWN-client/grpc/generated/ReactionData.pb.h b/BLUESPAWN-client/grpc/generated/ReactionData.pb.h index bd6b5c82..9445f8ef 100644 --- a/BLUESPAWN-client/grpc/generated/ReactionData.pb.h +++ b/BLUESPAWN-client/grpc/generated/ReactionData.pb.h @@ -197,15 +197,14 @@ inline bool Category_Parse( } enum Aggressiveness { Cursory = 0, - Moderate = 1, - Careful = 2, - Aggressive = 3, + Normal = 1, + Intensive = 2, Aggressiveness_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::google::protobuf::int32>::min(), Aggressiveness_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::google::protobuf::int32>::max() }; bool Aggressiveness_IsValid(int value); const Aggressiveness Aggressiveness_MIN = Cursory; -const Aggressiveness Aggressiveness_MAX = Aggressive; +const Aggressiveness Aggressiveness_MAX = Intensive; const int Aggressiveness_ARRAYSIZE = Aggressiveness_MAX + 1; const ::google::protobuf::EnumDescriptor* Aggressiveness_descriptor(); diff --git a/BLUESPAWN-client/headers/hunt/Hunt.h b/BLUESPAWN-client/headers/hunt/Hunt.h index 530e22bf..a4123868 100644 --- a/BLUESPAWN-client/headers/hunt/Hunt.h +++ b/BLUESPAWN-client/headers/hunt/Hunt.h @@ -13,8 +13,7 @@ class HuntRegister; #define GET_INFO() \ HuntInfo{ this->name, __func__ == std::string{"ScanCursory"} ? Aggressiveness::Cursory : \ - __func__ == std::string{"ScanModerate"} ? Aggressiveness::Moderate : \ - __func__ == std::string{"ScanCareful"} ? Aggressiveness::Careful : Aggressiveness::Aggressive, \ + __func__ == std::string{"ScanNormal"} ? Aggressiveness::Normal : Aggressiveness::Intensive, \ this->dwTacticsUsed, this->dwCategoriesAffected, this->dwSourcesInvolved, \ (long) std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() } @@ -28,7 +27,9 @@ class Hunt { std::wstring name; public: - Hunt(HuntRegister& hr, const std::wstring& name); + Hunt(const std::wstring& name); + + std::wstring GetName(); bool UsesTactics(DWORD tactics); bool UsesSources(DWORD sources); @@ -36,7 +37,8 @@ class Hunt { bool SupportsScan(Aggressiveness scan); virtual int ScanCursory(const Scope& scope, Reaction reaction); - virtual int ScanModerate(const Scope& scope, Reaction reaction); - virtual int ScanCareful(const Scope& scope, Reaction reaction); - virtual int ScanAggressive(const Scope& scope, Reaction reaction); + virtual int ScanNormal(const Scope& scope, Reaction reaction); + virtual int ScanIntensive(const Scope& scope, Reaction reaction); + + virtual void SetupMonitoring(HuntRegister& record, const Scope& scope, Aggressiveness level, Reaction reaction); }; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/HuntInfo.h b/BLUESPAWN-client/headers/hunt/HuntInfo.h index b48e8634..177d9e00 100644 --- a/BLUESPAWN-client/headers/hunt/HuntInfo.h +++ b/BLUESPAWN-client/headers/hunt/HuntInfo.h @@ -40,9 +40,8 @@ enum class Category { enum class Aggressiveness { Cursory = 0x1, // Most obvious indicators (least false positives) - Moderate = 0x2, // Examine more things - Careful = 0x4, // Examine even more things - Aggressive = 0x8 // Check everything imaginable (most false positives) + Normal = 0x2, // Examine more things + Intensive = 0x4 // Check everything imaginable (most false positives) }; // This struct is a POD type for storing information about a hunt to be logged. diff --git a/BLUESPAWN-client/headers/hunt/HuntRegister.h b/BLUESPAWN-client/headers/hunt/HuntRegister.h index 95141707..24982678 100644 --- a/BLUESPAWN-client/headers/hunt/HuntRegister.h +++ b/BLUESPAWN-client/headers/hunt/HuntRegister.h @@ -8,20 +8,25 @@ #include "Hunt.h" #include "Scope.h" +#include "user/CLI.h" using namespace std; class HuntRegister { private: - vector vRegisteredHunts{}; + vector> vRegisteredHunts{}; + IOBase& io; map>> mTactics{}; map>> mDataSources{}; map>> mAffectedThings{}; public: - void RunHunts(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction); + HuntRegister(IOBase& oIo); + + void RunHunts(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction); void RunHunt(Hunt& hunt, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction); - void RegisterHunt(Hunt* hunt); + void SetupMonitoring(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction); + void RegisterHunt(std::shared_ptr hunt); }; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/RegistryHunt.h b/BLUESPAWN-client/headers/hunt/RegistryHunt.h new file mode 100644 index 00000000..b3e120d5 --- /dev/null +++ b/BLUESPAWN-client/headers/hunt/RegistryHunt.h @@ -0,0 +1,71 @@ +#pragma once +#include "util/configurations/Registry.h" +#include "util/configurations/RegistryValue.h" +#include "HuntInfo.h" + +#include +#include +#include + +/** + * Forward facing API for checking registry key values against known good or known bad values. + * + * All hunts that check registry values should interact with functions here rather than the RegistryKey class + * when possible, and if functionality is needed that can't be provided from one of the functions here, it should + * be implemented here. This is so that the RegistryKey class can change without destroying hunts. + */ + +namespace Registry { + + typedef std::function REG_SZ_CHECK; + typedef std::function REG_DWORD_CHECK; + typedef std::function REG_BINARY_CHECK; + typedef std::function&, const std::vector&)> REG_MULTI_SZ_CHECK; + + extern REG_SZ_CHECK CheckSzEqual; + extern REG_SZ_CHECK CheckSzRegexMatch; + extern REG_SZ_CHECK CheckSzNotEqual; + extern REG_SZ_CHECK CheckSzRegexNotMatch; + extern REG_SZ_CHECK CheckSzEmpty; + + extern REG_DWORD_CHECK CheckDwordEqual; + extern REG_DWORD_CHECK CheckDwordNotEqual; + + extern REG_BINARY_CHECK CheckBinaryEqual; + extern REG_BINARY_CHECK CheckBinaryNotEqual; + extern REG_BINARY_CHECK CheckBinaryNull; + + extern REG_MULTI_SZ_CHECK CheckMultiSzSubset; + extern REG_MULTI_SZ_CHECK CheckMultiSzExclusion; + extern REG_MULTI_SZ_CHECK CheckMultiSzEmpty; + + /** + * A container class for registry values and associated data. + */ + struct RegistryCheck { + RegistryValue value; + bool MissingBad; + + REG_SZ_CHECK wCheck; + REG_DWORD_CHECK dwCheck; + REG_BINARY_CHECK lpCheck; + REG_MULTI_SZ_CHECK vCheck; + + RegistryCheck(const std::wstring& wValueName, RegistryType type, const std::wstring& wData, bool MissingBad = false, + const REG_SZ_CHECK& check = CheckSzEqual); + RegistryCheck(const std::wstring& wValueName, RegistryType type, const DWORD dwData, bool MissingBad = false, + const REG_DWORD_CHECK& check = CheckDwordEqual); + RegistryCheck(const std::wstring& wValueName, RegistryType type, const AllocationWrapper& lpData, bool MissingBad = false, + const REG_BINARY_CHECK& check = CheckBinaryEqual); + RegistryCheck(const std::wstring& wValueName, RegistryType type, const std::vector& wData, bool MissingBad = false, + const REG_MULTI_SZ_CHECK& check = CheckMultiSzSubset); + + RegistryType GetType() const; + }; + + std::vector CheckValues(const RegistryKey& key, const std::vector& values); + + std::vector CheckKeyValues(const RegistryKey& key); + + std::vector CheckSubkeys(const RegistryKey& key); +} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/RegistryHunt.hpp b/BLUESPAWN-client/headers/hunt/RegistryHunt.hpp deleted file mode 100644 index d5d9943a..00000000 --- a/BLUESPAWN-client/headers/hunt/RegistryHunt.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once -#include "util/configurations/Registry.h" -#include "hunt/reaction/Reaction.h" - -#include "util/log/HuntLogMessage.h" -#include "util/log/Log.h" - -#include - -/** - * Forward facing API for checking registry key values against known good or known bad values. - * - * All hunts that check registry values should interract with functions here rather than the RegistryKey class - * when possible, and if functionality is needed that can't be provided from one of the functions here, it should - * be implemented here. This is so that the RegistryKey class can change without destroying hunts. - */ - -namespace Registry { - enum class MatchAction { - MATCH_BAD, - NO_MATCH_BAD - }; - - /** - * The standard registry key check. This checks a given registry key against a given value. If the MatchAction is set to - * MATCH_BAD and the key's value matches the given value, the reaction is triggered. If the MatchAction is set to NO_MATCH_BAD - * and the key's value does not match the given value, the reaction is triggered. - * - * @param key The registry key object to check - * @param value The value to check the registry key's value against - * @param reaction The reaction to trigger if it is determined the registry key's value is bad - * @param bOnMatch An enum indicating whether or not the key's value should match the given value - * - * @return True if a detection occured and a reaction was dispatched; false otherwise - */ - template - inline bool CheckKey(RegistryKey key, T value, Reaction& reaction, MatchAction bOnMatch = MatchAction::NO_MATCH_BAD){ - bool equal = key.Get() == value; - - if(!equal && bOnMatch == MatchAction::NO_MATCH_BAD || equal && bOnMatch == MatchAction::MATCH_BAD){ - LOG_WARNING("Potentially bad registry key " << key << " with value \"" << key.Get() << "\". Value should " << (bOnMatch == MatchAction::NO_MATCH_BAD ? "" : "not ") << "be \"" << value << "\""); - - reaction.RegistryKeyIdentified(std::make_shared(key.GetPath(), key.GetName(), reinterpret_cast(key.GetRaw()))); - - return true; - } else { - LOG_VERBOSE(1, "Registry key value " << key << " is okay"); - - return false; - } - } - - /** - * The standard registry key check. This checks a given registry key against a list of values. If the MatchAction is set to - * MATCH_BAD and the key's value matches any given value, the reaction is triggered. If the MatchAction is set to NO_MATCH_BAD - * and the key's value does not match any given value, the reaction is triggered. - * - * @param key The registry key object to check - * @param value The values to check the registry key's value against - * @param reaction The reaction to trigger if it is determined the registry key's value is bad - * @param bOnMatch An enum indicating whether or not the key's value should match a given value - * - * @return True if a detection occured and a reaction was dispatched; false otherwise - */ - template - inline bool CheckKey(RegistryKey key, std::vector values, Reaction& reaction, MatchAction bOnMatch = MatchAction::NO_MATCH_BAD){ - T KeyValue = key.Get(); - bool matched = false; - for(auto value : values){ - bool equal = KeyValue == value; - - if(equal && bOnMatch == MATCH_BAD){ - LOG_WARNING("Potentially bad registry key " << key << " with value \"" << KeyValue << "\". Value should not be \"" << value << "\""); - - reaction.RegistryKeyIdentified(std::make_shared(key.GetPath(), key.GetName(), reinterpret_cast(key.GetRaw()))); - - return true; - } else if(equal && bOnMatch == NO_MATCH_BAD){ - matched = true; - } - } - if(bOnMatch == NO_MATCH_BAD && matched){ - LOG_VERBOSE(1, "Registry key value " << key << " is okay"); - - return false; - } else if(bOnMatch == NO_MATCH_BAD && !matched){ - std::stringstream stream; - stream << "\"" << values[0]; - for(int i = 1; i < values.size(); i++){ - stream << " or \"" << values[i] << "\""; - } - - LOG_WARNING("Potentially bad registry key " << key << " with value \"" << key.Get() << "\". Value should " << (bOnMatch == MatchAction::NO_MATCH_BAD ? "" : "not ") << "be " << stream << ""); - } - - return !matched && bOnMatch == NO_MATCH_BAD; - } - - /// A specialization of CheckKey in the case that the value is a C wide-string - template<> - inline bool CheckKey(RegistryKey key, LPCWSTR value, Reaction& reaction, MatchAction bOnMatch){ - return CheckKey(key, std::wstring(value), reaction, bOnMatch); - } - - /** - * A specialization of CheckKey in the case that the value is a string array. This handles cases where the registry key - * is a MULTI_SZ by checking all of the key's values against all of the given values. If the key is just a single string, - * then the same logic is applied to just the one value, acting like the CheckKey function normally does when given a vector. - */ - template<> - inline bool CheckKey(RegistryKey key, REG_MULTI_SZ_T values, Reaction& reaction, MatchAction bOnMatch){ - bool good = true; - - for(auto value : key.Get()){ - bool inList = find(values.begin(), values.end(), value) != values.end(); - if(inList && bOnMatch == MatchAction::MATCH_BAD || !inList && bOnMatch == MatchAction::NO_MATCH_BAD) { - LOG(Log::_LogCurrentSinks, Log::LogLevel::LogWarn, "Potentially malicious registry key value discovered - " << value << Log::endlog - << "Registry key is " << key << Log::endlog); - good = false; - } - } - - if(!good){ - reaction.RegistryKeyIdentified(std::make_shared(key.GetPath(), key.GetName(), reinterpret_cast(key.GetRaw()))); - - } else { - LOG_VERBOSE(1, "Registry key value " << key << " is okay"); - } - - return good; - } - - /** - * Checks if a registry key has any subkeys. - * - * @param key The registry key to check for subkeys - * @param reaction The reaction to trigger for each subkey - * - * @return The number of subkeys present - */ - inline int CheckForSubkeys(RegistryKey key, Reaction& reaction){ - int IDd = 0; - for(auto subkey : key.Subkeys()){ - IDd++; - reaction.RegistryKeyIdentified(std::make_shared(key.GetPath(), key.GetName(), reinterpret_cast(key.GetRaw()))); - } - - return IDd; - } - - - /** - * Checks if a registry key has any values. - * - * @param key The registry key to check for values - * @param reaction The reaction to trigger for each values - * - * @return The number of values present - */ - inline int CheckForValues(RegistryKey key, Reaction& reaction){ - int IDd = 0; - - auto values = key.KeyValues(); - - for(auto subkey : values){ - IDd++; - reaction.RegistryKeyIdentified(std::make_shared(subkey.GetPath(), subkey.GetName(), reinterpret_cast(subkey.GetRaw()))); - } - - return IDd; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/Scope.h b/BLUESPAWN-client/headers/hunt/Scope.h index ff78a3cf..7fc16315 100644 --- a/BLUESPAWN-client/headers/hunt/Scope.h +++ b/BLUESPAWN-client/headers/hunt/Scope.h @@ -10,24 +10,24 @@ */ class Scope { public: - virtual bool FileIsInScope(LPCSTR sFileName); - virtual bool FileIsInScope(HANDLE hFile); - virtual std::vector GetScopedFileHandles(); - virtual std::vector GetScopedFileNames(); + 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); - virtual bool RegistryKeyIsInScope(HKEY key); - virtual std::vector GetScopedKHEYs(); - virtual std::vector GetScopedRegKeyNames(); + 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(LPCSTR sProcessName); - virtual bool ProcessIsInScope(HANDLE hProcess); - virtual std::vector GetScopedProcessHandles(); - virtual std::vector GetScopedProcessNames(); + 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); - virtual bool ServiceIsInScope(SC_HANDLE hService); - virtual std::vector GetScopedServiceHandles(); - virtual std::vector GetScopedServiceNames(); + 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 index c17e96a1..4356cd27 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1004.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1004.h @@ -1,6 +1,7 @@ #pragma once #include "../Hunt.h" #include "hunt/reaction/Reaction.h" +#include "hunt/reaction/HuntTrigger.h" #include "hunt/reaction/Log.h" namespace Hunts { @@ -10,13 +11,12 @@ namespace Hunts { * persistence. * * @scans Cursory checks the values of the associated Winlogon keys that can be abused. - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1004 : public Hunt { public: - HuntT1004(HuntRegister& record); + HuntT1004(); virtual int ScanCursory(const Scope& scope, Reaction reaction) override; }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h index f12784f1..5dc1a87d 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1037.h @@ -11,13 +11,12 @@ namespace Hunts { * HuntT1037 examines the registry for logon scripts * * @scans Cursory checks the value of the UserInitMprLogonScript key for scripts - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1037 : public Hunt { public: - HuntT1037(HuntRegister& record); + HuntT1037(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h index e4df3461..485af63b 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1050.h @@ -2,6 +2,7 @@ #include "../Hunt.h" #include "hunt/reaction/Reaction.h" #include "hunt/reaction/Log.h" +#include "util/eventlogs/EventSubscription.h" namespace Hunts { @@ -9,14 +10,18 @@ namespace Hunts { * HuntT1050 examines Windows events for new services created * * @scans Cursory checks System logs for event id 7045 for new events - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. + * @monitor Triggers a hunt whenever System log event ID 7045 is generated */ class HuntT1050 : public Hunt { public: - HuntT1050(HuntRegister& record); + HuntT1050(); - virtual int ScanCursory(const Scope& scope, Reaction reaction) override; + virtual int ScanIntensive(const Scope& scope, Reaction reaction) override; + virtual void SetupMonitoring(HuntRegister& record, const Scope& scope, Aggressiveness level, Reaction reaction) override; + + private: + std::vector> eventSubscriptions; }; } diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1055.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1055.h new file mode 100644 index 00000000..af8736f3 --- /dev/null +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1055.h @@ -0,0 +1,26 @@ +#pragma once +#include "../Hunt.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/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 checks System logs for event id 7045 for new events + * @scans Moderate Scan not supported. + * @scans Careful Scan not supported. + * @scans Aggressive 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 index d572dd93..b1bedf3f 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1060.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1060.h @@ -9,13 +9,12 @@ namespace Hunts { * HuntT1060 examines associated Registry Run Keys * * @scans Cursory checks the values of the associated Registry Run Keys - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1060 : public Hunt { public: - HuntT1060(HuntRegister& record); + HuntT1060(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h index fbd28cb9..7ef25138 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1100.h @@ -14,14 +14,13 @@ namespace Hunts { * webshells. * * @scans Cursory Checks for obvious bad functions that indicate a webshell - * @scans Moderate Adds more suspicious indicators in the regex to look for - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @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 = { "C:\\inetpub\\wwwroot", "C:\\xampp\\htdocs" }; - std::vector web_exts = { ".php", ".jsp", ".jspx", ".asp", ".aspx", ".asmx", ".ashx", ".ascx" }; + 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{}; @@ -30,12 +29,12 @@ namespace Hunts { void SetRegexAggressivenessLevel(Aggressiveness aLevel); public: - HuntT1100(HuntRegister& record); + HuntT1100(); - void AddDirectoryToSearch(const std::string& sFileName); - void AddFileExtensionToSearch(const std::string& sFileExtension); + void AddDirectoryToSearch(const std::wstring& sFileName); + void AddFileExtensionToSearch(const std::wstring& sFileExtension); virtual int ScanCursory(const Scope& scope, Reaction reaction = Reactions::LogReaction()); - virtual int ScanModerate(const Scope& scope, Reaction reaction = Reactions::LogReaction()); + virtual int ScanNormal(const Scope& scope, Reaction reaction = Reactions::LogReaction()); }; } \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h index a6c943c5..0fe85b70 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1101.h @@ -13,16 +13,15 @@ namespace Hunts { * HuntT1101 examines Security Support Providers (SSPs) on the system * * @scans Cursory checks the names of the SSPs on the system. - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1101 : public Hunt { private: std::vector okSecPackages = { L"\"\"", L"wsauth", L"kerberos", L"msv1_0", L"schannel", L"wdigest", L"tspkg", L"pku2u" }; public: - HuntT1101(HuntRegister& record); + HuntT1101(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h index 0cdc66e6..ba65d124 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1103.h @@ -12,13 +12,12 @@ namespace Hunts { * persistence and privilege escalation. * * @scans Cursory checks the values of the associated AppInit DLLs keys that can be abused. - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1103 : public Hunt { public: - HuntT1103(HuntRegister& record); + HuntT1103(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h index af671eba..8ddda1c1 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1131.h @@ -12,16 +12,15 @@ namespace Hunts { * * @scans Cursory checks the values of the associated Authentication packages * keys that can be abused. - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1131 : public Hunt { private: std::vector okAuthPackages = { L"msv1_0", L"SshdPinAuthLsa" }; std::vector okNotifPackages = { L"scecli" }; public: - HuntT1131(HuntRegister& record); + HuntT1131(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h index 6d30f9f5..0e9d2887 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1138.h @@ -11,13 +11,12 @@ namespace Hunts { * * @scans Cursory checks the values of the associated Application Shimming registry * keys that can be abused. - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1138 : public Hunt { public: - HuntT1138(HuntRegister& record); + HuntT1138(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h index 557b8716..9153af5a 100644 --- a/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1182.h @@ -11,13 +11,12 @@ namespace Hunts { * * @scans Cursory checks the values of the associated AppCert DLLs keys that * can be abused. - * @scans Moderate Scan not supported. - * @scans Careful Scan not supported. - * @scans Aggressive Scan not supported. + * @scans Normal Scan not supported. + * @scans Intensive Scan not supported. */ class HuntT1182 : public Hunt { public: - HuntT1182(HuntRegister& record); + HuntT1182(); virtual int ScanCursory(const Scope& scope, Reaction reaction); }; diff --git a/BLUESPAWN-client/headers/hunt/hunts/HuntT1183.h b/BLUESPAWN-client/headers/hunt/hunts/HuntT1183.h new file mode 100644 index 00000000..6aefe865 --- /dev/null +++ b/BLUESPAWN-client/headers/hunt/hunts/HuntT1183.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../Hunt.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/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); + }; +} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/hunt/reaction/Detections.h b/BLUESPAWN-client/headers/hunt/reaction/Detections.h index dbb59de0..cfab719e 100644 --- a/BLUESPAWN-client/headers/hunt/reaction/Detections.h +++ b/BLUESPAWN-client/headers/hunt/reaction/Detections.h @@ -7,6 +7,7 @@ #include #include "hunt/HuntInfo.h" +#include "util/configurations/RegistryValue.h" enum class DetectionType { File, @@ -25,10 +26,12 @@ struct DETECTION { /// Note that the hash will have to be manually set. struct FILE_DETECTION : public DETECTION { std::wstring wsFileName; + std::wstring wsFilePath; BYTE hash[256]; - FILE_DETECTION(const std::wstring& wsFileName) : + FILE_DETECTION(const std::wstring& wsFileName, const std::wstring& wsFilePath) : DETECTION{ DetectionType::File }, wsFileName{ wsFileName }, + wsFilePath{ wsFilePath }, hash{}{} }; typedef std::function)> DetectFile; @@ -36,12 +39,10 @@ typedef std::function)> DetectFile; /// A struct containing information about a registry key value identified in a hunt struct REGISTRY_DETECTION : public DETECTION { std::wstring wsRegistryKeyPath; - std::wstring wsRegistryKeyValue; - BYTE* contents; - REGISTRY_DETECTION(const std::wstring& wsRegistryKeyPath, const std::wstring& wsRegistryKeyValue, BYTE* contents) : + Registry::RegistryValue contents; + REGISTRY_DETECTION(const std::wstring& wsRegistryKeyPath, const Registry::RegistryValue& contents) : DETECTION{ DetectionType::Registry }, wsRegistryKeyPath{ wsRegistryKeyPath }, - wsRegistryKeyValue{ wsRegistryKeyValue }, contents{ contents }{} }; typedef std::function)> DetectRegistry; @@ -63,11 +64,12 @@ struct SERVICE_DETECTION : public DETECTION { typedef std::function)> DetectService; enum class ProcessDetectionMethod { - NotImageBacked, - BackingImageMismatch, - NotInLoader, - NotSigned, - Other + Replaced = 1, + HeaderModified = 2, + Detached = 4, + Hooked = 8, + Implanted = 16, + Other = 32 }; /// A struct containing information about a process identified in a hunt @@ -78,10 +80,10 @@ struct PROCESS_DETECTION : public DETECTION { std::wstring wsCmdline; int PID; int TID; - ProcessDetectionMethod method; + DWORD method; BYTE AllocationStart[512]; // This member is intended to be used for signaturing purposes PROCESS_DETECTION(const std::wstring& wsImageName, const std::wstring& wsImagePath, const std::wstring& wsCmdLine, - const int& PID, const int& TID, ProcessDetectionMethod method) : + const int& PID, const int& TID, DWORD method) : DETECTION{ DetectionType::Process }, wsImageName{ wsImageName }, wsImagePath{ wsCmdLine }, diff --git a/BLUESPAWN-client/headers/hunt/reaction/HuntTrigger.h b/BLUESPAWN-client/headers/hunt/reaction/HuntTrigger.h new file mode 100644 index 00000000..c79b40b2 --- /dev/null +++ b/BLUESPAWN-client/headers/hunt/reaction/HuntTrigger.h @@ -0,0 +1,23 @@ +#pragma once +#include "Reaction.h" + +#include "hunt/Hunt.h" +#include "hunt/HuntRegister.h" + +namespace Reactions { + + class HuntTriggerReaction : public Reaction { + private: + Hunt* hunt; + Aggressiveness level; + const Scope scope; + Reaction reaction; + HuntRegister& record; + + public: + HuntTriggerReaction(HuntRegister& record, Hunt* hunt, const Scope& scope, Aggressiveness level, Reaction& reaction); + + void EventIdentified(std::shared_ptr detection); + }; +} + diff --git a/BLUESPAWN-client/headers/hunt/reaction/RemoveValue.h b/BLUESPAWN-client/headers/hunt/reaction/RemoveValue.h new file mode 100644 index 00000000..ccbe4cf6 --- /dev/null +++ b/BLUESPAWN-client/headers/hunt/reaction/RemoveValue.h @@ -0,0 +1,23 @@ +#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/hunt/reaction/SuspendProcess.h b/BLUESPAWN-client/headers/hunt/reaction/SuspendProcess.h new file mode 100644 index 00000000..ecf83431 --- /dev/null +++ b/BLUESPAWN-client/headers/hunt/reaction/SuspendProcess.h @@ -0,0 +1,29 @@ +#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/mitigation/Mitigation.h b/BLUESPAWN-client/headers/mitigation/Mitigation.h index 29d5c364..f6eaf1cc 100644 --- a/BLUESPAWN-client/headers/mitigation/Mitigation.h +++ b/BLUESPAWN-client/headers/mitigation/Mitigation.h @@ -8,6 +8,11 @@ enum class SecurityLevel { High }; +enum class MitigationMode { + Audit, + Enforce +}; + enum class MitigationSeverity { Low, // Corresponds to a CVSS score of <= 4 or a low severity item on a STIG Medium, // Corresponds to a CVSS score of <= 7 or a medium severity item on a STIG diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1025.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1025.h new file mode 100644 index 00000000..2594cce0 --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1025.h @@ -0,0 +1,22 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateM1025 looks for LSA to be run as a protected process light, + * which requires all loaded DLLs to be properly signed and prevents other processes + * from interfering with LSA. + */ + class MitigateM1025 : public Mitigation { + public: + MitigateM1025(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h new file mode 100644 index 00000000..db839e11 --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-LLMNR.h @@ -0,0 +1,21 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateM1042-LLMNR looks for LLMNR to be disabled. This helps + * to prevent against T1171 and is M1042 (LLMNR). + */ + class MitigateM1042LLMNR : public Mitigation { + public: + MitigateM1042LLMNR(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-NBT.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-NBT.h new file mode 100644 index 00000000..480241d7 --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-NBT.h @@ -0,0 +1,21 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateM1042-NBT looks for Netbios (NBT) to be disabled. This helps + * to prevent against T1171 and is M1042 (NBT). + */ + class MitigateM1042NBT : public Mitigation { + public: + MitigateM1042NBT(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-WSH.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-WSH.h new file mode 100644 index 00000000..4bd98b2e --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateM1042-WSH.h @@ -0,0 +1,23 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateM1042-WSH looks for Windows Script Host, a typically + * unused and unneeded feature to be disabled. Sean Metcalf + * recommends this is disabled at https://adsecurity.org/?p=3299 + * M1042-WSH + */ + class MitigateM1042WSH : public Mitigation { + public: + MitigateM1042WSH(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1153.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1153.h new file mode 100644 index 00000000..8886e38a --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV1153.h @@ -0,0 +1,18 @@ +#pragma once +#include "../Mitigation.h" +#include + +namespace Mitigations{ + + /** + * MitigateV1153 ensures NTLMv2 is used (V-1153). + */ + class MitigateV1153 : public Mitigation { + public: + MitigateV1153(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63597.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63597.h new file mode 100644 index 00000000..a8f7a3c1 --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63597.h @@ -0,0 +1,22 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateV63597 looks for the setting to filter privileged tokens + * over the network. (V-63597). This helps protect against T1075 + * (PTH) and is M1052 (UAC). + */ + class MitigateV63597 : public Mitigation { + public: + MitigateV63597(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63817.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63817.h new file mode 100644 index 00000000..01a381bd --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63817.h @@ -0,0 +1,22 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateV63817 looks for the setting to include the built-in + * administrator account in UAC Admin Approval mode to be enabled. + * (V-63817). M1052 (UAC). + */ + class MitigateV63817 : public Mitigation { + public: + MitigateV63817(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63825.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63825.h new file mode 100644 index 00000000..9353582d --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63825.h @@ -0,0 +1,21 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateV63825 looks for the setting to prompt application + * installations for elevation. (V-63825). M1052 (UAC). + */ + class MitigateV63825 : public Mitigation { + public: + MitigateV63825(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63829.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63829.h new file mode 100644 index 00000000..2ee8fefd --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV63829.h @@ -0,0 +1,21 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateV63829 looks for UAC to be enabled. + * (V-63829). M1052 (UAC). + */ + class MitigateV63829 : public Mitigation { + public: + MitigateV63829(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h index 9c38d2ba..45155808 100644 --- a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV72753.h @@ -1,6 +1,6 @@ #pragma once -#include "../Mitigation.h" -#include +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" #include "hunt/reaction/Reaction.h" #include "hunt/reaction/Log.h" diff --git a/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73519.h b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73519.h new file mode 100644 index 00000000..b0a091e4 --- /dev/null +++ b/BLUESPAWN-client/headers/mitigation/mitigations/MitigateV73519.h @@ -0,0 +1,20 @@ +#pragma once +#include "mitigation/Mitigation.h" +#include "mitigation/MitigationRegister.h" +#include "hunt/reaction/Reaction.h" +#include "hunt/reaction/Log.h" + +namespace Mitigations{ + + /** + * MitigateV73519 looks for SMBv1 to be disabled. (V-73519). + */ + class MitigateV73519 : public Mitigation { + public: + MitigateV73519(MitigationRegister& record); + + virtual bool MitigationIsEnforced(SecurityLevel level) override; + virtual bool EnforceMitigation(SecurityLevel level) override; + virtual bool MitigationApplies() override; + }; +} diff --git a/BLUESPAWN-client/headers/user/bluespawn.h b/BLUESPAWN-client/headers/user/bluespawn.h index a602152d..701724e2 100644 --- a/BLUESPAWN-client/headers/user/bluespawn.h +++ b/BLUESPAWN-client/headers/user/bluespawn.h @@ -14,7 +14,7 @@ #pragma warning(disable : 26451) #pragma warning(disable : 26444) -#include + #include "cxxopts.hpp" #pragma warning(pop) @@ -24,27 +24,23 @@ #include "util/log/CLISink.h" #include "util/configurations/Registry.h" -#include "monitor/ETW_Wrapper.h" - #include "hunt/Hunt.h" #include "hunt/HuntRegister.h" -#include "hunt/hunts/HuntT1004.h" -#include "hunt/hunts/HuntT1037.h" -#include "hunt/hunts/HuntT1050.h" -#include "hunt/hunts/HuntT1060.h" -#include "hunt/hunts/HuntT1100.h" -#include "hunt/hunts/HuntT1101.h" -#include "hunt/hunts/HuntT1103.h" -#include "hunt/hunts/HuntT1131.h" -#include "hunt/hunts/HuntT1138.h" -#include "hunt/hunts/HuntT1182.h" #include "mitigation/Mitigation.h" #include "mitigation/MitigationRegister.h" -#include "mitigation/mitigations/MitigateV1093.h" -#include "mitigation/mitigations/MitigateV3338.h" -#include "mitigation/mitigations/MitigateV72753.h" + +class Bluespawn { + public: + Bluespawn(); + + void dispatch_hunt(Aggressiveness aHuntLevel); + void dispatch_mitigations_analysis(MitigationMode mode, bool bForceEnforce); + void monitor_system(Aggressiveness aHuntLevel); + + static HuntRegister huntRecord; + static MitigationRegister mitigationRecord; + static IOBase& io; +}; void print_help(cxxopts::ParseResult result, cxxopts::Options options); -void dispatch_hunt(cxxopts::ParseResult result, cxxopts::Options options); -void dispatch_mitigations_analysis(cxxopts::ParseResult result, cxxopts::Options options, IOBase& io); \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/configurations/Registry.h b/BLUESPAWN-client/headers/util/configurations/Registry.h index 59513d6d..fe6effbc 100644 --- a/BLUESPAWN-client/headers/util/configurations/Registry.h +++ b/BLUESPAWN-client/headers/util/configurations/Registry.h @@ -1,199 +1,207 @@ #pragma once -#include +#include #include -#include #include -#include -#include +#include +#include -#include "util/log/Log.h" +#include "common/DynamicLinker.h" +#include "common/wrappers.hpp" -namespace Registry { - typedef std::wstring REG_SZ_T; - typedef std::vector REG_MULTI_SZ_T; - typedef DWORD REG_DWORD_T; +#include "util/log/Loggable.h" +#include "util/configurations/RegistryValue.h" +DEFINE_FUNCTION(DWORD, NtQueryKey, __stdcall, HANDLE KeyHandle, int KeyInformationClass, PVOID KeyInformation, ULONG Length, PULONG ResultLength); + +namespace Registry { extern std::map vHiveNames; extern std::map vHives; - extern std::map _globalOpenKeys; - - class RegistryKey : public Loggable { - HKEY hive; - std::wstring path; - std::wstring name; - - BYTE* lpbValue = nullptr; - DWORD dwDataSize{}; - DWORD dwDataType{}; - - bool bKeyExists = false; - bool bValueExists = false; + /** + * This class is for interaction with the Windows Registry. A single instance of this + * class represents a key in the registry, not to be confused with a value. Note that each + * key will have multiple values in addition to some number of subkeys. Each value is associated + * with data, which will generally be a REG_DWORD, REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, or + * REG_BINARY. This class provides support for all of these. + */ + class RegistryKey : + public Loggable { public: - HKEY key = nullptr; - - RegistryKey(HKEY hive, std::wstring path, std::wstring name = L"", bool Create = false); - - RegistryKey(std::wstring path, std::wstring name = L""); - - ~RegistryKey(); - - bool KeyExists(); - bool ValueExists(); - - std::wstring GetName(); - - bool Set(LPVOID value, DWORD dwSize, DWORD dwType = REG_BINARY); - bool Create(LPVOID value, DWORD dwSize, DWORD dwType = REG_BINARY); - - template - inline bool Set(T value) { - LOG_VERBOSE(1, "Setting registry key " << GetName() << " to " << value); + /* Copy constructor for a RegistryKey */ + RegistryKey(const RegistryKey& key) noexcept; - Set(&value, sizeof(value)); - } + /* Move constructor for a RegistryKey */ + RegistryKey(RegistryKey&& key) noexcept; - template<> - inline bool Set(REG_DWORD_T value) { - LOG_VERBOSE(1, "Setting registry key " << GetName() << " to " << value); + /** + * Creates a RegistryKey object from the backing associated HKEY. + * + * @param key The HKEY handle on the registry key to create an instance for. + */ + RegistryKey(HKEY key); - return Set(&value, sizeof(DWORD), REG_DWORD); - } + /** + * Creates a RegistryKey object from a path relative to a given HKEY. + * For example, the HKEY may reference the key at HKLM\SYSTEM and the path may be + * CurrentControlSet\Services. The resulting instance would reference the key stored at + * HKLM\SYSTEM\CurrentControlSet\Services. + * + * @param base The base key. + * @param path The path relative to the base key. + */ + RegistryKey(HKEY base, std::wstring path); - template<> - inline bool Set(REG_SZ_T value) { - LOG_VERBOSE(1, "Setting registry key " << GetName() << " to " << value); + /** + * Creates a RegistryKey object to reference a key at a given path. + * + * @param path The path of the registry key to reference. + */ + RegistryKey(std::wstring path); - return Set(const_cast(value.c_str()), sizeof(WCHAR) * static_cast(value.length() + 1), REG_SZ); - } + /** Copy operator overload */ + RegistryKey& operator=(const RegistryKey& key) noexcept; - template<> - inline bool Set(REG_MULTI_SZ_T value) { - SIZE_T size = 1; - for(auto string : value){ - size += (string.length() + 1); - } + /** Move operator overload */ + RegistryKey& operator=(RegistryKey&& key) noexcept; - WCHAR* data = new WCHAR[size]; - int ptr = 0; + private: + static std::map _ReferenceCounts; - std::wstring wsLogStatement{}; - for(auto string : value){ - wsLogStatement += string + L"; "; - LPCWSTR lpRawString = string.c_str(); - for(int i = 0; i < string.length() + 1; i++){ - if(ptr < size){ - data[ptr++] = lpRawString[i]; - } - } - } + HKEY hkBackingKey; - if(ptr < size){ - data[ptr] = { static_cast(0) }; - } + bool bKeyExists; - LOG_VERBOSE(1, "Setting registry key " << GetName() << " to " << wsLogStatement); + HKEY hkHive{}; + std::wstring path{}; - return Set(data, static_cast(size * sizeof(WCHAR)), REG_MULTI_SZ); - } - - LPVOID GetRaw(); + public: + /** Destructor for a RegistryKey. Decrements a reference count, and if zero, closes the handle */ + ~RegistryKey(); + /** + * Indicates whether this instance references a registry key that exists. + * + * @return true if the referenced key exists; false otherwise + */ + bool Exists() const; + + /** + * Indicates whether the referenced key contains a certain value. + * + * @param wsValueName The name of the value to check. + * + * @return true if the referenced key has the given value; false otherwise + */ + bool ValueExists(const std::wstring& wsValueName) const; + + /** + * If the registry key referenced by this instance doesn't exist, this will create it. + * + * @return true if the registry key already existed or was created; false otherwise. + */ + bool Create(); + + /** + * Reads the raw bytes present in a given value. + * + * @return A AllocationWrapper object pointing to the bytes read if the value is present, or + * an empty memory wrapper if the value is not present. The memory must be freed. + */ + AllocationWrapper GetRawValue(const std::wstring& wsValueName) const; + + /** + * Writes bytes to a given value under the key referenced by this object. + * + * @param name The name of the value to set + * @param bytes The bytes to write to the value + * @param type The datatype of the value + * + * @return True if the value was successfully set; false otherwise + */ + bool SetRawValue(const std::wstring& name, AllocationWrapper bytes, DWORD type = REG_BINARY) const; + + /** + * Reads data from the specified value and handles conversion to common types. + * Supported types: std::wstring (REG_SZ and REG_EXPAND_SZ), std::vector + * (REG_MULTI_SZ), and DWORD (REG_DWORD). + * In other types, the data stored in the value will be converted to the template type. + * + * @param wsValueName The name of the value to read. + * + * @return An optional containing the object stored in the registry, or nullopt if an error + * occured or the value does exist. + */ template - inline T Get(){ return reinterpret_cast(GetRaw()); } - - template<> - inline REG_DWORD_T Get(){ - DWORD value = *reinterpret_cast(GetRaw()); - LOG_VERBOSE(2, "Read value " << value << " from key " << GetName()); - - return value; - } - - template<> - inline REG_SZ_T Get() { - if(ValueExists()){ - LOG_VERBOSE(2, "Read value " << reinterpret_cast(GetRaw()) << " from key " << GetName()); - return reinterpret_cast(GetRaw()); - } - - LOG_VERBOSE(1, "Tried to read key " << GetName() << ", but the value did not exist!"); - return L""; - } - - template<> - inline REG_MULTI_SZ_T Get(){ - if(ValueExists()){ - std::vector strings{}; - std::wstring wsLogString{}; - - LPCWSTR data = reinterpret_cast(GetRaw()); - - LPCVOID base = data; - - while(reinterpret_cast(data) - reinterpret_cast(base) < dwDataSize && *data){ - std::wstring str = data; - strings.emplace_back(data); - wsLogString += str; - - data += str.length() + 1; - } - - LOG_VERBOSE(2, "Read value " << wsLogString << " from key " << GetName()); - - return strings; - } - - LOG_VERBOSE(1, "Tried to read key " << GetName() << ", but the value did not exist!"); - return {}; - } - + std::optional GetValue(const std::wstring& wsValueName) const; + + /** + * Returns the type of a value under the currently referenced registry key. + * Currently, this only supports REG_SZ, REG_EXPAND_SZ, REG_DWORD, and REG_MULTI_SZ. + * + * @param wsValueName The name of the value to check + * + * @return An optional containing the registry type, or nullopt if the value does not exist or + * an error ocurred. + */ + std::optional GetValueType(const std::wstring& wsValueName) const; + + /** + * Sets data for a specified value under the referenced key and handles conversions from + * common types. For common types, the size and type do not need to be specified. + * Supported types: std::wstring, std::string, LPCSTR, LPCWSTR, DWORD, and + * std::vector. + * + * @param name The name of the value to set. + * @param value The data to write to the value. + * @param size The size of the data to write. This is ignored if the type is one of the + * supported types for this function. + * @param type The registry datatype for the data to write. This is ignored if the type is + * one of the supported types for this function. + * + * @return True if the value was successfully set; false otherwise. + */ template - inline bool operator==(T value){ - return Get() == value; - } - - template<> - inline bool operator==(LPCWSTR wcsKnownGood){ - return operator==(std::wstring(wcsKnownGood)); - } - - template<> - inline bool operator==(REG_MULTI_SZ_T vKnownGood){ - auto data = Get>(); - - std::set GoodContents{}; - for(auto string : vKnownGood){ - GoodContents.insert(string); - } - - std::set ActualContents{}; - for(auto string : data){ - ActualContents.insert(string); - } - - for(auto string : GoodContents){ - if(ActualContents.find(string) == ActualContents.end()){ - return false; - } - } - - for(auto string : ActualContents){ - if(GoodContents.find(string) == GoodContents.end()){ - return false; - } - } - - return true; - } - - std::vector KeyValues(); - std::vector Subkeys(); - - std::wstring GetPath(); - - virtual std::wstring ToString(); + bool SetValue(const std::wstring&, T value, DWORD size = sizeof(T), DWORD type = REG_BINARY) const; + + /** + * Returns a list of values present under the currently referenced registry key. + * + * @return a list of values present under the currently referenced registry key. + */ + std::vector EnumerateValues() const; + + /** + * Returns a list of subkeys under the currently referenced registry key. + * + * @return a list of subkeys under the currently referenced registry key. + */ + std::vector EnumerateSubkeys() const; + + /** + * Returns the full path of the referenced registry key. + * + * @return the full path of the referenced registry key. + */ + std::wstring GetName() const; + + /** + * Returns the full path of the referenced registry key. + * + * @return the full path of the referenced registry key. + */ + virtual std::wstring ToString() const; + + /** + * Override the < operator so registry keys can be used in sets, maps, and trees. + * + * @param key The key to compare + * + * @return true or false + */ + bool operator<(const RegistryKey& key) const; + + bool RemoveValue(const std::wstring& wsValueName) const; }; } \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/configurations/RegistryValue.h b/BLUESPAWN-client/headers/util/configurations/RegistryValue.h new file mode 100644 index 00000000..d36666ed --- /dev/null +++ b/BLUESPAWN-client/headers/util/configurations/RegistryValue.h @@ -0,0 +1,47 @@ +#pragma once +#include "util/log/Loggable.h" + +#include "common/wrappers.hpp" + +#include +#include +#include +#include + +namespace Registry { + + /** + * This enum represents the datatypes stored in the registry. + * While other types do exist, for now, support only exists for the below types. + */ + enum class RegistryType { + REG_SZ_T, + REG_EXPAND_SZ_T, + REG_MULTI_SZ_T, + REG_DWORD_T, + REG_BINARY_T + }; + + /** + * A container class for registry values and associated data. + */ + struct RegistryValue : public Loggable { + std::wstring wValueName; + RegistryType type; + + // Only one of these will be valid data; which one will be indicated by `type` + std::wstring wData = {}; + DWORD dwData = {}; + AllocationWrapper lpData = { nullptr, 0 }; + std::vector vData = {}; + + RegistryValue(std::wstring wValueName, RegistryType type, std::wstring wData); + RegistryValue(std::wstring wValueName, RegistryType type, DWORD dwData); + RegistryValue(std::wstring wValueName, RegistryType type, AllocationWrapper lpData); + RegistryValue(std::wstring wValueName, RegistryType type, std::vector wData); + + RegistryType GetType() const; + + virtual std::wstring ToString() const; + }; +} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/eventlogs/EventLogs.h b/BLUESPAWN-client/headers/util/eventlogs/EventLogs.h index e8b1d43d..90cfeed2 100644 --- a/BLUESPAWN-client/headers/util/eventlogs/EventLogs.h +++ b/BLUESPAWN-client/headers/util/eventlogs/EventLogs.h @@ -2,33 +2,83 @@ #include "windows.h" #include -#include #include #include #include #include "hunt/reaction/Reaction.h" +#include "hunt/reaction/HuntTrigger.h" #include - -using namespace std; +#include "util/eventlogs/EventSubscription.h" #pragma comment(lib, "wevtapi.lib") -#define ARRAY_SIZE 10 -#define TIMEOUT 1000 // 1 second; Set and use in place of INFINITE in EvtNext call - -/** -@ param channel the channel to look for the event log (exe, 'Microsoft-Windows-Sysmon/Operational') -@param id the event ID to filter for -@param reaction the reaction to use when an event is detected -@return the number of events detected, or -1 if something went wrong. -*/ -int QueryEvents(const wchar_t* channel, unsigned int id, Reaction& reaction); - -/** -@ param channel the channel to look for the event log (exe, 'Microsoft-Windows-Sysmon/Operational') -@param id the event ID to filter for -@param reaction the reaction to use when an event is detected -@param params extra parameters to print in the output -@return the number of events detected, or -1 if something went wrong. -*/ -int QueryEvents(const wchar_t* channel, unsigned int id, std::set& params, Reaction& reaction); \ No newline at end of file +class EventLogs { + public: + static EventLogs* getLogs() { + if (!logs) + logs = new EventLogs; + return logs; + } + + /** + * @param channel the channel to look for the event log (exe, 'Microsoft-Windows-Sysmon/Operational') + * @param id the event ID to filter for + * @param reaction the reaction to use when an event is detected + * @return the number of events detected, or -1 if something went wrong. + */ + int QueryEvents(const wchar_t* channel, unsigned int id, Reaction& reaction); + + /** + * @param channel the channel to look for the event log (exe, 'Microsoft-Windows-Sysmon/Operational') + * @param id the event ID to filter for + * @param reaction the reaction to use when an event is detected + * @param params extra parameters to print in the output + * @return the number of events detected, or -1 if something went wrong. + */ + int QueryEvents(const wchar_t* channel, unsigned int id, std::set& params, Reaction& reaction); + + /** + * Get the string value of a parameter in an event + * + * @param hEvent a handle to an event + * @param value a pointer to a wstring where the parameter value will be stored + * @param param the parameter whose value is being queried. Must be a valud XPATH query + * @return the status of the operation + */ + DWORD GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param); + /** + * Get the XML representation of an event + * + * @param hEvent a handle to an event + * @param data pointer to a wstring where the XML result will be stored + * @return the status of the operation + */ + DWORD GetEventXML(EVT_HANDLE hEvent, std::wstring* data); + + /** + * Create an EVENT_DETECTION struct from an event handle + * + * @param hEvent the handle being turned into a detection object + * @param pDetection a pointer to the detection struct to store the results + * @param params a list of XPATH parameters to include optionally in the struct + * @return the status of the operation + */ + DWORD EventToDetection(EVT_HANDLE hEvent, EVENT_DETECTION * pDetection, std::set& params); + + /** + * Subscribe a HuntTriggerReaction to a specific Windows event + * + * @param pwsPath the event channel to subscribe to + * @param id the id of the event to subscribe to + * @param reaction the reaction to call when an event is generated + * @param status the status of the operation + * @returns a shared pointer to the datasturctures storing the event subscription information + */ + std::unique_ptr subscribe(LPWSTR pwsPath, unsigned int id, Reactions::HuntTriggerReaction& reaction, DWORD* status); + + private: + DWORD ProcessResults(EVT_HANDLE hResults, Reaction& reaction, int* numFound, std::set& params); + + EventLogs() {}; + static EventLogs* logs; +}; diff --git a/BLUESPAWN-client/headers/util/eventlogs/EventSubscription.h b/BLUESPAWN-client/headers/util/eventlogs/EventSubscription.h new file mode 100644 index 00000000..32bba126 --- /dev/null +++ b/BLUESPAWN-client/headers/util/eventlogs/EventSubscription.h @@ -0,0 +1,33 @@ +#pragma once + +#include "windows.h" +#include +#include +#include "hunt/reaction/HuntTrigger.h" + +#pragma comment(lib, "wevtapi.lib") + +/** +* A class used to connect a HuntTriggerReaction to the Windows async call when an event is generated +*/ +class EventSubscription { + public: + EventSubscription(Reactions::HuntTriggerReaction& reaction); + // Have a destructor to ensure we can clean up when this object is deleted + ~EventSubscription(); + + /** + * The function called by the underlying Windows OS as a callback. + * In turn calls reaction->EventIdentified + */ + DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action, EVT_HANDLE hEvent); + + /** + * Set the event handle so it can be closed when this object is deleted + */ + void setSubHandle(EVT_HANDLE hSubscription); + + private: + std::unique_ptr reaction; + EVT_HANDLE hSubscription; +}; \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/filesystem/FileSystem.h b/BLUESPAWN-client/headers/util/filesystem/FileSystem.h index 42c1cba3..d0245872 100644 --- a/BLUESPAWN-client/headers/util/filesystem/FileSystem.h +++ b/BLUESPAWN-client/headers/util/filesystem/FileSystem.h @@ -1,24 +1,268 @@ #pragma once -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING -#include +#include -#include -#include -#include -#include #include -#include -#include -#include -#include +#include +#include + +#include "common/wrappers.hpp" #define BUFSIZE 1024 #define MD5LEN 16 -using namespace std; -namespace fs = std::experimental::filesystem::v1; +namespace FileSystem { + bool CheckFileExists(std::wstring); + + struct FileAttribs { + std::wstring extension; + }; + + struct FileSearchAttribs { + std::vector extensions; + }; + + class File { + + //Whether or not this current file actually exists on the filesystem + bool FileExists; + + //Path to the file + std::wstring FilePath; + + //Handle for the file + HandleWrapper hFile; + + //Attributes of the file + FileAttribs Attribs; + + /** + * Function to get offsets in format needed by SetFilePointer + * + * @param val - value to be translated. Upper bit will be ignored + * @param lowerVal - variable to store lower value + * @param upperVal - variable to store upper value + * @param upper - variable to store pointer to upper value + */ + DWORD SetFilePointer(DWORD64 dwFilePointer) const; + public: + + /** + * 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); + + /** + * Return the path to the file + */ + std::wstring GetFilePath() const { + return FilePath; + } + + /** + * Function to get the file attributes + * + * @return the attributes struct for the file + */ + FileAttribs GetFileAttribs() const { + return Attribs; + } + + /** + * Function to get whether the file exists + * + * return true if file exists, false otherwise + */ + bool GetFileExists() const { + return FileExists; + } + + /** + * Function to write to arbitrary offset in the file + * + * @param value The value to be written + * @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; + + /** + * 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 + * @param amountRead How many bytes were successfully read + * + * @return true if read successful, false if read unsuccessful + */ + bool Read(OUT LPVOID buffer, __in_opt const unsigned long amount, __in_opt const long offset = 0, __out_opt PDWORD amountRead = nullptr) 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 + * @param amountRead How many bytes were successfully read + * + * @return true if read successful, false if read unsuccessful + */ + AllocationWrapper Read(__in_opt unsigned long amount, __in_opt long offset = 0, __out_opt PDWORD amountRead = nullptr) const; + + /** + * Function to compute the MD5 hash of the file + * + * @param buffer The buffer to write the hash to + * + * @return true if hashing successful, false if hashing unsuccessful + */ + std::optional GetMD5Hash() const; + + /** + * Function to create the file if it doesn't exist + * + * @return true if creation was successful, false if unsuccessful + */ + bool Create(); + + /** + * Function to delete the file + * + * @return true if deletion was successful, false if unsuccessful + */ + bool Delete(); + + /** + * Function to truncate or extend file length + * + * @param length - new length of the file in bytes + * + * @return true if trucation or extension was successful, false if unsuccessful + */ + bool ChangeFileLength(IN const long length) const; + + /** + * Gets the number of bytes in the referenced file + * + * @return The size of the referenced file + */ + DWORD64 GetFileSize() const; + }; + + class Folder { + + //Path to the current folder + std::wstring FolderPath; + + //Whether or not the current folder exists + bool FolderExists; + + //Handle to current file or directory + HandleWrapper hCurFile; + + //Is the current handle a file or directory + bool IsFile; + + //Information about found files + WIN32_FIND_DATA ffd; + public: + + /** + * Constructor for the folder object + * + * @param path - the path to the folder + */ + Folder(const std::wstring& path); + + /** + * Function to move to the next file + * + * @return true if successfully moved to next file false if no next file exists + */ + bool MoveToNextFile(); + + /** + * Function to move to the beginnning of the directory + * + * @return true if successful, false otherwise + */ + + bool MoveToBeginning(); + + /** + * Function to check if the folder exists + * + * @return whether or not the folder exists. + */ + bool GetFolderExists() const { + return FolderExists; + } + + /** + * Function to check if current handle is directory or file + * + * @return true if current is a file, false otherwise. + */ + bool GetCurIsFile() const { + return IsFile; + } + + /** + * Function to enter the current directory + * + * @return a folder object representing the currently pointed to directory if successful + */ + + std::optional EnterDir(); + + /** + * Function to open the current file for reading and writing + * + * @return The file if found, otherwise nothing + */ + std::optional Open() const; + + /** + * Function to add a file to the directory + * + * @return The file if successfully created + */ + + std::optional AddFile(IN const std::wstring& fileName) const; + + /** + * Function to remove current file and move to next handle + * + * @return true if the file was removed, false otherwise + * TODO: Add support for deleting folders + */ + bool RemoveFile() const; + + /** + * Function to return all files matching some attributes + * + * @param attribs - the attributes for returned files to match, NULL gets everything + * @param recurDepth - the depth to recursively search, -1 recurses infinitely + * + * @return all files that match the given parameters + */ + std::vector GetFiles(__in_opt std::optional attribs = std::nullopt, __in_opt int recurDepth = 0); -bool CheckFileExists(LPCWSTR); -string GetFileContents(LPCWSTR); -bool HashFileMD5(LPCWSTR, string&); + /** + * Function to return all subdirectories in the current folder + * + * @param recurDepth - the depth to recursively search, -1 recurses infinitely + * + * @return all subfolders in the current folder + */ + std::vector GetSubdirectories(__in_opt int recurDepth = 0); + }; +} \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/log/CLISink.h b/BLUESPAWN-client/headers/util/log/CLISink.h index f87a993f..a1a5c599 100644 --- a/BLUESPAWN-client/headers/util/log/CLISink.h +++ b/BLUESPAWN-client/headers/util/log/CLISink.h @@ -32,7 +32,7 @@ namespace Log { RED = 0xC, PINK = 0xD, YELLOW = 0xE, - WHITE = 0xF + WHITE = 0xF, }; std::string MessagePrepends[4] = { "[ERROR]", "[WARNING]", "[INFO]", "[OTHER]" }; MessageColor PrependColors[5] = { MessageColor::RED, MessageColor::YELLOW, MessageColor::BLUE, MessageColor::GREEN, MessageColor::GOLD }; diff --git a/BLUESPAWN-client/headers/util/log/HuntLogMessage.h b/BLUESPAWN-client/headers/util/log/HuntLogMessage.h index 6bd48cd6..bea333d4 100644 --- a/BLUESPAWN-client/headers/util/log/HuntLogMessage.h +++ b/BLUESPAWN-client/headers/util/log/HuntLogMessage.h @@ -11,7 +11,7 @@ #include // Creates a Hunt log message named _HuntLogMessage. This macro is only to be called inside -// ScanCursory, ScanModerate, ScanCareful, or ScanAggressive. +// ScanCursory, ScanNormal, or ScanIntensive. #define LOG_HUNT_BEGIN() \ auto _HuntLogMessage = Log::HuntLogMessage(GET_INFO(), Log::_LogHuntSinks) diff --git a/BLUESPAWN-client/headers/util/log/Log.h b/BLUESPAWN-client/headers/util/log/Log.h index c7738171..e99d24e7 100644 --- a/BLUESPAWN-client/headers/util/log/Log.h +++ b/BLUESPAWN-client/headers/util/log/Log.h @@ -136,7 +136,7 @@ namespace Log { * This is meant to serve as a handler for components implementing the Loggable * interface. */ - LogMessage& InnerLog(Loggable& loggable, std::true_type){ + LogMessage& InnerLog(const Loggable& loggable, std::true_type){ return operator<<(loggable.ToString()); } diff --git a/BLUESPAWN-client/headers/util/log/Loggable.h b/BLUESPAWN-client/headers/util/log/Loggable.h index 53a7de1f..decb9291 100644 --- a/BLUESPAWN-client/headers/util/log/Loggable.h +++ b/BLUESPAWN-client/headers/util/log/Loggable.h @@ -13,6 +13,6 @@ class Loggable { * * @return A string representation of the class. */ - virtual std::wstring ToString() = 0; + virtual std::wstring ToString() const = 0; }; diff --git a/BLUESPAWN-client/headers/util/pe/Image_Loader.h b/BLUESPAWN-client/headers/util/pe/Image_Loader.h index a21a5eb6..516ba7b6 100644 --- a/BLUESPAWN-client/headers/util/pe/Image_Loader.h +++ b/BLUESPAWN-client/headers/util/pe/Image_Loader.h @@ -92,13 +92,8 @@ struct Loaded_Image64 { DWORD ImageSize; DWORD64 ImageAddress; DWORD64 EntryPoint; - bool ImportsParsed; - Loaded_Image64(const LDR_ENTRY64& entry, const HandleWrapper& process, bool imported = true); - Loaded_Image64(const PE_Image& image, bool importsFinished = true, const std::wstring& ImagePath = L""); - - PE_Image GetImage() const; - LDR_ENTRY64 CreateLoaderEntry(PLIST_ENTRY64 before = nullptr, PLIST_ENTRY64 after = nullptr) const; + Loaded_Image64(const LDR_ENTRY64& entry, const HandleWrapper& process); }; struct Loaded_Image32 { @@ -108,13 +103,8 @@ struct Loaded_Image32 { DWORD ImageSize; DWORD ImageAddress; DWORD EntryPoint; - bool ImportsParsed; - - Loaded_Image32(const LDR_ENTRY32& entry, const HandleWrapper& process, bool imported = true); - Loaded_Image32(const PE_Image& image, bool importsFinished = true, const std::wstring& ImagePath = L""); - PE_Image GetImage() const; - LDR_ENTRY32 CreateLoaderEntry(PLIST_ENTRY32 before = nullptr, PLIST_ENTRY32 after = nullptr) const; + Loaded_Image32(const LDR_ENTRY32& entry, const HandleWrapper& process); }; struct Loaded_Image { @@ -123,11 +113,10 @@ struct Loaded_Image { std::optional image32; std::optional image64; - Loaded_Image(const LDR_ENTRY32& entry, const HandleWrapper& process, bool imported = true); - Loaded_Image(const LDR_ENTRY64& entry, const HandleWrapper& process, bool imported = true); - Loaded_Image(const PE_Image& image, bool importsFinished = true, const std::wstring& ImagePath = L""); - - PE_Image GetImage() const; + Loaded_Image(const LDR_ENTRY32& entry, const HandleWrapper& process); + Loaded_Image(const LDR_ENTRY64& entry, const HandleWrapper& process); + + std::wstring GetName(); }; class Image_Loader { @@ -139,14 +128,10 @@ class Image_Loader { Image_Loader(const HandleWrapper& process = GetCurrentProcess()); - bool AddImage(const Loaded_Image& image); - bool ContainsImage(const std::wstring& name) const; - bool RemoveImage(const Loaded_Image& image); - - bool MarkLoaded(const std::wstring& name); - std::optional GetImageInfo(const std::wstring& name) const; + + std::optional GetAssociatedImage(LPVOID address) const; }; diff --git a/BLUESPAWN-client/headers/util/processes/ProcessChecker.h b/BLUESPAWN-client/headers/util/processes/ProcessChecker.h new file mode 100644 index 00000000..519e9728 --- /dev/null +++ b/BLUESPAWN-client/headers/util/processes/ProcessChecker.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +bool ProcessIsDoppelganger(DWORD pid); +bool ProcessContainsShellcode(DWORD pid); +bool ProcessContainsHollows(DWORD pid); +bool ProcessContainsPEImplants(DWORD pid); +bool ProcessContainsHooks(DWORD pid); \ No newline at end of file diff --git a/BLUESPAWN-client/headers/util/processes/ProcessUtils.h b/BLUESPAWN-client/headers/util/processes/ProcessUtils.h new file mode 100644 index 00000000..df446e9a --- /dev/null +++ b/BLUESPAWN-client/headers/util/processes/ProcessUtils.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +struct Hook { + LPVOID ModificationAddress; + LPVOID RedirectionAddress; +}; + +bool HookIsOkay(const Hook& hook); + +std::vector GetExecutableNonImageSections(DWORD pid); +std::vector GetUnregisteredImages(DWORD pid); +std::vector GetModifiedImages(DWORD pid); +std::vector GetHooks(DWORD pid); \ No newline at end of file diff --git a/BLUESPAWN-client/libpeconv.vcxproj b/BLUESPAWN-client/libpeconv.vcxproj new file mode 100644 index 00000000..8884db1b --- /dev/null +++ b/BLUESPAWN-client/libpeconv.vcxproj @@ -0,0 +1,103 @@ + + + + + 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 new file mode 100644 index 00000000..9210e2de --- /dev/null +++ b/BLUESPAWN-client/pe-sieve.vcxproj @@ -0,0 +1,127 @@ + + + + + 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 index 4a96bac2..42cbc42f 100644 --- a/BLUESPAWN-client/src/hunt/Hunt.cpp +++ b/BLUESPAWN-client/src/hunt/Hunt.cpp @@ -2,8 +2,6 @@ #include "hunt/HuntRegister.h" #include "hunt/reaction/Reaction.h" -#include - HuntInfo::HuntInfo(const std::wstring& HuntName, Aggressiveness HuntAggressiveness, DWORD HuntTactics, DWORD HuntCategories, DWORD HuntDatasources, long HuntStartTime) : HuntName{ HuntName }, HuntAggressiveness{ HuntAggressiveness }, @@ -12,44 +10,40 @@ HuntInfo::HuntInfo(const std::wstring& HuntName, Aggressiveness HuntAggressivene HuntDatasources{ HuntDatasources }, HuntStartTime{ HuntStartTime }{} -Hunt::Hunt(HuntRegister& record, const std::wstring& name) : +Hunt::Hunt(const std::wstring& name) : name{ name }{ - record.RegisterHunt(this); - dwTacticsUsed = 0; dwSourcesInvolved = 0; dwCategoriesAffected = 0; dwSupportedScans = 0; } +std::wstring Hunt::GetName() { + return name; +} + int Hunt::ScanCursory(const Scope& scope, Reaction reaction){ - std::wcout << L"Running hunt handler for " << name << std::endl; if(!(dwSupportedScans & (DWORD) Aggressiveness::Cursory)){ return -1; } return 0; } -int Hunt::ScanModerate(const Scope& scope, Reaction reaction){ - if(!(dwSupportedScans & (DWORD) Aggressiveness::Moderate)){ +int Hunt::ScanNormal(const Scope& scope, Reaction reaction){ + if(!(dwSupportedScans & (DWORD) Aggressiveness::Normal)){ return -1; } return 0; } -int Hunt::ScanCareful(const Scope& scope, Reaction reaction){ - if(!(dwSupportedScans & (DWORD) Aggressiveness::Careful)){ +int Hunt::ScanIntensive(const Scope& scope, Reaction reaction){ + if(!(dwSupportedScans & (DWORD) Aggressiveness::Intensive)){ return -1; } return 0; } -int Hunt::ScanAggressive(const Scope& scope, Reaction reaction){ - if(!(dwSupportedScans & (DWORD) Aggressiveness::Aggressive)){ - return -1; - } - return 0; -} +void Hunt::SetupMonitoring(HuntRegister& record, const Scope& scope, Aggressiveness level, Reaction reaction) {} bool Hunt::AffectsCategory(DWORD dwStuff){ return (dwStuff && dwCategoriesAffected) == dwStuff; diff --git a/BLUESPAWN-client/src/hunt/HuntRegister.cpp b/BLUESPAWN-client/src/hunt/HuntRegister.cpp index 87fd9fa6..23f56a39 100644 --- a/BLUESPAWN-client/src/hunt/HuntRegister.cpp +++ b/BLUESPAWN-client/src/hunt/HuntRegister.cpp @@ -1,7 +1,9 @@ #include "hunt/HuntRegister.h" #include -void HuntRegister::RegisterHunt(Hunt* hunt){ +HuntRegister::HuntRegister(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. @@ -22,39 +24,82 @@ void HuntRegister::RegisterHunt(Hunt* hunt){ }*/ } +void HuntRegister::RunHunts(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction){ + io.InformUser(L"Starting a hunt for " + std::to_wstring(vRegisteredHunts.size()) + L" techniques."); + int huntsRan = 0; -void HuntRegister::RunHunts(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction){ - for (auto name : vRegisteredHunts) { - switch (aggressiveness) { - case Aggressiveness::Cursory: - name->ScanCursory(scope, reaction); - break; - case Aggressiveness::Moderate: - name->ScanModerate(scope, reaction); - break; - case Aggressiveness::Careful: - name->ScanCareful(scope, reaction); - break; - case Aggressiveness::Aggressive: - name->ScanAggressive(scope, reaction); - break; + for (auto name : vRegisteredHunts) { + int huntRunStatus = 0; + if (aggressiveness == Aggressiveness::Intensive) { + if (name->SupportsScan(Aggressiveness::Intensive)) { + huntRunStatus = name->ScanIntensive(scope, reaction); + } + else if (name->SupportsScan(Aggressiveness::Normal)) { + huntRunStatus = name->ScanNormal(scope, reaction); + } + else { + huntRunStatus = name->ScanCursory(scope, reaction); + } + } + else if (aggressiveness == Aggressiveness::Normal) { + if (name->SupportsScan(Aggressiveness::Normal)) { + huntRunStatus = name->ScanNormal(scope, reaction); + } + else { + huntRunStatus = name->ScanCursory(scope, reaction); + } } + else { + huntRunStatus = name->ScanCursory(scope, reaction); + } + 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& name, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction){ - switch(aggressiveness) { - case Aggressiveness::Cursory: - name.ScanCursory(scope, reaction); - break; - case Aggressiveness::Moderate: - name.ScanModerate(scope, reaction); - break; - case Aggressiveness::Careful: - name.ScanCareful(scope, reaction); - break; - case Aggressiveness::Aggressive: - name.ScanAggressive(scope, reaction); - break; +void HuntRegister::RunHunt(Hunt& hunt, const Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction){ + io.InformUser(L"Starting scan for " + hunt.GetName()); + int huntRunStatus = 0; + + if (aggressiveness == Aggressiveness::Intensive) { + if (hunt.SupportsScan(Aggressiveness::Intensive)) { + huntRunStatus = hunt.ScanIntensive(scope, reaction); + } + else if (hunt.SupportsScan(Aggressiveness::Normal)) { + huntRunStatus = hunt.ScanNormal(scope, reaction); + } + else { + huntRunStatus = hunt.ScanCursory(scope, reaction); + } + } + else if (aggressiveness == Aggressiveness::Normal) { + if (hunt.SupportsScan(Aggressiveness::Normal)) { + huntRunStatus = hunt.ScanNormal(scope, reaction); + } + else { + huntRunStatus = hunt.ScanCursory(scope, reaction); + } + } + else { + huntRunStatus = hunt.ScanCursory(scope, reaction); + } + 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(DWORD dwTactics, DWORD dwDataSource, DWORD dwAffectedThings, Scope& scope, Aggressiveness aggressiveness, const Reaction& reaction) { + for (auto name : vRegisteredHunts) { + name->SetupMonitoring(*this, scope, aggressiveness, reaction); } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/RegistryHunt.cpp b/BLUESPAWN-client/src/hunt/RegistryHunt.cpp new file mode 100644 index 00000000..575d913b --- /dev/null +++ b/BLUESPAWN-client/src/hunt/RegistryHunt.cpp @@ -0,0 +1,180 @@ +#include "hunt/RegistryHunt.h" +#include "hunt/reaction/Reaction.h" + +#include "util/log/HuntLogMessage.h" +#include "util/log/Log.h" + +#include +#include + +namespace Registry { + REG_SZ_CHECK CheckSzEqual = [](const std::wstring& s1, const std::wstring& s2){ return s1 == s2; }; + REG_SZ_CHECK CheckSzNotEqual = [](const std::wstring& s1, const std::wstring& s2){ return s1 != s2; }; + REG_SZ_CHECK CheckSzEmpty = [](const std::wstring& s1, const std::wstring& s2){ return s1.length() == 0; }; + REG_SZ_CHECK CheckSzRegexMatch = [](const std::wstring& s1, const std::wstring& s2){ return std::regex_match(s1, std::wregex(s2)); }; + REG_SZ_CHECK CheckSzRegexNotMatch = [](const std::wstring& s1, const std::wstring& s2){ return !std::regex_match(s1, std::wregex(s2)); }; + + REG_DWORD_CHECK CheckDwordEqual = [](DWORD d1, DWORD d2){ return d1 == d2; }; + REG_DWORD_CHECK CheckDwordNotEqual = [](DWORD d1, DWORD d2){ return d1 != d2; }; + + REG_BINARY_CHECK CheckBinaryEqual = [](const AllocationWrapper& s1, const AllocationWrapper& s2){ + return s1.CompareMemory(s2); + }; + REG_BINARY_CHECK CheckBinaryNotEqual = [](const AllocationWrapper& s1, const AllocationWrapper& s2){ + return !s1.CompareMemory(s2); + }; + REG_BINARY_CHECK CheckBinaryNull = [](const AllocationWrapper& s1, const AllocationWrapper& s2){ return !s1; }; + + REG_MULTI_SZ_CHECK CheckMultiSzSubset = [](const std::vector& s1, const std::vector& s2){ + std::unordered_set vals = { s2.begin(), s2.end() }; + for(auto string : s1){ + if(vals.find(string) == vals.end()){ + return false; + } + } + return true; + }; + REG_MULTI_SZ_CHECK CheckMultiSzExclusion = [](const std::vector& s1, const std::vector& s2){ + std::unordered_set vals = { s2.begin(), s2.end() }; + for(auto string : s1){ + if(vals.find(string) != vals.end()){ + return false; + } + } + return true; + }; + REG_MULTI_SZ_CHECK CheckMultiSzEmpty = [](const std::vector& s1, const std::vector& s2){ + return s1.size() == 0; + }; + + RegistryCheck::RegistryCheck(const std::wstring& wValueName, RegistryType type, const std::wstring& wData, + bool MissingBad, const REG_SZ_CHECK& check) : + value{ wValueName, type, wData }, + MissingBad{ MissingBad }, + wCheck{ check }{} + + RegistryCheck::RegistryCheck(const std::wstring& wValueName, RegistryType type, DWORD dwData, + bool MissingBad, const REG_DWORD_CHECK& check) : + value{ wValueName, type, dwData }, + MissingBad{ MissingBad }, + dwCheck{ check }{} + + RegistryCheck::RegistryCheck(const std::wstring& wValueName, RegistryType type, const AllocationWrapper& lpData, + bool MissingBad, const REG_BINARY_CHECK& check) : + value{ wValueName, type, lpData }, + MissingBad{ MissingBad }, + lpCheck{ check }{} + + RegistryCheck::RegistryCheck(const std::wstring& wValueName, RegistryType type, const std::vector& vData, + bool MissingBad, const REG_MULTI_SZ_CHECK& check) : + value{ wValueName, type, vData }, + MissingBad{ MissingBad }, + vCheck{ check }{} + + RegistryType RegistryCheck::GetType() const { + return value.type; + } + + std::vector CheckValues(const RegistryKey& key, const std::vector& checks){ + std::vector vIdentifiedValues = {}; + + LOG_VERBOSE(1, "Checking values under " << key.ToString()); + + for(const RegistryCheck& check : checks){ + if(check.GetType() == RegistryType::REG_SZ_T || check.GetType() == RegistryType::REG_EXPAND_SZ_T){ + auto data = key.GetValue(check.value.wValueName); + if(!data.has_value()){ + if(check.MissingBad){ + LOG_INFO("Under key " << key << ", desired value " << check.value.wValueName << " was missing."); + vIdentifiedValues.emplace_back(RegistryValue{ check.value.wValueName, check.GetType(), std::vector{} }); + } + } else if(!check.wCheck(*data, check.value.wData)){ + auto value = RegistryValue{ check.value.wValueName, check.GetType(), *data }; + LOG_INFO("Under key " << key << ", value " << check.value.wValueName << " had potentially malicious data " << value); + vIdentifiedValues.emplace_back(value); + } + } else if(check.GetType() == RegistryType::REG_MULTI_SZ_T){ + auto data = key.GetValue>(check.value.wValueName); + if(!data.has_value()){ + if(check.MissingBad){ + LOG_INFO("Under key " << key << ", desired value " << check.value.wValueName << " was missing."); + vIdentifiedValues.emplace_back(RegistryValue{ check.value.wValueName, check.GetType(), std::wstring{} }); + } + } else if(!check.vCheck(*data, check.value.vData)){ + auto value = RegistryValue{ check.value.wValueName, check.GetType(), *data }; + LOG_INFO("Under key " << key << ", value " << check.value.wValueName << " had potentially malicious data " << value); + vIdentifiedValues.emplace_back(value); + } + } else if(check.GetType() == RegistryType::REG_DWORD_T){ + auto data = key.GetValue(check.value.wValueName); + if(!data.has_value()){ + if(check.MissingBad){ + LOG_INFO("Under key " << key << ", desired value " << check.value.wValueName << " was missing."); + vIdentifiedValues.emplace_back(RegistryValue{ check.value.wValueName, check.GetType(), 0 }); + } + } else if(!check.dwCheck(*data, check.value.dwData)){ + auto value = RegistryValue{ check.value.wValueName, check.GetType(), *data }; + LOG_INFO("Under key " << key << ", value " << check.value.wValueName << " had potentially malicious data " << value); + vIdentifiedValues.emplace_back(value); + } + } else if(check.GetType() == RegistryType::REG_BINARY_T){ + auto data = key.GetRawValue(check.value.wValueName); + if(!data){ + if(check.MissingBad){ + LOG_INFO("Under key " << key << ", desired value " << check.value.wValueName << " was missing."); + vIdentifiedValues.emplace_back(RegistryValue{ check.value.wValueName, check.GetType(), AllocationWrapper{ nullptr, 0 }}); + } + } else if(!check.lpCheck(data, check.value.lpData)){ + auto value = RegistryValue{ check.value.wValueName, check.GetType(), data }; + LOG_INFO("Under key " << key << ", value " << check.value.wValueName << " had potentially malicious data " << value); + vIdentifiedValues.emplace_back(value); + } + } + } + return vIdentifiedValues; + } + + std::vector CheckKeyValues(const RegistryKey& key){ + auto values = key.EnumerateValues(); + std::vector vRegValues = {}; + + for(const auto& value : values){ + auto type = key.GetValueType(value); + if(type == RegistryType::REG_SZ_T || type == RegistryType::REG_EXPAND_SZ_T){ + auto data = key.GetValue(value); + auto regValue = RegistryValue{ value, *type, (!data ? L"" : *data) }; + + LOG_INFO("Under key " << key << ", value " << value << " 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{ value, *type, (!data ? std::vector{} : *data) }; + + LOG_INFO("Under key " << key << ", value " << value << " was present with data " << regValue); + + vRegValues.emplace_back(regValue); + } else if(type == RegistryType::REG_DWORD_T){ + auto data = key.GetValue(value); + auto regValue = RegistryValue{ value, *type, (!data ? 0 : *data) }; + + LOG_INFO("Under key " << key << ", value " << value << " was present with data " << regValue); + + vRegValues.emplace_back(regValue); + } else { + auto data = key.GetRawValue(value); + auto regValue = RegistryValue{ value, *type, data }; + + LOG_INFO("Under key " << key << ", value " << value << " was present with data " << regValue); + + vRegValues.emplace_back(regValue); + } + } + + return vRegValues; + } + + std::vector CheckSubkeys(const RegistryKey& key){ + return key.EnumerateSubkeys(); + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/Scope.cpp b/BLUESPAWN-client/src/hunt/Scope.cpp index c4b96ed9..68eda6ba 100644 --- a/BLUESPAWN-client/src/hunt/Scope.cpp +++ b/BLUESPAWN-client/src/hunt/Scope.cpp @@ -1,53 +1,53 @@ #include "hunt/Scope.h" -bool Scope::FileIsInScope(LPCSTR sFileName){ +bool Scope::FileIsInScope(LPCSTR sFileName) const { return true; } -bool Scope::FileIsInScope(HANDLE hFile){ +bool Scope::FileIsInScope(HANDLE hFile) const { return true; } -std::vector Scope::GetScopedFileHandles(){ +std::vector Scope::GetScopedFileHandles() const { return std::vector(); } -std::vector Scope::GetScopedFileNames(){ +std::vector Scope::GetScopedFileNames() const { return std::vector(); } -bool Scope::RegistryKeyIsInScope(LPCSTR sKeyPath){ +bool Scope::RegistryKeyIsInScope(LPCSTR pid) const { return true; } -bool Scope::RegistryKeyIsInScope(HKEY key){ +bool Scope::RegistryKeyIsInScope(HKEY key) const { return true; } -std::vector Scope::GetScopedKHEYs(){ +std::vector Scope::GetScopedKHEYs() const { return std::vector(); } -std::vector Scope::GetScopedRegKeyNames(){ +std::vector Scope::GetScopedRegKeyNames() const { return std::vector(); } -bool Scope::ProcessIsInScope(LPCSTR sProcessName){ +bool Scope::ProcessIsInScope(DWORD sProcessName) const { return true; } -bool Scope::ProcessIsInScope(HANDLE hProcess){ +bool Scope::ProcessIsInScope(HANDLE hProcess) const { return true; } -std::vector Scope::GetScopedProcessHandles(){ +std::vector Scope::GetScopedProcessHandles() const { return std::vector(); } -std::vector Scope::GetScopedProcessNames(){ - return std::vector(); +std::vector Scope::GetScopedProcessPIDs() const { + return std::vector(); } -bool Scope::ServiceIsInScope(LPCSTR sServiceName){ +bool Scope::ServiceIsInScope(LPCSTR sServiceName) const { return true; } -bool Scope::ServiceIsInScope(SC_HANDLE hService){ +bool Scope::ServiceIsInScope(SC_HANDLE hService) const { return true; } -std::vector Scope::GetScopedServiceHandles(){ +std::vector Scope::GetScopedServiceHandles() const { return std::vector(); } -std::vector Scope::GetScopedServiceNames(){ +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 index 0904f6d1..85c51093 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1004.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1004.cpp @@ -1,15 +1,17 @@ #include "hunt/hunts/HuntT1004.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/configurations/Registry.h" #include "util/log/Log.h" #include "util/log/HuntLogMessage.h" +#include "util/eventlogs/EventLogs.h" +#include "hunt/reaction/HuntTrigger.h" using namespace Registry; namespace Hunts { - HuntT1004::HuntT1004(HuntRegister& record) : Hunt(record, L"T1004 - Winlogon Helper DLL") { + HuntT1004::HuntT1004() : Hunt(L"T1004 - Winlogon Helper DLL") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -20,20 +22,63 @@ namespace Hunts { LOG_INFO("Hunting for T1004 - Winlogon Helper DLL at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; + std::map> keys; - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"Shell" }, L"explorer.exe", reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"Shell" }, L"explorer.exe", reaction); - identified += CheckKey({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"Shell"}, L"", reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"Userinit" }, - std::vector{ L"", L"C:\\Windows\\system32\\userinit.exe,", L"C:\\Windows\\system32\\userinit.exe" }, reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"Userinit"}, - std::vector{ L"", L"C:\\Windows\\system32\\userinit.exe,", L"C:\\Windows\\system32\\userinit.exe" }, reaction); - identified += CheckKey({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"Userinit"}, L"", reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify" }, reaction); + auto HKLMWinlogon = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" }; + keys.emplace(HKLMWinlogon, CheckValues(HKLMWinlogon, { + { L"Shell", RegistryType::REG_SZ_T, L"explorer\\.exe,?", true, CheckSzRegexMatch }, + { L"UserInit", RegistryType::REG_SZ_T, L"C:\\\\(Windows|WINDOWS|windows)\\\\(System32|SYSTEM32|system32)\\\\(U|u)(SERINIT|serinit)\\.(exe|EXE),?", false, CheckSzRegexMatch } + })); + + auto HKLMWinlogonWoW64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" }; + keys.emplace(HKLMWinlogonWoW64, CheckValues(HKLMWinlogonWoW64, { + { L"Shell", RegistryType::REG_SZ_T, L"explorer\\.exe,?", true, CheckSzRegexMatch }, + { L"UserInit", RegistryType::REG_SZ_T, L"C:\\\\(Windows|WINDOWS|windows)\\\\(System32|SYSTEM32|system32)\\\\(U|u)(SERINIT|serinit)\\.(exe|EXE),?", false, CheckSzRegexMatch } + })); + + auto HKCUWinlogon = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" }; + keys.emplace(HKCUWinlogon, CheckValues(HKCUWinlogon, { + { L"Shell", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + })); + + auto HKCUWinlogonWow64 = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" }; + keys.emplace(HKCUWinlogonWow64, CheckValues(HKCUWinlogonWow64, { + { L"Shell", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + })); + + for(const auto& subkey : CheckSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify" })){ + if(subkey.ValueExists(L"DllName")){ + keys.emplace(subkey, std::vector{ { L"DllName", RegistryType::REG_SZ_T, * subkey.GetValue(L"DllName") }}); + } + } + + for(const auto& subkey : CheckSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify" })){ + if(subkey.ValueExists(L"DllName")){ + keys.emplace(subkey, std::vector{ { L"DllName", RegistryType::REG_SZ_T, * subkey.GetValue(L"DllName") }}); + } + } + + for(const auto& subkey : CheckSubkeys({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify" })){ + if(subkey.ValueExists(L"DllName")){ + keys.emplace(subkey, std::vector{ { L"DllName", RegistryType::REG_SZ_T, * subkey.GetValue(L"DllName") }}); + } + } + + for(const auto& subkey : CheckSubkeys({ HKEY_CURRENT_USER, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify" })){ + if(subkey.ValueExists(L"DllName")){ + keys.emplace(subkey, std::vector{ { L"DllName", RegistryType::REG_SZ_T, * subkey.GetValue(L"DllName") }}); + } + } + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } reaction.EndHunt(); - return identified; + return detections; } - } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp index 6f7f1a37..3fe0669e 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1037.cpp @@ -1,12 +1,12 @@ #include "hunt/hunts/HuntT1037.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/log/Log.h" using namespace Registry; namespace Hunts { - HuntT1037::HuntT1037(HuntRegister& record) : Hunt(record, L"T1037 - Logon Scripts") { + HuntT1037::HuntT1037() : Hunt(L"T1037 - Logon Scripts") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -17,12 +17,22 @@ namespace Hunts { LOG_INFO("Hunting for T1037 - Logon Scripts at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; + std::map> keys; - identified += CheckKey({ HKEY_CURRENT_USER, L"Environment", L"UserInitMprLogonScript" }, L"", reaction); + auto HKCUEnvironment = RegistryKey{ HKEY_CURRENT_USER, L"Environment", }; + keys.emplace(HKCUEnvironment, CheckValues(HKCUEnvironment, { + { L"UserInitMprLogonScript", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty } + })); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } reaction.EndHunt(); - return identified; + return detections; } - } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp index 82a69e58..04f0e6f5 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1050.cpp @@ -5,20 +5,21 @@ namespace Hunts { - HuntT1050::HuntT1050(HuntRegister& record) : Hunt(record, L"T1050 - New Service") { + HuntT1050::HuntT1050() : Hunt(L"T1050 - New Service") { // TODO: update these categories - dwSupportedScans = (DWORD) Aggressiveness::Cursory; + dwSupportedScans = (DWORD) Aggressiveness::Intensive; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; dwTacticsUsed = (DWORD) Tactic::Persistence; } - int HuntT1050::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for T1050 - New Service at level Cursory"); + int HuntT1050::ScanIntensive(const Scope& scope, Reaction reaction){ + LOG_INFO("Hunting for T1050 - New Service at level Intensive"); reaction.BeginHunt(GET_INFO()); + int identified = 0; - identified += QueryEvents(L"System", 7045, std::set({L"Event/EventData/Data[@Name='ServiceName']", + identified += EventLogs::getLogs()->QueryEvents(L"System", 7045, std::set({L"Event/EventData/Data[@Name='ServiceName']", L"Event/EventData/Data[@Name='ImagePath']", L"Event/EventData/Data[@Name='ServiceType']", L"Event/EventData/Data[@Name='StartType']" }), reaction); if (identified == -1) { @@ -30,4 +31,14 @@ namespace Hunts { return identified; } + void HuntT1050::SetupMonitoring(HuntRegister& record, const Scope& scope, Aggressiveness level, Reaction reaction) { + Reactions::HuntTriggerReaction triggerReaction(record, dynamic_cast(this), scope, level, reaction); + DWORD status; + eventSubscriptions.push_back(EventLogs::getLogs()->subscribe(L"System", 7045, triggerReaction, &status)); + + if (status == ERROR_SUCCESS) + LOG_INFO("Monitoring for T1050 - New Service at level Cursory"); + else + LOG_WARNING("Monitoring for T1050 failed with error code " + std::to_string(status)); + } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1055.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1055.cpp new file mode 100644 index 00000000..993e49de --- /dev/null +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1055.cpp @@ -0,0 +1,113 @@ +#include +#include + +#include "hunt/hunts/HuntT1055.h" +#include "util/eventlogs/EventLogs.h" +#include "util/log/Log.h" +#include "util/log/HuntLogMessage.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") { + // TODO: update these categories + dwSupportedScans = (DWORD) Aggressiveness::Normal; + dwCategoriesAffected = (DWORD) Category::Configurations; + dwSourcesInvolved = (DWORD) DataSource::Registry; + dwTacticsUsed = (DWORD) Tactic::Persistence; + } + + 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 + }; + + auto summary = PESieve_scan(params); + if(summary.errors){ + LOG_WARNING("Unable to scan process " << pid << " due to an error in PE-Sieve.dll"); + } + + 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); + + HandleWrapper handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + std::wstring name = {}; + std::wstring path = {}; + if(handle){ + DWORD buffSize = 1024; + wchar_t buffer[1024]; + if(QueryFullProcessImageNameW(handle, 0, buffer, &buffSize)) + name = buffer; + + buffSize = 1024; + if(GetProcessImageFileNameW(handle, buffer, buffSize)) + path = buffer; + } + + + + reaction.ProcessIdentified(std::make_shared(name, path, std::wstring{}, pid, 0, identifiers)); + return true; + } + + return false; + } + + int HuntT1055::ScanNormal(const Scope& scope, Reaction reaction){ + LOG_INFO("Hunting for T1055 - Process Injection 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 index 551885e8..9226d7c4 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1060.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1060.cpp @@ -1,5 +1,5 @@ #include "hunt/hunts/HuntT1060.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/log/Log.h" #include "util/configurations/Registry.h" @@ -7,7 +7,7 @@ using namespace Registry; namespace Hunts { - HuntT1060::HuntT1060(HuntRegister& record) : Hunt(record, L"T1060 - Registry Run Keys / Startup Folder") { + HuntT1060::HuntT1060() : Hunt(L"T1060 - Registry Run Keys / Startup Folder") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -18,43 +18,60 @@ namespace Hunts { LOG_INFO("Hunting for T1060 - Registry Run Keys / Startup Folder at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; - - identified += CheckForValues({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForValues({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }, reaction); - identified += CheckForValues({ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForValues({ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }, reaction); - identified += CheckForValues({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }, reaction); - identified += CheckForValues({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }, reaction); - - identified += CheckForSubkeys({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForSubkeys({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }, reaction); - identified += CheckForSubkeys({ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run" }, reaction); - identified += CheckForSubkeys({ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }, reaction); - identified += CheckForSubkeys({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }, reaction); - - identified += CheckKey({ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", L"Startup" }, - L"%USERPROFILE%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", L"Common Startup"}, - L"%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", L"Common Startup" }, - L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", reaction); + std::map> keys; + + auto HKLMRun = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }; + auto HKCURun = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run" }; + auto HKLMRunOnce = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }; + auto HKCURunOnce = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }; + auto HKLMRunOnceEx = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }; + auto HKCURunOnceEx = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }; + auto HKLMRunWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run" }; + auto HKCURunWow64 = RegistryKey{ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run" }; + auto HKLMRunOnceWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }; + auto HKCURunOnceWow64 = RegistryKey{ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce" }; + auto HKLMRunOnceExWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }; + auto HKCURunOnceExWow64 = RegistryKey{ HKEY_CURRENT_USER, L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx" }; + auto HKLMExplorerRun = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }; + auto HKCUExplorerRun = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run" }; + + std::vector RunKeys = { + HKLMRun, HKLMRunOnce, HKLMRunOnceEx, + HKCURun, HKCURunOnce, HKCURunOnceEx, + HKLMRunWow64, HKLMRunOnceWow64, HKLMRunOnceExWow64, + HKCURunWow64, HKCURunOnceWow64, HKCURunOnceExWow64, + HKLMExplorerRun, HKCUExplorerRun, + }; + + for(auto key : RunKeys){ + keys.emplace(key, CheckKeyValues(key)); + } + + auto HKCUShell = RegistryKey{ HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders" }; + keys.emplace(HKCUShell, CheckValues(HKCUShell, { + { L"Startup", RegistryType::REG_EXPAND_SZ_T, L"%USERPROFILE%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", false, CheckSzEqual } + })); + + auto HKLMUShell = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders" }; + keys.emplace(HKLMUShell, CheckValues(HKLMUShell, { + { L"Common Startup", RegistryType::REG_EXPAND_SZ_T, L"%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", false, CheckSzEqual } + })); + + + auto HKLMShell = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders" }; + keys.emplace(HKLMShell, CheckValues(HKLMShell, { + { L"Common Startup", RegistryType::REG_EXPAND_SZ_T, L"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup", false, CheckSzEqual } + })); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } reaction.EndHunt(); - return identified; + return detections; } - } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp index daa66ba5..1734f7a3 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1100.cpp @@ -1,116 +1,139 @@ -#include "hunt/hunts/HuntT1100.h" - -#include "util/filesystem/FileSystem.h" -#include "util/log/Log.h" - -namespace Hunts { - HuntT1100::HuntT1100(HuntRegister& record) : Hunt(record, L"T1100 - Web Shells") { - smatch match_index; - - dwSupportedScans = (DWORD) Aggressiveness::Cursory | (DWORD) Aggressiveness::Moderate; - 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::Moderate) { - 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::string& sFileName){ - web_directories.emplace_back(sFileName); - } - - void HuntT1100::AddFileExtensionToSearch(const std::string& sFileExtension) { - web_exts.emplace_back(sFileExtension); - } - - int HuntT1100::ScanCursory(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for T1100 - Web Shells at level Cursory"); - reaction.BeginHunt(GET_INFO()); - SetRegexAggressivenessLevel(Aggressiveness::Cursory); - - int identified = 0; - - for (string path : web_directories) { - for (const auto& entry : fs::recursive_directory_iterator(path)) { - string file_ext = entry.path().extension().string(); - transform(file_ext.begin(), file_ext.end(), file_ext.begin(), ::tolower); - if (find(web_exts.begin(), web_exts.end(), file_ext) != web_exts.end()) { - string sus_file = GetFileContents(entry.path().wstring().c_str()); - transform(sus_file.begin(), sus_file.end(), sus_file.begin(), ::tolower); - - if (file_ext.compare(".php") == 0) { - if (regex_search(sus_file, match_index, php_vuln_functions)) { - LOG_ERROR("Located likely web shell in file " << entry.path().string() << " in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } else if (file_ext.substr(0, 4).compare(".jsp") == 0) { - if (regex_search(sus_file, match_index, jsp_indicators)) { - LOG_ERROR("Located likely web shell in file " << entry.path().string() << " in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } else if (file_ext.substr(0, 3).compare(".as") == 0) { - if (regex_search(sus_file, match_index, asp_indicators)) { - identified++; - LOG_ERROR("Located likely web shell in file " << entry.path().string() << " in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - } - } - } - - reaction.EndHunt(); - return identified; - } - - int HuntT1100::ScanModerate(const Scope& scope, Reaction reaction){ - LOG_INFO("Hunting for T1100 - Web Shells at level Moderate"); - reaction.BeginHunt(GET_INFO()); - SetRegexAggressivenessLevel(Aggressiveness::Moderate); - - int identified = 0; - - for (string path : web_directories) { - for (const auto& entry : fs::recursive_directory_iterator(path)) { - string file_ext = entry.path().extension().string(); - transform(file_ext.begin(), file_ext.end(), file_ext.begin(), ::tolower); - if (find(web_exts.begin(), web_exts.end(), file_ext) != web_exts.end()) { - string sus_file = GetFileContents(entry.path().wstring().c_str()); - transform(sus_file.begin(), sus_file.end(), sus_file.begin(), ::tolower); - - if (file_ext.compare(".php") == 0) { - if (regex_search(sus_file, match_index, php_vuln_functions)) { - identified++; - LOG_ERROR("Located likely web shell in file " << entry.path().string() << " in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - else if (file_ext.substr(0, 4).compare(".jsp") == 0) { - if (regex_search(sus_file, match_index, jsp_indicators)) { - identified++; - LOG_ERROR("Located likely web shell in file " << entry.path().string() << " in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - - else if (file_ext.substr(0, 3).compare(".as") == 0) { - if (regex_search(sus_file, match_index, asp_indicators)) { - identified++; - LOG_ERROR("Located likely web shell in file " << entry.path().string() << " in text " << sus_file.substr(match_index.position(), match_index.length())); - } - } - } - } - } - - reaction.EndHunt(); - return identified; - } -} \ No newline at end of file +#include "hunt/hunts/HuntT1100.h" + +#include "util/filesystem/FileSystem.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::ScanCursory(const Scope& scope, Reaction reaction){ + LOG_INFO("Hunting for T1100 - Web Shells at level Cursory"); + reaction.BeginHunt(GET_INFO()); + SetRegexAggressivenessLevel(Aggressiveness::Cursory); + + int identified = 0; + + for (std::wstring path : web_directories) { + auto f = FileSystem::Folder(path); + FileSystem::FileSearchAttribs attribs; + attribs.extensions = web_exts; + std::vector files = f.GetFiles(attribs, -1); + for (const auto& entry : files) { + long offset = 0; + unsigned long targetAmount = 1000000; + DWORD amountRead = 0; + std::wstring file_ext = 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.GetFilePath().substr(entry.GetFilePath().find_last_of(L"\\/")), entry.GetFilePath())); + LOG_ERROR("Located likely web shell in file " << WidestringToString(entry.GetFilePath())<< " 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.GetFilePath().substr(entry.GetFilePath().find_last_of(L"\\/")), entry.GetFilePath())); + LOG_ERROR("Located likely web shell in file " << WidestringToString(entry.GetFilePath()) << " 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.GetFilePath().substr(entry.GetFilePath().find_last_of(L"\\/")), entry.GetFilePath())); + LOG_ERROR("Located likely web shell in file " << WidestringToString(entry.GetFilePath()) << " in text " << sus_file.substr(match_index.position(), match_index.length())); + } + } + offset += amountRead - 1000; + } while (targetAmount <= amountRead); + } + } + reaction.EndHunt(); + return identified; + } + + int HuntT1100::ScanNormal(const Scope& scope, Reaction reaction){ + LOG_INFO("Hunting for T1100 - Web Shells at level Normal"); + reaction.BeginHunt(GET_INFO()); + SetRegexAggressivenessLevel(Aggressiveness::Normal); + + int identified = 0; + + for (std::wstring path : web_directories) { + FileSystem::Folder f = FileSystem::Folder(path); + FileSystem::FileSearchAttribs attribs; + attribs.extensions = web_exts; + std::vector files = f.GetFiles(attribs, -1); + for (const auto& entry : files) { + long offset = 0; + long targetAmount = 1000000; + DWORD amountRead = 0; + auto file_ext = 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.GetFilePath().substr(entry.GetFilePath().find_last_of(L"\\/")), entry.GetFilePath())); + LOG_ERROR("Located likely web shell in file " << WidestringToString(entry.GetFilePath()) << " 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.GetFilePath().substr(entry.GetFilePath().find_last_of(L"\\/")), entry.GetFilePath())); + LOG_ERROR("Located likely web shell in file " << WidestringToString(entry.GetFilePath()) << " 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.GetFilePath().substr(entry.GetFilePath().find_last_of(L"\\/")), entry.GetFilePath())); + LOG_ERROR("Located likely web shell in file " << WidestringToString(entry.GetFilePath()) << " in text " << sus_file.substr(match_index.position(), match_index.length())); + } + } + offset += amountRead - 1000; + } while (targetAmount <= amountRead); + } + } + reaction.EndHunt(); + return identified; + } +} diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp index f507ed38..73d144b4 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1101.cpp @@ -1,30 +1,45 @@ -#include "hunt/hunts/HuntT1101.h" -#include "hunt/RegistryHunt.hpp" - -#include "util/log/Log.h" -#include "util/configurations/Registry.h" - -using namespace Registry; - -namespace Hunts { - HuntT1101::HuntT1101(HuntRegister& record) : Hunt(record, 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("Hunting for T1101 - Security Support Provider at level Cursory"); - reaction.BeginHunt(GET_INFO()); - - int identified = 0; - - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", L"Security Packages" }, okSecPackages, reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa\\OSConfig", L"Security Packages" }, okSecPackages, reaction); - - reaction.EndHunt(); - return identified; - } - +#include "hunt/hunts/HuntT1101.h" +#include "hunt/RegistryHunt.h" + +#include "util/log/Log.h" +#include "util/configurations/Registry.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("Hunting for T1101 - Security Support Provider at level Cursory"); + reaction.BeginHunt(GET_INFO()); + + std::map> keys; + + auto Lsa = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; + keys.emplace(Lsa, CheckValues(Lsa, { + {L"Security Packages", RegistryType::REG_MULTI_SZ_T, okSecPackages, false, CheckMultiSzSubset }, + })); + + auto OSConfig = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa\\OSConfig" }; + keys.emplace(OSConfig, CheckValues(OSConfig, { + {L"Security Packages", RegistryType::REG_MULTI_SZ_T, okSecPackages, false, CheckMultiSzSubset }, + })); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } + + reaction.EndHunt(); + return detections; + } + } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp index 1fff7470..582afb56 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1103.cpp @@ -1,12 +1,12 @@ #include "hunt/hunts/HuntT1103.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/log/Log.h" #include "util/configurations/Registry.h" using namespace Registry; namespace Hunts { - HuntT1103::HuntT1103(HuntRegister& record) : Hunt(record, L"T1103 - AppInit DLLs") { + HuntT1103::HuntT1103() : Hunt(L"T1103 - AppInit DLLs") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations | (DWORD) Category::Processes; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -17,15 +17,30 @@ namespace Hunts { LOG_INFO("Hunting for T1103 - AppInit DLLs at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; + std::map> keys; - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", L"AppInit_DLLs" }, L"", reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE,L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows", L"AppInit_DLLs" }, L"", reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE,L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows", L"LoadAppInit_DLLs" }, 0, reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows", L"LoadAppInit_DLLs" }, 0, reaction); + auto WinKey = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows" }; + keys.emplace(WinKey, CheckValues(WinKey, { + { L"AppInit_Dlls", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + { L"LoadAppInit_Dlls", RegistryType::REG_DWORD_T, 0, false, CheckDwordEqual }, + })); + + auto WinKeyWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows" }; + keys.emplace(WinKeyWow64, CheckValues(WinKeyWow64, { + { L"AppInit_Dlls", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + { L"LoadAppInit_Dlls", RegistryType::REG_DWORD_T, 0, false, CheckDwordEqual }, + })); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } reaction.EndHunt(); - return identified; + return detections; } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp index d9d39e00..edd0ce7c 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1131.cpp @@ -1,5 +1,5 @@ #include "hunt/hunts/HuntT1131.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/log/Log.h" #include "util/configurations/Registry.h" @@ -7,7 +7,7 @@ using namespace Registry; namespace Hunts { - HuntT1131::HuntT1131(HuntRegister& record) : Hunt(record, L"T1131 - Authentication Package") { + HuntT1131::HuntT1131() : Hunt(L"T1131 - Authentication Package") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -18,13 +18,24 @@ namespace Hunts { LOG_INFO("Hunting for T1131 - Authentication Package at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; + std::map> keys; + + auto LSA = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa" }; + keys.emplace(LSA, CheckValues(LSA, { + { L"Authentication Packages", RegistryType::REG_MULTI_SZ_T, okAuthPackages, false, CheckMultiSzSubset }, + { L"Notification Packages", RegistryType::REG_MULTI_SZ_T, okNotifPackages, false, CheckMultiSzSubset }, + })); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", L"Authentication Packages" }, okAuthPackages, reaction); - identified += CheckKey({ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", L"Notification Packages" }, okNotifPackages, reaction); - reaction.EndHunt(); - return identified; + return detections; } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp index b3104aa1..be2de542 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1138.cpp @@ -1,5 +1,5 @@ #include "hunt/hunts/HuntT1138.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/log/Log.h" #include "util/configurations/Registry.h" @@ -7,7 +7,7 @@ using namespace Registry; namespace Hunts { - HuntT1138::HuntT1138(HuntRegister& record) : Hunt(record, L"T1138 - Application Shimming") { + HuntT1138::HuntT1138() : Hunt(L"T1138 - Application Shimming") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -18,13 +18,29 @@ namespace Hunts { LOG_INFO("Hunting for T1138 - Application Shimming at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; + std::map> keys; + + auto SDB = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB" }; + keys.emplace(SDB, CheckKeyValues(SDB)); + + auto Custom = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Customs" }; + keys.emplace(Custom, CheckKeyValues(Custom)); + + auto SDBWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB" }; + keys.emplace(SDBWow64, CheckKeyValues(SDBWow64)); + + auto CustomWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Customs" }; + keys.emplace(CustomWow64, CheckKeyValues(CustomWow64)); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB"}, reaction); - identified += CheckForSubkeys({ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom"}, reaction); - reaction.EndHunt(); - return identified; + return detections; } - } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp index c6daef05..0f776325 100644 --- a/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1182.cpp @@ -1,5 +1,5 @@ #include "hunt/hunts/HuntT1182.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/log/Log.h" #include "util/configurations/Registry.h" @@ -7,7 +7,7 @@ using namespace Registry; namespace Hunts { - HuntT1182::HuntT1182(HuntRegister& record) : Hunt(record, L"T1182 - AppCert DLLs") { + HuntT1182::HuntT1182() : Hunt(L"T1182 - AppCert DLLs") { dwSupportedScans = (DWORD) Aggressiveness::Cursory; dwCategoriesAffected = (DWORD) Category::Configurations; dwSourcesInvolved = (DWORD) DataSource::Registry; @@ -18,12 +18,24 @@ namespace Hunts { LOG_INFO("Hunting for T1182 - AppCert DLLs at level Cursory"); reaction.BeginHunt(GET_INFO()); - int identified = 0; + std::map> keys; + + auto SessMan = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Session Manager" }; + + keys.emplace(SessMan, CheckValues(SessMan, { + { L"AppCertDLLs", RegistryType::REG_MULTI_SZ_T, std::vector{}, false, CheckMultiSzEmpty }, + })); + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } - identified += CheckForSubkeys(RegistryKey(HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Session Manager\\AppCertDlls"), reaction); - reaction.EndHunt(); - return identified; + return detections; } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/hunts/HuntT1183.cpp b/BLUESPAWN-client/src/hunt/hunts/HuntT1183.cpp new file mode 100644 index 00000000..58e2c0ed --- /dev/null +++ b/BLUESPAWN-client/src/hunt/hunts/HuntT1183.cpp @@ -0,0 +1,71 @@ +#include "hunt/hunts/HuntT1183.h" +#include "hunt/RegistryHunt.h" + +#include "util/log/Log.h" +#include "util/configurations/Registry.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("Hunting for T1183 - Image File Execution Options at level Cursory"); + reaction.BeginHunt(GET_INFO()); + + std::map> keys; + + auto IFEO = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options" }; + for(auto subkey : IFEO.EnumerateSubkeys()){ + keys.emplace(subkey, CheckValues(subkey, { + { L"Debugger", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + { L"GlobalFlag", RegistryType::REG_DWORD_T, 0, false, [](DWORD d1, DWORD d2){ return !(d1 & 0x200); } }, + })); + auto GFlags = subkey.GetValue(L"GlobalFlag"); + if(GFlags && *GFlags & 0x200){ + auto name = subkey.GetName(); + name = name.substr(name.find_last_of(L"\\") + 1); + auto SilentProcessExit = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\" + name }; + keys.emplace(SilentProcessExit, CheckValues(SilentProcessExit, { + { L"ReportingMode", RegistryType::REG_DWORD_T, 0, false, CheckDwordEqual }, + { L"MonitorProcess", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + })); + } + } + + auto IFEOWow64 = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options" }; + for(auto subkey : IFEOWow64.EnumerateSubkeys()){ + keys.emplace(subkey, CheckValues(subkey, { + { L"Debugger", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + { L"GlobalFlag", RegistryType::REG_DWORD_T, 0, false, [](DWORD d1, DWORD d2){ return !(d1 & 0x200); } }, + })); + auto GFlags = subkey.GetValue(L"GlobalFlag"); + if(GFlags && *GFlags & 0x200){ + auto name = subkey.GetName(); + name = name.substr(name.find_last_of(L"\\") + 1); + auto SilentProcessExit = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\" + name }; + keys.emplace(SilentProcessExit, CheckValues(SilentProcessExit, { + { L"ReportingMode", RegistryType::REG_DWORD_T, 0, false, CheckDwordEqual }, + { L"MonitorProcess", RegistryType::REG_SZ_T, L"", false, CheckSzEmpty }, + })); + } + } + + int detections = 0; + for(const auto& key : keys){ + for(const auto& value : key.second){ + reaction.RegistryKeyIdentified(std::make_shared(key.first.GetName(), value)); + detections++; + } + } + + reaction.EndHunt(); + return detections; + } + +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/reaction/HuntTrigger.cpp b/BLUESPAWN-client/src/hunt/reaction/HuntTrigger.cpp new file mode 100644 index 00000000..e126924d --- /dev/null +++ b/BLUESPAWN-client/src/hunt/reaction/HuntTrigger.cpp @@ -0,0 +1,13 @@ +#include "hunt/reaction/HuntTrigger.h" +#include "util/log/huntlogmessage.h" + +namespace Reactions { + HuntTriggerReaction::HuntTriggerReaction(HuntRegister& record, Hunt* hunt, const Scope& scope, Aggressiveness level, Reaction& reaction) : + record(record), hunt(hunt), level(level), scope(scope), reaction(reaction) {} + + void HuntTriggerReaction::EventIdentified(std::shared_ptr detection) { + LOG_INFO("Event found through monitoring."); + + record.RunHunt(*hunt, scope, level, reaction); + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/hunt/reaction/ReactLog.cpp b/BLUESPAWN-client/src/hunt/reaction/ReactLog.cpp index ff7d61e8..2839fe57 100644 --- a/BLUESPAWN-client/src/hunt/reaction/ReactLog.cpp +++ b/BLUESPAWN-client/src/hunt/reaction/ReactLog.cpp @@ -26,14 +26,16 @@ namespace Reactions { if(HuntBegun){ _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); } else { - LOG_ERROR("Potentially malicious registry key " << detection->wsRegistryKeyPath << (detection->wsRegistryKeyValue.length() ? L": " : L"") << detection->wsRegistryKeyValue << " detected outside of a hunt!"); + LOG_ERROR(L"\tPotentially malicious registry key detected outside of a hunt - " << detection->wsRegistryKeyPath + << L": " << detection->contents.wValueName << L" with data " << detection->contents.ToString()); } } void LogReaction::LogProcessIdentified(std::shared_ptr detection){ if(HuntBegun){ _HuntLogMessage->AddDetection(std::static_pointer_cast(detection)); } else { - LOG_ERROR("Potentially malicious process " << detection->wsImageName << " (PID " << detection->PID << ") detected outside of a hunt!"); + LOG_ERROR("Potentially malicious process " << detection->wsImageName << " (PID " << detection->PID + << ") detected outside of a hunt!"); } } void LogReaction::LogServiceIdentified(std::shared_ptr detection){ diff --git a/BLUESPAWN-client/src/hunt/reaction/RemoveValue.cpp b/BLUESPAWN-client/src/hunt/reaction/RemoveValue.cpp new file mode 100644 index 00000000..43416fae --- /dev/null +++ b/BLUESPAWN-client/src/hunt/reaction/RemoveValue.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include "hunt/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->wsRegistryKeyPath + L" contains potentially malicious value " + + detection->contents.wValueName + L" with data " + detection->contents.ToString() + L". Remove value?") == 1){ + auto key = Registry::RegistryKey{ detection->wsRegistryKeyPath }; + if(!key.RemoveValue(detection->contents.wValueName)){ + LOG_ERROR("Unable to remove registry value " << detection->wsRegistryKeyPath << ": " << detection->contents.wValueName << " (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/hunt/reaction/SuspendProcess.cpp b/BLUESPAWN-client/src/hunt/reaction/SuspendProcess.cpp new file mode 100644 index 00000000..91d277d6 --- /dev/null +++ b/BLUESPAWN-client/src/hunt/reaction/SuspendProcess.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include "hunt/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->wsImageName + 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/mitigation/MitigationRegister.cpp b/BLUESPAWN-client/src/mitigation/MitigationRegister.cpp index 75fa2b53..4a57ec89 100644 --- a/BLUESPAWN-client/src/mitigation/MitigationRegister.cpp +++ b/BLUESPAWN-client/src/mitigation/MitigationRegister.cpp @@ -1,7 +1,7 @@ #include "mitigation/MitigationRegister.h" #include "util/log/Log.h" -MitigationRegister::MitigationRegister(IOBase& oIo) : io(oIo) {} +MitigationRegister::MitigationRegister(IOBase& io) : io(io) {} void MitigationRegister::RegisterMitigation(Mitigation* mitigation) { vRegisteredMitigations.emplace_back(mitigation); diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1025.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1025.cpp new file mode 100644 index 00000000..6f45768c --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1025.cpp @@ -0,0 +1,59 @@ +#include "mitigation/mitigations/MitigateM1025.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations{ + + MitigateM1025::MitigateM1025(MitigationRegister& record) : + Mitigation( + record, + L"M1025 - Privileged Process Integrity", + L"Protect processes with high privileges that can be used to interact with critical " + "system components through use of protected process light, anti-process injection defenses, " + "or other process integrity enforcement measures.", + L"lsa", + SoftwareAffected::ExposedService, + MitigationSeverity::Medium + ) {} + + bool CheckLSARunAsPPL(bool enforce){ + auto lsa = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa" }; + if(!lsa.ValueExists(L"LmCompatibilityLevel") || lsa.GetValue(L"RunAsPPL") != 1){ + if(enforce){ + LOG_VERBOSE(1, "Setting RunAsPPL Compatibility Level to 1"); + return lsa.SetValue(L"RunAsPPL", 1); + } else { + LOG_VERBOSE(1, "Detected LSA to not be running as a PPL"); + return false; + } + } + LOG_VERBOSE(1, "LSA is running as a PPL"); + return true; + } + + bool MitigateM1025::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + return CheckLSARunAsPPL(false); + } + + bool MitigateM1025::EnforceMitigation(SecurityLevel level) { + LOG_INFO("Enforcing Mitigation for " << name); + + return CheckLSARunAsPPL(level >= SecurityLevel::Medium); + } + + bool MitigateM1025::MitigationApplies(){ + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" }; + auto version = key.GetValue(L"CurrentVersion"); + DWORD dwMajorVersion = _wtoi(version->substr(0, version->find(L".", 0)).c_str()); + DWORD dwMinorVersion = _wtoi(version->substr(version->find(L".", 0) + 1).c_str()); + return dwMajorVersion > 6 || (dwMajorVersion == 6 && dwMinorVersion >= 3); + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp new file mode 100644 index 00000000..b11a0e2f --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-LLMNR.cpp @@ -0,0 +1,58 @@ +#include "mitigation/mitigations/MitigateM1042-LLMNR.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateM1042LLMNR::MitigateM1042LLMNR(MitigationRegister& record) : + Mitigation( + record, + L"M1042-LLMNR - Link-Local Multicast Name Resolution (LLMNR) should be disabled", + L"Link-Local Multicast Name Resolution (LLMNR) serve as alternate methods for " + "host identification. Adversaries can spoof an authoritative source for name " + "resolution on a victim network by responding to LLMNR (UDP 5355)/NBT-NS (UDP 137) " + "traffic as if they know the identity of the requested host.", + L"llmnr", + SoftwareAffected::ExposedService, + MitigationSeverity::Low + ) {} + + bool MitigateM1042LLMNR::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient" }; + std::wstring value = L"EnableMulticast"; + + if (!key.ValueExists(value)) { + LOG_VERBOSE(1, L"Value for " << value << L" does not exist."); + return false; + } + + if(key.GetValue(value) != 0){ + LOG_VERBOSE(1, L"Value for " << value << L" is not set to 0."); + return false; + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateM1042LLMNR::EnforceMitigation(SecurityLevel level) { + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient" }; + std::wstring value = L"EnableMulticast"; + DWORD data = 0; + + LOG_VERBOSE(1, L"Attempting to set " << value << L" to 0."); + return key.SetValue(value, data); + } + + bool MitigateM1042LLMNR::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp new file mode 100644 index 00000000..6a15c726 --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-NBT.cpp @@ -0,0 +1,73 @@ +#include "mitigation/mitigations/MitigateM1042-NBT.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateM1042NBT::MitigateM1042NBT(MitigationRegister& record) : + Mitigation( + record, + L"M1042-NBT - NetBIOS Name Service (NBT-NS) should be disabled", + L" NetBIOS Name Service (NBT-NS) serve as alternate methods for " + "host identification. Adversaries can spoof an authoritative source for name " + "resolution on a victim network by responding to LLMNR (UDP 5355)/NBT-NS (UDP 137) " + "traffic as if they know the identity of the requested host.", + L"nbt", + SoftwareAffected::ExposedService, + MitigationSeverity::Low + ) {} + + bool MitigateM1042NBT::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto base = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NetBT\\Parameters\\Interfaces" }; + std::wstring value = L"NetbiosOptions"; + + for (auto key : base.EnumerateSubkeys()) { + if (key.GetValue(value) != 2) { + LOG_VERBOSE(1, L"Value for " << key << value << L" is not set to 2."); + return false; + } + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateM1042NBT::EnforceMitigation(SecurityLevel level) { + auto base = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\NetBT\\Parameters\\Interfaces" }; + std::wstring value = L"NetbiosOptions"; + DWORD data = 2; + + int failcount = 0; + + for (auto key : base.EnumerateSubkeys()) { + if (key.GetValue(value) != 2) { + LOG_VERBOSE(1, L"Attempting to set " << key << value << L" to 2."); + if (!key.SetValue(value, data)) { + LOG_VERBOSE(1, L"Unable to set " << key << value << L" to 2."); + failcount++; + } + } + } + + if (failcount == 0) { + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + else { + LOG_VERBOSE(1, L"Failed to enforce " << failcount << L" values for Mitigation " << name); + return false; + } + } + + bool MitigateM1042NBT::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp new file mode 100644 index 00000000..8536cfe8 --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateM1042-WSH.cpp @@ -0,0 +1,58 @@ +#include "mitigation/mitigations/MitigateM1042-WSH.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateM1042WSH::MitigateM1042WSH(MitigationRegister& record) : + Mitigation( + record, + L"M1042-WSH - Windows Script Host (WSH) should be disabled", + L"Windows Script Host enables the execution of wscript and cscript " + "which allow VB, JS, and other scripts to be run. This feature is not " + "typically needed, and Sean Metcalf recommends disabling it https://adsecurity.org/?p=3299. " + "This corresponds to M1042.", + L"wsh", + SoftwareAffected::InternalService, + MitigationSeverity::Low + ) {} + + bool MitigateM1042WSH::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows Script Host\\Settings" }; + std::wstring value = L"Enabled"; + + if (!key.ValueExists(value)) { + LOG_VERBOSE(1, L"Value for " << value << L" does not exist."); + return false; + } + + if(key.GetValue(value) != 0){ + LOG_VERBOSE(1, L"Value for " << value << L" is not set to 0."); + return false; + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateM1042WSH::EnforceMitigation(SecurityLevel level) { + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows Script Host\\Settings" }; + std::wstring value = L"Enabled"; + DWORD data = 0; + + LOG_VERBOSE(1, L"Attempting to set " << value << L" to 0."); + return key.SetValue(value, data); + } + + bool MitigateM1042WSH::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp index e68fa0e2..b9915398 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1093.cpp @@ -1,4 +1,5 @@ #include "mitigation/mitigations/MitigateV1093.h" +#include "hunt/RegistryHunt.h" #include "util/configurations/Registry.h" #include "util/log/Log.h" @@ -19,29 +20,27 @@ namespace Mitigations { ) {} bool MitigateV1093::MitigationIsEnforced(SecurityLevel level) { - LOG_INFO("Checking for presence of " << name); - - auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa", L"RestrictAnonymous" }; - if (!key.ValueExists()) { - return false; + std::map> keys; + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa" }; + + keys.emplace(key, CheckValues(key, { + { L"RestrictAnonymous", RegistryType::REG_DWORD_T, 1, true, CheckDwordEqual }, + })); + + for (const auto& key : keys) { + for (const auto& value : key.second) { + LOG_INFO(L"[" + name + L"] RestrictAnonymous value is not set to 1"); + return false; + } } - if (key.Get() != 1) { - LOG_INFO("[V-1093 - Anonymous enumeration of shares must be restricted] RestrictAnonymous value is not set to 1"); - return false; - } - return true; } bool MitigateV1093::EnforceMitigation(SecurityLevel level) { - auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa", L"RestrictAnonymous" }; - if (!key.ValueExists()) { - DWORD value = 1; - return key.Create(&value, 4, REG_DWORD); - } + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa" }; - return key.Set(1); + return key.SetValue(L"restrictanonymous", 1); } bool MitigateV1093::MitigationApplies(){ diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1153.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1153.cpp new file mode 100644 index 00000000..3c437848 --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV1153.cpp @@ -0,0 +1,55 @@ +#include "mitigation/mitigations/MitigateV1153.h" + +#include "hunt/RegistryHunt.h" +#include "util/configurations/Registry.h" +#include "util/log/Log.h" +#include + +using namespace Registry; + +namespace Mitigations{ + + MitigateV1153::MitigateV1153(MitigationRegister& record) : + Mitigation( + record, + L"V-1153 - The LanMan authentication level must be set to send NTLMv2 response only, and to refuse LM and NTLM", + L"The Kerberos v5 authentication protocol is the default for authentication of users who are logging on to domain " + L"accounts. NTLM which is less secure, is retained in later Windows versions for compatibility with clients and servers " + L"that are running earlier versions of Windows or applications that still use it. It is also used to authenticate logons " + L"to stand-alone computers that are running later versions.", + L"lsa", + SoftwareAffected::ExposedService, + MitigationSeverity::Medium + ) {} + + bool CheckLMCompatibilityLevel(bool enforce){ + auto lsa = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Lsa" }; + if(!lsa.ValueExists(L"LmCompatibilityLevel") || lsa.GetValue(L"LmCompatibilityLevel") != 5){ + if(enforce){ + LOG_VERBOSE(1, "Setting LM Compatibility Level to 5 (allow only NTLMv2)"); + return lsa.SetValue(L"LmCompatibilityLevel", 5); + } else { + LOG_VERBOSE(1, "Detected misconfigured LM Compatibility level"); + return false; + } + } + LOG_VERBOSE(1, "LM Compatibility Level is correctly set"); + return true; + } + + bool MitigateV1153::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + return CheckLMCompatibilityLevel(false); + } + + bool MitigateV1153::EnforceMitigation(SecurityLevel level) { + LOG_INFO("Enforcing Mitigation for " << name); + + return CheckLMCompatibilityLevel(level >= SecurityLevel::Medium); + } + + bool MitigateV1153::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp index b7ea44fd..188cda41 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV3338.cpp @@ -22,30 +22,28 @@ namespace Mitigations { bool MitigateV3338::MitigationIsEnforced(SecurityLevel level) { LOG_INFO("Checking for presence of " << name); - - auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\LanManServer\\Parameters", L"NullSessionPipes" }; - if(key.ValueExists()){ - LOG_VERBOSE(2, L"Located value for " + key.GetName()); - auto values = key.Get(); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\LanManServer\\Parameters" }; + if(key.ValueExists(L"NullSessionPipes")){ + auto values = *key.GetValue>(L"NullSessionPipes"); + auto vGoodValues = std::vector{}; for(auto value : values){ - if(value.size() != 0){ // TODO: Add exceptions on domain controllers (netlogon, samr, lsarpc) + if(value.size() == 0){ // TODO: Add exceptions on domain controllers (netlogon, samr, lsarpc) LOG_VERBOSE(1, "Found a non-zero number of named pipes accessible anonymously."); return false; } } - } + } LOG_VERBOSE(1, "Found no named pipes accessible anonymously."); - return true; } bool MitigateV3338::EnforceMitigation(SecurityLevel level) { LOG_INFO("Enforcing Mitigation for " << name); - - auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\LanManServer\\Parameters", L"NullSessionPipes" }; - if(key.ValueExists()){ - LOG_VERBOSE(2, L"Located value for " + key.GetName()); - auto values = key.Get(); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\LanManServer\\Parameters" }; + if(key.ValueExists(L"NullSessionPipes")){ + auto values = *key.GetValue>(L"NullSessionPipes"); /* TODO: Add prompt to ask if this is a domain controller */ //auto vGoodValues = std::vector{L"NETLOGON", L"SAMR", L"LSARPC"}; auto vGoodValues = std::vector{}; @@ -55,9 +53,8 @@ namespace Mitigations { } } LOG_VERBOSE(2, L"Setting accessible named pipes to specified good values."); - return key.Set(vGoodValues); + return key.SetValue>(L"NullSessionPipes", vGoodValues); } - return true; } diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63597.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63597.cpp new file mode 100644 index 00000000..5315e22f --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63597.cpp @@ -0,0 +1,57 @@ +#include "mitigation/mitigations/MitigateV63597.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateV63597::MitigateV63597(MitigationRegister& record) : + Mitigation( + record, + L"V-63597 - Apply UAC privileged token filtering for network logons", + L"With User Account Control enabled, filtering the privileged token for built-in " + "administrator accounts will prevent the elevated privileges of these accounts " + "from being used over the network.", + L"uac", + SoftwareAffected::OperatingSystem, + MitigationSeverity::Medium + ) {} + + bool MitigateV63597::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"LocalAccountTokenFilterPolicy"; + + if (!key.ValueExists(value)) { + LOG_VERBOSE(1, L"Value for " << value << L" does not exist."); + return false; + } + + if(key.GetValue(value) != 0){ + LOG_VERBOSE(1, L"Value for " << value << L" is not set to 0."); + return false; + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateV63597::EnforceMitigation(SecurityLevel level) { + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"LocalAccountTokenFilterPolicy"; + DWORD data = 0; + + LOG_VERBOSE(1, L"Attempting to set " << value << L" to 0."); + return key.SetValue(value, data); + } + + bool MitigateV63597::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63817.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63817.cpp new file mode 100644 index 00000000..68140cab --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63817.cpp @@ -0,0 +1,57 @@ +#include "mitigation/mitigations/MitigateV63817.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateV63817::MitigateV63817(MitigationRegister& record) : + Mitigation( + record, + L"V-63817 - User Account Control approval mode for the built-in Administrator must be enabled", + L"User Account Control (UAC) is a security mechanism for limiting the elevation of privileges, " + "including administrative accounts, unless authorized. This setting configures the built-in " + "Administrator account so that it runs in Admin Approval Mode.", + L"uac", + SoftwareAffected::OperatingSystem, + MitigationSeverity::Medium + ) {} + + bool MitigateV63817::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"FilterAdministratorToken"; + + if (!key.ValueExists(value)) { + LOG_VERBOSE(1, L"Value for " << value << L" does not exist."); + return false; + } + + if(key.GetValue(value) != 1){ + LOG_VERBOSE(1, L"Value for " << value << L" is not set to 1."); + return false; + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateV63817::EnforceMitigation(SecurityLevel level) { + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"FilterAdministratorToken"; + DWORD data = 1; + + LOG_VERBOSE(1, L"Attempting to set " << value << L" to 1."); + return key.SetValue(value, data); + } + + bool MitigateV63817::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63825.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63825.cpp new file mode 100644 index 00000000..0570dfb3 --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63825.cpp @@ -0,0 +1,57 @@ +#include "mitigation/mitigations/MitigateV63825.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateV63825::MitigateV63825(MitigationRegister& record) : + Mitigation( + record, + L"V-63825 - User Account Control must be configured to detect application installations and prompt for elevation", + L"User Account Control (UAC) is a security mechanism for limiting the elevation of privileges, including " + "administrative accounts, unless authorized. This setting requires Windows to respond to application installation " + "requests by prompting for credentials.", + L"uac", + SoftwareAffected::OperatingSystem, + MitigationSeverity::Medium + ) {} + + bool MitigateV63825::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"EnableInstallerDetection"; + + if (!key.ValueExists(value)) { + LOG_VERBOSE(1, L"Value for " << value << L" does not exist."); + return false; + } + + if(key.GetValue(value) != 1){ + LOG_VERBOSE(1, L"Value for " << value << L" is not set to 1."); + return false; + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateV63825::EnforceMitigation(SecurityLevel level) { + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"EnableInstallerDetection"; + DWORD data = 1; + + LOG_VERBOSE(1, L"Attempting to set " << value << L" to 1."); + return key.SetValue(value, data); + } + + bool MitigateV63825::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63829.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63829.cpp new file mode 100644 index 00000000..03beccf6 --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV63829.cpp @@ -0,0 +1,56 @@ +#include "mitigation/mitigations/MitigateV63829.h" +#include "hunt/RegistryHunt.h" + +#include "util/configurations/Registry.h" +#include "util/log/Log.h" + +#include + +using namespace Registry; + +namespace Mitigations { + + MitigateV63829::MitigateV63829(MitigationRegister& record) : + Mitigation( + record, + L"V-63829 - User Account Control must run all administrators in Admin Approval Mode, enabling UAC", + L"User Account Control (UAC) is a security mechanism for limiting the elevation of privileges, " + "including administrative accounts, unless authorized. This setting enables UAC.", + L"uac", + SoftwareAffected::OperatingSystem, + MitigationSeverity::Medium + ) {} + + bool MitigateV63829::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"EnableLUA"; + + if (!key.ValueExists(value)) { + LOG_VERBOSE(1, L"Value for " << value << L" does not exist."); + return false; + } + + if(key.GetValue(value) != 1){ + LOG_VERBOSE(1, L"Value for " << value << L" is not set to 1."); + return false; + } + + LOG_VERBOSE(1, L"Mitigation " << name << L" is enforced."); + return true; + } + + bool MitigateV63829::EnforceMitigation(SecurityLevel level) { + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System" }; + std::wstring value = L"EnableLUA"; + DWORD data = 1; + + LOG_VERBOSE(1, L"Attempting to set " << value << L" to 1."); + return key.SetValue(value, data); + } + + bool MitigateV63829::MitigationApplies(){ + return true; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp index eb28ab91..11f3387d 100644 --- a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV72753.cpp @@ -1,5 +1,5 @@ #include "mitigation/mitigations/MitigateV72753.h" -#include "hunt/RegistryHunt.hpp" +#include "hunt/RegistryHunt.h" #include "util/configurations/Registry.h" #include "util/log/Log.h" @@ -24,19 +24,20 @@ namespace Mitigations { bool MitigateV72753::MitigationIsEnforced(SecurityLevel level) { LOG_INFO("Checking for presence of " << name); - auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest", L"UseLogonCredential" }; + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest" }; + std::wstring value = L"UseLogonCredential"; - if(IsWindows8Point1OrGreater()){ - if(!key.ValueExists()){ + if(IsWindowsVersionOrGreater(6, 2, 0)){ // Win 8.1+ + if(!key.ValueExists(value)){ return true; } - } else if(!key.ValueExists()){ + } else if(!key.ValueExists(value)){ return false; } - if(key.Get() == 1){ + if(key.GetValue(value) == 1){ if(level == SecurityLevel::Low){ - LOG_INFO("[V-72753 - WDigest Authentication must be disabled] Mitigation is not being enforced due to low security level."); + LOG_INFO(L"[" + name + L"] Mitigation is not being enforced due to low security level."); return true; } return false; @@ -45,16 +46,18 @@ namespace Mitigations { } bool MitigateV72753::EnforceMitigation(SecurityLevel level) { - auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest", L"UseLogonCredential" }; - if(!IsWindows8Point1OrGreater() && !key.ValueExists()){ - DWORD value = 0; - return key.Create(&value, 4, REG_DWORD); + auto key = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Wdigest" }; + std::wstring value = L"UseLogonCredential"; + DWORD data = 0; + + if (!IsWindowsVersionOrGreater(6, 2, 0) || key.ValueExists(value)) { + return key.SetValue(value, data); } - return key.Set(0); + return true; } bool MitigateV72753::MitigationApplies(){ - return IsWindows7OrGreater(); + return IsWindowsVersionOrGreater(6, 0, 0); // Win 7+ } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73519.cpp b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73519.cpp new file mode 100644 index 00000000..5b6b180a --- /dev/null +++ b/BLUESPAWN-client/src/mitigation/mitigations/MitigateV73519.cpp @@ -0,0 +1,61 @@ +#include "mitigation/mitigations/MitigateV73519.h" + +#include "hunt/RegistryHunt.h" +#include "util/configurations/Registry.h" +#include "util/log/Log.h" +#include + +using namespace Registry; + +namespace Mitigations{ + + MitigateV73519::MitigateV73519(MitigationRegister& record) : + Mitigation( + record, + L"V-73519 - The Server Message Block (SMB) v1 protocol must be disabled on the SMB server", + L"SMBv1 is a legacy protocol that uses the MD5 algorithm as part of SMB. MD5 is known to be vulnerable to a " + L"number of attacks such as collision and preimage attacks as well as not being FIPS compliant. Disabling SMBv1 " + L"support may prevent access to file or print sharing resources with systems or devices that only support SMBv1. " + "File shares and print services hosted on Windows Server 2003 are an example, however Windows Server 2003 is no " + "longer a supported operating system. Some older network attached devices may only support SMBv1.", + L"lanmanserver", + SoftwareAffected::ExposedService, + MitigationSeverity::Medium + ) {} + + bool CheckSMBv1(bool enforce){ + auto lanmankey = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Parameters" }; + auto lanmanchecks = std::vector{ + { L"SMB1", RegistryType::REG_DWORD_T, 0, true, CheckDwordEqual } + }; + for(auto check : lanmanchecks){ + if(CheckValues(lanmankey, { check }).size() != 0){ + LOG_VERBOSE(1, L"SMBv1 has been detected."); + if(enforce){ + LOG_VERBOSE(1, L"Disabling SMBv1... Restart required"); + return lanmankey.SetValue(L"SMB1", 0); + } + return false; + } + } + LOG_VERBOSE(1, "SMBv1 not detected"); + return true; + } + + bool MitigateV73519::MitigationIsEnforced(SecurityLevel level) { + LOG_INFO("Checking for presence of " << name); + + return CheckSMBv1(false); + } + + bool MitigateV73519::EnforceMitigation(SecurityLevel level) { + LOG_INFO("Enforcing Mitigation for " << name); + + return CheckSMBv1(level >= SecurityLevel::Medium); + } + + bool MitigateV73519::MitigationApplies(){ + auto lanmankey = RegistryKey{ HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\LanmanServer" }; + return lanmankey.Exists() && lanmankey.ValueExists(L"Start") && *lanmankey.GetValue(L"Start") != 4; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/user/BLUESPAWN.cpp b/BLUESPAWN-client/src/user/BLUESPAWN.cpp index 05dc613c..b5362d0a 100644 --- a/BLUESPAWN-client/src/user/BLUESPAWN.cpp +++ b/BLUESPAWN-client/src/user/BLUESPAWN.cpp @@ -5,11 +5,113 @@ #include "common/DynamicLinker.h" #include "common/StringUtils.h" #include "util/eventlogs/EventLogs.h" +#include "hunt/reaction/SuspendProcess.h" +#include "hunt/reaction/RemoveValue.h" + +#include "hunt/hunts/HuntT1004.h" +#include "hunt/hunts/HuntT1037.h" +#include "hunt/hunts/HuntT1050.h" +#include "hunt/hunts/HuntT1055.h" +#include "hunt/hunts/HuntT1060.h" +#include "hunt/hunts/HuntT1100.h" +#include "hunt/hunts/HuntT1101.h" +#include "hunt/hunts/HuntT1103.h" +#include "hunt/hunts/HuntT1131.h" +#include "hunt/hunts/HuntT1138.h" +#include "hunt/hunts/HuntT1182.h" +#include "hunt/hunts/HuntT1183.h" + +#include "monitor/ETW_Wrapper.h" + +#include "mitigation/mitigations/MitigateM1042-LLMNR.h" +#include "mitigation/mitigations/MitigateM1042-NBT.h" +#include "mitigation/mitigations/MitigateM1042-WSH.h" +#include "mitigation/mitigations/MitigateV1093.h" +#include "mitigation/mitigations/MitigateV1153.h" +#include "mitigation/mitigations/MitigateV3338.h" +#include "mitigation/mitigations/MitigateV63597.h" +#include "mitigation/mitigations/MitigateV63817.h" +#include "mitigation/mitigations/MitigateV63825.h" +#include "mitigation/mitigations/MitigateV63829.h" +#include "mitigation/mitigations/MitigateV72753.h" +#include "mitigation/mitigations/MitigateV73519.h" #include -int main(int argc, char* argv[]) -{ +IOBase& Bluespawn::io = CLI(); +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()); + + using namespace Mitigations; + + MitigateM1042LLMNR* m1042llmnr = new MitigateM1042LLMNR(mitigationRecord); + MitigateM1042NBT* m1042nbt = new MitigateM1042NBT(mitigationRecord); + MitigateM1042WSH* m1042wsh = new MitigateM1042WSH(mitigationRecord); + MitigateV1093* v1093 = new MitigateV1093(mitigationRecord); + MitigateV1153* v1153 = new MitigateV1153(mitigationRecord); + MitigateV3338* v3338 = new MitigateV3338(mitigationRecord); + MitigateV63597* v63597 = new MitigateV63597(mitigationRecord); + MitigateV63817* v63817 = new MitigateV63817(mitigationRecord); + MitigateV63825* v63825 = new MitigateV63825(mitigationRecord); + MitigateV63829* v63829 = new MitigateV63829(mitigationRecord); + MitigateV72753* v72753 = new MitigateV72753(mitigationRecord); + MitigateV73519* v73519 = new MitigateV73519(mitigationRecord); +} + +void Bluespawn::dispatch_hunt(Aggressiveness aHuntLevel) { + Bluespawn::io.InformUser(L"Starting a Hunt"); + DWORD tactics = UINT_MAX; + DWORD dataSources = UINT_MAX; + DWORD affectedThings = UINT_MAX; + Scope scope{}; + Reaction logreact = Reactions::LogReaction(); + Reaction suspendreact = Reactions::SuspendProcessReaction(io); + Reaction logsuspend = logreact.Combine(suspendreact); + Reaction removereact = Reactions::RemoveValueReaction(io); + auto reaction = logsuspend.Combine(removereact); + huntRecord.RunHunts(tactics, dataSources, affectedThings, scope, aHuntLevel, reaction); +} + +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{}; + Reaction reaction = Reactions::LogReaction(); + + Bluespawn::io.InformUser(L"Monitoring the system"); + huntRecord.SetupMonitoring(tactics, dataSources, affectedThings, scope, aHuntLevel, reaction); + + while (true) {} +} + +int main(int argc, char* argv[]){ Linker::LinkFunctions(); Log::DebugSink DebugOutput{}; @@ -17,20 +119,15 @@ int main(int argc, char* argv[]) Log::AddSink(DebugOutput); Log::AddHuntSink(ConsoleOutput); - IOBase& io = CLI(); + Bluespawn bluespawn; print_banner(); - /* - // Create and initialize the ETW wrapper - ETW_Wrapper wrapper; - wrapper.init(); - */ - 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")) @@ -39,7 +136,7 @@ int main(int argc, char* argv[]) ; options.add_options("hunt") - ("l,level", "Aggressiveness of Hunt. Either Cursory, Moderate, Careful, or Aggressive", + ("l,level", "Aggressiveness of Hunt. Either Cursory, Normal, or Intensive", cxxopts::value()) ; @@ -71,11 +168,52 @@ int main(int argc, char* argv[]) if (result.count("help")) { print_help(result, options); } - else if (result.count("hunt")) { - dispatch_hunt(result, options); + else if (result.count("hunt") || result.count("monitor")) { + std::string flag("level"); + if (result.count("monitor")) + flag = "monitor"; + + // Parse the hunt level + std::string sHuntLevelFlag = "Normal"; + Aggressiveness aHuntLevel; + try { + sHuntLevelFlag = result[flag].as < std::string >(); + } + catch (int e) {} + + if (sHuntLevelFlag == "Cursory") { + aHuntLevel = Aggressiveness::Cursory; + } + else if (sHuntLevelFlag == "Normal") { + aHuntLevel = Aggressiveness::Normal; + } + else if (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; + } + + if (result.count("hunt")) + bluespawn.dispatch_hunt(aHuntLevel); + else if (result.count("monitor")) + bluespawn.monitor_system(aHuntLevel); + } else if (result.count("mitigate")) { - dispatch_mitigations_analysis(result, options, io); + 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"); @@ -102,70 +240,3 @@ void print_help(cxxopts::ParseResult result, cxxopts::Options options) { std::cerr << ("Unknown help category") << std::endl; } } - -void dispatch_hunt(cxxopts::ParseResult result, cxxopts::Options options) { - std::string sHuntLevelFlag = "Moderate"; - Aggressiveness aHuntLevel; - if(result.count("level")) { - try { - sHuntLevelFlag = result["level"].as < std::string >(); - } catch(int e) { - std::cerr << "Error " << e << " - Unknown hunt level. Please specify either Cursory, Moderate, Careful, or Aggressive" << std::endl; - } - } - if(sHuntLevelFlag == "Cursory") { - aHuntLevel = Aggressiveness::Cursory; - } else if(sHuntLevelFlag == "Moderate") { - aHuntLevel = Aggressiveness::Moderate; - } else if(sHuntLevelFlag == "Careful") { - aHuntLevel = Aggressiveness::Careful; - } else if (sHuntLevelFlag == "Aggressive") { - aHuntLevel = Aggressiveness::Aggressive; - } else { - std::cerr << "Error " << sHuntLevelFlag << " - Unknown hunt level. Please specify either Cursory, Moderate, Careful, or Aggressive" << std::endl; - std::cerr << "Will default to Moderate for this run." << std::endl; - aHuntLevel = Aggressiveness::Moderate; - } - - HuntRegister record{}; - Hunts::HuntT1004 t1004(record); - Hunts::HuntT1037 t1037(record); - Hunts::HuntT1050 t1050(record); - Hunts::HuntT1060 t1060(record); - Hunts::HuntT1100 t1100(record); - Hunts::HuntT1101 t1101(record); - Hunts::HuntT1103 t1103(record); - Hunts::HuntT1131 t1131(record); - Hunts::HuntT1138 t1138(record); - Hunts::HuntT1182 t1182(record); - - DWORD tactics = UINT_MAX; - DWORD dataSources = UINT_MAX; - DWORD affectedThings = UINT_MAX; - Scope scope{}; - Reaction reaction = Reactions::LogReaction(); - record.RunHunts(tactics, dataSources, affectedThings, scope, aHuntLevel, reaction); -} - - -void dispatch_mitigations_analysis(cxxopts::ParseResult result, cxxopts::Options options, IOBase& io) { - bool bForceEnforce = false; - if (result.count("force")) { - bForceEnforce = true; - } - - MitigationRegister record{io}; - - Mitigations::MitigateV1093 v1093(record); - Mitigations::MitigateV3338 v3338(record); - Mitigations::MitigateV72753 v72753(record); - - if (result["mitigate"].as() == "e" || result["mitigate"].as() == "enforce") { - io.InformUser(L"Enforcing Mitigations"); - record.EnforceMitigations(SecurityLevel::High, bForceEnforce); - } - else { - io.InformUser(L"Auditing Mitigations"); - record.AuditMitigations(SecurityLevel::High); - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/user/CLI.cpp b/BLUESPAWN-client/src/user/CLI.cpp index 934c821b..e5e2b0bc 100644 --- a/BLUESPAWN-client/src/user/CLI.cpp +++ b/BLUESPAWN-client/src/user/CLI.cpp @@ -17,7 +17,7 @@ CLI::CLI() : input{ input }, output{ output } { - instances.emplace(std::pair(input, output), *this); + //instances.emplace(std::pair(input, output), *this); } void SetConsoleColor(MessageColor color) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); diff --git a/BLUESPAWN-client/src/util/configurations/CollectInfo.cpp b/BLUESPAWN-client/src/util/configurations/CollectInfo.cpp index 30d7e5dd..32e1040f 100644 --- a/BLUESPAWN-client/src/util/configurations/CollectInfo.cpp +++ b/BLUESPAWN-client/src/util/configurations/CollectInfo.cpp @@ -17,8 +17,7 @@ void OutputComputerInformation() { } std::wstring GetOSVersion() { - auto key = Registry::RegistryKey(L"HKLM\\SOFTWARE\\Microsoft\\WIndows NT\\CurrentVersion", L"ProductName"); - return key.Get(); + return *Registry::RegistryKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\WIndows NT\\CurrentVersion").GetValue(L"ProductName"); } std::wstring GetComputerDNSName() { diff --git a/BLUESPAWN-client/src/util/configurations/Registry.cpp b/BLUESPAWN-client/src/util/configurations/Registry.cpp deleted file mode 100644 index 758d7069..00000000 --- a/BLUESPAWN-client/src/util/configurations/Registry.cpp +++ /dev/null @@ -1,258 +0,0 @@ -#include - -#include -#include - -#include "util/configurations/Registry.h" -#include "util/log/Log.h" - -namespace Registry { - std::map vHiveNames{ - {L"HKLM", HKEY_LOCAL_MACHINE}, - {L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, - - {L"HKCR", HKEY_CLASSES_ROOT}, - {L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT}, - - {L"HKCU", HKEY_CURRENT_USER}, - {L"HKEY_CURRENT_USER", HKEY_CURRENT_USER}, - - {L"HKU", HKEY_USERS}, - {L"HKEY_USERS", HKEY_USERS}, - - {L"HKCC", HKEY_CURRENT_CONFIG}, - {L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, - }; - - std::map vHives{ - {HKEY_LOCAL_MACHINE, L"HKEY_LOCAL_MACHINE"}, - {HKEY_CLASSES_ROOT, L"HKEY_CLASSES_ROOT"}, - {HKEY_CURRENT_USER, L"HKEY_CURRENT_USER"}, - {HKEY_USERS, L"HKEY_USERS"}, - {HKEY_CURRENT_CONFIG, L"HKEY_CURRENT_CONFIG"}, - }; - - std::map _globalOpenKeys{}; - - HKEY RemoveHive(std::wstring* path){ - SIZE_T fslashIdx = path->find(L"/"); - SIZE_T bslashIdx = path->find(L"\\"); - if(fslashIdx == (SIZE_T) -1 && bslashIdx == (SIZE_T) -1){ - LOG_ERROR("Registry hive not found!"); - return nullptr; - } - - std::wstring sHiveName = path->substr(0, fslashIdx > bslashIdx ? bslashIdx : fslashIdx); - transform(sHiveName.begin(), sHiveName.end(), sHiveName.begin(), ::toupper); - if(vHiveNames.find(sHiveName) == vHiveNames.end()){ - LOG_ERROR("Unknown registry hive " << sHiveName); - return nullptr; - } - - HKEY hive = vHiveNames[sHiveName]; - *path = path->substr((fslashIdx > bslashIdx ? bslashIdx : fslashIdx) + 1); - - return hive; - } - - RegistryKey::RegistryKey(HKEY hive, std::wstring path, std::wstring name, bool Create) : hive{ hive }, name{ name }, path{ path } { - LSTATUS status{}; - if(Create){ - if(status = RegCreateKeyEx(hive, path.c_str(), 0, nullptr, 0, KEY_READ, nullptr, &key, nullptr)){ - SetLastError(status); - LOG_ERROR("Error " << status << " occured when attempting to create registry key " << GetName()); - - // Don't do any more initialization if the key couldn't be created. - return; - } - } - - else { - status = RegOpenKeyEx(hive, path.c_str(), 0, KEY_READ, &key); - if(status != ERROR_SUCCESS){ - LOG_VERBOSE(1, "Error " << status << " occured when attempting to read registry key " << GetName() << ". Probably means key was not found"); - SetLastError(status); - return; - } - } - - if(_globalOpenKeys.find(key) != _globalOpenKeys.end()){ - _globalOpenKeys[key] = 1; - } else { - _globalOpenKeys[key]++; - } - bKeyExists = true; - - LOG_VERBOSE(2, "Searching for value " << name << " under " << vHives[hive] << "\\" << path); - - status = RegQueryValueEx(key, name.length() == 0 ? nullptr : name.c_str(), 0, &dwDataType, nullptr, &dwDataSize); - if(status != ERROR_SUCCESS && status != ERROR_MORE_DATA){ - LOG_VERBOSE(1, "Unable to query value " << GetName() << ". Probably means value was not found"); - SetLastError(status); - - return; - } - - bValueExists = true; - - LOG_VERBOSE(3, "Value is of type " << dwDataType << " and size " << dwDataSize); - lpbValue = new BYTE[dwDataSize]; - status = RegQueryValueEx(key, name.length() == 0 ? nullptr : name.c_str(), 0, &dwDataType, lpbValue, &dwDataSize); - if(status != ERROR_SUCCESS){ - LOG_ERROR("Unable to read value " << GetName()); - SetLastError(status); - } - - bKeyExists = true; - - LOG_VERBOSE(1, "Created new registry key object - " << GetName()); - } - - RegistryKey::RegistryKey(std::wstring path, std::wstring name) : RegistryKey(RemoveHive(&path), path, name){}; - - RegistryKey::~RegistryKey() { - if(!--_globalOpenKeys[key]){ - RegCloseKey(key); - } - } - - std::wstring RegistryKey::GetName(){ - return name; - } - - std::wstring RegistryKey::GetPath(){ - return vHives[hive] + L"\\" + path; - } - - bool RegistryKey::Set(LPVOID value, DWORD dwSize, DWORD dwType) { - if(dwType == -1) dwType = dwDataType; - - if(!KeyExists()){ - SetLastError(SPAPI_E_KEY_DOES_NOT_EXIST); - - LOG_ERROR("Attempted to set a registry value belonging to a key that does not exist - " << GetName()); - } - HKEY temp_key{}; - LOG_VERBOSE(3, "Opening a duplicate key for with write access to set " << GetName()); - LSTATUS status = RegOpenKeyEx(key, nullptr, 0, KEY_WRITE, &temp_key); - if(status != ERROR_SUCCESS){ - LOG_ERROR("Error " << status << " occurred when reopen key to set " << GetName()); - SetLastError(status); - - return false; - } - - LOG_VERBOSE(2, "Setting registry key " << GetName()); - status = RegSetValueEx(temp_key, name.length() == 0 ? nullptr : name.c_str(), 0, dwType, reinterpret_cast(value), dwSize); - RegCloseKey(temp_key); - if(status != ERROR_SUCCESS){ - LOG_ERROR("Error " << status << " occurred when attempting to set key " << GetName()); - SetLastError(status); - - return false; - } - - lpbValue = reinterpret_cast(value); - - return true; - }; - - bool RegistryKey::Create(LPVOID value, DWORD dwSize, DWORD dwType){ - if(!KeyExists()){ - LSTATUS status = RegCreateKeyEx(hive, path.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, nullptr); - - if(status != ERROR_SUCCESS){ - LOG_ERROR("Error " << status << " occurred when attempting to create key " << GetName()); - SetLastError(status); - - return false; - } - - if(_globalOpenKeys.find(key) != _globalOpenKeys.end()){ - _globalOpenKeys[key] = 1; - } else { - _globalOpenKeys[key]++; - } - bKeyExists = true; - - LOG_VERBOSE(2, "Successfully created registry key " << vHives[hive] << "\\" << path); - } - - return Set(value, dwSize, dwType); - } - - LPVOID RegistryKey::GetRaw(){ - if(!ValueExists()){ - lpbValue = new BYTE[2]{}; - } - return lpbValue; - } - - std::wstring RegistryKey::ToString(){ - return GetName(); - } - - inline bool RegistryKey::KeyExists() { return bKeyExists; } - bool RegistryKey::ValueExists() { return bValueExists; } - - std::vector RegistryKey::KeyValues(){ - if(!KeyExists()){ - LOG_VERBOSE(1, "Attempting to enumerate values of nonexistent key " << GetName()); - return {}; - } - - DWORD dwValueCount{}; - DWORD dwLongestValue{}; - LSTATUS status = RegQueryInfoKey(key, nullptr, nullptr, 0, nullptr, nullptr, nullptr, &dwValueCount, &dwLongestValue, nullptr, nullptr, nullptr); - - LOG_VERBOSE(1, dwValueCount << " subkeys detected under " << vHives[hive] << "\\" << path); - - auto vSubKeys = std::vector(); - - if(status == ERROR_SUCCESS && dwValueCount) { - for(unsigned i = 0; i < dwValueCount; i++) { - LPWSTR lpwName = new WCHAR[dwLongestValue]; - DWORD length = dwLongestValue * 2; - status = RegEnumValueW(key, i, lpwName, &length, nullptr, nullptr, nullptr, nullptr); - - if(status == ERROR_SUCCESS) { - vSubKeys.push_back(RegistryKey{ hive, path, std::wstring{lpwName} }); - } else { - LOG_WARNING("Error " << status << " when enumerating the next value!"); - } - } - } - - return vSubKeys; - } - - std::vector RegistryKey::Subkeys(){ - if(!KeyExists()){ - LOG_VERBOSE(1, "Attempting to enumerate values of nonexistent key " << GetName()); - return {}; - } - - DWORD dwSubkeyCount{}; - DWORD dwLongestSubkey{}; - LSTATUS status = RegQueryInfoKey(key, nullptr, nullptr, 0, &dwSubkeyCount, &dwLongestSubkey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); - - LOG_VERBOSE(1, dwSubkeyCount << " subkeys detected under " << vHives[hive] << "\\" << path); - - std::vector vSubKeys{}; - - if(status == ERROR_SUCCESS && dwSubkeyCount) { - for(unsigned i = 0; i < dwSubkeyCount; i++) { - LPWSTR lpwName = new WCHAR[dwLongestSubkey]; - DWORD length = dwLongestSubkey * 2; - status = RegEnumKey(key, i, lpwName, length); - - if(status == ERROR_SUCCESS) { - vSubKeys.push_back({ hive, path + L"\\" + lpwName, L"" }); - } else { - LOG_WARNING("Error " << status << " when enumerating the next value!"); - } - } - } - return vSubKeys; - } -} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/configurations/RegistryKey.cpp b/BLUESPAWN-client/src/util/configurations/RegistryKey.cpp new file mode 100644 index 00000000..26736cff --- /dev/null +++ b/BLUESPAWN-client/src/util/configurations/RegistryKey.cpp @@ -0,0 +1,491 @@ +#include + +#include +#include + +#include "util/configurations/Registry.h" +#include "common/StringUtils.h" + +LINK_FUNCTION(NtQueryKey, ntdll.dll); + +namespace Registry { + std::map vHiveNames{ + {L"HKLM", HKEY_LOCAL_MACHINE}, + {L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, + + {L"HKCR", HKEY_CLASSES_ROOT}, + {L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT}, + + {L"HKCU", HKEY_CURRENT_USER}, + {L"HKEY_CURRENT_USER", HKEY_CURRENT_USER}, + + {L"HKU", HKEY_USERS}, + {L"HKEY_USERS", HKEY_USERS}, + + {L"HKCC", HKEY_CURRENT_CONFIG}, + {L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, + }; + + std::map vHives{ + {HKEY_LOCAL_MACHINE, L"HKEY_LOCAL_MACHINE"}, + {HKEY_CLASSES_ROOT, L"HKEY_CLASSES_ROOT"}, + {HKEY_CURRENT_USER, L"HKEY_CURRENT_USER"}, + {HKEY_USERS, L"HKEY_USERS"}, + {HKEY_CURRENT_CONFIG, L"HKEY_CURRENT_CONFIG"}, + }; + + std::map RegistryKey::_ReferenceCounts = {}; + + RegistryKey::RegistryKey(const RegistryKey& key) noexcept { + this->bKeyExists = key.bKeyExists; + this->hkBackingKey = key.hkBackingKey; + + if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ + _ReferenceCounts[hkBackingKey] = 1; + } else { + _ReferenceCounts[hkBackingKey]++; + } + } + + RegistryKey& RegistryKey::operator=(const RegistryKey& key) noexcept { + this->bKeyExists = key.bKeyExists; + this->hkBackingKey = key.hkBackingKey; + + if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ + _ReferenceCounts[hkBackingKey] = 1; + } else { + _ReferenceCounts[hkBackingKey]++; + } + + return *this; + } + + RegistryKey::RegistryKey(RegistryKey&& key) noexcept { + this->bKeyExists = key.bKeyExists; + this->hkBackingKey = key.hkBackingKey; + + key.bKeyExists = false; + key.path = {}; + key.hkHive = nullptr; + key.hkBackingKey = nullptr; + } + + RegistryKey& RegistryKey::operator=(RegistryKey&& key) noexcept { + this->bKeyExists = key.bKeyExists; + this->hkBackingKey = key.hkBackingKey; + + key.bKeyExists = false; + key.path = {}; + key.hkHive = nullptr; + key.hkBackingKey = nullptr; + + return *this; + } + + RegistryKey::RegistryKey(HKEY key){ + this->hkBackingKey = key; + + this->bKeyExists = true; + + if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ + _ReferenceCounts[hkBackingKey] = 1; + } else { + _ReferenceCounts[hkBackingKey]++; + } + } + + RegistryKey::RegistryKey(HKEY hive, std::wstring path){ + LSTATUS status = RegOpenKeyEx(hive, path.c_str(), 0, KEY_ALL_ACCESS, &hkBackingKey); + if(status == ERROR_ACCESS_DENIED){ + status = RegOpenKeyEx(hive, path.c_str(), 0, KEY_READ, &hkBackingKey); + } + + if(status != ERROR_SUCCESS){ + bKeyExists = false; + + this->hkHive = hive; + this->path = path; + } else { + bKeyExists = true; + + if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ + _ReferenceCounts[hkBackingKey] = 1; + } else { + _ReferenceCounts[hkBackingKey]++; + } + } + } + + RegistryKey::RegistryKey(std::wstring name){ + name = ToUpperCase(name); + + SIZE_T slash = name.find_first_of(L"/\\"); + + std::wstring HiveName = slash == std::wstring::npos ? name : name.substr(0, slash); + + if(vHiveNames.find(HiveName) == vHiveNames.end()){ + this->bKeyExists = false; + this->hkBackingKey = nullptr; + } + + else { + hkHive = vHiveNames[HiveName]; + + if(slash == name.length()){ + this->bKeyExists = true; + this->hkBackingKey = hkHive; + } + + else { + path = name.substr(slash + 1, name.length()); + + LSTATUS status = RegOpenKeyEx(hkHive, path.c_str(), 0, KEY_ALL_ACCESS, &hkBackingKey); + if(status == ERROR_ACCESS_DENIED){ + status = RegOpenKeyEx(hkHive, path.c_str(), 0, KEY_READ, &hkBackingKey); + } + + if(status == ERROR_SUCCESS){ + if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ + _ReferenceCounts[hkBackingKey] = 1; + } else { + _ReferenceCounts[hkBackingKey]++; + } + + bKeyExists = true; + } else { + bKeyExists = false; + } + } + } + } + + RegistryKey::~RegistryKey(){ + if(_ReferenceCounts.find(hkBackingKey) != _ReferenceCounts.end()){ + if(!--_ReferenceCounts[hkBackingKey]){ + _ReferenceCounts.erase(hkBackingKey); + CloseHandle(hkBackingKey); + } + } + } + + bool RegistryKey::Exists() const { + return bKeyExists; + } + + bool RegistryKey::ValueExists(const std::wstring& wsValueName) const { + return ERROR_SUCCESS == RegQueryValueExW(hkBackingKey, wsValueName.c_str(), nullptr, nullptr, nullptr, nullptr); + } + + bool RegistryKey::Create(){ + if(Exists()){ + return true; + } + + if(!hkHive){ + SetLastError(ERROR_NOT_FOUND); + return false; + } + + LSTATUS status = RegCreateKeyEx(hkHive, path.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &hkBackingKey, nullptr); + if(status == ERROR_SUCCESS){ + bKeyExists = true; + + if(_ReferenceCounts.find(hkBackingKey) == _ReferenceCounts.end()){ + _ReferenceCounts[hkBackingKey] = 1; + } else { + _ReferenceCounts[hkBackingKey]++; + } + + return true; + } + + SetLastError(status); + + return false; + } + + AllocationWrapper RegistryKey::GetRawValue(const std::wstring& ValueName) const { + if(!Exists()){ + SetLastError(FILE_DOES_NOT_EXIST); + return { nullptr, 0 }; + } + + DWORD dwDataSize{}; + + LSTATUS status = RegQueryValueExW(hkBackingKey, ValueName.c_str(), 0, nullptr, nullptr, &dwDataSize); + if(status != ERROR_SUCCESS && status != ERROR_MORE_DATA){ + SetLastError(status); + return { nullptr, 0 }; + } + + auto lpbValue = new BYTE[dwDataSize]; + status = RegQueryValueExW(hkBackingKey, ValueName.c_str(), 0, nullptr, lpbValue, &dwDataSize); + if(status != ERROR_SUCCESS){ + SetLastError(status); + return { nullptr, 0 }; + } + + return { lpbValue, dwDataSize }; + } + + std::optional RegistryKey::GetValueType(const std::wstring& ValueName) const { + if(!Exists()){ + SetLastError(FILE_DOES_NOT_EXIST); + return std::nullopt; + } + + DWORD dwType{}; + + LSTATUS status = RegQueryValueExW(hkBackingKey, ValueName.c_str(), 0, &dwType, nullptr, nullptr); + if(status != ERROR_SUCCESS && status != ERROR_MORE_DATA){ + SetLastError(status); + return std::nullopt; + } + + if(dwType == REG_SZ){ + return RegistryType::REG_SZ_T; + } else if(dwType == REG_EXPAND_SZ){ + return RegistryType::REG_EXPAND_SZ_T; + } else if(dwType == REG_MULTI_SZ){ + return RegistryType::REG_MULTI_SZ_T; + } else if(dwType == REG_DWORD){ + return RegistryType::REG_DWORD_T; + } + + return RegistryType::REG_BINARY_T; + } + + template + std::optional RegistryKey::GetValue(const std::wstring& wsValueName) const { + if(ValueExists(wsValueName)){ + return GetRawValue(wsValueName).Dereference(); + } + return std::nullopt; + } + + template std::optional RegistryKey::GetValue(const std::wstring& wsValueName) const; + + template<> + std::optional RegistryKey::GetValue(const std::wstring& wsValueName) const { + if(ValueExists(wsValueName)){ + return GetRawValue(wsValueName).ReadWString(); + } + return std::nullopt; + } + + template<> + std::optional> RegistryKey::GetValue(const std::wstring& wsValueName) const { + if(ValueExists(wsValueName)){ + std::vector strings{}; + + LPCWSTR data = reinterpret_cast((LPVOID) GetRawValue(wsValueName)); + + while(*data){ + std::wstring str = data; + strings.emplace_back(data); + + data += str.length() + 1; + } + + return strings; + } + + return std::nullopt; + } + + bool RegistryKey::SetRawValue(const std::wstring& name, AllocationWrapper bytes, DWORD dwType) const { + if(!Exists()){ + return false; + } + + LSTATUS status = RegSetValueEx(hkBackingKey, name.c_str(), 0, dwType, reinterpret_cast((LPVOID) bytes), bytes.GetSize()); + if(status != ERROR_SUCCESS){ + SetLastError(status); + return false; + } + + return true; + } + + template + bool RegistryKey::SetValue(const std::wstring& name, T value, DWORD size, DWORD type) const { + return SetRawValue(name, { reinterpret_cast(value), size, AllocationWrapper::STACK_ALLOC }, type); + } + + template<> + bool RegistryKey::SetValue(const std::wstring& name, LPCWSTR value, DWORD size, DWORD type) const { + return RegistryKey::SetRawValue(name, { PBYTE(value), wcslen(value), AllocationWrapper::STACK_ALLOC }, type); + } + template bool RegistryKey::SetValue(const std::wstring& name, LPCWSTR value, DWORD size, DWORD type) const; + + template<> + bool RegistryKey::SetValue(const std::wstring& name, LPCSTR value, DWORD size, DWORD type) const { + return RegistryKey::SetRawValue(name, { PBYTE(value), strlen(value), AllocationWrapper::STACK_ALLOC }, type); + } + template bool RegistryKey::SetValue(const std::wstring& name, LPCSTR value, DWORD size, DWORD type) const; + + template<> + bool RegistryKey::SetValue(const std::wstring& name, DWORD value, DWORD size, DWORD type) const { + return SetRawValue(name, { reinterpret_cast(&value), 4, AllocationWrapper::STACK_ALLOC }, REG_DWORD); + } + template bool RegistryKey::SetValue(const std::wstring& name, DWORD value, DWORD size, DWORD type) const; + + template<> + bool RegistryKey::SetValue(const std::wstring& name, std::wstring value, DWORD size, DWORD type) const { + return SetValue(name, value.c_str(), static_cast((value.size() + 1) * 2), REG_SZ); + } + template bool RegistryKey::SetValue(const std::wstring& name, std::wstring value, DWORD size, DWORD type) const; + + template<> + bool RegistryKey::SetValue(const std::wstring& name, std::string value, DWORD size, DWORD type) const { + return SetValue(name, StringToWidestring(value)); + } + template bool RegistryKey::SetValue(const std::wstring& name, std::string value, DWORD size, DWORD type) const; + + template<> + bool RegistryKey::SetValue(const std::wstring& name, std::vector value, DWORD _size, DWORD type) const { + SIZE_T size = 1; + for(auto string : value){ + size += (string.length() + 1); + } + + auto data = new WCHAR[size]; + auto allocation = AllocationWrapper{ data, static_cast(size * sizeof(WCHAR)), AllocationWrapper::CPP_ARRAY_ALLOC }; + unsigned ptr = 0; + + for(auto string : value){ + LPCWSTR lpRawString = string.c_str(); + for(unsigned i = 0; i < string.length() + 1; i++){ + if(ptr < size){ + data[ptr++] = lpRawString[i]; + } + } + } + + if(ptr < size){ + data[ptr] = { static_cast(0) }; + } + + bool succeeded = SetRawValue(name, allocation, REG_MULTI_SZ); + + return succeeded; + } + + template bool RegistryKey::SetValue>(const std::wstring& name, std::vector value, + DWORD _size, DWORD type) const; + + std::vector RegistryKey::EnumerateSubkeys() const { + if(!Exists()){ + SetLastError(ERROR_NOT_FOUND); + return {}; + } + + DWORD dwSubkeyCount{}; + DWORD dwLongestSubkey{}; + LSTATUS status = RegQueryInfoKey(hkBackingKey, nullptr, nullptr, 0, &dwSubkeyCount, &dwLongestSubkey, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + + std::vector vSubKeys{}; + + if(status == ERROR_SUCCESS && dwSubkeyCount) { + for(unsigned i = 0; i < dwSubkeyCount; i++) { + DWORD length = dwLongestSubkey * 2; + LPWSTR lpwName = new WCHAR[length]; + status = RegEnumKey(hkBackingKey, i, lpwName, length); + + if(status == ERROR_SUCCESS) { + vSubKeys.push_back({ hkBackingKey, lpwName }); + } + + delete[] lpwName; + } + } else { + SetLastError(status); + } + + return vSubKeys; + } + + std::vector RegistryKey::EnumerateValues() const { + if(!Exists()){ + SetLastError(ERROR_NOT_FOUND); + return {}; + } + + DWORD dwValueCount{}; + DWORD dwLongestValue{}; + LSTATUS status = RegQueryInfoKey(hkBackingKey, nullptr, nullptr, 0, nullptr, nullptr, nullptr, &dwValueCount, + &dwLongestValue, nullptr, nullptr, nullptr); + + std::vector vSubKeys{}; + + if(status == ERROR_SUCCESS && dwValueCount) { + for(unsigned i = 0; i < dwValueCount; i++) { + DWORD length = dwLongestValue * 2; + LPWSTR lpwName = new WCHAR[length]; + status = RegEnumValueW(hkBackingKey, i, lpwName, &length, nullptr, nullptr, nullptr, nullptr); + + if(status == ERROR_SUCCESS) { + vSubKeys.push_back({ lpwName }); + } + + delete[] lpwName; + } + } else { + SetLastError(status); + } + + return vSubKeys; + } + + std::wstring RegistryKey::GetName() const { + if(!Exists()){ + SetLastError(ERROR_NOT_FOUND); + return {}; + } + + // Taken largely from https://stackoverflow.com/questions/937044/determine-path-to-registry-key-from-hkey-handle-in-c + std::wstring keyPath = {}; + if(hkBackingKey && Linker::NtQueryKey){ + DWORD size = 0; + DWORD result = 0; + result = Linker::NtQueryKey(hkBackingKey, 3, 0, 0, &size); + if(result == ((NTSTATUS) 0xC0000023L)){ + size = size + sizeof(wchar_t); + wchar_t* buffer = new wchar_t[size / sizeof(wchar_t)]; + if(buffer != NULL){ + result = Linker::NtQueryKey(hkBackingKey, 3, buffer, size, &size); + if(result == 0){ + buffer[size / sizeof(wchar_t)] = L'\0'; + keyPath = std::wstring(buffer + 2); + } + delete[] buffer; + auto location = keyPath.find(L"\\REGISTRY\\MACHINE"); + if(location != std::string::npos){ + keyPath.replace(location, 17, L"HKEY_LOCAL_MACHINE"); + } + location = keyPath.find(L"\\REGISTRY\\USER"); + if(location != std::string::npos){ + keyPath.replace(location, 14, L"HKEY_USERS"); + } + } + } + } + return keyPath; + } + + std::wstring RegistryKey::ToString() const { + return GetName(); + } + + + bool RegistryKey::operator<(const RegistryKey& key) const { + return hkBackingKey < key.hkBackingKey; + } + + bool RegistryKey::RemoveValue(const std::wstring& wsValueName) const { + auto status = RegDeleteValueW(hkBackingKey, wsValueName.c_str()); + SetLastError(status); + return status == ERROR_SUCCESS; + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/configurations/RegistryValue.cpp b/BLUESPAWN-client/src/util/configurations/RegistryValue.cpp new file mode 100644 index 00000000..1e667279 --- /dev/null +++ b/BLUESPAWN-client/src/util/configurations/RegistryValue.cpp @@ -0,0 +1,55 @@ +#include "util/configurations/RegistryValue.h" + +namespace Registry { + + RegistryValue::RegistryValue(std::wstring wValueName, RegistryType type, std::wstring wData) : + wValueName{ wValueName }, + type{ type }, + wData{ wData }{} + + RegistryValue::RegistryValue(std::wstring wValueName, RegistryType type, DWORD dwData) : + wValueName{ wValueName }, + type{ type }, + dwData{ dwData }{} + + RegistryValue::RegistryValue(std::wstring wValueName, RegistryType type, AllocationWrapper lpData) : + wValueName{ wValueName }, + type{ type }, + lpData{ lpData }{} + + RegistryValue::RegistryValue(std::wstring wValueName, RegistryType type, std::vector vData) : + wValueName{ wValueName }, + type{ type }, + vData{ vData }{} + + RegistryType RegistryValue::GetType() const { + return type; + } + + std::wstring RegistryValue::ToString() const { + if(type == RegistryType::REG_SZ_T || type == RegistryType::REG_EXPAND_SZ_T) + return wData; + else if(type == RegistryType::REG_DWORD_T) + return std::to_wstring(dwData); + else if(type == RegistryType::REG_MULTI_SZ_T){ + std::wstring string = L"[\""; + for(auto str : vData){ + string += str + L"\", \""; + } + return string.substr(0, string.length() - 3) + L"\"]"; + } else { + if(!lpData){ + return L"(null)"; + } + + std::wstring string = L""; + for(auto i = 0; i < lpData.GetSize(); i++){ + wchar_t buf[3]; + wsprintf(buf, L"%02x", lpData[i]); + string += buf; + string += L" "; + } + return string; + } + } +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp b/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp index 6fc907af..9c72188f 100644 --- a/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp +++ b/BLUESPAWN-client/src/util/eventlogs/EventLogs.cpp @@ -4,9 +4,19 @@ #include "util/log/Log.h" const int SIZE_DATA = 4096; +const int ARRAY_SIZE = 10; -DWORD GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param) -{ +EventLogs* EventLogs::logs = NULL; + +/** +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); +} + +DWORD EventLogs::GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param) { DWORD status = ERROR_SUCCESS; EVT_HANDLE hContext = NULL; DWORD dwBufferSize = 0; @@ -25,7 +35,7 @@ DWORD GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param) if (NULL == hContext) { status = GetLastError(); - LOG_ERROR("EvtCreateRenderContext failed with " + std::to_string(status)); + LOG_ERROR("EventLogs::GetEventParam: EvtCreateRenderContext failed with " + std::to_string(status)); goto cleanup; } @@ -44,7 +54,7 @@ DWORD GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param) } else { - LOG_ERROR("GetEventParam malloc failed"); + LOG_ERROR("EventLogs::GetEventParam: GetEventParam malloc failed"); status = ERROR_OUTOFMEMORY; goto cleanup; } @@ -52,7 +62,7 @@ DWORD GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param) if (ERROR_SUCCESS != (status = GetLastError())) { - LOG_ERROR("EvtRender in GetEventParam failed with " + std::to_string(status)); + LOG_ERROR("EventLogs::GetEventParam: EvtRender in GetEventParam failed with " + std::to_string(status)); goto cleanup; } } @@ -92,7 +102,7 @@ DWORD GetEventParam(EVT_HANDLE hEvent, std::wstring* value, std::wstring param) return status; } -DWORD GetEventXML(EVT_HANDLE hEvent, std::wstring * data) +DWORD EventLogs::GetEventXML(EVT_HANDLE hEvent, std::wstring * data) { DWORD status = ERROR_SUCCESS; DWORD dwBufferSize = 0; @@ -115,7 +125,7 @@ DWORD GetEventXML(EVT_HANDLE hEvent, std::wstring * data) } else { - LOG_ERROR("GetEventXML malloc failed"); + LOG_ERROR("EventLogs::GetEventXML: GetEventXML malloc failed"); status = ERROR_OUTOFMEMORY; goto cleanup; } @@ -123,7 +133,7 @@ DWORD GetEventXML(EVT_HANDLE hEvent, std::wstring * data) if (ERROR_SUCCESS != (status = GetLastError())) { - LOG_ERROR("EvtRender in GetEventXML failed with " + std::to_string(GetLastError())); + LOG_ERROR("EventLogs::GetEventXML: EvtRender in GetEventXML failed with " + std::to_string(GetLastError())); goto cleanup; } } @@ -136,7 +146,7 @@ DWORD GetEventXML(EVT_HANDLE hEvent, std::wstring * data) } // Enumerate all the events in the result set. -DWORD ProcessResults(EVT_HANDLE hResults, Reaction& reaction, int* numFound, std::set ¶ms) +DWORD EventLogs::ProcessResults(EVT_HANDLE hResults, Reaction& reaction, int* numFound, std::set ¶ms) { DWORD status = ERROR_SUCCESS; EVT_HANDLE hEvents[ARRAY_SIZE]; @@ -145,50 +155,17 @@ DWORD ProcessResults(EVT_HANDLE hResults, Reaction& reaction, int* numFound, std while (true) { // Get a block of events from the result set. - if (!EvtNext(hResults, ARRAY_SIZE, hEvents, INFINITE, 0, &dwReturned)) - { + if (!EvtNext(hResults, ARRAY_SIZE, hEvents, INFINITE, 0, &dwReturned)) { if (ERROR_NO_MORE_ITEMS != (status = GetLastError())) - { - LOG_ERROR("EvtNext failed with " + std::to_string(status)); - } + LOG_ERROR("EventLogs::ProcessResults: EvtNext failed with " + std::to_string(status)); goto cleanup; } - - // For each event, call the PrintEvent function which renders the - // event for display. PrintEvent is shown in RenderingEvents. - std::wstring eventIDStr; - std::wstring eventRecordIDStr; - std::wstring timeCreated; - std::wstring channel; - std::wstring rawXML; - for (DWORD i = 0; i < dwReturned; i++) - { - if (ERROR_SUCCESS != (status = GetEventParam(hEvents[i], &eventIDStr, L"Event/System/EventID"))) - goto cleanup; - if (ERROR_SUCCESS != (status = GetEventParam(hEvents[i], &eventRecordIDStr, L"Event/System/EventRecordID"))) + + for (DWORD i = 0; i < dwReturned; i++) { + EVENT_DETECTION detect(0, 0, L"", L"", L""); + if(ERROR_SUCCESS != (status = EventToDetection(hEvents[i], &detect, params))) goto cleanup; - if (ERROR_SUCCESS != (status = GetEventParam(hEvents[i], &timeCreated, L"Event/System/TimeCreated/@SystemTime"))) - goto cleanup; - if (ERROR_SUCCESS != (status = GetEventParam(hEvents[i], &channel, L"Event/System/Channel"))) - goto cleanup; - if (ERROR_SUCCESS != (status = GetEventXML(hEvents[i], &rawXML))) - goto cleanup; - - // Specify extra parameters - std::unordered_map extraParams; - for (std::wstring key : params) { - std::wstring val; - if (ERROR_SUCCESS != (status = GetEventParam(hEvents[i], &val, key))) { - LOG_ERROR(L"Failed query parameter " + key + L" with code " + std::to_wstring(status)); - goto cleanup; - } - - extraParams.insert({ key, val }); - } - - EVENT_DETECTION detect(std::stoul(eventIDStr), std::stoul(eventRecordIDStr), timeCreated, channel, rawXML); - detect.params = extraParams; reaction.EventIdentified(std::make_shared(detect)); (*numFound) += 1; @@ -211,12 +188,54 @@ DWORD ProcessResults(EVT_HANDLE hResults, Reaction& reaction, int* numFound, std return status; } -int QueryEvents(const wchar_t* channel, unsigned int id, Reaction& reaction) +DWORD EventLogs::EventToDetection(EVT_HANDLE hEvent, EVENT_DETECTION* pDetection, std::set& params) { + DWORD status = ERROR_SUCCESS; + + std::wstring eventIDStr; + std::wstring eventRecordIDStr; + std::wstring timeCreated; + std::wstring channel; + std::wstring rawXML; + + if (ERROR_SUCCESS != (status = GetEventParam(hEvent, &eventIDStr, L"Event/System/EventID"))) + return status; + if (ERROR_SUCCESS != (status = GetEventParam(hEvent, &eventRecordIDStr, L"Event/System/EventRecordID"))) + return status; + if (ERROR_SUCCESS != (status = GetEventParam(hEvent, &timeCreated, L"Event/System/TimeCreated/@SystemTime"))) + return status; + if (ERROR_SUCCESS != (status = GetEventParam(hEvent, &channel, L"Event/System/Channel"))) + return status; + if (ERROR_SUCCESS != (status = GetEventXML(hEvent, &rawXML))) + return status; + + // Specify extra parameters + std::unordered_map extraParams; + for (std::wstring key : params) { + std::wstring val; + if (ERROR_SUCCESS != (status = GetEventParam(hEvent, &val, key))) { + LOG_ERROR(L"EventLogs::EventToDetection: Failed query parameter " + key + L" with code " + std::to_wstring(status)); + return status; + } + + extraParams.insert({ key, val }); + } + + pDetection->eventID = std::stoul(eventIDStr); + pDetection->eventRecordID = std::stoul(eventRecordIDStr); + pDetection->timeCreated = timeCreated; + pDetection->channel = channel; + pDetection->rawXML = rawXML; + pDetection->params = extraParams; + + return status; +} + +int EventLogs::QueryEvents(const wchar_t* channel, unsigned int id, Reaction& reaction) { return QueryEvents(channel, id, std::set(), reaction); } -int QueryEvents(const wchar_t* channel, unsigned int id, std::set& params, Reaction& reaction) +int EventLogs::QueryEvents(const wchar_t* channel, unsigned int id, std::set& params, Reaction& reaction) { DWORD status = ERROR_SUCCESS; EVT_HANDLE hResults = NULL; @@ -230,13 +249,13 @@ int QueryEvents(const wchar_t* channel, unsigned int id, std::set& status = GetLastError(); if (ERROR_EVT_CHANNEL_NOT_FOUND == status) - LOG_ERROR("The channel was not found."); + LOG_ERROR("EventLogs::QueryEvents: The channel was not found."); else if (ERROR_EVT_INVALID_QUERY == status) // You can call the EvtGetExtendedStatus function to try to get // additional information as to what is wrong with the query. - LOG_ERROR(L"The query " + query + L" is not valid."); + LOG_ERROR(L"EventLogs::QueryEvents: The query " + query + L" is not valid."); else - LOG_ERROR("EvtQuery failed with " + std::to_string(status)); + LOG_ERROR("EventLogs::QueryEvents: EvtQuery failed with " + std::to_string(status)); goto cleanup; } @@ -252,4 +271,31 @@ int QueryEvents(const wchar_t* channel, unsigned int id, std::set& if (status == ERROR_SUCCESS) return numFound; return -1; +} + +std::unique_ptr EventLogs::subscribe(LPWSTR pwsPath, unsigned int id, Reactions::HuntTriggerReaction& reaction, DWORD * pStatus) { + *pStatus = ERROR_SUCCESS; + EVT_HANDLE hSubscription = NULL; + + auto query = std::wstring(L"Event/System[EventID=") + std::to_wstring(id) + std::wstring(L"]"); + auto wquery = query.c_str(); + + auto eventSub = std::make_unique(reaction); + + hSubscription = EvtSubscribe(NULL, NULL, pwsPath, wquery, NULL, reinterpret_cast(eventSub.get()), + CallbackWrapper, EvtSubscribeToFutureEvents); + + if (hSubscription == NULL) { + // Cleanup a failed subscription + *pStatus = GetLastError(); + + if (ERROR_EVT_CHANNEL_NOT_FOUND == *pStatus) + LOG_ERROR("EventLogs::subscribe: Channel was not found."); + else if (ERROR_EVT_INVALID_QUERY == *pStatus) + LOG_ERROR(L"EventLogs::subscribe: query " + query + L" is not valid."); + else + LOG_ERROR("EventLogs::subscribe: EvtSubscribe failed with " + std::to_string(*pStatus)); + } + + return eventSub; } \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/eventlogs/EventSubscription.cpp b/BLUESPAWN-client/src/util/eventlogs/EventSubscription.cpp new file mode 100644 index 00000000..cfa97b88 --- /dev/null +++ b/BLUESPAWN-client/src/util/eventlogs/EventSubscription.cpp @@ -0,0 +1,61 @@ +#include "util/eventlogs/EventSubscription.h" +#include "hunt/reaction/Detections.h" +#include "util/log/Log.h" +#include "util/eventlogs/EventLogs.h" + +// The callback that receives the events that match the query criteria. +DWORD WINAPI EventSubscription::SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action, EVT_HANDLE hEvent) { + DWORD status = ERROR_SUCCESS; + + EVENT_DETECTION detect(0, 0, L"", L"", L""); + + switch (action) + { + // You should only get the EvtSubscribeActionError action if your subscription flags + // includes EvtSubscribeStrict and the channel contains missing event records. + case EvtSubscribeActionError: + if (ERROR_EVT_QUERY_RESULT_STALE == (DWORD)hEvent) + { + LOG_ERROR("EventSubscription::SubscriptionCallback: The subscription callback was notified that event records are missing"); + // Handle if this is an issue for your application. + } + else + { + LOG_ERROR("EventSubscription::SubscriptionCallback: The subscription callback received the following Win32 error: " + std::to_string((int)hEvent)); + } + break; + + case EvtSubscribeActionDeliver: + if (ERROR_SUCCESS != (status = EventLogs::getLogs()->EventToDetection(hEvent, &detect, std::set()))) + goto cleanup; + reaction->EventIdentified(std::make_shared< EVENT_DETECTION>(detect)); + + break; + + default: + LOG_ERROR(L"EventSubscription::SubscriptionCallback: Unknown action."); + } + +cleanup: + + if (ERROR_SUCCESS != status) + { + // End subscription - Use some kind of IPC mechanism to signal + // your application to close the subscription handle. + } + + return status; // The service ignores the returned status. +} + +EventSubscription::EventSubscription(Reactions::HuntTriggerReaction& reaction) { + this->reaction = std::make_unique(reaction); +} + +EventSubscription::~EventSubscription() { + if (hSubscription) + EvtClose(hSubscription); +} + +void EventSubscription::setSubHandle(EVT_HANDLE hSubscription) { + this->hSubscription = hSubscription; +} \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp b/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp index b8fb8955..d3dcbf5f 100644 --- a/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp +++ b/BLUESPAWN-client/src/util/filesystem/FileSystem.cpp @@ -1,127 +1,458 @@ #include "util/filesystem/FileSystem.h" +#include "util/log/Log.h" +#include "common/StringUtils.h" -bool CheckFileExists(LPCWSTR filename) { - //Function from https://stackoverflow.com/a/4404259/3302799 - GetFileAttributesW(filename); - if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(filename) && - GetLastError() == ERROR_FILE_NOT_FOUND) - { - return false; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/wrappers.hpp" + +namespace FileSystem{ + bool CheckFileExists(std::wstring filename) { + if(INVALID_FILE_ATTRIBUTES == GetFileAttributes(filename.c_str()) && GetLastError() == ERROR_FILE_NOT_FOUND){ + LOG_VERBOSE(3, "File " << filename << " does not exist."); + return false; + } + LOG_VERBOSE(3, "File " << filename << " exists"); + return true; } - return true; -} - -string GetFileContents(LPCWSTR filename) { - if (CheckFileExists(filename)) { - ifstream ifs(filename); - string content((std::istreambuf_iterator(ifs)), - (std::istreambuf_iterator())); - return content; - } - else { - return ""; - } -} - -bool HashFileMD5(LPCWSTR filename, string& out) { - //Function from Microsoft - //https://docs.microsoft.com/en-us/windows/desktop/SecCrypto/example-c-program--creating-an-md-5-hash-from-file-content - DWORD dwStatus = 0; - BOOL bResult = FALSE; - HCRYPTPROV hProv = 0; - HCRYPTHASH hHash = 0; - HANDLE hFile = NULL; - BYTE rgbFile[BUFSIZE]; - DWORD cbRead = 0; - BYTE rgbHash[MD5LEN]; - DWORD cbHash = 0; - CHAR rgbDigits[] = "0123456789abcdef"; - - hFile = CreateFileW(filename, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_FLAG_SEQUENTIAL_SCAN, - NULL); - - if (INVALID_HANDLE_VALUE == hFile) - { - dwStatus = GetLastError(); - //printf("Error opening file %s\nError: %d\n", filename,dwStatus); - return false; + + 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); } - // Get handle to the crypto provider - if (!CryptAcquireContext(&hProv, - NULL, - NULL, - PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) - { - dwStatus = GetLastError(); - //printf("CryptAcquireContext failed: %d\n", dwStatus); - CloseHandle(hFile); - return false; + File::File(IN const std::wstring& path) : hFile{ nullptr }{ + FilePath = path; + LOG_VERBOSE(2, "Attempting to open file: " << path << "."); + hFile = CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_NORMAL, nullptr); + if(!hFile){ + LOG_VERBOSE(2, "Couldn't open file " << path << "."); + FileExists = false; + } else { + LOG_VERBOSE(2, "File " << path << " opened."); + FileExists = true; + } + Attribs.extension = PathFindExtension(path.c_str()); } - if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) - { - dwStatus = GetLastError(); - //printf("CryptAcquireContext failed: %d\n", dwStatus); - CloseHandle(hFile); - CryptReleaseContext(hProv, 0); - return false; + 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); + + if(!FileExists) { + LOG_ERROR("Can't write to file " << FilePath << ". File doesn't exist"); + 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, nullptr, 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, nullptr, 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, nullptr, 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; } - while (bResult = ReadFile(hFile, rgbFile, BUFSIZE, - &cbRead, NULL)) - { - if (0 == cbRead) + 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(!FileExists) { + LOG_ERROR("Can't write to " << FilePath << ". File doesn't exist."); + SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + + if(SetFilePointer(offset) == INVALID_SET_FILE_POINTER) { + LOG_ERROR("Can't set file pointer to " << offset << " in file " << FilePath << "."); + return false; + } + 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 }; + } + + std::optional File::GetMD5Hash() const { + LOG_VERBOSE(3, "Attempting to get MD5 hash of " << FilePath); + if(!FileExists) { + LOG_ERROR("Can't get MD5 hash of " << FilePath << ". File doesn't exist"); + 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 + DWORD dwStatus = 0; + BOOL bResult = FALSE; + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + BYTE rgbFile[BUFSIZE]; + DWORD cbRead = 0; + BYTE rgbHash[MD5LEN]; + DWORD cbHash = 0; + CHAR rgbDigits[] = "0123456789abcdef"; + + // Get handle to the crypto provider + if(!CryptAcquireContext(&hProv, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { - break; + dwStatus = GetLastError(); + LOG_ERROR("CryptAcquireContext failed: " << dwStatus << " while getting MD5 hash of " << FilePath); + return std::nullopt; } - if (!CryptHashData(hHash, rgbFile, cbRead, 0)) + if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { dwStatus = GetLastError(); - //printf("CryptHashData failed: %d\n", dwStatus); + LOG_ERROR("CryptCreateHash failed: " << dwStatus << " while getting MD5 hash of " << FilePath); + CryptReleaseContext(hProv, 0); + return std::nullopt; + } + + while(bResult = ReadFile(hFile, rgbFile, BUFSIZE, + &cbRead, NULL)) + { + if(0 == cbRead) + { + break; + } + + if(!CryptHashData(hHash, rgbFile, cbRead, 0)) + { + dwStatus = GetLastError(); + LOG_ERROR("CryptHashData failed: " << dwStatus << " while getting MD5 hash of " << FilePath); + CryptReleaseContext(hProv, 0); + CryptDestroyHash(hHash); + return std::nullopt; + } + } + + if(!bResult) + { + dwStatus = GetLastError(); + LOG_ERROR("ReadFile failed: " << dwStatus << " while getting MD5 hash of " << FilePath); CryptReleaseContext(hProv, 0); CryptDestroyHash(hHash); - CloseHandle(hFile); + return std::nullopt; + } + + std::string buffer = {}; + + cbHash = MD5LEN; + if(CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) + { + for(DWORD i = 0; i < cbHash; i++) + { + buffer += rgbDigits[rgbHash[i] >> 4]; + buffer += rgbDigits[rgbHash[i] & 0xf]; + } + return buffer; + } else + { + dwStatus = GetLastError(); + LOG_ERROR("CryptGetHashParam failed: " << dwStatus << " while getting MD5 hash of " << FilePath); + } + + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + LOG_VERBOSE(3, "Successfully got MD5 Hash of " << FilePath); + return std::nullopt; + } + + bool File::Create() { + LOG_VERBOSE(1, "Attempting to create file: " << FilePath); + if(FileExists) { + 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); + FileExists = false; return false; } + LOG_VERBOSE(1, FilePath << " successfully created."); + FileExists = true; + return true; } - if (!bResult) - { - dwStatus = GetLastError(); - //printf("ReadFile failed: %d\n", dwStatus); - CryptReleaseContext(hProv, 0); - CryptDestroyHash(hHash); + bool File::Delete() { + LOG_VERBOSE(1, "Attempting to delete file " << FilePath); + if(!FileExists) { + 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); + FileExists = false; + return false; + } + FileExists = true; + return false; + } + LOG_VERBOSE(1, FilePath << "deleted."); + FileExists = 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; + } + + Folder::Folder(const std::wstring& path) : hCurFile{ nullptr } { + FolderPath = path; + std::wstring searchName = FolderPath; + searchName += L"\\*"; + FolderExists = true; + hCurFile = HandleWrapper(FindFirstFile(searchName.c_str(), &ffd)); + if(hCurFile == INVALID_HANDLE_VALUE) { + LOG_ERROR("Couldn't open folder " << path); + FolderExists = false; + } + if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + IsFile = false; + } else { + IsFile = true; + } + } + + bool Folder::MoveToNextFile() { + if(FindNextFileW(hCurFile, &ffd) != 0) { + if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + IsFile = false; + } else { + IsFile = true; + } + return true; + } return false; } - cbHash = MD5LEN; - if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) - { - for (DWORD i = 0; i < cbHash; i++) - { - out += rgbDigits[rgbHash[i] >> 4]; - out += rgbDigits[rgbHash[i] & 0xf]; + 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) { + IsFile = false; + } else { + IsFile = true; } return true; } - else - { - dwStatus = GetLastError(); - //printf("CryptGetHashParam failed: %d\n", dwStatus); + + std::optional Folder::Open() const { + if(IsFile) { + 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; } - CryptDestroyHash(hHash); - CryptReleaseContext(hProv, 0); - CloseHandle(hFile); + std::optional Folder::EnterDir() { + if(!IsFile) { + std::wstring folderName = FolderPath; + folderName += L"\\"; + folderName += ffd.cFileName; + Folder folder = Folder(folderName.c_str()); + if(folder.GetFolderExists()) return folder; + } + return std::nullopt; + } - return false; + 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 std::optional attribs, IN 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(recurDepth != 0 && ffd.cFileName != L"." && ffd.cFileName != 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 != L"." && ffd.cFileName != L".."){ + std::vector temp; + std::optional f = EnterDir(); + if(f) { + if(recurDepth == -1) { + temp = f->GetSubdirectories(recurDepth); + } else { + temp = f->GetSubdirectories(recurDepth - 1); + } + while(!temp.empty()) { + auto file = temp.at(temp.size() - 1); + temp.pop_back(); + toRet.emplace_back(file); + } + } + } + } while(MoveToNextFile()); + return toRet; + } } \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/log/CLISink.cpp b/BLUESPAWN-client/src/util/log/CLISink.cpp index c6bf9302..fb8f5a5c 100644 --- a/BLUESPAWN-client/src/util/log/CLISink.cpp +++ b/BLUESPAWN-client/src/util/log/CLISink.cpp @@ -16,9 +16,8 @@ namespace Log { SetConsoleColor(CLISink::PrependColors[static_cast(level.severity)]); if(level.severity == Severity::LogHunt){ - std::wstring aggressiveness = info->HuntAggressiveness == Aggressiveness::Aggressive ? L"Aggressive" : - info->HuntAggressiveness == Aggressiveness::Careful ? L"Careful" : - info->HuntAggressiveness == Aggressiveness::Moderate ? L"Moderate" : L"Cursory"; + 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; @@ -34,9 +33,9 @@ namespace Log { 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->wsRegistryKeyPath << (lpRegistryDetection->wsRegistryKeyValue.length() ? L": " : L"") << lpRegistryDetection->wsRegistryKeyValue << std::endl; - } - else if (detection->Type == DetectionType::Event) { + std::wcout << L"\tPotentially malicious registry key detected - " << lpRegistryDetection->wsRegistryKeyPath << L": " << lpRegistryDetection->contents.wValueName + << L" with data " << lpRegistryDetection->contents.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; @@ -47,8 +46,7 @@ namespace Log { std::wcout << "\t\t" << iter->first << ": " << iter->second << std::endl; } - } - else { + } else { std::wcout << L"\tUnknown detection type!" << std::endl; } } diff --git a/BLUESPAWN-client/src/util/log/DebugSink.cpp b/BLUESPAWN-client/src/util/log/DebugSink.cpp index c8788e18..c4808c00 100644 --- a/BLUESPAWN-client/src/util/log/DebugSink.cpp +++ b/BLUESPAWN-client/src/util/log/DebugSink.cpp @@ -9,9 +9,8 @@ namespace Log { const std::vector>& detections){ if(level.Enabled()){ if(level.severity == Severity::LogHunt){ - std::wstring aggressiveness = info->HuntAggressiveness == Aggressiveness::Aggressive ? L"Aggressive" : - info->HuntAggressiveness == Aggressiveness::Careful ? L"Careful" : - info->HuntAggressiveness == Aggressiveness::Moderate ? L"Moderate" : L"Cursory"; + 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){ @@ -26,7 +25,8 @@ namespace Log { 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->wsRegistryKeyPath + (lpRegistryDetection->wsRegistryKeyValue.length() ? L": " : L"") + lpRegistryDetection->wsRegistryKeyValue).c_str()); + OutputDebugStringW((sLogHeader + L"\tPotentially malicious registry key detected - " + lpRegistryDetection->wsRegistryKeyPath + L": " + lpRegistryDetection->contents.wValueName + L" with value " + + lpRegistryDetection->contents.ToString()).c_str()); } else { OutputDebugStringW((sLogHeader + L"\tUnknown detection type!").c_str()); } diff --git a/BLUESPAWN-client/src/util/pe/Image_Loader.cpp b/BLUESPAWN-client/src/util/pe/Image_Loader.cpp index 7e4d1fdf..0c899daf 100644 --- a/BLUESPAWN-client/src/util/pe/Image_Loader.cpp +++ b/BLUESPAWN-client/src/util/pe/Image_Loader.cpp @@ -28,7 +28,7 @@ bool CompareStrings(const UNICODE_STRING64& s1, const HandleWrapper& process1, c return MemoryWrapper<>(reinterpret_cast(s1.Buffer), s1.Length * 2 + 2, process1).ReadWstring() == s2; } -Loaded_Image32::Loaded_Image32(const LDR_ENTRY32& entry, const HandleWrapper& process, bool imported) : +Loaded_Image32::Loaded_Image32(const LDR_ENTRY32& entry, const HandleWrapper& process) : EntryPoint{ entry.EntryPoint }, ImageAddress{ entry.DllBase }, ImagePath{ MemoryWrapper{ reinterpret_cast(static_cast(entry.FullDllName.Buffer)), @@ -36,10 +36,9 @@ Loaded_Image32::Loaded_Image32(const LDR_ENTRY32& entry, const HandleWrapper& pr ImageName{ MemoryWrapper{ reinterpret_cast(static_cast(entry.BaseDllName.Buffer)), static_cast(entry.BaseDllName.Length + 1), process }.ReadWstring() }, ImageSize{ entry.SizeOfImage }, - ImportsParsed{ imported }, process{ process }{} -Loaded_Image64::Loaded_Image64(const LDR_ENTRY64& entry, const HandleWrapper& process, bool imported) : +Loaded_Image64::Loaded_Image64(const LDR_ENTRY64& entry, const HandleWrapper& process) : EntryPoint{ entry.EntryPoint }, ImageAddress{ entry.DllBase }, ImagePath{ MemoryWrapper{ reinterpret_cast(entry.FullDllName.Buffer), static_cast(entry.FullDllName.Length + 1), @@ -47,9 +46,22 @@ Loaded_Image64::Loaded_Image64(const LDR_ENTRY64& entry, const HandleWrapper& pr ImageName{ MemoryWrapper{ reinterpret_cast(entry.BaseDllName.Buffer), static_cast(entry.BaseDllName.Length + 1), process }.ReadWstring() }, ImageSize{ entry.SizeOfImage }, - ImportsParsed{ imported }, process{ process }{} +Loaded_Image::Loaded_Image(const LDR_ENTRY32& entry, const HandleWrapper& process) : + arch{ Architecture::x86 }, + image32{ Loaded_Image32{ entry, process } }, + image64{ std::nullopt } {} + +Loaded_Image::Loaded_Image(const LDR_ENTRY64& entry, const HandleWrapper& process) : + arch{ Architecture::x64 }, + image64{ Loaded_Image64{ entry, process } }, + image32{ std::nullopt } {} + +std::wstring Loaded_Image::GetName(){ + return arch == x64 ? image64->ImageName : image32->ImageName; +} + Image_Loader::Image_Loader(const HandleWrapper& process) : process{ process }, LoadedImages{}{ if(process){ @@ -100,76 +112,35 @@ Image_Loader::Image_Loader(const HandleWrapper& process) : } } -bool Image_Loader::AddImage(const Loaded_Image& image){ - if(image.arch != arch){ - return false; - } - - if(ContainsImage(image.arch == x64 ? image.image64->ImageName : image.image32->ImageName)){ - return true; - } - - if(arch == x64){ - auto Loader = *MemoryWrapper{ reinterpret_cast(address), sizeof(LDR_DATA64), process }; - auto LastEntry = *MemoryWrapper{ reinterpret_cast(Loader.InLoadOrderModuleList.Blink), sizeof(LDR_ENTRY64), process }; - auto DllNameMemory = VirtualAllocEx(process, nullptr, (image.image64->ImageName.length() + image.image64->ImagePath.length()) * 2 + 4, - MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - MemoryWrapper<>(DllNameMemory).Write(reinterpret_cast(image.image64->ImageName.c_str()), image.image64->ImageName.length() * 2, 0); - MemoryWrapper<>(DllNameMemory).Write(reinterpret_cast(image.image64->ImagePath.c_str()), image.image64->ImagePath.length() * 2, - image.image64->ImageName.length() * 2 + 2); - /*auto Entry = LDR_ENTRY64{ - LIST_ENTRY64{ LastEntry.InLoadOrderModuleList.Flink, Loader.InLoadOrderModuleList.Blink }, - LIST_ENTRY64{ LastEntry.InMemoryOrderModuleList.Flink, Loader.InMemoryOrderModuleList.Blink }, - LIST_ENTRY64{ LastEntry.InInitializationOrderModuleList.Flink, Loader.InInitializationOrderModuleList.Blink }, - image.image64->ImageAddress, - image.image64->EntryPoint, - image.image64->ImageSize, - UNICODE_STRING64{ image.image64->ImageName.length(), image.image64->ImageName.length(), reinterpret_cast(DllNameMemory) }, - UNICODE_STRING64{ - image.image64->ImagePath.length(), - image.image64->ImagePath.length(), - reinterpret_cast(DllNameMemory) + image.image64->ImageName.length() * 2 + 2 - }, - LastEntry.Flags, - 1, - LastEntry.TlsIndex + 1, - LIST_ENTRY64{ 0, 0 }, - 0 - };*/ - LoadedImages.emplace_back(image); - } else { - auto FirstAddress = MemoryWrapper{ reinterpret_cast(address), sizeof(LDR_DATA32), process }->InLoadOrderModuleList.Flink; - auto entry = *MemoryWrapper{reinterpret_cast(static_cast(FirstAddress)), sizeof(LDR_ENTRY32), process}; - while(entry.EntryPoint){ - LoadedImages.emplace_back(Loaded_Image{ entry, process }); - entry = *MemoryWrapper{reinterpret_cast(static_cast(entry.InLoadOrderModuleList.Flink)), sizeof(LDR_ENTRY32), process}; +bool Image_Loader::ContainsImage(const std::wstring& wImageName) const { + for(auto image : LoadedImages){ + if(image.GetName() == wImageName){ + return true; } } return false; } -bool Image_Loader::RemoveImage(const Loaded_Image& image){ - if(arch != image.arch){ - return false; - } - if(arch == x64){ - auto FirstAddress = MemoryWrapper{ reinterpret_cast(address), sizeof(LDR_DATA64), process }->InLoadOrderModuleList.Flink; - auto entry = *MemoryWrapper{reinterpret_cast(static_cast(FirstAddress)), sizeof(LDR_ENTRY64), process}; - while(entry.EntryPoint){ - if(CompareStrings(entry.BaseDllName, process, image.image64->ImageName)){ - //*MemoryWrapper{reinterpret_cast(entry.InInitializationOrderModuleList.Blink), sizeof(LDR_ENTRY64), process} - } - entry = *MemoryWrapper{reinterpret_cast(static_cast(entry.InLoadOrderModuleList.Flink)), sizeof(LDR_ENTRY64), process}; - } - } else { - auto FirstAddress = MemoryWrapper{ reinterpret_cast(address), sizeof(LDR_DATA32), process }->InLoadOrderModuleList.Flink; - auto entry = *MemoryWrapper{reinterpret_cast(static_cast(FirstAddress)), sizeof(LDR_ENTRY32), process}; - while(entry.EntryPoint){ - LoadedImages.emplace_back(Loaded_Image{ entry, process }); - entry = *MemoryWrapper{reinterpret_cast(static_cast(entry.InLoadOrderModuleList.Flink)), sizeof(LDR_ENTRY32), process}; +std::optional Image_Loader::GetImageInfo(const std::wstring& wImageName) const { + for(auto image : LoadedImages){ + if(image.GetName() == wImageName){ + return image; } } + return std::nullopt; +} - return false; +std::optional Image_Loader::GetAssociatedImage(LPVOID address) const { + for(auto image : LoadedImages){ + if(image.arch == x86 && reinterpret_cast(address) >= image.image32->ImageAddress && + reinterpret_cast(address) < image.image32->ImageAddress + image.image32->ImageSize && + reinterpret_cast(address) == reinterpret_cast(address)){ + return image; + } else if(image.arch == x64 && reinterpret_cast(address) >= image.image64->ImageAddress && + reinterpret_cast(address) < image.image64->ImageAddress + image.image64->ImageSize){ + return image; + } + } + return std::nullopt; } \ No newline at end of file diff --git a/BLUESPAWN-client/src/util/processes/ProcessUtils.cpp b/BLUESPAWN-client/src/util/processes/ProcessUtils.cpp new file mode 100644 index 00000000..46b53881 --- /dev/null +++ b/BLUESPAWN-client/src/util/processes/ProcessUtils.cpp @@ -0,0 +1,11 @@ +#include "util/processes/ProcessUtils.h" + +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 {}; +} diff --git a/BLUESPAWN-common/CommonLib.vcxproj.filters b/BLUESPAWN-common/CommonLib.vcxproj.filters deleted file mode 100644 index 34a2b43d..00000000 --- a/BLUESPAWN-common/CommonLib.vcxproj.filters +++ /dev/null @@ -1,45 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {75481f2e-51b6-44de-bde7-f1cd89f8cad9} - - - {577c8323-9b91-4c93-b774-d43a16032f38} - - - {0eef133f-70fe-4086-bb7e-ddb15508a3d9} - - - - - Header Files\Wrappers - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - \ No newline at end of file diff --git a/BLUESPAWN-common/CommonLib.vcxproj.user b/BLUESPAWN-common/CommonLib.vcxproj.user index dc63f8a8..429333de 100644 --- a/BLUESPAWN-common/CommonLib.vcxproj.user +++ b/BLUESPAWN-common/CommonLib.vcxproj.user @@ -1,6 +1,6 @@  - false + true \ No newline at end of file diff --git a/BLUESPAWN-common/headers/common/DynamicLinker.h b/BLUESPAWN-common/headers/common/DynamicLinker.h index 6ce84a55..29b529ca 100644 --- a/BLUESPAWN-common/headers/common/DynamicLinker.h +++ b/BLUESPAWN-common/headers/common/DynamicLinker.h @@ -10,13 +10,9 @@ typedef retval(convention *name##_type)(__VA_ARGS__); \ namespace Linker { extern name##_type name##; } -#define LINK_FUNCTION(name, dll) \ - namespace Linker { \ - name##_type name##; \ - auto res_##name = LoadCalls.emplace_back(std::bind([](name##_type* param){ \ - *param = reinterpret_cast(GetProcAddress(LoadLibraryW(L#dll), #name)); \ - return *param == nullptr; \ - }, &name)); \ +#define LINK_FUNCTION(name, dll) \ + namespace Linker { \ + name##_type name## = reinterpret_cast(GetProcAddress(LoadLibraryW(L#dll), #name)); \ } namespace Linker { diff --git a/BLUESPAWN-common/headers/common/wrappers.hpp b/BLUESPAWN-common/headers/common/wrappers.hpp index ac353d08..f2ee00ff 100644 --- a/BLUESPAWN-common/headers/common/wrappers.hpp +++ b/BLUESPAWN-common/headers/common/wrappers.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include template class GenericWrapper { @@ -13,7 +16,7 @@ class GenericWrapper { T BadValue; bool bFreeOnDestruction = true; - void (*freeResource)(T); + std::function freeResource; void DestroyReference(){ mReferenceCounts[WrappedObject]--; @@ -23,9 +26,11 @@ class GenericWrapper { freeResource(WrappedObject); } } + WrappedObject = BadValue; } void SetReference(T object){ + WrappedObject = object; if(mReferenceCounts.find(object) == mReferenceCounts.end()){ mReferenceCounts.emplace(object, 1); } else { @@ -35,7 +40,7 @@ class GenericWrapper { public: - GenericWrapper(T object, void(*freeFunction)(T) = [](T object){ delete object; }, T BadValue = nullptr) + GenericWrapper(T object, std::function freeFunction = [](T object){ delete object; }, T BadValue = nullptr) : WrappedObject{ object }, freeResource{ freeFunction }, BadValue{ BadValue } { SetReference(object); } GenericWrapper(const GenericWrapper& copy) @@ -46,7 +51,7 @@ class GenericWrapper { GenericWrapper& operator=(const GenericWrapper& copy){ freeResource = copy.freeResource; - DestroyReference(); + DestroyReference(); SetReference(copy.WrappedObject); return *this; } @@ -69,8 +74,124 @@ class GenericWrapper { class HandleWrapper : public GenericWrapper { public: - HandleWrapper(HANDLE handle) : - GenericWrapper(handle, (void(*)(HANDLE)) CloseHandle, INVALID_HANDLE_VALUE){}; + HandleWrapper(HANDLE handle) : + GenericWrapper(handle, std::function(CloseHandle), INVALID_HANDLE_VALUE){}; +}; + +class AllocationWrapper { + std::optional> Memory; + PCHAR pointer; + SIZE_T AllocationSize; + +public: + enum AllocationFunction { + VIRTUAL_ALLOC, HEAP_ALLOC, MALLOC, CPP_ALLOC, CPP_ARRAY_ALLOC, STACK_ALLOC + }; + + AllocationWrapper(LPVOID memory, SIZE_T size, AllocationFunction AllocationType = STACK_ALLOC) : + pointer{ reinterpret_cast(memory) }, + Memory{ + size && memory ? std::optional>{{ + reinterpret_cast(memory), [AllocationType](char* value){ + if(AllocationType == CPP_ALLOC) + delete value; + else if(AllocationType == CPP_ARRAY_ALLOC) + delete[] value; + else if(AllocationType == MALLOC) + free(value); + else if(AllocationType == HEAP_ALLOC) + HeapFree(GetProcessHeap(), 0, value); + else if(AllocationType == VIRTUAL_ALLOC) + VirtualFree(value, 0, MEM_RELEASE); + } + }} : std::nullopt + }, + AllocationSize{ size }{} + + CHAR operator[](int i) const { + return Memory && i < AllocationSize ? pointer[i] : 0; + } + + operator bool() const { + return Memory.has_value(); + } + + operator LPVOID() const { + return pointer; + } + + DWORD GetSize() const { + return Memory.has_value() ? AllocationSize : 0; + } + + template + std::optional operator*() const { + return Dereference(); + } + + template + std::optional Dereference() const { + if(AllocationSize < sizeof(T) || !Memory.has_value()){ + return std::nullopt; + } else { + return *reinterpret_cast(pointer); + } + } + + std::optional ReadWString() const { + if(Memory.has_value()){ + SIZE_T size = 0; + while(size * 2 + 1 < AllocationSize && (pointer[size * 2] || pointer[size * 2 + 1])) + size++; + char* buffer = new char[size * 2 + 2]; + for(int i = 0; i < size * 2; i++){ + buffer[i] = pointer[i]; + } + buffer[size * 2] = buffer[size * 2 + 1] = 0; + auto str = std::wstring{ reinterpret_cast(buffer) }; + delete[] buffer; + return str; + } else return std::nullopt; + } + + std::optional ReadString() const { + if(Memory.has_value()){ + SIZE_T size = 0; + while(size < AllocationSize && pointer[size]) + size++; + char* buffer = new char[size + 1]; + for(SIZE_T i = 0; i < size; i++){ + buffer[i] = pointer[i]; + } + buffer[size] = 0; + auto str = std::string{ buffer }; + delete[] buffer; + return str; + } else return std::nullopt; + } + + 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 false; + } + } + + bool SetByte(SIZE_T offset, char value){ + if(offset < AllocationSize){ + pointer[offset] = value; + return true; + } + return false; + } }; template @@ -82,7 +203,7 @@ class MemoryWrapper { HandleWrapper process; SIZE_T MemorySize; - MemoryWrapper(LPVOID lpMemoryBase, SIZE_T size = sizeof(T), HANDLE process = GetCurrentProcess()) + MemoryWrapper(LPVOID lpMemoryBase, SIZE_T size = sizeof(T), HANDLE process = GetCurrentProcess()) : address{ reinterpret_cast(lpMemoryBase) }, process{ process }, MemorySize{ size } {} T Dereference() const { @@ -129,23 +250,6 @@ class MemoryWrapper { return { reinterpret_cast(PCHAR(address) + offset), MemorySize - offset, process }; } } - - bool Write(const T* lpToWrite, SIZE_T nWriteSize = sizeof(T), SIZE_T offset = 0) const { - if(offset != 0){ - return GetOffset(offset).Write(lpToWrite, nWriteSize); - } - - if(nWriteSize > MemorySize){ - return false; - } else { - if(!process){ - CopyMemory(address, lpToWrite, nWriteSize); - return true; - } else { - return WriteProcessMemory(process, address, lpToWrite, nWriteSize, nullptr); - } - } - } bool CompareMemory(MemoryWrapper memory) const { auto data1 = Dereference(); @@ -169,16 +273,16 @@ class MemoryWrapper { } else { int idx = 0; int maxIdx = 10; - char* memory = new char[maxIdx]; + char* memory = new char[maxIdx * 2]; bool valid = false; - while(!valid && !ReadProcessMemory(process, address, memory, maxIdx *= 2, nullptr)){ - delete[] memory; - memory = new char[maxIdx]; + while(!valid && !ReadProcessMemory(process, address, memory, maxIdx = min(maxIdx * 2, MemorySize), nullptr)){ for(; idx < maxIdx; idx++){ if(memory[idx] == 0){ valid = true; break; } + delete[] memory; + memory = new char[maxIdx * 2]; } } if(valid){ @@ -195,15 +299,17 @@ class MemoryWrapper { } else { int idx = 0; int maxIdx = 10; - wchar_t* memory = new wchar_t[maxIdx]; + wchar_t* memory = new wchar_t[maxIdx * 2]; bool valid = false; - while(!valid && !ReadProcessMemory(process, address, memory, (maxIdx *= 2) * sizeof(wchar_t), nullptr)){ + while(!valid && !ReadProcessMemory(process, address, memory, (maxIdx = min(maxIdx * 2, MemorySize / sizeof(WCHAR))) * sizeof(WCHAR), nullptr)){ for(; idx < maxIdx; idx++){ if(memory[idx] == 0){ valid = true; break; } } + delete[] memory; + memory = new wchar_t[maxIdx * 2]; } if(valid){ return std::wstring{ memory }; @@ -217,22 +323,8 @@ class MemoryWrapper { bool operator !() const { return !address; } }; -template -class MemoryAllocationWrapper : - public GenericWrapper, - public MemoryWrapper { -public: - MemoryAllocationWrapper(T* lpAddress, SIZE_T nSize = sizeof(T)) : - GenericWrapper(reinterpret_cast(lpAddress), [](T* memory){ - VirtualFree(memory, 0, MEM_RELEASE); - }, nullptr), - MemoryWrapper(reinterpret_cast(lpAddress), nSize, GetCurrentProcess()) {}; - - using MemoryWrapper::operator*; - using MemoryWrapper::operator&; - using MemoryWrapper::operator!; - using MemoryWrapper::operator bool; -}; - #define WRAP(type, name, value, function) \ - GenericWrapper name = {value, [](type data){ function; }} \ No newline at end of file + GenericWrapper name = {value, [&](type data){ function; }} + +#define SCOPE_LOCK(function, name) \ + GenericWrapper __##name = { 1, [&](DWORD data){ function; }, 0 } \ No newline at end of file diff --git a/BLUESPAWN-common/src/DynamicLinker.cpp b/BLUESPAWN-common/src/DynamicLinker.cpp index 86971a9f..1c617c91 100644 --- a/BLUESPAWN-common/src/DynamicLinker.cpp +++ b/BLUESPAWN-common/src/DynamicLinker.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace Linker { std::vector> LoadCalls = {}; diff --git a/BLUESPAWN.sln b/BLUESPAWN.sln index bca330d1..de1d8ee1 100644 --- a/BLUESPAWN.sln +++ b/BLUESPAWN.sln @@ -7,6 +7,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLib", "BLUESPAWN-comm EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BLUESPAWN-client", "BLUESPAWN-client\BLUESPAWN-client.vcxproj", "{159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pe-sieve", "BLUESPAWN-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}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -31,6 +35,22 @@ Global {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}.Release|x64.Build.0 = Release|x64 {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}.Release|x86.ActiveCfg = Release|Win32 {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0}.Release|x86.Build.0 = Release|Win32 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Debug|x64.ActiveCfg = Debug|x64 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Debug|x64.Build.0 = Debug|x64 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Debug|x86.ActiveCfg = Debug|Win32 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Debug|x86.Build.0 = Debug|Win32 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Release|x64.ActiveCfg = Release|x64 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Release|x64.Build.0 = Release|x64 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Release|x86.ActiveCfg = Release|Win32 + {BEC01F8E-5892-3F6F-A741-5BBD1D0F4EF9}.Release|x86.Build.0 = Release|Win32 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Debug|x64.ActiveCfg = Debug|x64 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Debug|x64.Build.0 = Debug|x64 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Debug|x86.ActiveCfg = Debug|Win32 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Debug|x86.Build.0 = Debug|Win32 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Release|x64.ActiveCfg = Release|x64 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Release|x64.Build.0 = Release|x64 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Release|x86.ActiveCfg = Release|Win32 + {C9D09618-1DE6-3323-AED8-9B885AC8D9F3}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index b9a739be..056ffff7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BLUESPAWN -[![BLUESPAWN client build](https://github.com/ION28/BLUESPAWN/workflows/BLUESPAWN%20client%20build/badge.svg)](https://github.com/ION28/BLUESPAWN/actions) ![Version](https://img.shields.io/github/v/release/ION28/BLUESPAWN?include_prereleases) ![License](https://img.shields.io/github/license/ION28/BLUESPAWN) +[![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) ![Platform](https://img.shields.io/badge/platform-win--32%20%7C%20win--64-lightgrey) ![Operating System](https://img.shields.io/badge/os-Windows%207%2F08%2B-blue) ## Our Mission BLUESPAWN helps blue teams monitor Windows systems in real-time against active attackers by detecting anomalous activity @@ -10,6 +10,7 @@ BLUESPAWN is an **active defense** and **endpoint detection and response tool** ## Why we made BLUESPAWN We've created and open-sourced this for a number of reasons which include the following: + * **Move Faster**: We wanted tooling specifically designed to quickly identify malicious activity on a system * **Know our Coverage**: We wanted to know exactly what our tools could detect and not rely on blackbox software as much (ie AV programs). This approach will help us to better focus our efforts on specific lines of effort and have confidence in the status of others. * **Better Understanding**: We wanted to better understand the Windows attack surface in order to defend it better @@ -17,10 +18,12 @@ We've created and open-sourced this for a number of reasons which include the fo * **Demonstrate Features of Windows API**: We combed through a ton of Microsoft Documentation, StackOverflow Answers, and more to create this. Hopefully others may find some of the code useful. ## Coverage of MITRE ATT&CK -Visit [this map](https://ion28.github.io/BLUESPAWN/#layerURL=https%3A%2F%2Fion28.github.io%2FBLUESPAWN%2Fassets%2Fcoverage.json) to see current coverage capabilities +Visit [this map](https://ion28.github.io/BLUESPAWN/) to see current coverage capabilities ## Try out BLUESPAWN + > Note: BLUESPAWN is under active *alpha* development, so many features may not work as expected yet and detections may be too narrow scope or generate lots of false positives. + 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 @@ -43,10 +46,15 @@ Set-ItemProperty "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\" ## 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. + * **Hunt** (Hunts for evidence of malicious behavior) + * **Mitigate** (Mitigates vulnerabilities by applying security settings) + * **Monitor** (Continuously monitors the system for potentially malicious behavior) + * **User** (Contains program main, IOBase, and other similar functions) + * **Util** (Contains a collection of modules that support core operations) * Configurations * Event Logs @@ -57,6 +65,7 @@ BLUESPAWN consists of 3 major modes as listed below. Several of these modules ha ## Project Authors Made with :heart: by the UVA Cyber Defense Team Windows Group + * Jake Smith ([Github](https://github.com/ION28), [Twitter](https://twitter.com/jtsmith282)) * Calvin Krist ([Github](https://github.com/CalvinKrist), [Twitter](https://twitter.com/CalvinKrist)) * Jack McDowell ([Github](https://github.com/jnmcd/)) @@ -65,13 +74,19 @@ Made with :heart: by the UVA Cyber Defense Team Windows Group ## Contributors Thanks to all of the folks listed below for their contributions to BLUESPAWN! + * Alexander Kluth ([Github](https://github.com/alexclooze)) Want to help? Take a look at the current issues, add ideas for new features, write some code, and create a pull request! ## Special Thanks We would like to provide a special thank you to the following projects that have helped us to build BLUESPAWN: + * Microsoft's documentation and examples on the Windows API +* The Department of Defense's Defense Information Systems Agency (DISA) for their great work in publishing STIGs and various other technical security guidance for Windows. +* [@hasherezade](https://github.com/hasherezade)'s [PE Sieve](https://github.com/hasherezade/pe-sieve), which currently manages our process analytics * The [MITRE's ATT&CK Project](https://attack.mitre.org/) which has put together an amazing framework for which to consider, document, and categorize attacker tradercraft +* [Sean Metcalf](https://twitter.com/PyroTek3)'s Active Directory Security blog [ADSecurity](https://adsecurity.org/) * Red Canary's [Atomic Red Team Project](https://github.com/redcanaryco/atomic-red-team) which has been incredibly useful in helping to test the detections we are building +* [@op7ic](https://github.com/op7ic)'s [EDR-Testing-Script](https://github.com/op7ic/EDR-Testing-Script) Project * The Japan Computer Emergency Response Team (JPCERT)'s [Tool Analysis Result Sheet](https://jpcertcc.github.io/ToolAnalysisResultSheet/) for its documentation of attacker behavior and correlation with detection opportunities diff --git a/config/buildsettings.props b/config/buildsettings.props index 75d88eea..db11c4b2 100644 --- a/config/buildsettings.props +++ b/config/buildsettings.props @@ -18,7 +18,8 @@ Disabled MaxSpeed %(AdditionalIncludeDirectories) - _UNICODE;UNICODE;_ITERATOR_DEBUG_LEVEL=1;_WIN32_WINNT=0x601%(PreprocessorDefinitions) + DELAYLOAD_IMPORTS_DEFINED;_UNICODE;UNICODE;_ITERATOR_DEBUG_LEVEL=0;_WIN32_WINNT=0x601;%(PreprocessorDefinitions) + MultiThreaded Console diff --git a/docs/assets/config.json b/docs/assets/config.json index dc707ff2..203a7128 100644 --- a/docs/assets/config.json +++ b/docs/assets/config.json @@ -20,7 +20,7 @@ "default_layers": { "enabled": true, - "urls": ["assets/example.json"] + "urls": ["assets/coverage-hunts.json", "assets/coverage-mitigations.json", "assets/coverage-all.json"] }, "comment_color": "yellow", diff --git a/docs/assets/coverage-all.json b/docs/assets/coverage-all.json new file mode 100644 index 00000000..bdd55f61 --- /dev/null +++ b/docs/assets/coverage-all.json @@ -0,0 +1,131 @@ +{ + "description": "Current Coverage of BLUESPAWN", + "filters": { + "stages": [ + "act" + ], + "platforms": [ + "windows" + ] + }, + "version": "2.1", + "name": "BLUESPAWN Overall", + "domain": "mitre-enterprise", + "techniques": [ + { + "color": "#5AADFF", + "comment": "Covered by registry checking HKCU\\Environment\\UserInitMprLogonScript", + "techniqueID": "T1037" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking AppInit_DLLs and LoadAppInit_DLLs keys", + "techniqueID": "T1103" + }, + { + "color": "#5AADFF", + "comment": "Coverd by checking most Run* keys. Future coverage will be obtained through checking files in Startup folders", + "techniqueID": "T1060" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking AppCert DLLs", + "techniqueID": "T1182" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking for Application Shimming and Custom DBs", + "techniqueID": "T1138" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking for any additional SSPs", + "techniqueID": "T1101" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking for any additional Auth/Notif Packages", + "techniqueID": "T1131" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking the Notify, Userinit, and Shell values", + "techniqueID": "T1004" + }, + { + "color": "#5AADFF", + "comment": "Covered by analyzing files in web locations for bad functions/strings", + "techniqueID": "T1100" + }, + { + "color": "#5AADFF", + "comment": "Covered by notifying a user when event logs are created for new service creation.", + "techniqueID": "T1050" + }, + { + "color": "#5AADFF", + "comment": "Covered by scanning all running processes for indicators of process injection.", + "techniqueID": "T1055" + }, + { + "color": "#5AADFF", + "comment": "Covered by scanning all running processes for indicators of process injection.", + "techniqueID": "T1093" + }, + { + "color": "#5AADFF", + "comment": "Covered by scanning all running processes for indicators of process injection.", + "techniqueID": "T1186" + }, + { + "color": "#5AADFF", + "comment": "Covered by checking Registry keys for Image File Execution Options.", + "techniqueID": "T1183" + }, + { + "color": "#FCF26B", + "comment": "Disables Wdigest which limits effectiveness of Credential Dumping (V-72753)", + "techniqueID": "T1003" + }, + { + "color": "#5AADFF", + "comment": "Runs LSASS as a PPL and requires drivers loaded into LSASS to be signed by Microsoft (M1025)", + "techniqueID": "T1177" + }, + { + "color": "#5AADFF", + "comment": "Disables LLMNR and NBT-NS to prevent against LLMNR/NBT-NS spoofing attacks (M1042-LLMNR, M1042-NBT)", + "techniqueID": "T1171" + }, + { + "color": "#FCF26B", + "comment": "Disables Windows Script Hosts which prevents some scripts from executing (M1042-WSH)", + "techniqueID": "T1064" + }, + { + "color": "#FCF26B", + "comment": "Limits anonymous access to shares (V-1093)", + "techniqueID": "T1135" + }, + { + "color": "#FCF26B", + "comment": "Filters Admin tokens over the network (V-63597)", + "techniqueID": "T1075" + }, + { + "color": "#5AADFF", + "comment": "A number of settings to strengthen UAC are applied (V-63817)", + "techniqueID": "T1088" + } + ], + "legendItems": [ + { + "label": "Strong BLUESPAWN Coverage", + "color": "#5AADFF" + }, + { + "label": "Partial BLUESPAWN Coverage", + "color": "#FCF26B" + } + ] +} diff --git a/docs/assets/coverage-hunts.json b/docs/assets/coverage-hunts.json new file mode 100644 index 00000000..877ca28b --- /dev/null +++ b/docs/assets/coverage-hunts.json @@ -0,0 +1,96 @@ +{ + "description": "Current Coverage of Hunts in BLUESPAWN", + "filters": { + "stages": [ + "act" + ], + "platforms": [ + "windows" + ] + }, + "version": "2.1", + "name": "BLUESPAWN Hunts", + "domain": "mitre-enterprise", + "techniques": [ + { + "color": "#5AADFF", + "comment": "Covered by registry checking HKCU\\Environment\\UserInitMprLogonScript", + "techniqueID": "T1037" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking AppInit_DLLs and LoadAppInit_DLLs keys", + "techniqueID": "T1103" + }, + { + "color": "#5AADFF", + "comment": "Coverd by checking most Run* keys. Future coverage will be obtained through checking files in Startup folders", + "techniqueID": "T1060" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking AppCert DLLs", + "techniqueID": "T1182" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking for Application Shimming and Custom DBs", + "techniqueID": "T1138" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking for any additional SSPs", + "techniqueID": "T1101" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking for any additional Auth/Notif Packages", + "techniqueID": "T1131" + }, + { + "color": "#5AADFF", + "comment": "Covered by registry checking the Notify, Userinit, and Shell values", + "techniqueID": "T1004" + }, + { + "color": "#5AADFF", + "comment": "Covered by analyzing files in web locations for bad functions/strings", + "techniqueID": "T1100" + }, + { + "color": "#5AADFF", + "comment": "Covered by notifying a user when event logs are created for new service creation.", + "techniqueID": "T1050" + }, + { + "color": "#5AADFF", + "comment": "Covered by scanning all running processes for indicators of process injection.", + "techniqueID": "T1055" + }, + { + "color": "#5AADFF", + "comment": "Covered by scanning all running processes for indicators of process injection.", + "techniqueID": "T1093" + }, + { + "color": "#5AADFF", + "comment": "Covered by scanning all running processes for indicators of process injection.", + "techniqueID": "T1186" + }, + { + "color": "#5AADFF", + "comment": "Covered by checking Registry keys for Image File Execution Options.", + "techniqueID": "T1183" + } + ], + "legendItems": [ + { + "label": "Strong BLUESPAWN Coverage", + "color": "#5AADFF" + }, + { + "label": "Partial BLUESPAWN Coverage", + "color": "#FCF26B" + } + ] +} diff --git a/docs/assets/coverage-mitigations.json b/docs/assets/coverage-mitigations.json new file mode 100644 index 00000000..9bbb1998 --- /dev/null +++ b/docs/assets/coverage-mitigations.json @@ -0,0 +1,61 @@ +{ + "description": "Current Coverage of Mitigations in BLUESPAWN", + "filters": { + "stages": [ + "act" + ], + "platforms": [ + "windows" + ] + }, + "version": "2.1", + "name": "BLUESPAWN Mitigations", + "domain": "mitre-enterprise", + "techniques": [ + { + "color": "#FCF26B", + "comment": "Disables Wdigest which limits effectiveness of Credential Dumping (V-72753)", + "techniqueID": "T1003" + }, + { + "color": "#5AADFF", + "comment": "Runs LSASS as a PPL and requires drivers loaded into LSASS to be signed by Microsoft (M1025)", + "techniqueID": "T1177" + }, + { + "color": "#5AADFF", + "comment": "Disables LLMNR and NBT-NS to prevent against LLMNR/NBT-NS spoofing attacks (M1042-LLMNR, M1042-NBT)", + "techniqueID": "T1171" + }, + { + "color": "#FCF26B", + "comment": "Disables Windows Script Hosts which prevents some scripts from executing (M1042-WSH)", + "techniqueID": "T1064" + }, + { + "color": "#FCF26B", + "comment": "Limits anonymous access to shares (V-1093)", + "techniqueID": "T1135" + }, + { + "color": "#FCF26B", + "comment": "Filters Admin tokens over the network (V-63597)", + "techniqueID": "T1075" + }, + { + "color": "#5AADFF", + "comment": "A number of settings to strengthen UAC are applied (V-63817)", + "techniqueID": "T1088" + } + ], + "legendItems": [ + { + "label": "Strong BLUESPAWN Coverage", + "color": "#5AADFF" + }, + { + "label": "Partial BLUESPAWN Coverage", + "color": "#FCF26B" + } + ] +} diff --git a/docs/assets/coverage.json b/docs/assets/coverage.json deleted file mode 100644 index 2b244eb9..00000000 --- a/docs/assets/coverage.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "description": "Current Coverage of BLUESPAWN", - "filters": { - "stages": [ - "act" - ], - "platforms": [ - "windows" - ] - }, - "version": "2.1", - "name": "BLUESPAWN", - "domain": "mitre-enterprise", - "techniques": [ - { - "color": "#5AADFF", - "comment": "Covered by registry checking HKCU\\Environment\\UserInitMprLogonScript", - "techniqueID": "T1037" - }, - { - "color": "#5AADFF", - "comment": "Covered by registry checking AppInit_DLLs and LoadAppInit_DLLs keys", - "techniqueID": "T1103" - }, - { - "color": "#FF9E3C", - "comment": "(In Progress) Currently checks most Run* keys, needs to check actual files in Startup folders", - "techniqueID": "T1060" - }, - { - "color": "#5AADFF", - "comment": "Covered by registry checking AppCert DLLs", - "techniqueID": "T1182" - }, - { - "color": "#5AADFF", - "comment": "Covered by registry checking for Application Shimming and Custom DBs", - "techniqueID": "T1138" - }, - { - "color": "#5AADFF", - "comment": "Covered by registry checking for any additional SSPs", - "techniqueID": "T1101" - }, - { - "color": "#5AADFF", - "comment": "Covered by registry checking for any additional Auth/Notif Packages", - "techniqueID": "T1131" - }, - { - "color": "#5AADFF", - "comment": "Covered by registry checking the Notify, Userinit, and Shell values", - "techniqueID": "T1004" - }, - { - "color": "#5AADFF", - "comment": "Covered by analyzing files in web locations for bad functions/strings", - "techniqueID": "T1100" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1035" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1031" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1050" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1084" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1015" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1128" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1077" - }, - { - "color": "#fcf26b", - "comment": "Planned coverage", - "techniqueID": "T1053" - } - ], - "legendItems": [ - { - "label": "BLUESPAWN Coverage", - "color": "#5AADFF" - }, - { - "label": "(In Progress) BLUESPAWN Coverage", - "color": "#FF9E3C" - }, - { - "label": "Planned BLUESPAWN Coverage", - "color": "#fcf26b" - } - ] -} diff --git a/testing/attack/hunt-t1183-001.bat b/testing/attack/hunt-t1183-001.bat new file mode 100644 index 00000000..4e1bf5a7 --- /dev/null +++ b/testing/attack/hunt-t1183-001.bat @@ -0,0 +1,3 @@ +reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v GlobalFlag /t REG_DWORD /d 512 +reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\notepad.exe" /v ReportingMode /t REG_DWORD /d 1 +reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\notepad.exe" /v MonitorProcess /d "C:\temp\evil.exe" \ No newline at end of file diff --git a/testing/attack/hunt-t1183-002.bat b/testing/attack/hunt-t1183-002.bat new file mode 100644 index 00000000..19201c6c --- /dev/null +++ b/testing/attack/hunt-t1183-002.bat @@ -0,0 +1 @@ +REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\paint.exe" /v Debugger /d "C:\windows\system32\calc.exe" \ No newline at end of file diff --git a/testing/clean/hunt-t1183-001-clean.bat b/testing/clean/hunt-t1183-001-clean.bat new file mode 100644 index 00000000..a3362070 --- /dev/null +++ b/testing/clean/hunt-t1183-001-clean.bat @@ -0,0 +1,2 @@ +reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v GlobalFlag /f +reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\notepad.exe" /f \ No newline at end of file diff --git a/testing/clean/hunt-t1183-002-clean.bat b/testing/clean/hunt-t1183-002-clean.bat new file mode 100644 index 00000000..f89f5df5 --- /dev/null +++ b/testing/clean/hunt-t1183-002-clean.bat @@ -0,0 +1 @@ +reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\paint.exe" /f \ No newline at end of file