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
workingset_scanner.cpp
Go to the documentation of this file.
2#include "module_data.h"
3#include "artefact_scanner.h"
4#include "scanner.h"
5
9
10#include <fstream>
11
12using namespace pesieve;
13using namespace pesieve::util;
14
16
17namespace pesieve {
18
19 inline bool is_by_stats(const t_shellc_mode& shellc_mode)
20 {
21 switch (shellc_mode) {
22 case SHELLC_STATS:
25 return true;
26 }
27 return false;
28 }
29
30 inline bool match_to_tag(std::ofstream& patch_report, const char delimiter, size_t start_offset, const sig_finder::Match &match)
31 {
32 if (patch_report.is_open() && match.sign) {
33 patch_report << std::hex << match.offset + start_offset;
34 patch_report << delimiter;
35 patch_report << match.sign->name;
36 patch_report << delimiter;
37 patch_report << match.sign->size();
38 patch_report << std::endl;
39 return true;
40 }
41 return false;
42 }
43};
44
45
46size_t WorkingSetScanReport::generateTags(const std::string& reportPath)
47{
48 if (this->custom_matched.size() == 0) {
49 return 0;
50 }
51 std::ofstream patch_report;
52 patch_report.open(reportPath);
53 if (patch_report.is_open() == false) {
54 return 0;
55 }
56 size_t count = 0;
57 for (auto itr = custom_matched.begin(); itr != custom_matched.end(); ++itr) {
58 sig_finder::Match m = *itr;
59 if (match_to_tag(patch_report, ';', this->match_area_start, m)) count++;
60 }
61 if (patch_report.is_open()) {
62 patch_report.close();
63 }
64 return count;
65}
66
67//---
68
70{
71 if (!memPage.load()) {
72 return false;
73 }
74
75 const bool noPadding = true;
76
77 bool isByStats = is_by_stats(this->args.shellcode);
78
79 bool code = false;
80 bool codeP = false;
81 bool codeS = false;
82 bool obfuscated = false;
83
84 size_t custom_matched_count = 0;
85
86 if (g_Matcher.isReady()) {
87 std::vector<sig_finder::Match> allMatched;
88 my_report->all_matched_count = g_Matcher.findAllPatterns(memPage.getLoadedData(noPadding), memPage.getLoadedSize(noPadding), allMatched);
89 custom_matched_count = g_Matcher.filterCustom(allMatched, my_report->custom_matched);
90 if (my_report->all_matched_count) {
91 my_report->match_area_start = memPage.getStartOffset(noPadding);
92 codeP = true;
93 code = true;
94 if (this->args.shellcode == SHELLC_PATTERNS_OR_STATS) {
95 isByStats = false; // condition satisfied, no more checks required
96 }
97 }
98 else {
99 if (this->args.shellcode == SHELLC_PATTERNS_AND_STATS) {
100 isByStats = false; // condition NOT satisfied, no more checks required
101 }
102 }
103 }
104#ifdef CALC_PAGE_STATS
105 if (isByStats || this->args.obfuscated) {
106
107 // fill default settings
108 MultiStatsSettings settings;
110
111 AreaStatsCalculator calc(memPage.loadedData);
112 if (calc.fill(my_report->stats, &settings)) {
113
115 if (codeMatcher.findMatches(my_report->stats, my_report->area_info)) {
116 codeS = true;
117 code = true;
118 }
119
120 if (!codeS && (this->args.obfuscated != OBFUSC_NONE)) {
121 int rules = 0;
122 if (this->args.obfuscated == OBFUSC_ANY) rules = RuleMatcher::RULE_OBFUSCATED | RuleMatcher::RULE_ENCRYPTED;
123 if (this->args.obfuscated == OBFUSC_STRONG_ENC) rules = RuleMatcher::RULE_ENCRYPTED;
124 if (this->args.obfuscated == OBFUSC_WEAK_ENC) rules = RuleMatcher::RULE_OBFUSCATED;
125 pesieve::RuleMatchersSet obfMatcher(rules);
126 if (obfMatcher.findMatches(my_report->stats, my_report->area_info)) {
127 obfuscated = true;
128 // filter out cache:
129 if (memPage.mapping_type == MEM_MAPPED // mapped memory
130 && !util::is_executable(memPage.mapping_type, memPage.protection) // non executable page
131 && memPage.loadMappedName()) //named
132 {
133 obfuscated = false;
134 }
135 }
136 }
137 }
138 }
139#endif
140
141 if (this->args.shellcode == SHELLC_PATTERNS_AND_STATS) {
142 code = (codeP && codeS);
143 }
144 else if (this->args.shellcode == SHELLC_PATTERNS_OR_STATS) {
145 code = (codeP || codeS);
146 }
147
148 my_report->has_shellcode = code;
149
150 if ( (obfuscated && this->args.obfuscated != OBFUSC_NONE)
151 || (code && (this->args.shellcode != SHELLC_NONE || custom_matched_count) ))
152 {
153 my_report->status = SCAN_SUSPICIOUS;
154 }
155 if (my_report->status == SCAN_SUSPICIOUS) {
156 my_report->data_cache = memPage.loadedData;
157 }
158 return true;
159}
160
162{
164 return true;
165 }
166 return isPotentiallyExecutable(memPage, this->args.data);
167}
168
170{
171 if (mode == pesieve::PE_DATA_NO_SCAN) {
172 return false;
173 }
174
175 // check preconditions:
176 const bool is_managed = this->processReport.isManagedProcess();
177 if (mode == pesieve::PE_DATA_SCAN_NO_DEP
178 && this->pDetails.isDEP && !is_managed)
179 {
180 return false;
181 }
182 if (mode == pesieve::PE_DATA_SCAN_DOTNET
183 && !is_managed)
184 {
185 return false;
186 }
187 // preconditions are fulfilled, now check the access:
188 const bool is_page_readable = pesieve::util::is_readable(memPage.mapping_type, memPage.protection);
189 if (mode != pesieve::PE_DATA_SCAN_INACCESSIBLE_ONLY) {
190 if (is_page_readable) {
191 return true;
192 }
193 }
194 if ((mode >= pesieve::PE_DATA_SCAN_INACCESSIBLE) || (mode == pesieve::PE_DATA_SCAN_INACCESSIBLE_ONLY)) {
195 if (this->pDetails.isReflection && (memPage.protection & PAGE_NOACCESS)) {
196 return true;
197 }
198 }
199 return false;
200}
201
203{
204 if (!_memPage.load()) {
205 return nullptr;
206 }
207 // check for PE artifacts (regardless if it has shellcode patterns):
208 if (!isScannedAsModule(_memPage)) {
209 ArtefactScanner artefactScanner(this->processHandle, this->pDetails, _memPage, this->processReport);
210 WorkingSetScanReport *my_report1 = artefactScanner.scanRemote();
211 if (my_report1) {
212 //pe artefacts found
213 return my_report1;
214 }
215 }
216 if ((!g_Matcher.isReady())
217 && (this->args.obfuscated == OBFUSC_NONE))
218 {
219 // not a PE file, and we are not interested in patterns or obfuscated contents, so just finish it here
220 return nullptr;
221 }
222
223 //report about shellcode:
224 ULONGLONG region_start = _memPage.region_start;
225 const size_t region_size = size_t(_memPage.region_end - region_start);
226 WorkingSetScanReport* my_report = new WorkingSetScanReport((HMODULE)region_start, region_size, SCAN_NOT_SUSPICIOUS);
227 if (!my_report) {
228 return nullptr;
229 }
230
231 if (!checkAreaContent(_memPage, my_report)) { // check for shellcode patterns & stats
232 my_report->status = SCAN_ERROR;
233 }
234 if (my_report->status == SCAN_NOT_SUSPICIOUS) {
235 // do not keep reports for not suspicious areas
236 delete my_report;
237 return nullptr;
238 }
239 my_report->has_pe = isScannedAsModule(_memPage) && this->processReport.hasModule(_memPage.region_start);
240 return my_report;
241}
242
244{
245 if (memPage.mapping_type != MEM_IMAGE) {
246 return false;
247 }
248 if (this->processReport.hasModule((ULONGLONG)memPage.alloc_base)) {
249 return true; // it was already scanned as a PE
250 }
251 return false;
252}
253
255{
256 if (!memPage.loadMappedName()) {
257 //cannot retrieve the mapped name
258 return false;
259 }
260
261 const HMODULE module_start = (HMODULE)memPage.alloc_base;
262
263 if (!args.quiet) {
264 std::cout << "[!] Scanning detached: " << std::hex << module_start << " : " << memPage.mapped_name << std::endl;
265 }
266 RemoteModuleData remoteModData(this->processHandle, this->pDetails.isReflection, module_start);
267 if (!remoteModData.isInitialized()) {
268 if (!args.quiet) {
269 std::cout << "[-] Could not read the remote PE at: " << std::hex << module_start << std::endl;
270 }
271 return false;
272 }
273
274 //load module from file:
275 ModuleData modData(processHandle, module_start, false, args.use_cache, memPage.mapped_name.c_str());
276 if (!modData.loadOriginal()) {
277 if (!args.quiet) {
278 std::cerr << "[-] [" << std::hex << modData.moduleHandle << "] Could not read the module file" << std::endl;
279 }
280 processReport.appendReport(new UnreachableModuleReport(module_start, 0, memPage.mapped_name));
281 return false;
282 }
283 t_scan_status scan_status = ProcessScanner::scanForHollows(processHandle, modData, remoteModData, processReport);
284#ifdef _DEBUG
285 std::cout << "[*] Scanned for hollows. Status: " << scan_status << std::endl;
286#endif
287 if (scan_status == SCAN_ERROR) {
288 // failed scanning it as a loaded PE module
289 return false;
290 }
291 if (scan_status == SCAN_SUSPICIOUS) {
292 // detected as hollowed, no need for further scans
293 return true;
294 }
295 if (!args.no_hooks) {
296 const bool scan_data = (this->args.data >= pesieve::PE_DATA_SCAN_ALWAYS && this->args.data != PE_DATA_SCAN_INACCESSIBLE_ONLY)
297 || (!this->pDetails.isDEP && (this->args.data == pesieve::PE_DATA_SCAN_NO_DEP));
298 const bool scan_inaccessible = (this->pDetails.isReflection && (this->args.data >= pesieve::PE_DATA_SCAN_INACCESSIBLE));
299 scan_status = ProcessScanner::scanForHooks(processHandle, modData, remoteModData, processReport, scan_data, scan_inaccessible);
300#ifdef _DEBUG
301 std::cout << "[*] Scanned for hooks. Status: " << scan_status << std::endl;
302#endif
303 }
304 return true;
305}
306
308{
309 MemPageData memPage(this->processHandle, this->pDetails.isReflection, this->memRegion.base, 0);
310 memPage.is_listed_module = this->processReport.hasModule(this->memRegion.base);
311
312 if (!memPage.isInfoFilled() && !memPage.fillInfo()) {
313#ifdef _DEBUG
314 std::cout << "[!] Could not fill: " << std::hex << memPage.start_va << " to: " << memPage.region_end << "\n";
315#endif
316 return nullptr;
317 }
318 // sanity checks to make sure that we are scanning the same page that was previously collected:
319 if (memPage.alloc_base != this->memRegion.alloc_base) {
320#ifdef _DEBUG
321 std::cerr << "WARNING: Alloc Base mismatch: " << std::hex << memPage.alloc_base << " vs " << this->memRegion.alloc_base << std::endl;
322#endif
323 return nullptr;
324 }
325 if ((memPage.region_end - memPage.region_start) != this->memRegion.size) {
326#ifdef _DEBUG
327 std::cerr << "WARNING: Size mismatch: " << std::hex << (memPage.region_end - memPage.region_start) << " vs " << this->memRegion.size << std::endl;
328#endif
329 return nullptr;
330 }
331
332 // is the page executable?
333 const bool is_any_exec = isExecutable(memPage);
334 if (!is_any_exec) {
335 // probably not interesting
336 return nullptr;
337 }
338
339 if (memPage.mapping_type == MEM_MAPPED && memPage.isRealMapping()) {
340 //probably legit
341 return nullptr;
342 }
343
344 if (memPage.mapping_type == MEM_IMAGE) {
345 memPage.loadModuleName();
346 memPage.loadMappedName();
347 if (!isScannedAsModule(memPage)) {
348 scanImg(memPage);
349 }
350 const size_t region_size = (memPage.region_end) ? (memPage.region_end - memPage.region_start) : 0;
351 if (this->processReport.hasModuleContaining(memPage.region_start, region_size)) {
352 // the area was already scanned
353 return nullptr;
354 }
355 }
356#ifdef _DEBUG
357 std::cout << std::hex << memPage.start_va << ": Scanning executable area" << std::endl;
358#endif
359 WorkingSetScanReport* my_report = this->scanExecutableArea(memPage);
360 if (!my_report) {
361 return nullptr;
362 }
363 my_report->is_executable = true;
364 my_report->protection = memPage.protection;
365 my_report->mapping_type = memPage.mapping_type;
366 my_report->mapped_name = memPage.mapped_name;
367 return my_report;
368}
A class responsible for filling in the statistics with the data from the particular buffer.
Definition stats.h:73
bool fill(AreaStats &stats, StatsSettings *settings)
Definition stats.h:80
A scanner for detection of artefacts related to PE implants in the process workingset.
virtual ArtefactScanReport * scanRemote()
DWORD protection
page protection
std::string mapped_name
if the region is mapped from a file, stores its file name
ULONGLONG start_va
VA that was requested. May not be beginning of the region.
Loads a module from the disk, corresponding to the module in the scanned process' memory.
Definition module_data.h:15
size_t findAllPatterns(BYTE *loadedData, size_t loadedSize, ::std::vector< sig_finder::Match > &allMatches)
size_t filterCustom(::std::vector< sig_finder::Match > &allMatches, ::std::vector< sig_finder::Match > &customPatternMatches)
static t_scan_status scanForHooks(HANDLE hProcess, ModuleData &modData, RemoteModuleData &remoteModData, ProcessScanReport &process_report, bool scan_data, bool scan_inaccessible)
Definition scanner.cpp:148
static t_scan_status scanForHollows(HANDLE hProcess, ModuleData &modData, RemoteModuleData &remoteModData, ProcessScanReport &process_report)
Definition scanner.cpp:92
Buffers the data from the module loaded in the scanned process into the local memory.
A report from the working set scan, generated by WorkingSetScanner.
std::vector< sig_finder::Match > custom_matched
size_t generateTags(const std::string &reportPath)
WorkingSetScanReport * scanExecutableArea(MemPageData &memPageData)
bool isPotentiallyExecutable(MemPageData &memPageData, const t_data_scan_mode &mode)
bool checkAreaContent(IN MemPageData &_memPage, OUT WorkingSetScanReport *my_report)
bool isScannedAsModule(MemPageData &memPageData)
bool scanImg(MemPageData &memPage)
bool isExecutable(MemPageData &memPageData)
virtual WorkingSetScanReport * scanRemote()
size_t fillCodeStrings(OUT std::set< std::string > &codeStrings)
bool is_readable(DWORD mapping_type, DWORD protection)
bool is_executable(DWORD mapping_type, DWORD protection)
bool match_to_tag(std::ofstream &patch_report, const char delimiter, size_t start_offset, const sig_finder::Match &match)
enum pesieve::module_scan_status t_scan_status
bool is_by_stats(const t_shellc_mode &shellc_mode)
pesieve::PatternMatcher g_Matcher
Definition pe_sieve.cpp:23
@ SHELLC_STATS
detect shellcodes by stats
@ SHELLC_NONE
do not detect shellcode
@ SHELLC_PATTERNS_OR_STATS
detect shellcodes by patterns or stats (any match)
@ SHELLC_PATTERNS_AND_STATS
detect shellcodes by patterns and stats (both match)
@ PE_DATA_SCAN_INACCESSIBLE_ONLY
scan inaccessible pages (if running in reflection mode)
@ OBFUSC_ANY
detect both: possible strong or weak encryption
@ OBFUSC_WEAK_ENC
detect areas possibly encrypted with weak encryption (lower entropy, possible XOR patterns)
@ OBFUSC_STRONG_ENC
detect areas possibly encrypted with strong encryption
@ OBFUSC_NONE
do not detect obfuscated contents
Settings defining what type of stats should be collected.
Definition multi_stats.h:18
std::set< std::string > watchedStrings
Definition multi_stats.h:50
size_t findMatches(IN const AreaMultiStats &stats, OUT AreaInfo &info)
pesieve::PatternMatcher g_Matcher
Definition pe_sieve.cpp:23