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