PE-sieve
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
scanner.cpp
Go to the documentation of this file.
1#include "scanner.h"
2
3#include <sstream>
4#include <fstream>
5#include <string>
6#include <locale>
7#include <codecvt>
8
9#include <tlhelp32.h>
10
17
18#include "headers_scanner.h"
19#include "code_scanner.h"
20#include "iat_scanner.h"
21#include "workingset_scanner.h"
22#include "mapping_scanner.h"
24#include "thread_scanner.h"
25
26using namespace pesieve;
27using namespace pesieve::util;
28
29namespace pesieve {
30
32 {
33 if (!strparam.buffer || strparam.length == 0) {
34 return false;
35 }
36 if (IsBadReadPtr(strparam.buffer, strparam.length)) {
37 return false;
38 }
39 return true;
40 }
41
42 namespace util {
43
44 void print_scantime(std::stringstream& stream, size_t timeInMs)
45 {
46 float seconds = ((float)timeInMs / 1000);
47 float minutes = ((float)timeInMs / 60000);
48 stream << std::dec << timeInMs << " ms.";
49 if (seconds > 0.5) {
50 stream << " = " << seconds << " sec.";
51 }
52 if (minutes > 0.5) {
53 stream << " = " << minutes << " min.";
54 }
55 }
56
57 }; // namespace util
58
59 void print_scan_time(const char* scanned_element, size_t total_time)
60 {
61 std::stringstream ss;
62 ss << "[*] "<< scanned_element << " scanned in ";
64 std::cout << ss.str() << std::endl;
65 }
66
67};
68
70 : args(_args), isDEP(false), isReflection(is_reflection)
71{
72 this->processHandle = procHndl;
73 if (validate_param_str(args.modules_ignored)) {
75 }
76}
77
79{
81#ifdef _WIN64
82 is_process_wow64(processHandle, &isWow64);
83#endif
84 HeadersScanner scanner(processHandle, modData, remoteModData);
85 HeadersScanReport *scan_report = scanner.scanRemote();
86 if (!scan_report) {
87 return SCAN_ERROR;
88 }
89
90 if (scan_report->archMismatch && isWow64) {
91#ifdef _DEBUG
92 std::cout << "Arch mismatch, reloading..." << std::endl;
93#endif
94 if (modData.reloadWow64()) {
95 delete scan_report; // delete previous report
96 scan_report = scanner.scanRemote();
97 }
98 }
99 scan_report->moduleFile = modData.szModName;
100 scan_report->isInPEB = modData.isModuleInPEBList();
101
103 if (is_replaced && !scan_report->isHdrReplaced()) {
105 }
106 process_report.appendReport(scan_report);
107 return is_replaced;
108}
109
111{
112 const peconv::ExportsMapper *expMap = process_report.exportsMap;
113 if (!expMap) {
114 return SCAN_ERROR;
115 }
116
117 if (process_report.isModuleReplaced(modData.moduleHandle)) {
118#ifdef _DEBUG
119 std::cout << "Cannot scan replaced module for IAT hooks!\n";
120#endif
121 return SCAN_ERROR;
122 }
123 IATScanner scanner(processHandle, modData, remoteModData, *expMap, process_report.modulesInfo, filter);
124 IATScanReport *scan_report = scanner.scanRemote();
125 if (!scan_report) {
126 return SCAN_ERROR;
127 }
129 scan_report->moduleFile = modData.szModName;
130 process_report.appendReport(scan_report);
131 return scan_res;
132}
133
135{
136 CodeScanner scanner(processHandle, modData, remoteModData);
137 scanner.setScanData(scan_data);
138 scanner.setScanInaccessible(scan_inaccessible);
139 CodeScanReport *scan_report = scanner.scanRemote();
140 if (!scan_report) {
141 return SCAN_ERROR;
142 }
144
145 scan_report->moduleFile = modData.szModName;
146 process_report.appendReport(scan_report);
147 return is_hooked;
148}
149
157
158inline bool set_non_suspicious(const std::set<ModuleScanReport*> &scan_reports, bool dnet_modules_only)
159{
160 bool is_set = false;
161 std::set<ModuleScanReport*>::iterator itr;
162 for (itr = scan_reports.begin(); itr != scan_reports.end(); ++itr) {
164 if (!report) {
165 //this should never happen
166 continue;
167 }
168 if (dnet_modules_only && !report->isDotNetModule) {
169 continue;
170 }
171 if (report->status == SCAN_SUSPICIOUS) {
172 report->status = SCAN_NOT_SUSPICIOUS;
173 is_set = true;
174 }
175 }
176 return is_set;
177}
178
180{
181 if (!process_report.isManaged // Not a .NET process
182 || this->args.dotnet_policy == pesieve::PE_DNET_NONE) // .NET policy not set
183 {
184 return false; // no filtering needed
185 }
186 bool is_set = false;
187 if (this->args.dotnet_policy == pesieve::PE_DNET_SKIP_MAPPING
188 || this->args.dotnet_policy == pesieve::PE_DNET_SKIP_ALL)
189 {
190 // set hook modules as not suspicious
191 const std::set<ModuleScanReport*> &reports = process_report.reportsByType[ProcessScanReport::REPORT_MAPPING_SCAN];
193 }
194 if (this->args.dotnet_policy == pesieve::PE_DNET_SKIP_HOOKS
195 || this->args.dotnet_policy == pesieve::PE_DNET_SKIP_ALL)
196 {
197 // set hook modules as not suspicious
198 const std::set<ModuleScanReport*> &reports = process_report.reportsByType[ProcessScanReport::REPORT_CODE_SCAN];
200 }
201 if (this->args.dotnet_policy == pesieve::PE_DNET_SKIP_SHC
202 || this->args.dotnet_policy == pesieve::PE_DNET_SKIP_ALL)
203 {
204 // set shellcodes detected by mempage scan as not suspicious
205 const std::set<ModuleScanReport*> &reports = process_report.reportsByType[ProcessScanReport::REPORT_MEMPAGE_SCAN];
206 const bool is_set1 = set_non_suspicious(reports, false);
207
208 // set shellcodes detected by thread scan as not-suspicious
209 const std::set<ModuleScanReport*>& reports2 = process_report.reportsByType[ProcessScanReport::REPORT_THREADS_SCAN];
210 const bool is_set2 = set_non_suspicious(reports2, false);
212 }
213 return is_set;
214}
215
217{
218 this->isDEP = is_DEP_enabled(this->processHandle);
219
220 const bool is_64bit = pesieve::util::is_process_64bit(this->processHandle);
221
222 ProcessScanReport *pReport = new ProcessScanReport(this->args.pid, is_64bit, this->isReflection, &this->args);
223
224 char image_buf[MAX_PATH] = { 0 };
225 GetProcessImageFileNameA(this->processHandle, image_buf, MAX_PATH);
227
228 std::stringstream errorsStr;
229
230 // scan modules
231 size_t modulesScanned = 0;
232 try {
233 modulesScanned = scanModules(*pReport);
234 } catch (std::exception &e) {
235 modulesScanned = 0;
236 errorsStr << e.what();
237 }
238
239 // scan working set
240 size_t regionsScanned = 0;
241 try {
242 regionsScanned = scanWorkingSet(*pReport);
243 } catch (std::exception &e) {
244 regionsScanned = 0;
245 errorsStr << e.what();
246 }
247
248 // scan IATs
249 size_t iatsScanned = 0;
250 if (args.iat) {
251 try {
252 iatsScanned = scanModulesIATs(*pReport);
253 }
254 catch (std::exception& e) {
255 iatsScanned = 0;
256 errorsStr << e.what();
257 }
258 }
259
260 // scan threads
261 size_t threadsScanned = 0;
262 if (args.threads) {
263 try {
264 threadsScanned = scanThreads(*pReport);
265 }
266 catch (std::exception& e) {
267 threadsScanned = 0;
268 errorsStr << e.what();
269 }
270 }
271
272 // throw error only if none of the scans was successful
274 throw std::runtime_error(errorsStr.str());
275 }
276 //post-process hooks
277 resolveHooksTargets(*pReport);
278
279 //post-process detection reports according to the .NET policy
280 filterDotNetReport(*pReport);
281 return pReport;
282}
283
285{
286 if (!util::count_workingset_entries(this->processHandle)) {
287 throw std::runtime_error("Could not query the working set. ");
288 return 0;
289 }
290 process_details proc_details(this->isReflection, this->isDEP);
291
293 std::set<mem_region_info> region_bases;
294 size_t pages_count = util::enum_workingset(processHandle, region_bases);
295 if (!args.quiet) {
296 std::cout << "Scanning workingset: " << std::dec << pages_count << " memory regions." << std::endl;
297 }
298 size_t counter = 0;
299 //now scan all the nodes:
300
301 for (auto set_itr = region_bases.begin(); set_itr != region_bases.end(); ++set_itr, ++counter) {
303
304 WorkingSetScanner scanner(this->processHandle, proc_details, region, this->args, pReport);
305 WorkingSetScanReport *my_report = scanner.scanRemote();
306 if (!my_report) {
307 continue;
308 }
309 my_report->is_listed_module = pReport.hasModule((ULONGLONG) my_report->module);
310 // this is a code section inside a PE file that was already detected
311 if (!my_report->has_pe
312 && (pReport.hasModuleContaining((ULONGLONG)my_report->module, my_report->moduleSize))
313 )
314 {
316 }
317
318 pReport.appendReport(my_report);
319 }
320 if (!args.quiet) {
322 print_scan_time("Workingset", total_time);
323 }
324 return counter;
325}
326
328{
329 MappingScanner scanner(processHandle, modData);
330 MappingScanReport *scan_report = scanner.scanRemote();
331
332 process_report.appendReport(scan_report);
333 return scan_report;
334}
335
337{
338 HMODULE hMods[1024] = { 0 };
339 const size_t modules_count = enum_modules(this->processHandle, hMods, sizeof(hMods), LIST_MODULES_ALL);
340 if (modules_count == 0) {
341 return 0;
342 }
343 if (args.imprec_mode != PE_IMPREC_NONE || args.iat != pesieve::PE_IATS_NONE) {
344 pReport.exportsMap = new peconv::ExportsMapper();
345 }
346
347 size_t counter = 0;
348 for (counter = 0; counter < modules_count; counter++) {
349 if (processHandle == nullptr) break;
351 //load module from file:
352 ModuleData modData(processHandle, module_base, true, args.use_cache);
353 ModuleScanReport *mappingScanReport = this->scanForMappingMismatch(modData, pReport);
354
355 //load the original file to make the comparisons:
356 if (!modData.loadOriginal()) {
357 if (!args.quiet) {
358 std::cout << "[!][" << args.pid << "] Suspicious: could not read the module file!" << std::endl;
359 }
360 //make a report that finding original module was not possible
361 pReport.appendReport(new UnreachableModuleReport(module_base, 0, modData.szModName));
362 continue;
363 }
364 if (modData.isDotNet()) {
365 // the process contains at least one .NET module. Treat it as managed process:
366 pReport.isManaged = true;
367 }
368 // Don't scan modules that are in the ignore list
369 const std::string plainName = peconv::get_file_name(modData.szModName);
370 if (is_in_list(plainName, this->ignoredModules)) {
371 // ...but add such modules to the exports lookup:
372 if (pReport.exportsMap) {
373 pReport.exportsMap->add_to_lookup(modData.szModName, (HMODULE)modData.original_module, (ULONGLONG)modData.moduleHandle);
374 }
375 if (!args.quiet) {
376 std::cout << "[*] Skipping ignored: " << std::hex << (ULONGLONG)modData.moduleHandle << " : " << modData.szModName << std::endl;
377 }
378 pReport.appendReport(new SkippedModuleReport(modData.moduleHandle, modData.original_size, modData.szModName));
379 continue;
380 }
381
382 if (!args.quiet) {
383 std::cout << "[*] Scanning: " << modData.szModName;
384 if (modData.isDotNet()) {
385 std::cout << " (.NET) ";
386 }
387 std::cout << std::endl;
388 }
389 //load data about the remote module
390 RemoteModuleData remoteModData(processHandle, this->isReflection, module_base);
391 if (!remoteModData.isInitialized()) {
392 //make a report that initializing remote module was not possible
393 pReport.appendReport(new MalformedHeaderReport(module_base, 0, modData.szModName));
394 continue;
395 }
396 t_scan_status is_hollowed = scanForHollows(processHandle, modData, remoteModData, pReport);
397 if (is_hollowed == SCAN_ERROR) {
398 continue;
399 }
401 //if the content does not differ, ignore the different name of the mapped file
403 }
404
405 // the module is not hollowed, so we can add it to the exports lookup:
406 if (pReport.exportsMap) {
407 pReport.exportsMap->add_to_lookup(modData.szModName, (HMODULE) modData.original_module, (ULONGLONG) modData.moduleHandle);
408 }
409
410 if (!args.no_hooks //if hooks not disabled
411 && (is_hollowed == SCAN_NOT_SUSPICIOUS) // and process is not hollowed
412 )
413 {
414 const bool scan_data = ((this->args.data >= pesieve::PE_DATA_SCAN_ALWAYS) && (this->args.data != pesieve::PE_DATA_SCAN_INACCESSIBLE_ONLY))
415 || (!this->isDEP && (this->args.data == pesieve::PE_DATA_SCAN_NO_DEP));
416
417 const bool scan_inaccessible = (this->isReflection && (this->args.data >= PE_DATA_SCAN_INACCESSIBLE));
418 scanForHooks(processHandle, modData, remoteModData, pReport, scan_data, scan_inaccessible);
419 }
420 }
421 return counter;
422}
423
425{
426 if (!pReport.exportsMap) {
427 return 0; // this feature cannot work without Exports Map
428 }
429 HMODULE hMods[1024];
430 const size_t modules_count = enum_modules(this->processHandle, hMods, sizeof(hMods), LIST_MODULES_ALL);
431 if (modules_count == 0) {
432 return 0;
433 }
434 if (!args.quiet) {
435 std::cout << "Scanning for IAT hooks: " << modules_count << " modules." << std::endl;
436 }
438 size_t counter = 0;
439 for (counter = 0; counter < modules_count; counter++) {
440 if (!processHandle) break; // this should never happen
441
443 //load module from file:
444 ModuleData modData(processHandle, module_base, true, args.use_cache);
445
446 // Don't scan modules that are in the ignore list
447 std::string plainName = peconv::get_file_name(modData.szModName);
448 if (is_in_list(plainName, this->ignoredModules)) {
449 continue;
450 }
451
452 //load data about the remote module
453 RemoteModuleData remoteModData(processHandle, this->isReflection, module_base);
454 if (remoteModData.isInitialized() == false) {
455 //make a report that initializing remote module was not possible
456 pReport.appendReport(new MalformedHeaderReport(module_base, 0, modData.szModName));
457 continue;
458 }
459
460 // do the IAT scan:
461 scanForIATHooks(processHandle, modData, remoteModData, pReport, this->args.iat);
462 }
463 if (!args.quiet) {
466 }
467 return counter;
468}
469
470
472{
473 const DWORD pid = pReport.pid; //original PID, not a reflection!
474
475 const bool is_64bit = pesieve::util::is_process_64bit(this->processHandle);
476#ifndef _WIN64
477 if (is_64bit) return 0;
478#endif
479
480 if (!args.quiet) {
481 std::cout << "Scanning threads." << std::endl;
482 }
484
485 std::vector<thread_info> threads_info;
486 if (!pesieve::util::fetch_threads_info(pid, threads_info)) { //extended info, but doesn't work on old Windows...
487
488 if (!pesieve::util::fetch_threads_by_snapshot(pid, threads_info)) { // works on old Windows, but gives less data..
489
490 if (!args.quiet) {
491 std::cout << "[-] Failed enumerating threads." << std::endl;
492 }
493 return 0;
494 }
495 }
496
497 ThreadScanner::InitSymbols(this->processHandle);
498 std::vector<thread_info>::iterator itr;
499 for (itr = threads_info.begin(); itr != threads_info.end(); ++itr) {
500 const thread_info &info = *itr;
501
502 ThreadScanner scanner(this->processHandle, this->isReflection, info, pReport.modulesInfo, pReport.exportsMap);
503 ThreadScanReport* report = scanner.scanRemote();
504 pReport.appendReport(report);
505 }
506 ThreadScanner::FreeSymbols(this->processHandle);
507
508 if (!args.quiet) {
510 print_scan_time("Threads", total_time);
511 }
512 return 0;
513}
A report from the code scan, generated by CodeScanner.
A scanner for detection of patches in the code.
A report from the headers scan, generated by HeadersScanner.
A scanner for detection of PE header's modifications.
Processes the list of the collected patches (preprocessed by PatchAnalyzer), and for those of them th...
A report from an IAT scan, generated by IATScanner.
Definition iat_scanner.h:12
A scanner for detection of IAT hooking.
Definition iat_scanner.h:62
A scanner for detection of inconsistencies in mapping. Checks if the mapped file name is different th...
Loads a module from the disk, corresponding to the module in the scanned process' memory.
Definition module_data.h:15
A base class of all the reports detailing on the output of the performed module's scan.
static t_scan_status get_scan_status(const ModuleScanReport *report)
The report aggregating the results of the performed scan.
Definition scan_report.h:19
size_t scanWorkingSet(ProcessScanReport &pReport)
Definition scanner.cpp:284
size_t scanModules(ProcessScanReport &pReport)
Definition scanner.cpp:336
size_t scanThreads(ProcessScanReport &pReport)
Definition scanner.cpp:471
ProcessScanner(HANDLE procHndl, bool is_reflection, pesieve::t_params _args)
Definition scanner.cpp:69
static t_scan_status scanForHooks(HANDLE hProcess, ModuleData &modData, RemoteModuleData &remoteModData, ProcessScanReport &process_report, bool scan_data, bool scan_inaccessible)
Definition scanner.cpp:134
size_t scanModulesIATs(ProcessScanReport &pReport)
Definition scanner.cpp:424
pesieve::t_params args
Definition scanner.h:53
static t_scan_status scanForHollows(HANDLE hProcess, ModuleData &modData, RemoteModuleData &remoteModData, ProcessScanReport &process_report)
Definition scanner.cpp:78
bool resolveHooksTargets(ProcessScanReport &process_report)
Definition scanner.cpp:150
bool filterDotNetReport(ProcessScanReport &process_report)
Definition scanner.cpp:179
ModuleScanReport * scanForMappingMismatch(ModuleData &modData, ProcessScanReport &process_report)
Definition scanner.cpp:327
static t_scan_status scanForIATHooks(HANDLE hProcess, ModuleData &modData, RemoteModuleData &remoteModData, ProcessScanReport &process_report, t_iat_scan_mode filter)
Definition scanner.cpp:110
ProcessScanReport * scanRemote()
The main function of ProcessScanner, deploying the scan. Throws exceptions in case of a failure.
Definition scanner.cpp:216
std::set< std::string > ignoredModules
Definition scanner.h:55
Buffers the data from the module loaded in the scanned process into the local memory.
A report from the thread scan, generated by ThreadScanner.
static bool FreeSymbols(HANDLE hProc)
static bool InitSymbols(HANDLE hProc)
A report from the working set scan, generated by WorkingSetScanner.
A scanner for detection of code implants in the process workingset.
bool is_process_64bit(IN HANDLE process)
bool fetch_threads_info(DWORD pid, std::vector< thread_info > &threads_info)
size_t enum_modules(IN HANDLE hProcess, IN OUT HMODULE hMods[], IN const DWORD hModsMax, IN DWORD filters)
void print_scantime(std::stringstream &stream, size_t timeInMs)
Definition scanner.cpp:44
BOOL is_process_wow64(IN HANDLE processHandle, OUT BOOL *isProcWow64)
bool is_DEP_enabled(HANDLE hProcess)
DWORD count_workingset_entries(HANDLE processHandle)
size_t string_to_list(IN::std::string s, IN char _delim, OUT std::set< std::string > &elements_list, bool to_lower=true)
std::string device_path_to_win32_path(const std::string &full_path)
size_t enum_workingset(HANDLE processHandle, std::set< mem_region_info > &regions)
bool is_in_list(std::string searched_string, std::set< std::string > &string_list, bool to_lower=true)
BOOL(CALLBACK *_MiniDumpWriteDump)(HANDLE hProcess
DWORD(__stdcall *_PssCaptureSnapshot)(HANDLE ProcessHandle
bool fetch_threads_by_snapshot(DWORD pid, std::vector< thread_info > &threads_info)
bool validate_param_str(PARAM_STRING &strparam)
Definition scanner.cpp:31
int MAX_PATH
Definition pesieve.py:10
void print_scan_time(const char *scanned_element, size_t total_time)
Definition scanner.cpp:59
size_t fill_iat(BYTE *vBuf, size_t vBufSize, IN const peconv::ExportsMapper *exportsMap, IN OUT IATBlock &iat, IN ThunkFoundCallback *callback)
Definition iat_finder.h:31
std::string info()
The string with the basic information about the scanner.
Definition pe_sieve.cpp:268
enum pesieve::module_scan_status t_scan_status
#define PARAM_LIST_SEPARATOR
@ PE_DATA_SCAN_INACCESSIBLE
scan data unconditionally, and inaccessible pages (if running in reflection mode)
@ PE_IMPREC_NONE
do not try to recover imports
bool set_non_suspicious(const std::set< ModuleScanReport * > &scan_reports, bool dnet_modules_only)
Definition scanner.cpp:158
Final summary about the scanned process.