diff --git a/.gitignore b/.gitignore index bb4eba2e..9cbbfcde 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ vcpkg/ *.lib *.aps +# BLUESPAWN-common +BLUESPAWN-common/bluespawnpb/*.h +BLUESPAWN-common/bluespawnpb/*.cc + # BLUESPAWN-win-client BLUESPAWN-win-client/external/ BLUESPAWN-win-client/resources/severe diff --git a/BLUESPAWN-common/bluespawnpb/bluespawn.proto b/BLUESPAWN-common/bluespawnpb/bluespawn.proto new file mode 100644 index 00000000..5420212c --- /dev/null +++ b/BLUESPAWN-common/bluespawnpb/bluespawn.proto @@ -0,0 +1,193 @@ +syntax = "proto3"; +package bluespawn.protobuffer; + +option go_package = "BLUESPAWN/BLUESPAWN-common/bluespawnpb"; + +/* Core BLUESPAWN RPC Server */ +service BluespawnRPC { + // Clients logging to Server + rpc SendLogMessage(LogMessage) returns (ResponseMessage) {} + rpc RecordDetection(Detection) returns (ResponseMessage) {} + rpc AddAssociation(DetectionAssociation) returns (ResponseMessage) {} + rpc UpdateCertainty(DetectionCertaintyUpdate) returns (ResponseMessage) {} +} + +/* Data Type Enums */ +// General and Log +enum LogDetail { + Low = 0; + Moderate = 1; + High = 2; +} + +enum LogSeverity { + LogError = 0; + LogWarn = 1; + LogInfo = 2; + LogVerbose = 3; +} + +// Windows Objects +enum RegistryType { + RegSz = 0; + RegExpandSz = 1; + RegMultiSz = 2; + RegDword = 3; + RegBinary = 4; +} + +// Detections +enum DetectionType { + ProcessDetection = 0; + RegistryDetection = 1; + FileDetection = 2; + ServiceDetection = 3; + OtherDetection = 4; +} + +enum DetectionRecordType { + PreScan = 0; + PostScan = 1; +} + +enum RegistryDetectionType { + CommandReference = 0; + FileReference = 1; + FolderReference = 2; + PipeReference = 3; + ShareReference = 4; + UserReference = 5; + Configuration = 6; + Unknown = 7; +} + +enum ProcessDetectionType { + MaliciousProcess = 0; + MaliciousImage = 1; + MaliciousMemory = 2; + MaliciousCommand = 3; +} + +/* Message Serializers Types */ +// General and Log +message LogMessage { + string client_id = 1; + int64 timestamp = 2; + string message = 3; + LogSeverity severity = 4; + LogDetail detail = 5; +} + +message ResponseMessage { + bool received = 1; + bool success = 2; + string message = 3; // optional +} + +// Windows Objects +message RegistryKey { + string key_path = 1; + bool exists = 2; +} + +message RegistryValue { + string value_name = 1; + string value_data = 2; +} + +// Detections +message DetectionAssociation { + int64 detection_id = 1; // NOTE: Request change to string + int64 associated_id = 2; // NOTE: Request change to string + double strength = 3; +} + +message DetectionCertaintyUpdate { + int64 id = 1; // NOTE: Request change to string + double raw_certainty = 2; + double certainty = 3; +} + +message YaraScanResult { + repeated string known_bad_rules = 1; + repeated string indicator_rules = 2; +} + +message ProcessDetectionData { + ProcessDetectionType type = 1; + uint64 pid = 2; // optional + uint64 tid = 3; // optional + string process_name = 4; // optional + string process_path = 5; // optional + string process_command = 6; // optional + // TODO: add repeated ParentProcess which is a ProcessDetectionData + string base_address = 7; // optional + uint64 memory_size = 8; // optional + string image_name = 9; // optional +} +message FileDetectionData { + bool exists = 1; + string file_path = 2; + string file_name = 3; + string file_extension = 4; // optional + string file_type = 5; // optional + string executor = 6; // optional + string md5 = 7; // optional + string sha1 = 8; // optional + string sha256 = 9; // optional + uint64 last_opened = 10; // optional + uint64 file_created = 11; // optional + YaraScanResult yara = 12; // optional + bool file_signed = 13; // optional + string signer = 14; // optional +} +message RegistryDetectionData { + string key_path = 1; + RegistryKey key = 2; + RegistryValue value = 3; // optional + RegistryDetectionType type = 4; // optional +} +message ServiceDetectionData { + string service_name = 1; // optional + string display_name = 2; // optional + string description = 3; // optional + string file_path = 4; // optional +} +message OtherDetectionData { + string type = 1; + map properties = 2; +} + +message ScanInfo { + double raw_certainty = 1; + double certainty = 2; + map assocations = 3; // Detection Id (int64), certainity (double) +} + +message DetectionData { + oneof data { + ProcessDetectionData process_data = 1; + FileDetectionData file_data = 2; + RegistryDetectionData registry_data = 3; + ServiceDetectionData service_data = 4; + OtherDetectionData other_data = 5; + } +} + +message DetectionContext { + repeated string hunts = 1; + int64 first_evidence_time = 2; // optional + int64 detection_created_time = 3; + string note = 4; // optional +} + +message Detection { + int64 id = 1; // NOTE: Request change to string + int64 timestamp = 2; + DetectionType type = 3; + DetectionRecordType record_type = 4; + ScanInfo info = 5; + DetectionData data = 6; + DetectionContext context = 7; +} + diff --git a/BLUESPAWN-server/docker-compose.yml b/BLUESPAWN-server/docker-compose.yml new file mode 100644 index 00000000..cc3fec87 --- /dev/null +++ b/BLUESPAWN-server/docker-compose.yml @@ -0,0 +1,37 @@ +version: '3.8' +services: + # MySQL: https://hub.docker.com/_/mysql + mysql: + build: + context: . + dockerfile: ./mysql/Dockerfile + environment: + - MYSQL_ROOT_PASSWORD=Chiapet1 + - MYSQL_DATABASE=bluespawn_server + - MYSQL_USER=bluespawn + - MYSQL_PASSWORD=Chiapet1 + restart: on-failure + ports: + - "3306:3306" + networks: + - server_core + volumes: + - /data/mysql + - type: bind + source: ./mysql/init + target: /docker-entrypoint-initdb.d/ + read_only: true + # RPC Server for Client<->Server Communication written in Go/GRPC + rpc_server: + build: + context: ../ + dockerfile: ./BLUESPAWN-server/rpc_server/Dockerfile + ports: + - "50052:50052" + depends_on: + - mysql + networks: + - server_core +networks: + server_core: + driver: bridge diff --git a/BLUESPAWN-server/mysql/Dockerfile b/BLUESPAWN-server/mysql/Dockerfile new file mode 100644 index 00000000..57138cf0 --- /dev/null +++ b/BLUESPAWN-server/mysql/Dockerfile @@ -0,0 +1 @@ +FROM mysql:8 diff --git a/BLUESPAWN-server/mysql/init/build_db_schema.sql b/BLUESPAWN-server/mysql/init/build_db_schema.sql new file mode 100644 index 00000000..ce779269 --- /dev/null +++ b/BLUESPAWN-server/mysql/init/build_db_schema.sql @@ -0,0 +1,64 @@ +-- MySQL dump 10.13 Distrib 8.0.21, for Win64 (x86_64) +-- +-- Host: 127.0.0.1 Database: bluespawn_server +-- ------------------------------------------------------ +-- Server version 8.0.21 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +USE bluespawn_server; + +-- +-- Table structure for table `clients` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE IF NOT EXISTS `clients` ( + `client_id` int unsigned NOT NULL AUTO_INCREMENT, + `client_guid` varchar(45) DEFAULT NULL, + `hostname` varchar(256) DEFAULT NULL, + `ip_address` varchar(46) DEFAULT NULL, + `last_heartbeat` datetime DEFAULT NULL, + PRIMARY KEY (`client_id`), + UNIQUE KEY `client_id_UNIQUE` (`client_id`), + UNIQUE KEY `client_guid_UNIQUE` (`client_guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `messages` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE IF NOT EXISTS `messages` ( + `message_id` int unsigned NOT NULL AUTO_INCREMENT, + `client_id` varchar(45) DEFAULT NULL, + `timestamp` datetime DEFAULT NULL, + `message` varchar(256) DEFAULT NULL, + `severity` int DEFAULT NULL, + `detail` int DEFAULT NULL, + PRIMARY KEY (`message_id`), + UNIQUE KEY `message_id_UNIQUE` (`message_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/BLUESPAWN-server/rpc_server/Dockerfile b/BLUESPAWN-server/rpc_server/Dockerfile new file mode 100644 index 00000000..81e1fb15 --- /dev/null +++ b/BLUESPAWN-server/rpc_server/Dockerfile @@ -0,0 +1,34 @@ +FROM golang:1.15 + +ENV PROTOC_VER 3.13.0 + +# Install dependencies +RUN apt-get update --fix-missing && apt-get install -y \ + zip unzip build-essential curl wget + +# Install protoc +WORKDIR /tmp +RUN wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \ + && unzip protoc-${PROTOC_VER}-linux-x86_64.zip \ + && cp -vv ./bin/protoc /usr/local/bin + +# Install Go Protocol Buffers plugin +RUN go get -u google.golang.org/protobuf/cmd/protoc-gen-go && go install google.golang.org/protobuf/cmd/protoc-gen-go + +# Install gRPC +RUN go get -u google.golang.org/grpc && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc + +# Change to /usr/src +WORKDIR /usr/src + +# Copy BLUESPAWN source code into container +COPY ./ ./ + +# Compile Protobuf +RUN protoc --go_out="$GOPATH/src" --go-grpc_out="$GOPATH/src" ./BLUESPAWN-common/bluespawnpb/bluespawn.proto + +# Build and start rpc_server +RUN cd ./BLUESPAWN-server/rpc_server && \ + go build + +ENTRYPOINT ["./BLUESPAWN-server/rpc_server/rpc_server"] diff --git a/BLUESPAWN-server/rpc_server/rpc_server.go b/BLUESPAWN-server/rpc_server/rpc_server.go new file mode 100644 index 00000000..4d859ada --- /dev/null +++ b/BLUESPAWN-server/rpc_server/rpc_server.go @@ -0,0 +1,62 @@ +package main + +import ( + "context" + "log" + "net" + + "google.golang.org/grpc" + pb "BLUESPAWN/BLUESPAWN-common/bluespawnpb" +) + +const ( + port = ":50052" +) + +type server struct {} + +func (s *server) Svc() *pb.BluespawnRPCService { + return &pb.BluespawnRPCService{ + SendLogMessage: s.SendLogMessage, + RecordDetection: s.RecordDetection, + AddAssociation: s.AddAssociation, + UpdateCertainty: s.UpdateCertainty, + } +} + +func (s *server) SendLogMessage(ctx context.Context, in *pb.LogMessage) (*pb.ResponseMessage, error) { + log.Printf("Received Log Message: %v", in.GetMessage()) + return &pb.ResponseMessage{Received: true, Success: true}, nil +} + +func (s *server) RecordDetection(ctx context.Context, in *pb.Detection) (*pb.ResponseMessage, error) { + log.Printf("Received Detection: ID %d", in.GetId()) + return &pb.ResponseMessage{Received: true, Success: true}, nil +} + +func (s *server) AddAssociation(ctx context.Context, in *pb.DetectionAssociation) (*pb.ResponseMessage, error) { + log.Printf("Received Association for Detection ID %d and ID %d with strength %f", in.GetDetectionId(), in.GetAssociatedId(), in.GetStrength()) + return &pb.ResponseMessage{Received: true, Success: true}, nil +} + +func (s *server) UpdateCertainty(ctx context.Context, in *pb.DetectionCertaintyUpdate) (*pb.ResponseMessage, error) { + log.Printf("Received Detection Certainty update for ID %d to %f", in.GetId(), in.GetCertainty()) + return &pb.ResponseMessage{Received: true, Success: true}, nil +} + +func createServer() *server { + s := &server{} + return s +} + +func main() { + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + pb.RegisterBluespawnRPCService(s, createServer().Svc()) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/BLUESPAWN-win-client/BLUESPAWN-client.vcxproj b/BLUESPAWN-win-client/BLUESPAWN-client.vcxproj index 1d142e74..6f65a868 100644 --- a/BLUESPAWN-win-client/BLUESPAWN-client.vcxproj +++ b/BLUESPAWN-win-client/BLUESPAWN-client.vcxproj @@ -19,6 +19,8 @@ + + @@ -96,7 +98,9 @@ + + @@ -149,6 +153,8 @@ + + @@ -237,6 +243,7 @@ + @@ -265,6 +272,7 @@ + @@ -297,12 +305,15 @@ true + + true + $(SolutionDir)build\$(PlatformTarget)\$(Configuration)\$(MSBuildProjectName).log - $(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\libpeconv\libpeconv\include;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\include;%(AdditionalIncludeDirectories) + $(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\libpeconv\libpeconv\include;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\;$(SolutionDir)BLUESPAWN-win-client\external\pe-sieve\include;$(SolutionDir);%(AdditionalIncludeDirectories) MultiThreaded MultiThreadedDebug Async @@ -316,6 +327,15 @@ Adding manifest to BLUESPAWN-client.exe + + Generate corresponding .h/cc files based on the current gRPC protobuf configuration + + + "$(SolutionDir)vcpkg\packages\protobuf_x86-windows-static\tools\protobuf\protoc.exe" --error_format=msvs --proto_path="$(SolutionDir)BLUESPAWN-common\bluespawnpb" --cpp_out="$(SolutionDir)BLUESPAWN-common\bluespawnpb" "bluespawn.proto" && "$(SolutionDir)vcpkg\packages\protobuf_x86-windows-static\tools\protobuf\protoc.exe" --error_format=msvs --proto_path="$(SolutionDir)BLUESPAWN-common\bluespawnpb" --grpc_out="$(SolutionDir)BLUESPAWN-common\bluespawnpb" --plugin=protoc-gen-grpc="$(SolutionDir)vcpkg\packages\grpc_x86-windows-static\tools\grpc\grpc_cpp_plugin.exe" "$(SolutionDir)BLUESPAWN-common\bluespawnpb\bluespawn.proto" + + + "$(SolutionDir)vcpkg\packages\protobuf_x64-windows-static\tools\protobuf\protoc.exe" --error_format=msvs --proto_path="$(SolutionDir)BLUESPAWN-common\bluespawnpb" --cpp_out="$(SolutionDir)BLUESPAWN-common\bluespawnpb" "bluespawn.proto" && "$(SolutionDir)vcpkg\packages\protobuf_x64-windows-static\tools\protobuf\protoc.exe" --error_format=msvs --proto_path="$(SolutionDir)BLUESPAWN-common\bluespawnpb" --grpc_out="$(SolutionDir)BLUESPAWN-common\bluespawnpb" --plugin=protoc-gen-grpc="$(SolutionDir)vcpkg\packages\grpc_x64-windows-static\tools\grpc\grpc_cpp_plugin.exe" "$(SolutionDir)BLUESPAWN-common\bluespawnpb\bluespawn.proto" + {159B2E72-9553-4E17-9BEC-CB92FCA8D0B0} diff --git a/BLUESPAWN-win-client/external/pe-sieve b/BLUESPAWN-win-client/external/pe-sieve index 78db1a1e..adf33da7 160000 --- a/BLUESPAWN-win-client/external/pe-sieve +++ b/BLUESPAWN-win-client/external/pe-sieve @@ -1 +1 @@ -Subproject commit 78db1a1e73614828f8d9e71a4502aae4414930b7 +Subproject commit adf33da795be5f0a345cb693578e2b3d568e1a53 diff --git a/BLUESPAWN-win-client/headers/util/Utils.h b/BLUESPAWN-win-client/headers/util/Utils.h index 79ad0470..8fd34f18 100644 --- a/BLUESPAWN-win-client/headers/util/Utils.h +++ b/BLUESPAWN-win-client/headers/util/Utils.h @@ -2,17 +2,18 @@ #include -#include #include +#include #define ADD_ALL_VECTOR(v1, v2) \ { \ auto& tmp = v2; \ - for(auto& v : tmp){ \ - v1.emplace_back(v); \ - } \ + for(auto& v : tmp) { \ + v1.emplace_back(v); \ + } \ } +int64_t FileTimeToInteger(const FILETIME& st); int64_t SystemTimeToInteger(const SYSTEMTIME& st); std::wstring FormatWindowsTime(const SYSTEMTIME& systemtime); std::wstring FormatWindowsTime(const FILETIME& systemtime); diff --git a/BLUESPAWN-win-client/headers/util/log/LogLevel.h b/BLUESPAWN-win-client/headers/util/log/LogLevel.h index d9e4ba08..8f40e3de 100644 --- a/BLUESPAWN-win-client/headers/util/log/LogLevel.h +++ b/BLUESPAWN-win-client/headers/util/log/LogLevel.h @@ -2,131 +2,118 @@ #include -#include #include +#include #include namespace Log { - /** + /** * This denotes the severity of a log message. This is intended to be used by * LogSinks to choose how to record a given log message. */ - enum class Severity { - LogError = 0, - LogWarn = 1, - LogInfo = 2, - LogVerbose = 3 - }; - - /** + enum class Severity { LogError = 0, LogWarn = 1, LogInfo = 2, LogVerbose = 3 }; + + /** * This indicates the level of detail in the log level. */ - enum class Detail { - Low = 0, - Moderate = 1, - High = 2 - }; + enum class Detail { Low = 0, Moderate = 1, High = 2 }; - /// Forward declare log sink - class LogSink; + /// Forward declare log sink + class LogSink; - /** + /** * This class represents the "level" of a log message. This is similar to Severity * in that it categorizes logs, but it's inteded to extend the functionality present * in a manner that doesn't affect the log sinks by allowing the enabling or disabling * of certain logging levels. */ - class LogLevel { - private: - - /// Whether or not sinks should record log messages under this level - bool enabled; - - /// The sinks to which messages at this level will be recorded - std::vector sinks; - - public: - /// The severity at which this log level operates - const Severity severity; - - /// The level of detail present at this logging level - const std::optional detail; - - /// Default logging levels available, though custom ones can be created - static LogLevel - LogError, // Intended for logging errors - LogWarn, // Intended for logging warnings - LogInfo1, // Intended for logging high level operational information - LogInfo2, // Intended for logging moderately detailed operational information - LogInfo3, // Intended for logging very detailed operational information - LogVerbose1, // Intended for a low level of verbosity - LogVerbose2, // Intended for a moderate level of verbosity - LogVerbose3; // Intended for a high level of verbosity - - /** + class LogLevel { + private: + /// Whether or not sinks should record log messages under this level + bool enabled; + + /// The sinks to which messages at this level will be recorded + std::vector sinks; + + public: + /// The severity at which this log level operates + const Severity severity; + + /// The level of detail present at this logging level + const std::optional detail; + + /// Default logging levels available, though custom ones can be created + static LogLevel LogError, // Intended for logging errors + LogWarn, // Intended for logging warnings + LogInfo1, // Intended for logging high level operational information + LogInfo2, // Intended for logging moderately detailed operational information + LogInfo3, // Intended for logging very detailed operational information + LogVerbose1, // Intended for a low level of verbosity + LogVerbose2, // Intended for a moderate level of verbosity + LogVerbose3; // Intended for a high level of verbosity + + /** * Creates a new log level, enabled by default, with a given severity. * * @param severity The severity of messages under this logging level * @param detail The level of detail present at this logging level */ - LogLevel( - IN Severity severity, - IN CONST std::optional& detail = std::nullopt OPTIONAL - ); + LogLevel(IN Severity severity, IN CONST std::optional& detail = std::nullopt OPTIONAL); - /** + /** * Creates a new log level with a given severity. * * @param severity The severity of messages under this logging level * @param DefaultState Indicates whether or not log messages should be recorded * by default when logged at this logging level. */ - LogLevel( - IN Severity severity, - IN bool DefaultState, - IN CONST std::optional& detail = std::nullopt OPTIONAL - ); + LogLevel(IN Severity severity, + IN bool DefaultState, + IN CONST std::optional& detail = std::nullopt OPTIONAL); - /** + /** * Enables logging at this level */ - void Enable(); + void Enable(); - /** + /** * Disables logging at this level */ - void Disable(); + void Disable(); - /** + /** * Toggles logging at this level */ - bool Toggle(); + bool Toggle(); - /** + /** * Indicates whether or not this log level is enabled. * * @return A boolean indicating whether or not log messages at this level should * be recorded. */ - bool Enabled() const; + bool Enabled() const; - /** + /** * Adds a sink to which messages logged at this level are recorded. If the level already * is logging to the sink, this has no effect. * * @param sink The sink to add */ - void AddSink( - IN LogSink* sink - ); + void AddSink(IN LogSink* sink); - /** + /** * Logs the given message at this level in the sinks configured for this level * * @param message The message to log */ - void LogMessage( - IN CONST std::wstring& message - ); - }; -} \ No newline at end of file + void LogMessage(IN CONST std::wstring& message); + + /** + * Implicit cast to an int to return the severity + * + * @return severity as an int + */ + operator int() const; + }; +} // namespace Log diff --git a/BLUESPAWN-win-client/headers/util/log/ServerSink.h b/BLUESPAWN-win-client/headers/util/log/ServerSink.h new file mode 100644 index 00000000..2d7809a7 --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/log/ServerSink.h @@ -0,0 +1,77 @@ +#pragma once + +#include "util/rpc/RpcClient.h" + +#include "DetectionSink.h" +#include "LogSink.h" + +namespace Log { + + /** + * ServerSink provides a sink for the logger that will send logs to a remote server, usually a BLUESPAWN-server installation + */ + class ServerSink : public LogSink, public DetectionSink { + /// Rpc Server Client + RpcClient::RpcClient client; + + /// The remote server (http(s)://)IP:PORT or (http(s)://)FQDN:PORT that will recieve the logs + std::string ServerAddress; + + /// Tags for messages sent at different levels + std::string MessageTags[4] = { "error", "warning", "info", "other" }; + + /// A set of IDs created for detections already sent to the server + std::set detections; + + void AddAssociation(IN DWORD detection_id, IN DWORD associated, IN double strength); + + public: + /** + * Default constructor for ServerSink. Must provide a server address to send the logs + */ + ServerSink(const std::string address); + + /** + * Outputs a message to the target server if its logging level is enabled. + * + * @param level The level at which the message is being logged + * @param message The message to log + */ + virtual void LogMessage(const LogLevel& level, const std::wstring& message); + + /** + * Compares this ServerSink to another LogSink. All LogSink objects referring to the same file are considered + * equal + * + * @param sink The LogSink to compare + * + * @return Whether or not the argument and this sink are considered equal. + */ + virtual bool operator==(const LogSink& sink) const; + + /** + * Updates the raw and combined certainty values associated with a detection + * + * @param detection The detection to update + */ + virtual void UpdateCertainty(IN CONST std::shared_ptr& detection); + + /** + * Records a detection, sending the information to the server. + * + * @param detection The detection to record + * @param type The type of record this is, either PreScan or PostScan + */ + virtual void RecordDetection(IN CONST std::shared_ptr& detection, IN RecordType type); + + /** + * Records an association between two detections and informs the server + * + * @param first The first detection in the assocation. This detection's ID will be lower than the second's. + * @param second The second detection in the association. + */ + virtual void RecordAssociation(IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength); + }; +} // namespace Log diff --git a/BLUESPAWN-win-client/headers/util/rpc/RpcClient.h b/BLUESPAWN-win-client/headers/util/rpc/RpcClient.h new file mode 100644 index 00000000..71e4bdea --- /dev/null +++ b/BLUESPAWN-win-client/headers/util/rpc/RpcClient.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include + +#include "util/log/DetectionSink.h" +#include "util/log/LogLevel.h" +#include "util/log/Loggable.h" +#include "util/wrappers.hpp" + +#include "BLUESPAWN-common/bluespawnpb/bluespawn.grpc.pb.h" + +using namespace bluespawn; +using grpc::Channel; +using grpc::ClientContext; +using grpc::Status; + +namespace RpcClient { + class RpcClient { + private: + std::unique_ptr stub_; + + protobuffer::Detection SerializeDetectionObject(IN CONST std::shared_ptr& detection, + IN CONST RecordType type); + + public: + RpcClient() : + stub_(protobuffer::BluespawnRPC::NewStub( + grpc::CreateChannel("localhost:50052", grpc::InsecureChannelCredentials()))){}; + RpcClient(std::shared_ptr channel) : stub_(protobuffer::BluespawnRPC::NewStub(channel)){}; + + bool RecordDetection(IN CONST std::shared_ptr& detection, IN CONST RecordType type); + bool AddAssociation(IN DWORD detection_id, IN DWORD associated, IN double strength); + bool UpdateCertainty(IN const std::shared_ptr& detection); + + bool SendLogMessage(const std::wstring& msg, Log::Severity severity, Log::Detail detail); + }; + +}; // namespace RpcClient diff --git a/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp b/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp index 1186c2f2..8d534844 100644 --- a/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp +++ b/BLUESPAWN-win-client/src/user/BLUESPAWN.cpp @@ -10,6 +10,7 @@ #include "util/log/CLISink.h" #include "util/log/DebugSink.h" #include "util/log/JSONSink.h" +#include "util/log/ServerSink.h" #include "util/log/XMLSink.h" #include "hunt/hunts/HuntT1036.h" @@ -255,7 +256,22 @@ void Bluespawn::check_correct_arch() { } } -void ParseLogSinks(const std::string& sinks, const std::string& logdir) { +void ParseLogSinks(const std::string& sinks, const std::string& logdir, const std::string& serverAddress) { + bool serverEnabled = false; + if(serverAddress.length() > 0 && sinks.find("server") == std::string::npos) { + LOG_WARNING("Specified a remote server for logs, but did not enable server log sink. Enabling..."); + Bluespawn::io.InformUser(L"Specified a remote server for logs, but did not enable server log sink. " + L"Enabling..."); + serverEnabled = true; + } else if(serverAddress.length() == 0 && sinks.find("server") != std::string::npos) { + LOG_ERROR("Tried to enable the server log sink, but did not specify a remote server. No logs will be sent!"); + Bluespawn::io.AlertUser(L"Tried to enable the server log sink, but did not specify a remote server. No logs " + "will be sent!", + 5000, ImportanceLevel::MEDIUM); + } else if(serverAddress.length() > 0 && sinks.find("server") != std::string::npos) { + serverEnabled = true; + } + std::set sink_set; for(unsigned startIdx = 0; startIdx < sinks.size();) { auto endIdx{ sinks.find(',', startIdx) }; @@ -298,6 +314,10 @@ void ParseLogSinks(const std::string& sinks, const std::string& logdir) { auto JSON = std::make_shared(outputFolderPath); Log::AddSink(JSON, levels); Bluespawn::detectionSinks.emplace_back(JSON); + } else if(serverEnabled) { + auto server = std::make_shared(serverAddress); + Log::AddSink(server, levels); + Bluespawn::detectionSinks.emplace_back(server); } else if(sink == "debug") { auto debug = std::make_shared(); Log::AddSink(debug, levels); @@ -380,6 +400,8 @@ int main(int argc, char* argv[]) { options.add_options("log") ("o,output", "Specify the output folder for any logs written to a file", cxxopts::value()->default_value(".")) + ("server", "Specify the remote address of the server in the form of IP, FQDN, IP:PORT, or FQDN:PORT. Must be provided if you specify server as a log sink", + cxxopts::value()->default_value("")) ; options.add_options("mitigate") @@ -420,7 +442,8 @@ int main(int argc, char* argv[]) { } } - ParseLogSinks(result["log"].as(), result["output"].as()); + ParseLogSinks(result["log"].as(), result["output"].as(), + result["server"].as()); if(result.count("hunt") || result.count("monitor")) { if(result.count("hunt")) { diff --git a/BLUESPAWN-win-client/src/util/Utils.cpp b/BLUESPAWN-win-client/src/util/Utils.cpp index 65cf3ede..cb0f8818 100644 --- a/BLUESPAWN-win-client/src/util/Utils.cpp +++ b/BLUESPAWN-win-client/src/util/Utils.cpp @@ -1,58 +1,67 @@ #include "util/Utils.h" + #include + +#include #include #include -#include - -int64_t SystemTimeToInteger(const SYSTEMTIME& st){ - FILETIME ft; - SystemTimeToFileTime(&st, &ft); - ULARGE_INTEGER lv_Large; +int64_t FileTimeToInteger(const FILETIME& ft) { + ULARGE_INTEGER lv_Large; - lv_Large.LowPart = ft.dwLowDateTime; - lv_Large.HighPart = ft.dwHighDateTime; + lv_Large.LowPart = ft.dwLowDateTime; + lv_Large.HighPart = ft.dwHighDateTime; - return lv_Large.QuadPart; + return lv_Large.QuadPart; } -std::wstring FormatWindowsTime(const SYSTEMTIME& st){ - std::wostringstream w; - w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " << - std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." << - ((st.wMilliseconds % 10000000) * 100) << "Z"; - return w.str(); -} +int64_t SystemTimeToInteger(const SYSTEMTIME& st) { + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + + ULARGE_INTEGER lv_Large; + lv_Large.LowPart = ft.dwLowDateTime; + lv_Large.HighPart = ft.dwHighDateTime; -std::wstring FormatWindowsTime(const FILETIME& ft){ - SYSTEMTIME st; - FileTimeToSystemTime(&ft, &st); + return lv_Large.QuadPart; +} - std::wostringstream w; - w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " << - std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." << - st.wMilliseconds << "Z"; - return w.str(); +std::wstring FormatWindowsTime(const SYSTEMTIME& st) { + std::wostringstream w; + w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " + << std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." + << ((st.wMilliseconds % 10000000) * 100) << "Z"; + return w.str(); } +std::wstring FormatWindowsTime(const FILETIME& ft) { + SYSTEMTIME st; + FileTimeToSystemTime(&ft, &st); + + std::wostringstream w; + w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " + << std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." + << st.wMilliseconds << "Z"; + return w.str(); +} -std::wstring FormatWindowsTime(const std::wstring& windowsTime){ - SYSTEMTIME st; - FILETIME ft; +std::wstring FormatWindowsTime(const std::wstring& windowsTime) { + SYSTEMTIME st; + FILETIME ft; - ULONGLONG time = (ULONGLONG) stoull(windowsTime); - ULONGLONG nano = 0; + ULONGLONG time = (ULONGLONG) stoull(windowsTime); + ULONGLONG nano = 0; - ft.dwHighDateTime = (DWORD) ((time >> 32) & 0xFFFFFFFF); - ft.dwLowDateTime = (DWORD) (time & 0xFFFFFFFF); + ft.dwHighDateTime = (DWORD)((time >> 32) & 0xFFFFFFFF); + ft.dwLowDateTime = (DWORD)(time & 0xFFFFFFFF); - FileTimeToSystemTime(&ft, &st); - nano = (time % 10000000) * 100; // Display nanoseconds instead of milliseconds for higher resolution + FileTimeToSystemTime(&ft, &st); + nano = (time % 10000000) * 100; // Display nanoseconds instead of milliseconds for higher resolution - std::wostringstream w; - w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " << - std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." << - nano << "Z"; - return w.str(); + std::wostringstream w; + w << std::setfill(L'0') << st.wYear << "-" << std::setw(2) << st.wMonth << "-" << std::setw(2) << st.wDay << " " + << std::setw(2) << st.wHour << ":" << std::setw(2) << st.wMinute << ":" << std::setw(2) << st.wSecond << "." + << nano << "Z"; + return w.str(); } diff --git a/BLUESPAWN-win-client/src/util/log/LogLevel.cpp b/BLUESPAWN-win-client/src/util/log/LogLevel.cpp index 0c76d76a..07be8c65 100644 --- a/BLUESPAWN-win-client/src/util/log/LogLevel.cpp +++ b/BLUESPAWN-win-client/src/util/log/LogLevel.cpp @@ -1,56 +1,52 @@ #include "util/log/LogLevel.h" + #include "util/log/LogSink.h" namespace Log { - LogLevel::LogLevel(IN Severity severity, - IN CONST std::optional& detail OPTIONAL) : - enabled{ true }, - severity{ severity }, - detail{ detail }{} - LogLevel::LogLevel(IN Severity severity, - IN bool DefaultState, - IN CONST std::optional& detail OPTIONAL) : - enabled{ enabled }, - severity{ severity }, - detail{ detail }{} + LogLevel::LogLevel(IN Severity severity, IN CONST std::optional& detail OPTIONAL) : + enabled{ true }, severity{ severity }, detail{ detail } {} + LogLevel::LogLevel(IN Severity severity, IN bool DefaultState, IN CONST std::optional& detail OPTIONAL) : + enabled{ enabled }, severity{ severity }, detail{ detail } {} + + LogLevel LogLevel::LogError{ Severity::LogError, true }; - LogLevel LogLevel::LogError{Severity::LogError, true }; + LogLevel LogLevel::LogWarn{ Severity::LogWarn, true }; - LogLevel LogLevel::LogWarn{Severity::LogWarn, true }; + LogLevel LogLevel::LogInfo1{ Severity::LogInfo, true, Detail::Low }; - LogLevel LogLevel::LogInfo1{ Severity::LogInfo, true, Detail::Low }; + LogLevel LogLevel::LogInfo2{ Severity::LogInfo, false, Detail::Moderate }; - LogLevel LogLevel::LogInfo2{ Severity::LogInfo, false, Detail::Moderate }; + LogLevel LogLevel::LogInfo3{ Severity::LogInfo, false, Detail::High }; - LogLevel LogLevel::LogInfo3{ Severity::LogInfo, false, Detail::High }; + LogLevel LogLevel::LogVerbose1{ Severity::LogVerbose, false, Detail::Low }; - LogLevel LogLevel::LogVerbose1{ Severity::LogVerbose, false, Detail::Low }; + LogLevel LogLevel::LogVerbose2{ Severity::LogVerbose, false, Detail::Moderate }; - LogLevel LogLevel::LogVerbose2{ Severity::LogVerbose, false, Detail::Moderate }; + LogLevel LogLevel::LogVerbose3{ Severity::LogVerbose, false, Detail::High }; - LogLevel LogLevel::LogVerbose3{ Severity::LogVerbose, false, Detail::High }; + void LogLevel::Enable() { enabled = true; } + void LogLevel::Disable() { enabled = false; } + bool LogLevel::Toggle() { return enabled = !enabled; } + bool LogLevel::Enabled() const { return enabled; } - void LogLevel::Enable(){ enabled = true; } - void LogLevel::Disable(){ enabled = false; } - bool LogLevel::Toggle(){ return enabled = !enabled; } - bool LogLevel::Enabled() const { return enabled; } + void LogLevel::AddSink(IN LogSink* sink) { + for(auto existing : sinks) { + if(*existing == *sink) { + return; + } + } - void LogLevel::AddSink(IN LogSink* sink){ - for(auto existing : sinks){ - if(*existing == *sink){ - return; - } - } + sinks.emplace_back(sink); + } - sinks.emplace_back(sink); - } + void LogLevel::LogMessage(IN CONST std::wstring& message) { + if(enabled) { + for(auto sink : sinks) { + sink->LogMessage(*this, message); + } + } + } - void LogLevel::LogMessage(IN CONST std::wstring& message){ - if(enabled){ - for(auto sink : sinks){ - sink->LogMessage(*this, message); - } - } - } -} \ No newline at end of file + LogLevel::operator int() const { return static_cast(severity); } +} // namespace Log diff --git a/BLUESPAWN-win-client/src/util/log/ServerSink.cpp b/BLUESPAWN-win-client/src/util/log/ServerSink.cpp new file mode 100644 index 00000000..73eaa81f --- /dev/null +++ b/BLUESPAWN-win-client/src/util/log/ServerSink.cpp @@ -0,0 +1,68 @@ +#include "util/log/ServerSink.h" + +#include +#include +#include +#include + +#include "util/StringUtils.h" +#include "util/Utils.h" +#include "util/rpc/RpcClient.h" + +#include "user/bluespawn.h" + +namespace Log { + + ServerSink::ServerSink(const std::string address) : + ServerAddress{ address }, client{ grpc::CreateChannel(address, grpc::InsecureChannelCredentials()) } {} + + void ServerSink::UpdateCertainty(IN CONST std::shared_ptr& detection) { + BeginCriticalSection __{ *detection }; + + if(detections.find(detection->dwID) != detections.end()) { + bool response = client.UpdateCertainty(detection); + } + } + + void ServerSink::AddAssociation(IN DWORD detection_id, IN DWORD associated, IN double strength) { + if(detections.find(detection_id) != detections.end()) { + bool response = client.AddAssociation(detection_id, associated, strength); + } + } + + void ServerSink::RecordDetection(IN CONST std::shared_ptr& detection, IN RecordType type) { + if(type == RecordType::PreScan && !Bluespawn::EnablePreScanDetections) { + return; + } + + BeginCriticalSection __{ *detection }; + + bool response = client.RecordDetection(detection, type); + } + + void ServerSink::RecordAssociation(IN CONST std::shared_ptr& first, + IN CONST std::shared_ptr& second, + IN CONST Association& strength) { + UpdateCertainty(first); + UpdateCertainty(second); + + if(detections.find(first->dwID) != detections.end()) { + AddAssociation(first->dwID, second->dwID, strength); + } + + if(detections.find(second->dwID) != detections.end()) { + AddAssociation(second->dwID, first->dwID, strength); + } + } + + void ServerSink::LogMessage(const LogLevel& level, const std::wstring& message) { + if(level.Enabled()) { + bool response = client.SendLogMessage(message, level.severity, *level.detail); + } + } + + bool ServerSink::operator==(const LogSink& sink) const { + return (bool) dynamic_cast(&sink) && + dynamic_cast(&sink)->ServerAddress == ServerAddress; + } +}; // namespace Log diff --git a/BLUESPAWN-win-client/src/util/rpc/RpcClient.cpp b/BLUESPAWN-win-client/src/util/rpc/RpcClient.cpp new file mode 100644 index 00000000..407f245a --- /dev/null +++ b/BLUESPAWN-win-client/src/util/rpc/RpcClient.cpp @@ -0,0 +1,191 @@ +#include "util/rpc/RpcClient.h" + +#include +#include + +#include + +namespace RpcClient { + protobuffer::Detection RpcClient::SerializeDetectionObject(IN CONST std::shared_ptr& detection, + IN CONST RecordType type = RecordType::PostScan) { + protobuffer::Detection message; + message.set_id(detection->dwID); + message.set_timestamp(FileTimeToInteger(detection->context.DetectionCreatedTime)); + message.set_type(static_cast(detection->type)); + message.set_record_type(static_cast(type)); + + protobuffer::ScanInfo* info = message.mutable_info(); + info->set_certainty(detection->info.GetCertainty()); + info->set_raw_certainty(detection->info.GetIntrinsicCertainty()); + + protobuffer::DetectionData* data = message.mutable_data(); + if(std::holds_alternative(detection->data)) { + ProcessDetectionData& processData = std::get(detection->data); + protobuffer::ProcessDetectionData* pbProcessData = data->mutable_process_data(); + pbProcessData->set_type(static_cast(processData.type)); + pbProcessData->set_pid(*processData.PID); + pbProcessData->set_tid(*processData.TID); + pbProcessData->set_process_name(processData.ProcessName ? WidestringToString(*processData.ProcessName) : + ""); + pbProcessData->set_process_path(processData.ProcessPath ? WidestringToString(*processData.ProcessPath) : + ""); + pbProcessData->set_process_command( + processData.ProcessCommand ? WidestringToString(*processData.ProcessCommand) : ""); + std::wstringstream baseAddress{}; + baseAddress << std::hex << *processData.BaseAddress; + pbProcessData->set_base_address(WidestringToString(baseAddress.str())); + pbProcessData->set_memory_size(*processData.MemorySize); + pbProcessData->set_image_name(processData.ImageName ? WidestringToString(*processData.ImageName) : ""); + } else if(std::holds_alternative(detection->data)) { + FileDetectionData fileData = std::get(detection->data); + protobuffer::FileDetectionData* pbFileData = data->mutable_file_data(); + pbFileData->set_exists(fileData.FileFound); + pbFileData->set_file_path(WidestringToString(fileData.FilePath)); + pbFileData->set_file_name(WidestringToString(fileData.FileName)); + pbFileData->set_file_extension(fileData.FileExtension ? WidestringToString(*fileData.FileExtension) : ""); + pbFileData->set_file_type(fileData.FileType ? WidestringToString(*fileData.FileType) : ""); + pbFileData->set_executor(fileData.Executor ? WidestringToString(*fileData.Executor) : ""); + pbFileData->set_md5(fileData.MD5 ? WidestringToString(*fileData.MD5) : ""); + pbFileData->set_sha1(fileData.SHA1 ? WidestringToString(*fileData.SHA1) : ""); + pbFileData->set_sha256(fileData.SHA256 ? WidestringToString(*fileData.SHA256) : ""); + pbFileData->set_last_opened(FileTimeToInteger(*fileData.LastOpened)); + pbFileData->set_last_opened(FileTimeToInteger(*fileData.FileCreated)); + protobuffer::YaraScanResult* pbYaraScanResult = pbFileData->mutable_yara(); + if(fileData.yara.has_value()) { + for(auto& str : fileData.yara.value().vKnownBadRules) { + pbYaraScanResult->add_known_bad_rules(WidestringToString(str)); + } + for(auto& str : fileData.yara.value().vIndicatorRules) { + pbYaraScanResult->add_indicator_rules(WidestringToString(str)); + } + } + pbFileData->set_file_signed(*fileData.FileSigned); + pbFileData->set_signer(fileData.Signer ? WidestringToString(*fileData.Signer) : ""); + } else if(std::holds_alternative(detection->data)) { + RegistryDetectionData registryData = std::get(detection->data); + protobuffer::RegistryDetectionData* pbRegistryData = data->mutable_registry_data(); + pbRegistryData->set_key_path(WidestringToString(registryData.KeyPath)); + protobuffer::RegistryKey* pbRegistryKey = pbRegistryData->mutable_key(); + pbRegistryKey->set_key_path(WidestringToString(registryData.key.GetName())); + pbRegistryKey->set_exists(registryData.key.Exists()); + protobuffer::RegistryValue* pbRegistryValue = pbRegistryData->mutable_value(); + if(registryData.value.has_value()) { + pbRegistryValue->set_value_name(WidestringToString((*registryData.value).wValueName)); + pbRegistryValue->set_value_data(WidestringToString((*registryData.value).ToString())); + } + pbRegistryData->set_type(static_cast(registryData.type)); + } else if(std::holds_alternative(detection->data)) { + ServiceDetectionData serviceData = std::get(detection->data); + protobuffer::ServiceDetectionData* pbServiceData = data->mutable_service_data(); + pbServiceData->set_service_name(serviceData.ServiceName ? WidestringToString(*serviceData.ServiceName) : + ""); + pbServiceData->set_display_name(serviceData.DisplayName ? WidestringToString(*serviceData.DisplayName) : + ""); + pbServiceData->set_description(serviceData.Description ? WidestringToString(*serviceData.Description) : ""); + pbServiceData->set_file_path(serviceData.FilePath ? WidestringToString(*serviceData.FilePath) : ""); + } else { + OtherDetectionData otherData = std::get(detection->data); + protobuffer::OtherDetectionData* pbOtherData = data->mutable_other_data(); + pbOtherData->set_type(WidestringToString(otherData.DetectionType)); + auto mutable_properties = pbOtherData->mutable_properties(); + for(const auto& [key, value] : otherData.DetectionProperties) { + mutable_properties->insert({ WidestringToString(key), WidestringToString(value) }); + } + } + + protobuffer::DetectionContext* context = message.mutable_context(); + + if(detection->context.hunts.size()) { + for(const auto& hunt : detection->context.hunts) { + context->add_hunts(WidestringToString(hunt)); + } + } + + if(detection->context.FirstEvidenceTime) { + context->set_first_evidence_time(FileTimeToInteger(*detection->context.FirstEvidenceTime)); + } + + context->set_first_evidence_time(FileTimeToInteger(detection->context.DetectionCreatedTime)); + + if(detection->context.note) { + context->set_note(WidestringToString(*detection->context.note)); + } + + return message; + } + + bool RpcClient::RpcClient::RecordDetection(IN const std::shared_ptr& detection, + IN CONST RecordType type) { + auto request = SerializeDetectionObject(detection, type); + + protobuffer::ResponseMessage response; + ClientContext context; + + Status status = stub_->RecordDetection(&context, request, &response); + + if(status.ok() && response.received()) { + return true; + } else { + return false; + } + } + + bool RpcClient::AddAssociation(IN DWORD detection_id, IN DWORD associated, IN double strength) { + protobuffer::DetectionAssociation request; + request.set_detection_id(detection_id); + request.set_associated_id(associated); + request.set_strength(strength); + + protobuffer::ResponseMessage response; + ClientContext context; + + Status status = stub_->AddAssociation(&context, request, &response); + + if(status.ok() && response.received()) { + return true; + } else { + return false; + } + } + + bool RpcClient::UpdateCertainty(IN const std::shared_ptr& detection) { + protobuffer::DetectionCertaintyUpdate request; + request.set_id(detection->dwID); + request.set_raw_certainty(detection->info.GetIntrinsicCertainty()); + request.set_certainty(detection->info.GetCertainty()); + + protobuffer::ResponseMessage response; + ClientContext context; + + Status status = stub_->UpdateCertainty(&context, request, &response); + + if(status.ok() && response.received()) { + return true; + } else { + return false; + } + } + + bool RpcClient::SendLogMessage(const std::wstring& msg, Log::Severity sev, Log::Detail det) { + SYSTEMTIME st; + GetSystemTime(&st); + + protobuffer::LogMessage request; + request.set_client_id("TO_BE_SET_IN_THE_FUTURE"); + request.set_timestamp(SystemTimeToInteger(st)); + request.set_message(WidestringToString(msg)); + request.set_severity(static_cast(sev)); + request.set_detail(static_cast(det)); + + protobuffer::ResponseMessage response; + ClientContext context; + + Status status = stub_->SendLogMessage(&context, request, &response); + + if(status.ok() && response.received()) { + return true; + } else { + return false; + } + } +} // namespace RpcClient diff --git a/config/buildsettings.props b/config/buildsettings.props index 262ebcc7..0180035a 100644 --- a/config/buildsettings.props +++ b/config/buildsettings.props @@ -20,7 +20,8 @@ stdcpplatest Disabled MaxSpeed - DELAYLOAD_IMPORTS_DEFINED;_UNICODE;UNICODE;_ITERATOR_DEBUG_LEVEL=0;_WIN32_WINNT=0x601;%(PreprocessorDefinitions) + DELAYLOAD_IMPORTS_DEFINED;_UNICODE;UNICODE;_ITERATOR_DEBUG_LEVEL=0;_WIN32_WINNT=0x601;%(PreprocessorDefinitions) + DELAYLOAD_IMPORTS_DEFINED;_UNICODE;UNICODE;_ITERATOR_DEBUG_LEVEL=2;_WIN32_WINNT=0x601;%(PreprocessorDefinitions) MultiThreaded MultiThreadedDebug false diff --git a/vcpkg_response_file.txt b/vcpkg_response_file.txt index 30bc44c5..4fb10b80 100644 --- a/vcpkg_response_file.txt +++ b/vcpkg_response_file.txt @@ -6,3 +6,5 @@ libzip:x64-windows-static libzip:x86-windows-static nlohmann-json:x64-windows-static nlohmann-json:x86-windows-static +grpc:x64-windows-static +grpc:x86-windows-static