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 hh::util::wstring_to_utf8(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) {
145 this->outDir = files_util::make_dir_name(hh_args.out_dir, scan_time);
146 files_util::set_output_dir(pesieve_args, outDir);
147 }
148 else {
149 this->outDir = hh_args.out_dir;
150 files_util::set_output_dir(pesieve_args, 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 << " " << 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 if (!Process32First(hProcessSnapShot, &pe32)) {
205 CloseHandle(hProcessSnapShot);
206 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
207 std::cerr << "[-] Could not enumerate processes. Error: " << GetLastError() << std::endl;
208 return 0;
209 }
210 do {
211 if (pe32.th32ProcessID == 0) continue;
212
213 USHORT processMachine;
214 USHORT nativeMachine;
215
216 // scan callback
217 const t_single_scan_status stat = scanNextProcess(pe32.th32ProcessID, pe32.szExeFile, my_report);
218 if (stat == SSCAN_IGNORED) ignored_count++;
219 if (stat == SSCAN_NOT_MATCH) filtered_count++;
220 if (stat == SSCAN_SUCCESS) scanned_count++;
221 count++;
222
223 } while (Process32Next(hProcessSnapShot, &pe32));
224
225 //close the handles
226 CloseHandle(hProcessSnapShot);
227
228 printScanRoundStats(scanned_count, ignored_count, filtered_count);
229 return count;
230}
231
232void HHScanner::printSingleReport(pesieve::t_report& report)
233{
234 if (hh_args.quiet) return;
235
236 if (report.errors == pesieve::ERROR_SCAN_FAILURE) {
237 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
238 WORD old_color = hh::util::set_color(MAKE_COLOR(SILVER, DARK_RED));
239 if (report.errors == pesieve::ERROR_SCAN_FAILURE) {
240 std::cout << "[!] Could not access: " << std::dec << report.pid;
241 }
242 hh::util::set_color(old_color);
243 std::cout << std::endl;
244 return;
245 }
246#ifndef _WIN64
247 if (report.is_64bit) {
248 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
250 std::cout << "[!] Partial scan: " << std::dec << report.pid << " : " << (report.is_64bit ? 64 : 32) << "b";
251 hh::util::set_color(old_color);
252 std::cout << std::endl;
253 }
254#endif
255 if (report.suspicious) {
256 int color = YELLOW;
257 if (report.replaced || report.implanted) {
258 color = RED;
259 }
260 if (report.is_managed) {
261 color = MAKE_COLOR(color, DARK_BLUE);
262 }
263 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
264 WORD old_color = hh::util::set_color(color);
265 std::cout << ">> Detected: " << std::dec << report.pid;
266 if (report.is_managed) {
267 std::cout << " [.NET]";
268 }
269 hh::util::set_color(old_color);
270 std::cout << std::endl;
271 }
272}
273
274bool is_arch_ok(const t_process_type& process_arch, const bool is_process_wow64)
275{
276 if (process_arch == PROCESS_ALL) {
277 return true;
278 }
279#ifdef _WIN64
280 static const int is_curr_wow = false;
281 static const int is_curr_32 = false;
282 if (process_arch == PROCESS_32BIT && is_process_wow64) {
283 return true;
284 }
285 if (process_arch == PROCESS_64BIT && !is_process_wow64) {
286 return true;
287 }
288#else
289 static const int is_curr_wow = process_util::is_wow_64(GetCurrentProcess());
290 static const int is_curr_32 = true;
291 if (is_curr_wow) {
292 if (is_process_wow64 == is_curr_wow) {
293 if (process_arch == PROCESS_32BIT) return true;
294 }
295 else {
296 if (process_arch == PROCESS_64BIT) return true;
297 }
298 }
299#endif
300 return false;
301}
302
303t_single_scan_status HHScanner::shouldScanProcess(const hh_params &hh_args, const time_t hh_initTime, const DWORD pid, const WCHAR* exe_file)
304{
305 bool found = false;
306
307 const bool check_time = (hh_args.ptimes != TIME_UNDEFINED) ? true : false;
308 // filter by the time
309
310 if (check_time) { // if the parameter was set
311 const time_t process_time = static_cast<time_t>(util::process_start_time(pid));
312 if (process_time == INVALID_TIME) return SSCAN_ERROR0; //skip process if cannot retrieve the time
313
314 // if HH was started after the process
315 if (hh_initTime > process_time) {
316 time_t time_diff = hh_initTime - process_time;
317 if (time_diff > hh_args.ptimes) return SSCAN_NOT_MATCH; // skip process created before the supplied time
318 }
319 }
320 //filter by the names/PIDs
321 if (hh_args.names_list.size() || hh_args.pids_list.size()) {
322 if (!util::is_searched_name(exe_file, hh_args.names_list) && !util::is_searched_pid(pid, hh_args.pids_list)) {
323 //it is not the searched process, so skip it
324 return SSCAN_NOT_MATCH;
325 }
326 found = true;
327 }
328 if (!found && hh_args.ignored_names_list.size()) {
329 if (util::is_searched_name(exe_file, hh_args.ignored_names_list)) {
330 return SSCAN_IGNORED;
331 }
332 }
333 return SSCAN_READY;
334}
335
336t_single_scan_status HHScanner::scanNextProcess(DWORD pid, WCHAR* exe_file, HHScanReport &my_report)
337{
338 const bool is_process_wow64 = process_util::is_wow_64_by_pid(pid);
339 if (!is_arch_ok(hh_args.process_arch, is_process_wow64)) {
340 return SSCAN_IGNORED;
341 }
343 if (res != SSCAN_READY) {
344 return res;
345 }
346 if (!hh_args.quiet) {
347 const std::lock_guard<std::mutex> stdOutLock(g_stdOutMutex);
348 std::wcout << ">> Scanning PID: "
349 << std::setw(PID_FIELD_SIZE) << std::dec << pid
350 << " : " << exe_file;
351
352 if (is_process_wow64) {
353 std::cout << " : 32b";
354 }
355 std::cout << std::endl;
356 }
357 //perform the scan:
358 pesieve::t_params &pesieve_args = this->hh_args.pesieve_args;
359 pesieve_args.pid = pid;
360
361 pesieve::t_report report = PESieve_scan(&pesieve_args);
362 my_report.appendReport(report, exe_file);
363
364 printSingleReport(report);
365 if (report.scanned > 0) {
366 return SSCAN_SUCCESS;
367 }
368 return SSCAN_ERROR1;
369}
370
372{
373 const time_t scan_start = time(NULL); //start time of the current scan
374 pesieve::t_params &pesieve_args = this->hh_args.pesieve_args;
375 initOutDir(scan_start, pesieve_args);
376
377 HHScanReport *my_report = new HHScanReport(GetTickCount(), scan_start);
378 scanProcesses(*my_report);
379
380 my_report->setEndTick(GetTickCount(), time(NULL));
381 return my_report;
382}
383
385{
386 if (!hh_args.log) {
387 return false;
388 }
389
390 std::wstringstream stream;
391 hh_report->toString(stream, pesieve::SHOW_ALL);
392
393 static std::mutex logMutx;
394 const std::lock_guard<std::mutex> lock(logMutx);
395 return files_util::write_to_file("hollows_hunter.log", stream.str(), true);
396}
397
398void HHScanner::summarizeScan(HHScanReport *hh_report, const pesieve::t_results_filter rfilter)
399{
400 if (!hh_report) return;
401 std::wstringstream summary_str;
402
403 if (!this->hh_args.json_output) {
404 hh_report->toString(summary_str, rfilter);
405 std::wcout << summary_str.rdbuf();
406 }
407 else {
408 hh_report->toJSON(summary_str, this->hh_args);
409 std::wcout << summary_str.rdbuf();
410 }
411
412 if (hh_args.pesieve_args.out_filter != OUT_NO_DIR) {
413 //file the same report into the directory with dumps:
414 if (hh_report->countReports(rfilter)) {
415 std::string report_path = files_util::join_path(this->outDir, "summary.json");
416
417 static std::mutex summaryMutx;
418 const std::lock_guard<std::mutex> lock(summaryMutx);
419 //TODO: fix JSON formatting for the appended reports
420 std::wstringstream summary_str1;
421 hh_report->toJSON(summary_str1, this->hh_args);
422 files_util::write_to_file(report_path, summary_str1.str(), true);
423 }
424 }
425 if (hh_args.log) {
426 writeToLog(hh_report);
427 }
428 if (hh_args.suspend_suspicious) {
430 }
431 if (hh_args.kill_suspicious) {
433 }
434}
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)
struct hh_params t_hh_params
#define TIME_UNDEFINED
Definition hh_params.h:7
t_process_type
Definition hh_params.h:18
@ PROCESS_64BIT
Definition hh_params.h:21
@ PROCESS_32BIT
Definition hh_params.h:20
@ PROCESS_ALL
Definition hh_params.h:19
bool is_arch_ok(const t_process_type &process_arch, const bool is_process_wow64)
#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)
WORD set_color(WORD color)
Definition term_util.cpp:21
std::string wstring_to_utf8(const std::wstring &wstr)
Definition term_util.cpp:40
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(IN DWORD processID)
Definition time_util.cpp:56
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::mutex g_stdOutMutex
Definition term_util.cpp:10
#define MAKE_COLOR(fg_color, bg_color)
Definition term_util.h:27
#define SILVER
Definition term_util.h:17
#define RED
Definition term_util.h:22
#define DARK_MAGENTA
Definition term_util.h:15
#define DARK_BLUE
Definition term_util.h:11
#define YELLOW
Definition term_util.h:24
#define DARK_RED
Definition term_util.h:14
#define INVALID_TIME
Definition time_util.h:7