Panoptes 1.0.0
Endpoint Detection and Response
Loading...
Searching...
No Matches
grpc.cpp
Go to the documentation of this file.
1#include <grpcpp/grpcpp.h>
2#include "panoptes.grpc.pb.h"
3
4#include "panoptes_service.h"
5#include "grpc.hpp"
6#include "hash.h"
7#include "pano_log.h"
8#include "utils.h"
9
10#include "TrayNotifications.h"
11#include "Configuration.hpp"
12#include <vector>
13#include <string>
14#include <algorithm>
15#include <filesystem>
16#include <regex>
17#include <filesystem>
18#include "PanoptesAMSI.h"
19
20using grpc::Channel;
21using grpc::ClientContext;
22using grpc::Status;
23
24std::unique_ptr<PanoptesExtensibility::Stub> stub_;
25std::unique_ptr<PanoptesService::Stub> selfStub_;
26std::vector<std::pair<ContainerType, int>> g_containerServerPorts;
28
29
30namespace fs = std::filesystem;
31
32void MoveFileToQuarantine(std::string filePath) {
33 std::string quarantinePath = "C:\\ProgramData\\Panoptes\\Quarantine";
34
35 if (!fs::exists(quarantinePath)) {
36 fs::create_directories(quarantinePath);
37 }
38 std::filesystem::path sourcePath(filePath);
39 std::filesystem::path destinationPath = fs::path(quarantinePath) / sourcePath.filename();
40 try {
41 fs::rename(sourcePath, destinationPath);
42 }
43 catch (const std::filesystem::filesystem_error& e) {
44 std::cerr << "Error moving file to quarantine: " << e.what() << std::endl;
45 }
46}
47
49{
50 HKEY hKey;
51 DWORD dwType = REG_DWORD;
52 DWORD dwSize = sizeof(DWORD);
53
54 // Open the key
55 LONG lResult = RegOpenKeyExA(
56 HKEY_LOCAL_MACHINE,
57 "SOFTWARE\\Panoptes",
58 0,
59 KEY_READ,
60 &hKey
61 );
62
63 if (lResult != ERROR_SUCCESS) {
64 std::cerr << "Error opening registry key. Error code: " << lResult << std::endl;
65 return false;
66 }
67
68 // Read the SRV_PORT value
69 lResult = RegQueryValueExA(
70 hKey,
71 "SRV_PORT",
72 NULL,
73 &dwType,
74 reinterpret_cast<LPBYTE>(&portValue),
75 &dwSize
76 );
77
78 RegCloseKey(hKey);
79
80 if (lResult != ERROR_SUCCESS) {
81 std::cerr << "Error reading registry value. Error code: " << lResult << std::endl;
82 return false;
83 }
84
85 if (dwType != REG_DWORD) {
86 std::cerr << "Unexpected value type in registry." << std::endl;
87 return false;
88 }
89
90 return true;
91}
92
93bool isPathInExclusions(const std::vector<std::string>& exclusions, const std::string& fullPath)
94{
95 std::filesystem::path fullPathNormalized = std::filesystem::path(fullPath).lexically_normal();
96
97 return std::any_of(exclusions.begin(), exclusions.end(),
98 [&fullPathNormalized](const std::string& path) {
99 std::filesystem::path pathNormalized = std::filesystem::path(path).lexically_normal();
100 return fullPathNormalized.string().find(pathNormalized.string()) == 0;
101 });
102}
103
104bool CheckIfMalicious(std::string jsonString) {
105 nlohmann::json jsonObject = nlohmann::json::parse(jsonString);
106 if (jsonObject.contains("yara_scan") && jsonObject["yara_scan"].contains("detected_rules"))
107 {
108 nlohmann::json yaraObject = jsonObject["yara_scan"];
109 int detected_rules = yaraObject["detected_rules"].size();
110 if (detected_rules > 0) {
111 return true;
112 }
113 }
114
115 if (jsonObject.contains("amsi_result"))
116 {
117 AmsiScanner::AMSI_RESULT_PANO amsiResult = jsonObject["amsi_result"];
118 if (amsiResult == AmsiScanner::AMSI_RESULT_PANO_DETECTED) {
119 return true;
120 }
121 }
122
123
124 return false;
125}
126
128 HKEY hKey;
129 DWORD dwDisposition;
130
131 // Create or open the key
132 LONG lResult = RegCreateKeyExA(
133 HKEY_LOCAL_MACHINE,
134 "SOFTWARE\\Panoptes",
135 0,
136 NULL,
137 REG_OPTION_NON_VOLATILE,
138 KEY_ALL_ACCESS,
139 NULL,
140 &hKey,
141 &dwDisposition
142 );
143
144 if (lResult != ERROR_SUCCESS) {
145 std::cerr << "Error creating/opening registry key. Error code: " << lResult << std::endl;
146 return false;
147 }
148
149 // Set the SRV_PORT value
150 lResult = RegSetValueExA(
151 hKey,
152 "SRV_PORT",
153 0,
154 REG_DWORD,
155 reinterpret_cast<const BYTE*>(&dwPort),
156 sizeof(dwPort)
157 );
158
159 if (lResult != ERROR_SUCCESS) {
160 std::cerr << "Error setting registry value. Error code: " << lResult << std::endl;
161 RegCloseKey(hKey);
162 return false;
163 }
164
165 RegCloseKey(hKey);
166 std::cout << "Registry entry created successfully." << std::endl;
167 return true;
168}
169
170std::string CleanUpProtobufMessage(std::string msg) {
171 size_t pos;
172 while ((pos = msg.find("\\u0000")) != std::string::npos) {
173 msg.erase(pos, 6);
174 }
175
176 nlohmann::json j = nlohmann::json::parse(msg);
177 std::time_t now = std::time(nullptr);
178 std::string formattedTime = FormatTime(now);
179 j["Time"] = std::string(formattedTime);
180 std::string dumpAgain = j.dump();
181
182 return dumpAgain;
183}
184
186 std::string server_url = "localhost:" + std::to_string(containerPort);
187 std::shared_ptr<grpc::Channel> channel = grpc::CreateChannel(server_url, grpc::InsecureChannelCredentials());
188 stub_ = PanoptesExtensibility::NewStub(channel);
189}
190
192 AckMessage reply;
193 ClientContext g_context;
194
195 MemoryScanInfo request;
196 request.set_process_id(processId);
197
198 Status status = stub_->MemoryScan(&g_context, request, &reply);
199
200 if (!status.ok()) {
201 std::cout << status.error_code() << ": " << status.error_message()
202 << std::endl;
203 }
204
205 return reply.ack_type();
206}
207
208bool PanoptesContainerClient::SendPeScanRequest(std::string pePath, std::string fileHash) {
209 AckMessage reply;
210 ClientContext context;
211
212 PeScanInfo request;
213 request.set_file_hash(fileHash);
214 request.set_portable_executable_path(pePath);
215
216 Status status = stub_->PEScan(&context, request, &reply);
217 if (!status.ok()) {
218 std::cout << status.error_code() << ": " << status.error_message() << std::endl;
219 }
220
221 return reply.ack_type();
222}
223
224void SelfQueuePeScan(std::string pePath, std::string fileHash) {
225 auto configuration = serviceContext->config;
226 if (isPathInExclusions(configuration->m_exclusions, pePath)) {
227 return;
228 }
229
230 DWORD containerPort = 0;
231 GetRegistryPortValue(containerPort);
232 std::string server_url = "localhost:" + std::to_string(containerPort);
233 std::shared_ptr<grpc::Channel> channel = grpc::CreateChannel(server_url, grpc::InsecureChannelCredentials());
234 selfStub_ = PanoptesService::NewStub(channel);
235
236 AckMessage reply;
237 ClientContext context;
238 PeScanInfo request;
239 request.set_file_hash(fileHash);
240 request.set_portable_executable_path(pePath);
241
242 Status status = selfStub_->QueuePeScan(&context, request, &reply);
243 if (!status.ok()) {
244 std::cout << status.error_code() << ": " << status.error_message() << std::endl;
245 }
246}
247
248class PanoptesImpl : public PanoptesService::Service {
249 ::grpc::Status ScanResults(::grpc::ServerContext* context, const ::ContainerReply* request, ::AckMessage* response) override {
250 google::protobuf::util::JsonPrintOptions options;
251 auto configuration = serviceContext->config;
252 options.add_whitespace = false;
253 // options.always_print_primitive_fields = true;
254 options.preserve_proto_field_names = true;
255 options.always_print_enums_as_ints = false;
256
257
258 std::string fileHash = request->file_hash();
259 std::string filePath = request->portable_executable_path();
260 std::string json_string;
261 google::protobuf::util::MessageToJsonString(*request, &json_string, options);
262 std::string cleanMsg = CleanUpProtobufMessage(json_string);
263 WriteToLogFile(cleanMsg + "\n");
264
266 auto loadedDB = serviceContext->database.load();
267 std::string entry = loadedDB.GetEntry(fileHash);
268 if (entry.empty()) {
269 loadedDB.AddEntry(fileHash, cleanMsg);
270 entry = cleanMsg;
271 }
272 else
273 {
274 entry = loadedDB.UpdateEntry(fileHash, cleanMsg);
275 }
276
277 // The PE Module just provides extra analysis on top of the AMSI/Yara Scan
278 if (!request->has_pe_scan()) {
279 if (CheckIfMalicious(entry)) {
280 std::string displayMessage = "Malicious File Detected: " + GetBaseName(filePath);
281 TrayNotifications::Tray::ShowTrayIconBalloon("Panoptes EDR Detection", displayMessage.c_str());
282
283 if (configuration->m_quartine) {
284 MoveFileToQuarantine(filePath);
285 }
286 }
287 }
288
289 response->set_ack_type(SUCCESS);
290 return ::grpc::Status::OK;
291 }
292
293 ::grpc::Status QueuePeScan(::grpc::ServerContext* context, const ::PeScanInfo* request, ::AckMessage* response) override {
294 std::string fileToScan = request->portable_executable_path();
295 if (fileToScan.empty()) {
296 return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "PE path not provided");
297 }
298
299 auto configuration = serviceContext->config;
300 if (isPathInExclusions(configuration->m_exclusions, fileToScan)) {
301 std::string message = "File is excluded from scan: " + fileToScan;
302 return ::grpc::Status(::grpc::StatusCode::UNKNOWN, message);
303 }
304
305 std::string fileHash = request->file_hash();
306 if (fileHash.empty()) {
307 fileHash = GenerateMD5(fileToScan);
308 if (fileHash.empty()) {
309 std::string message = "Failed to generate hash for " + fileToScan;
310 return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, message);
311 }
312 }
313
314 auto loadedDB = serviceContext->database.load();
315 std::string entry = loadedDB.GetEntry(fileHash);
316 if (!entry.empty())
317 {
318 WriteToLogFile(entry + "\n");
319 response->set_ack_type(AckType::SUCCESS);
320 response->set_message(entry);
321 if (CheckIfMalicious(entry)) {
322 std::string displayMessage = "Malicious File Detected: " + GetBaseName(fileToScan.c_str());
323 TrayNotifications::Tray::ShowTrayIconBalloon("Panoptes EDR Detection", displayMessage.c_str());
324
325 if (configuration->m_quartine) {
326 MoveFileToQuarantine(request->portable_executable_path());
327 }
328
329 for (auto container : g_containerServerPorts) {
330 if (container.first == ContainerType::CONTAINER_TYPE_PE) {
331 PanoptesContainerClient client = PanoptesContainerClient(container.second);
332 client.SendPeScanRequest(request->portable_executable_path(), fileHash);
333 }
334 }
335 }
336 }
337 else {
338
339 for (auto container : g_containerServerPorts) {
340 if (container.first == ContainerType::CONTAINER_TYPE_PE) {
341 continue;
342 }
343 PanoptesContainerClient client = PanoptesContainerClient(container.second);
344 client.SendPeScanRequest(request->portable_executable_path(), fileHash);
345 }
346 }
347
348 return ::grpc::Status::OK;
349 }
350
351 ::grpc::Status HealthCheck(::grpc::ServerContext* context, const ::HealthCheckRequest* request, ::HealthCheckResponse* response) override {
352 response->set_pong("pong");
353 return ::grpc::Status::OK;
354 }
355
356 ::grpc::Status Hello(::grpc::ServerContext* context, const ::ContainerInfo* request, ::AckMessage* response) override {
357 if (request->container_type() == NULL) {
358 return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "No container type provided");
359 }
360
361 std::cout << "Hello: " << request->container_type() << std::endl;
362
363 std::pair<ContainerType, int> ContainerPortInfo = std::make_pair(request->container_type(), request->grpc_port());
364 g_containerServerPorts.push_back(ContainerPortInfo);
365
366 response->set_ack_type(AckType::SUCCESS);
367 return ::grpc::Status::OK;
368 }
369};
370
371void RunServiceServer(LPVOID lpParam)
372{
373 serviceContext = reinterpret_cast<PanoptesContext*>(lpParam);
374 PanoptesImpl service;
375 grpc::ServerBuilder builder;
376 int selected_port = 0;
377 std::string server_url = "localhost:0";
378
379 //Setting the server address to localhost:0 will allow the OS to assign an available port
380 builder.AddListeningPort(server_url, grpc::InsecureServerCredentials(), &selected_port);
381 builder.RegisterService(&service);
382
383 std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
384 if (server == nullptr) {
385 auto threadError = &serviceContext->threadError;
386 bool threadState = threadError->load();
387 threadError->store(true);
388 }
389
390 bool updatedReg = CreateRegistryEntryWithPort(selected_port);
391 if (!updatedReg) {
392 auto threadError = &serviceContext->threadError;
393 bool threadState = threadError->load();
394 threadError->store(true);
395 }
396
397 server->Wait();
398}
AMSI_RESULT_PANO
The result of the AMSI scan.
@ AMSI_RESULT_PANO_DETECTED
PanoptesContainerClient(int ContainerPort)
Definition grpc.cpp:185
bool SendMemoryScanRequest(DWORD ProcessId)
Definition grpc.cpp:191
bool SendPeScanRequest(std::string PePath, std::string FileHash)
Definition grpc.cpp:208
The PanoptesImpl class is a class that implements the PanoptesExtensibility::Service interface from t...
static BOOL ShowTrayIconBalloon(LPCSTR pszTitle, LPCSTR pszText)
Display a tray icon balloon notification.
void HealthCheck()
The HealthCheck function is a thread that checks the health of the Panoptes main service.
Definition container.cpp:83
PanoptesContext * serviceContext
Definition grpc.cpp:27
std::string CleanUpProtobufMessage(std::string msg)
Definition grpc.cpp:170
void MoveFileToQuarantine(std::string filePath)
Definition grpc.cpp:32
std::unique_ptr< PanoptesExtensibility::Stub > stub_
Definition grpc.cpp:24
bool CheckIfMalicious(std::string jsonString)
Definition grpc.cpp:104
bool CreateRegistryEntryWithPort(DWORD dwPort)
Definition grpc.cpp:127
bool GetRegistryPortValue(DWORD &portValue)
Definition grpc.cpp:48
void SelfQueuePeScan(std::string pePath, std::string fileHash)
Definition grpc.cpp:224
std::unique_ptr< PanoptesService::Stub > selfStub_
Definition grpc.cpp:25
void RunServiceServer(LPVOID lpParam)
Definition grpc.cpp:371
std::vector< std::pair< ContainerType, int > > g_containerServerPorts
Definition grpc.cpp:26
bool isPathInExclusions(const std::vector< std::string > &exclusions, const std::string &fullPath)
Definition grpc.cpp:93
std::string GenerateMD5(std::string filePath)
Generate an MD5 hash of a file using the Windows Crypto API https://learn.microsoft....
Definition hash.cpp:8
unsigned char BYTE
Definition inject.h:4
unsigned long DWORD
Definition inject.h:2
void WriteToLogFile(const std::string &message)
Definition pano_log.cpp:32
std::unique_ptr< PanoptesService::Stub > stub_
std::atomic< bool > threadError
Configuration * config
std::atomic< PanoptesDatabase > database
std::string GetBaseName(const std::string &path)
Definition utils.cpp:82
std::string FormatTime(const std::time_t &time)
Definition utils.cpp:87