HollowsHunter
Scans all running processes. Recognizes and dumps a variety of potentially malicious implants (replaced/implanted PEs, shellcodes, hooks, in-memory patches).
Loading...
Searching...
No Matches
hh_scanner.cpp
Go to the documentation of this file.
1#include "hh_scanner.h"
2
3#include <iostream>
4#include <fstream>
5#include <sstream>
6#include <iomanip>
7#include <codecvt>
8#include <locale>
9#include <time.h>
10#include <tlhelp32.h>
11
12#include "util/suspend.h"
13#include "util/time_util.h"
14#include "term_util.h"
15#include "util/process_util.h"
16
17#include <paramkit.h>
18#include <mutex>
19
20#define PID_FIELD_SIZE 8
21
22using namespace pesieve;
23
24namespace files_util {
25
26 std::string join_path(const std::string &baseDir, const std::string &subpath)
27 {
28 std::stringstream stream;
29 if (baseDir.length() > 0) {
30 stream << baseDir;
31 stream << "\\";
32 }
33 stream << subpath;
34 return stream.str();
35 }
36
37 std::string make_dir_name(const std::string &baseDir, time_t timestamp)
38 {
39 std::stringstream stream;
40 if (baseDir.length() > 0) {
41 stream << baseDir;
42 stream << "\\";
43 }
44 stream << "scan_";
45 stream << timestamp;
46 return stream.str();
47 }
48
49 bool set_output_dir(t_params &args, const std::string &new_dir)
50 {
51 const size_t new_len = new_dir.length();
52 if (!new_len) return false;
53
54 const char* new_dir_cstr = new_dir.c_str();
55 size_t buffer_len = sizeof(args.output_dir) - 1; //leave one char for '\0'
56 if (new_len > buffer_len) return false;
57
58 memset(args.output_dir, 0, buffer_len);
59 memcpy(args.output_dir, new_dir_cstr, new_len);
60 return true;
61 }
62
63 bool write_to_file(const std::string &report_path, const std::wstring &summary_str, const bool append)
64 {
65 std::wofstream final_report;
66 if (append) {
67 final_report.open(report_path, std::ios_base::app);
68 }
69 else {
70 final_report.open(report_path);
71 }
72 if (final_report.is_open()) {
73 final_report << summary_str;
74 final_report.close();
75 return true;
76 }
77 return false;
78 }
79}; // namespace files_util
80
81namespace util {
82
83 bool is_searched_name(const WCHAR* processName, const std::set<std::wstring> &names_list)
84 {
85 for (auto itr = names_list.begin(); itr != names_list.end(); ++itr) {
86 const WCHAR* searchedName = itr->c_str();
87 if (_wcsicmp(processName, searchedName) == 0) {
88 return true;
89 }
90 }
91 return false;
92 }
93
94 bool is_searched_pid(long pid, const std::set<long> &pids_list)
95 {
96 std::set<long>::iterator found = pids_list.find(pid);
97 if (found != pids_list.end()) {
98 return true;
99 }
100 return false;
101 }
102
103 template <typename TYPE_T>
104 std::string list_to_str(const std::set<TYPE_T> &list)
105 {
106 std::wstringstream stream;
107
108 for (auto itr = list.begin(); itr != list.end(); ) {
109 stream << *itr;
110 ++itr;
111 if (itr != list.end()) {
112 stream << ", ";
113 }
114 }
115 return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(stream.str());
116 }
117
118}; //namespace util
119
120//----
121
122HHScanner::HHScanner(t_hh_params& _args, time_t _initTime)
123 : hh_args(_args), initTime(_initTime)
124{
125 if (!initTime) {
126 initTime = time(NULL);
127 }
128 isScannerWow64 = process_util::is_wow_64(GetCurrentProcess());
129}
130
132{
133#ifndef _WIN64
134 if (process_util::is_wow_64(GetCurrentProcess())) {
135 return false;
136 }
137#endif
138 return true;
139}
140
141void HHScanner::initOutDir(time_t scan_time, pesieve::t_params &pesieve_args)
142{
143 //set unique path
144 if (hh_args.unique_dir) {
146 files_util::set_output_dir(pesieve_args, outDir);
147 }
148 else {
149 this->outDir = hh_args.out_dir;
151 }
152}
153
154void HHScanner::printScanRoundStats(size_t found, size_t ignored_count, size_t not_matched_count)
155{
156#ifdef _DEBUG
157 if (!found && not_matched_count) {
158 if (!hh_args.quiet) {
159 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
160 std::cout << "[WARNING] Some processes were filtered out basing on the defined criteria: " << not_matched_count << " skipped" << std::endl;
161 }
162 }
163#endif
164 if (!found && hh_args.names_list.size() > 0) {
165 if (!hh_args.quiet) {
166 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
167 std::cout << "[WARNING] No process from the list: {" << util::list_to_str(hh_args.names_list) << "} was scanned!" << std::endl;
168 }
169 }
170 if (!found && hh_args.pids_list.size() > 0) {
171 if (!hh_args.quiet) {
172 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
173 std::cout << "[WARNING] No process from the list: {" << util::list_to_str(hh_args.pids_list) << "} was scanned!" << std::endl;
174 }
175 }
176 if (ignored_count > 0) {
177 if (!hh_args.quiet) {
178 std::string info1 = (ignored_count > 1) ? "processes" : "process";
179 std::string info2 = (ignored_count > 1) ? "were" : "was";
180 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
181 std::cout << "[INFO] " << std::dec << ignored_count << " " << info1 << " from the list : {" << util::list_to_str(hh_args.ignored_names_list) << "} " << info2 << " ignored!" << std::endl;
182 }
183 }
184}
185
187{
188 size_t count = 0;
189 size_t scanned_count = 0;
190 size_t ignored_count = 0;
191 size_t filtered_count = 0;
192
193 HANDLE hProcessSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
194 if (hProcessSnapShot == INVALID_HANDLE_VALUE) {
195 const DWORD err = GetLastError();
196 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
197 std::cerr << "[-] Could not create modules snapshot. Error: " << std::dec << err << std::endl;
198 return 0;
199 }
200
201 PROCESSENTRY32 pe32 = { 0 };
202 pe32.dwSize = sizeof(PROCESSENTRY32);
203
204 //check all modules in the process, including the main module:
205 if (!Process32First(hProcessSnapShot, &pe32)) {
206 CloseHandle(hProcessSnapShot);
207 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
208 std::cerr << "[-] Could not enumerate processes. Error: " << GetLastError() << std::endl;
209 return 0;
210 }
211 do {
212 if (pe32.th32ProcessID == 0) continue;
213 // scan callback
214 const t_single_scan_status stat = scanNextProcess(pe32.th32ProcessID, pe32.szExeFile, my_report);
215 if (stat == SSCAN_IGNORED) ignored_count++;
216 if (stat == SSCAN_NOT_MATCH) filtered_count++;
217 if (stat == SSCAN_SUCCESS) scanned_count++;
218 count++;
219
220 } while (Process32Next(hProcessSnapShot, &pe32));
221
222 //close the handles
223 CloseHandle(hProcessSnapShot);
224
225 printScanRoundStats(scanned_count, ignored_count, filtered_count);
226 return count;
227}
228
229void HHScanner::printSingleReport(pesieve::t_report& report)
230{
231 if (hh_args.quiet) return;
232
233 if (report.errors == pesieve::ERROR_SCAN_FAILURE) {
234 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
235 WORD old_color = set_color(MAKE_COLOR(SILVER, DARK_RED));
236 if (report.errors == pesieve::ERROR_SCAN_FAILURE) {
237 std::cout << "[!] Could not access: " << std::dec << report.pid;
238 }
239 set_color(old_color);
240 std::cout << std::endl;
241 return;
242 }
243#ifndef _WIN64
244 if (report.is_64bit) {
245 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
246 WORD old_color = set_color(MAKE_COLOR(SILVER, DARK_MAGENTA));
247 std::cout << "[!] Partial scan: " << std::dec << report.pid << " : " << (report.is_64bit ? 64 : 32) << "b";
248 set_color(old_color);
249 std::cout << std::endl;
250 }
251#endif
252 if (report.suspicious) {
253 int color = YELLOW;
254 if (report.replaced || report.implanted) {
255 color = RED;
256 }
257 if (report.is_managed) {
258 color = MAKE_COLOR(color, DARK_BLUE);
259 }
260 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
261 WORD old_color = set_color(color);
262 std::cout << ">> Detected: " << std::dec << report.pid;
263 if (report.is_managed) {
264 std::cout << " [.NET]";
265 }
266 set_color(old_color);
267 std::cout << std::endl;
268 }
269}
270
271t_single_scan_status HHScanner::shouldScanProcess(const hh_params &hh_args, const time_t hh_initTime, const DWORD pid, const WCHAR* exe_file)
272{
273 bool found = false;
274 const bool check_time = (hh_args.ptimes != TIME_UNDEFINED) ? true : false;
275 // filter by the time
276 time_t time_diff = 0;
277 if (check_time) { // if the parameter was set
278 const time_t process_time = util::process_start_time(pid);
279 if (process_time == INVALID_TIME) return SSCAN_ERROR0; //skip process if cannot retrieve the time
280
281 // if HH was started after the process
282 if (hh_initTime > process_time) {
283 time_diff = hh_initTime - process_time;
284 if (time_diff > hh_args.ptimes) return SSCAN_NOT_MATCH; // skip process created before the supplied time
285 }
286 }
287 //filter by the names/PIDs
288 if (hh_args.names_list.size() || hh_args.pids_list.size()) {
290 //it is not the searched process, so skip it
291 return SSCAN_NOT_MATCH;
292 }
293 found = true;
294 }
295 if (!found && hh_args.ignored_names_list.size()) {
297 return SSCAN_IGNORED;
298 }
299 }
300 return SSCAN_READY;
301}
302
303t_single_scan_status HHScanner::scanNextProcess(DWORD pid, WCHAR* exe_file, HHScanReport &my_report)
304{
305 const bool is_process_wow64 = process_util::is_wow_64_by_pid(pid);
307 if (res != SSCAN_READY) {
308 return res;
309 }
310 if (!hh_args.quiet) {
311 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
312 std::wcout << ">> Scanning PID: "
313 << std::setw(PID_FIELD_SIZE) << std::dec << pid
314 << " : " << exe_file;
315
316 if (is_process_wow64) {
317 std::cout << " : 32b";
318 }
319 std::cout << std::endl;
320 }
321 //perform the scan:
322 pesieve::t_params &pesieve_args = this->hh_args.pesieve_args;
323 pesieve_args.pid = pid;
324
325 pesieve::t_report report = PESieve_scan(pesieve_args);
326 my_report.appendReport(report, exe_file);
327
328 printSingleReport(report);
329 if (report.scanned > 0) {
330 return SSCAN_SUCCESS;
331 }
332 return SSCAN_ERROR1;
333}
334
336{
337 const time_t scan_start = time(NULL); //start time of the current scan
338 pesieve::t_params &pesieve_args = this->hh_args.pesieve_args;
339 initOutDir(scan_start, pesieve_args);
340
341 HHScanReport *my_report = new HHScanReport(GetTickCount(), scan_start);
342 scanProcesses(*my_report);
343
344 my_report->setEndTick(GetTickCount(), time(NULL));
345 return my_report;
346}
347
349{
350 if (!hh_args.log) {
351 return false;
352 }
353
354 std::wstringstream stream;
355 hh_report->toString(stream, pesieve::SHOW_ALL);
356
357 static std::mutex logMutx;
358 const std::lock_guard<std::mutex> lock(logMutx);
359 return files_util::write_to_file("hollows_hunter.log", stream.str(), true);
360}
361
362void HHScanner::summarizeScan(HHScanReport *hh_report, const pesieve::t_results_filter rfilter)
363{
364 if (!hh_report) return;
365 std::wstringstream summary_str;
366
367 if (!this->hh_args.json_output) {
368 hh_report->toString(summary_str, rfilter);
369 std::wcout << summary_str.rdbuf();
370 }
371 else {
372 hh_report->toJSON(summary_str, this->hh_args);
373 std::wcout << summary_str.rdbuf();
374 }
375
376 if (hh_args.pesieve_args.out_filter != OUT_NO_DIR) {
377 //file the same report into the directory with dumps:
378 if (hh_report->countReports(rfilter)) {
379 std::string report_path = files_util::join_path(this->outDir, "summary.json");
380
381 static std::mutex summaryMutx;
382 const std::lock_guard<std::mutex> lock(summaryMutx);
383 //TODO: fix JSON formatting for the appended reports
384 std::wstringstream summary_str1;
385 hh_report->toJSON(summary_str1, this->hh_args);
386 files_util::write_to_file(report_path, summary_str1.str(), true);
387 }
388 }
389 if (hh_args.log) {
390 writeToLog(hh_report);
391 }
394 }
397 }
398}
bool appendReport(pesieve::t_report &scan_report, const std::wstring &img_name)
Definition hh_report.cpp:19
bool setEndTick(DWORD end_tick, time_t end_time)
Definition hh_report.h:21
void toString(std::wstringstream &stream, const pesieve::t_results_filter rfilter)
std::vector< DWORD > suspicious
Definition hh_report.h:85
size_t countReports(const pesieve::t_results_filter rfilter) const
Definition hh_report.h:39
size_t toJSON(std::wstringstream &stream, const t_hh_params &params)
void printScanRoundStats(size_t found, size_t ignored_count, size_t not_matched_count)
bool writeToLog(HHScanReport *hh_report)
std::string outDir
Definition hh_scanner.h:45
void summarizeScan(HHScanReport *hh_report, const pesieve::t_results_filter rfilter)
t_single_scan_status scanNextProcess(DWORD pid, WCHAR *image_buf, HHScanReport &report)
bool isScannerWow64
Definition hh_scanner.h:49
static t_single_scan_status shouldScanProcess(const hh_params &hh_args, const time_t hh_initTime, const DWORD pid, const WCHAR *exe_file)
size_t scanProcesses(HHScanReport &my_report)
time_t initTime
Definition hh_scanner.h:48
t_hh_params & hh_args
Definition hh_scanner.h:44
HHScanReport * scan()
void initOutDir(time_t scan_time, pesieve::t_params &pesieve_args)
HHScanner(t_hh_params &_args, time_t _initTime=0)
static bool isScannerCompatibile()
void printSingleReport(pesieve::t_report &report)
#define TIME_UNDEFINED
Definition hh_params.h:7
#define PID_FIELD_SIZE
@ SSCAN_NOT_MATCH
Definition hh_scanner.h:18
@ SSCAN_IGNORED
Definition hh_scanner.h:19
@ SSCAN_READY
Definition hh_scanner.h:21
@ SSCAN_ERROR1
Definition hh_scanner.h:16
@ SSCAN_ERROR0
Definition hh_scanner.h:17
@ SSCAN_SUCCESS
Definition hh_scanner.h:20
enum single_status t_single_scan_status
bool write_to_file(const std::string &report_path, const std::wstring &summary_str, const bool append)
std::string join_path(const std::string &baseDir, const std::string &subpath)
bool set_output_dir(t_params &args, const std::string &new_dir)
std::string make_dir_name(const std::string &baseDir, time_t timestamp)
bool is_wow_64(HANDLE process)
size_t suspend_suspicious(std::vector< DWORD > &suspicious_pids)
size_t kill_suspicious(std::vector< DWORD > &suspicious_pids)
bool is_wow_64_by_pid(DWORD processID)
LONGLONG process_start_time(DWORD processID)
Definition time_util.cpp:42
bool is_searched_pid(long pid, const std::set< long > &pids_list)
bool is_searched_name(const WCHAR *processName, const std::set< std::wstring > &names_list)
std::string list_to_str(const std::set< TYPE_T > &list)
std::set< long > pids_list
Definition hh_params.h:34
bool log
Definition hh_params.h:29
bool json_output
Definition hh_params.h:30
bool kill_suspicious
Definition hh_params.h:27
std::set< std::wstring > ignored_names_list
Definition hh_params.h:35
pesieve::t_params pesieve_args
Definition hh_params.h:36
std::string out_dir
Definition hh_params.h:22
bool quiet
Definition hh_params.h:28
std::set< std::wstring > names_list
Definition hh_params.h:33
bool suspend_suspicious
Definition hh_params.h:26
bool unique_dir
Definition hh_params.h:23
LONGLONG ptimes
Definition hh_params.h:31
WORD set_color(WORD color)
Definition term_util.cpp:20
std::mutex g_stdOutMutex
Definition term_util.cpp:9
#define MAKE_COLOR(fg_color, bg_color)
Definition term_util.h:26
#define SILVER
Definition term_util.h:16
#define RED
Definition term_util.h:21
#define DARK_MAGENTA
Definition term_util.h:14
#define DARK_BLUE
Definition term_util.h:10
#define YELLOW
Definition term_util.h:23
#define DARK_RED
Definition term_util.h:13
#define INVALID_TIME
Definition time_util.h:7