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
code_scanner.cpp
Go to the documentation of this file.
1#include "code_scanner.h"
2
3#include <peconv.h>
4
5#include "patch_analyzer.h"
7//---
8#include <iostream>
9
10using namespace pesieve;
11using namespace pesieve::util;
12
14{
15 if (patchesList.size() == 0) {
16 return 0;
17 }
18 std::ofstream patch_report;
20 if (patch_report.is_open() == false) {
21 return 0;
22 }
23 size_t patches = patchesList.toTAGs(patch_report, ';');
24 if (patch_report.is_open()) {
25 patch_report.close();
26 }
27 return patches;
28}
29//---
30
31bool pesieve::CodeScanner::clearIAT(PeSection &originalSec, PeSection &remoteSec)
32{
33 // collect IAT fields:
34 std::set<DWORD> impThunkRVAs;
35 moduleData.loadImportThunks(impThunkRVAs);
36 if (impThunkRVAs.size() == 0) {
37 return false;
38 }
39
40 const size_t thunk_size = moduleData.is64bit() ? sizeof(ULONGLONG) : sizeof(DWORD);
41 std::set<DWORD>::iterator itr;
42 for (itr = impThunkRVAs.begin(); itr != impThunkRVAs.end(); ++itr) {
43 const DWORD iat_field = *itr;
44 // clear fields one by one:
45 if (originalSec.isContained(iat_field, thunk_size)) {
46 const DWORD offset = iat_field - originalSec.rva;
47 memset(originalSec.loadedSection + offset, 0, thunk_size);
48 memset(remoteSec.loadedSection + offset, 0, thunk_size);
49 }
50 }
51 return true;
52}
53
54bool pesieve::CodeScanner::clearLoadConfig(PeSection &originalSec, PeSection &remoteSec)
55{
56 // check if the Guard flag is enabled:
57 WORD charact = peconv::get_dll_characteristics(moduleData.original_module);
58 if ((charact & 0x4000) == 0) {
59 return false; //no guard flag
60 }
61 BYTE *ldconf_ptr = peconv::get_load_config_ptr(moduleData.original_module, moduleData.original_size);
62 if (!ldconf_ptr) return false;
63
64 peconv::t_load_config_ver ver = peconv::get_load_config_version(moduleData.original_module, moduleData.original_size, ldconf_ptr);
65 if (ver != peconv::LOAD_CONFIG_W8_VER && ver != peconv::LOAD_CONFIG_W10_VER) {
66 return false; // nothing to cleanup
67 }
69 size_t field_size = 0;
70 if (this->moduleData.is64bit()) {
71 peconv::IMAGE_LOAD_CONFIG_DIR64_W8* ldc = (peconv::IMAGE_LOAD_CONFIG_DIR64_W8*) ldconf_ptr;
72 cflag_va = ldc->GuardCFCheckFunctionPointer;
73 field_size = sizeof(ULONGLONG);
74 }
75 else {
76 peconv::IMAGE_LOAD_CONFIG_DIR32_W8* ldc = (peconv::IMAGE_LOAD_CONFIG_DIR32_W8*) ldconf_ptr;
77 cflag_va = ldc->GuardCFCheckFunctionPointer;
78 field_size = sizeof(DWORD);
79 }
80 if (cflag_va == 0) return false;
81
82 const ULONGLONG module_base = (ULONG_PTR)moduleData.moduleHandle;
84 if (!originalSec.isContained(cflag_rva, field_size)) {
85 return false;
86 }
87 //clear the field:
88 size_t sec_offset = size_t(cflag_rva - originalSec.rva);
89 memset(originalSec.loadedSection + sec_offset, 0, field_size);
90 memset(remoteSec.loadedSection + sec_offset, 0, field_size);
91 return true;
92}
93
94bool pesieve::CodeScanner::clearExports(PeSection &originalSec, PeSection &remoteSec)
95{
96 IMAGE_DATA_DIRECTORY* dir = peconv::get_directory_entry(moduleData.original_module, IMAGE_DIRECTORY_ENTRY_EXPORT);
97 if (!dir) {
98 return false;
99 }
100 DWORD iat_rva = dir->VirtualAddress;
101 DWORD iat_size = dir->Size;
102
103 if (originalSec.isContained(iat_rva, iat_size))
104 {
105#ifdef _DEBUG
106 std::cout << "Exports are in the Code section!" << std::endl;
107#endif
108 DWORD offset = iat_rva - originalSec.rva;
110 if (!peconv::validate_ptr(originalSec.loadedSection, originalSec.loadedSize, exports, sizeof(IMAGE_EXPORT_DIRECTORY))) {
111 return false;
112 }
113 DWORD functions_offset = exports->AddressOfFunctions - originalSec.rva;
114 DWORD functions_count = exports->NumberOfFunctions;
115
116 const size_t func_area_size = functions_count * sizeof(DWORD);
117 if (!peconv::validate_ptr(originalSec.loadedSection, originalSec.loadedSize,
118 originalSec.loadedSection + functions_offset,
120 {
121 return false;
122 }
124 memset(remoteSec.loadedSection + functions_offset, 0, func_area_size);
125 }
126 return true;
127}
128
129size_t pesieve::CodeScanner::collectPatches(DWORD section_rva, PBYTE orig_code, PBYTE patched_code, size_t code_size, OUT PatchList &patchesList)
130{
132 PatchList::Patch *currPatch = nullptr;
133
134 for (DWORD i = 0; i < (DWORD) code_size; i++) {
135 if (orig_code[i] == patched_code[i]) {
136 if (currPatch != nullptr) {
137 // close the patch
139 currPatch = nullptr;
140 }
141 continue;
142 }
143 if (currPatch == nullptr) {
144 //open a new patch
145 currPatch = new(std::nothrow) PatchList::Patch(moduleData.moduleHandle, patchesList.size(), (DWORD) section_rva + i);
146 if (!currPatch) continue;
147 patchesList.insert(currPatch);
149 if (parsed_size > 0) {
151 currPatch = nullptr; // close this patch
152 i += (parsed_size - 1); //substract 1 because of i++ executed after continue
153 continue;
154 }
155 }
156 }
157 // if there is still unclosed patch, close it now:
158 if (currPatch != nullptr) {
159 //this happens if the patch lasts till the end of code, so, its end is the end of code
161 currPatch = nullptr;
162 }
163 return patchesList.size();
164}
165
166namespace pesieve {
167 inline BYTE* first_different(const BYTE *buf_ptr, size_t bif_size, const BYTE padding)
168 {
169 for (size_t i = 0; i < bif_size; i++) {
170 if (buf_ptr[i] != padding) {
171 return (BYTE*)(buf_ptr + i);
172 }
173 }
174 return nullptr;
175 }
176};
177
178CodeScanReport::t_section_status pesieve::CodeScanner::scanSection(PeSection &originalSec, PeSection &remoteSec, OUT PatchList &patchesList)
179{
180 if (!originalSec.isInitialized() || !remoteSec.isInitialized()) {
182 }
183 clearIAT(originalSec, remoteSec);
184 clearExports(originalSec, remoteSec);
185 clearLoadConfig(originalSec, remoteSec);
186 //TODO: handle sections that have inside Delayed Imports (they give false positives)
187
188 const size_t smaller_size = originalSec.loadedSize > remoteSec.loadedSize ? remoteSec.loadedSize : originalSec.loadedSize;
189#ifdef _DEBUG
190 std::cout << "Code RVA: "
191 << std::hex << originalSec.rva
192 << " to "
193 << std::hex << originalSec.loadedSize
194 << std::endl;
195#endif
196 //check if the code of the loaded module is same as the code of the module on the disk:
197 int res = memcmp(remoteSec.loadedSection, originalSec.loadedSection, smaller_size);
198
199 if ((originalSec.rawSize == 0 || peconv::is_padding(originalSec.loadedSection, smaller_size, 0))
200 && !peconv::is_padding(remoteSec.loadedSection, smaller_size, 0))
201 {
203 }
204
205 if (res != 0) {
206 collectPatches(originalSec.rva, originalSec.loadedSection, remoteSec.loadedSection, smaller_size, patchesList);
207 }
208
209 if (remoteSec.loadedSize > originalSec.loadedSize) {
210
211 const size_t diff = remoteSec.loadedSize - originalSec.loadedSize;
212 const BYTE *diff_bgn = remoteSec.loadedSection + originalSec.loadedSize;
213
215 if (not_padding) {
218 PatchList::Patch* currPatch = new(std::nothrow) PatchList::Patch(moduleData.moduleHandle, patchesList.size(), found_rva);
219 if (currPatch) {
221 patchesList.insert(currPatch);
222 }
223 }
224 }
225 if (patchesList.size()) {
227 }
228 if (res == 0) {
230 }
232}
233
234size_t pesieve::CodeScanner::collectExecutableSections(RemoteModuleData &_remoteModData, std::map<size_t, PeSection*> &sections, CodeScanReport &my_report)
235{
236 size_t initial_size = sections.size();
237 const size_t sec_count = peconv::get_sections_count(_remoteModData.headerBuffer, _remoteModData.getHeaderSize());
238 for (DWORD i = 0; i < sec_count; i++) {
239 PIMAGE_SECTION_HEADER section_hdr = peconv::get_section_hdr(_remoteModData.headerBuffer, _remoteModData.getHeaderSize(), i);
240 if (section_hdr == nullptr) {
241 continue;
242 }
243
244 const bool is_entry = _remoteModData.isSectionEntry(i);
245
246 if (!is_entry // entry section may be set as non executable, but it will still be executed
247 && !(section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE)
248 && !_remoteModData.isSectionExecutable(i, this->isScanData, this->isScanInaccessible))
249 {
250 //not executable, skip it
251 continue;
252 }
253
254 //get the code section from the remote module:
255 PeSection *remoteSec = new(std::nothrow) PeSection(_remoteModData, i);
256 if (remoteSec && remoteSec->isInitialized()) {
257 if (is_entry // always scan section containing Entry Point
258 || is_code(remoteSec->loadedSection, remoteSec->loadedSize))
259 {
261 continue;
262 }
263 }
264 else {
265 // report about failed initialization
267 }
268 // the section was not added to the list, delete it instead:
269 delete remoteSec;
270 }
271 //corner case: PEs without sections
272 if (sec_count == 0) {
273 PeSection *remoteSec = new(std::nothrow) PeSection(_remoteModData, 0);
274 if (remoteSec && remoteSec->isInitialized()) {
275 sections[0] = remoteSec;
276 }
277 else {
278 // report about failed initialization
279 my_report.sectionToResult[0] = CodeScanReport::SECTION_SCAN_ERR;
280 // the section was not added to the list, delete it instead:
281 delete remoteSec;
282 }
283 }
284 return sections.size() - initial_size;
285}
286
287void pesieve::CodeScanner::freeExecutableSections(std::map<size_t, PeSection*> &sections)
288{
289 std::map<size_t, PeSection*>::iterator itr;
290 for (itr = sections.begin(); itr != sections.end(); ++itr) {
291 PeSection *sec = itr->second;
292 delete sec;
293 }
294 sections.clear();
295}
296
297t_scan_status pesieve::CodeScanner::scanUsingBase(
299 IN std::map<size_t, PeSection*> &remote_code,
300 OUT std::map<DWORD, CodeScanReport::t_section_status> &sectionToResult,
301 OUT PatchList &patchesList)
302{
304
305 // before scanning, ensure that the original module is relocated to the base where it was loaded
306 if (!moduleData.relocateToBase(load_base)) {
307 return SCAN_ERROR;
308 }
309
310 size_t errors = 0;
311 size_t modified = 0;
312 std::map<size_t, PeSection*>::iterator itr;
313
314 for (itr = remote_code.begin(); itr != remote_code.end(); ++itr) {
315 size_t sec_indx = itr->first;
316 PeSection *remoteSec = itr->second;
317
318 PeSection originalSec(moduleData, sec_indx);
319
321 sectionToResult[originalSec.rva] = sec_status; //save the status for the section
322
325 modified++;
326 }
327 }
328
329 if (modified > 0) {
330 last_res = SCAN_SUSPICIOUS; //the highest priority for modified
331 }
332 else if (errors > 0) {
334 }
335 return last_res;
336}
337
339{
340 if (!moduleData.isInitialized()) {
341 std::cerr << "[-] Module not initialized" << std::endl;
342 return nullptr;
343 }
344 if (!remoteModData.isInitialized()) {
345 std::cerr << "[-] Failed to read the module header" << std::endl;
346 return nullptr;
347 }
348 CodeScanReport *my_report = new(std::nothrow) CodeScanReport(moduleData.moduleHandle, remoteModData.getModuleSize());
349 if (!my_report) return nullptr; //this should not happen...
350
351 my_report->isDotNetModule = moduleData.isDotNet();
352
354 std::map<size_t, PeSection*> remote_code;
355
356 if (!collectExecutableSections(remoteModData, remote_code, *my_report)) {
357 my_report->status = last_res;
358 if (my_report->countInaccessibleSections() > 0) {
359 my_report->status = SCAN_ERROR;
360 }
361 return my_report;
362 }
363 ULONGLONG load_base = (ULONGLONG)moduleData.moduleHandle;
364 ULONGLONG hdr_base = remoteModData.getHdrImageBase();
365
366 my_report->relocBase = load_base;
367 last_res = scanUsingBase(load_base, remote_code, my_report->sectionToResult, my_report->patchesList);
368
369 if (load_base != hdr_base && my_report->patchesList.size() > 0) {
370#ifdef _DEBUG
371 std::cout << "[WARNING] Load Base: " << std::hex << load_base << " is different than the Hdr Base: " << hdr_base << "\n";
372#endif
374 std::map<DWORD, CodeScanReport::t_section_status> section_to_result;
376 if (list2.size() < my_report->patchesList.size()) {
377 my_report->relocBase = hdr_base;
378 my_report->patchesList = list2;
379 my_report->sectionToResult = section_to_result;
381 }
382#ifdef _DEBUG
383 std::cout << "Using patches list for the base: " << my_report->relocBase << " list size: " << my_report->patchesList.size() << "\n";
384#endif
385 }
386
387 this->freeExecutableSections(remote_code);
388 //post-process collected patches:
389 postProcessScan(*my_report);
390
391 my_report->status = last_res;
392 return my_report; // last result
393}
394
395bool pesieve::CodeScanner::postProcessScan(IN OUT CodeScanReport &report)
396{
397 // we need only exports from the current module, not the global mapping
398 if (report.patchesList.size() == 0) {
399 return false;
400 }
401 peconv::ExportsMapper local_mapper;
402 local_mapper.add_to_lookup(moduleData.szModName, (HMODULE) moduleData.original_module, (ULONGLONG) moduleData.moduleHandle);
403 report.patchesList.checkForHookedExports(local_mapper);
404 return true;
405}
A report from the code scan, generated by CodeScanner.
size_t generateTags(std::string reportPath)
enum pesieve::CodeScanReport::section_status t_section_status
virtual CodeScanReport * scanRemote()
A postprocessor of the detected code patches. Detects if the patch is a hook, and if so,...
void setEnd(DWORD end_rva)
Definition patch_list.h:48
const size_t toTAGs(std::ofstream &patch_report, const char delimiter)
Buffers the defined PE section belonging to the module loaded in the scanned process into the local m...
Definition pe_section.h:12
Buffers the data from the module loaded in the scanned process into the local memory.
#define MASK_TO_DWORD(val)
Definition iat_finder.h:9
bool is_code(BYTE *loadedData, size_t loadedSize)
DWORD(__stdcall *_PssCaptureSnapshot)(HANDLE ProcessHandle
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
BYTE * first_different(const BYTE *buf_ptr, size_t bif_size, const BYTE padding)
enum pesieve::module_scan_status t_scan_status
Final summary about the scanned process.