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
imp_reconstructor.cpp
Go to the documentation of this file.
1#include "imp_reconstructor.h"
2
3#include "iat_finder.h"
5
6#include <fstream>
7
8using namespace pesieve;
9
10#define MIN_THUNKS_COUNT 2
11
12namespace pesieve {
13 BYTE* get_buffer_space_at(IN BYTE* buffer, IN const size_t buffer_size, IN const DWORD buffer_rva, IN const DWORD required_rva, IN const size_t required_size)
14 {
15 if (!buffer || buffer_rva > required_rva) return nullptr;
16 size_t offset = required_rva - buffer_rva;
17
18 BYTE* req_ptr = offset + buffer;
19 if (peconv::validate_ptr(buffer, buffer_size, req_ptr, required_size)) {
20 return req_ptr;
21 }
22 return nullptr;
23 }
24};
25
26//---
27
29{
30 return get_buffer_space_at(this->namesBuf, this->namesBufSize, this->namesRVA, rva, required_size);
31}
32
34{
35 return get_buffer_space_at(this->dllsBuf, this->dllsBufSize, this->dllsRVA, rva, required_size);
36}
37
38//---
39
40bool pesieve::ImpReconstructor::hasDynamicIAT() const
41{
42 size_t maxSize = getMaxDynamicIATSize(true);
43 return (maxSize >= MIN_THUNKS_COUNT);
44}
45
46size_t pesieve::ImpReconstructor::getMainIATSize() const
47{
48 std::map<DWORD, IATBlock*>::const_iterator iats_itr;
49
50 //the main IAT can be in chunks, so join them together...
51 size_t totalIatSize = 0;
52 for (iats_itr = foundIATs.cbegin(); iats_itr != foundIATs.cend(); ++iats_itr) {
53 const IATBlock* iblock = iats_itr->second;
54 const size_t currCount = iblock->countThunks();
55
56 if (iblock->isInMain) {
58 }
59 }
60 return totalIatSize;
61}
62
63size_t pesieve::ImpReconstructor::getMaxDynamicIATSize(IN bool isIatTerminated) const
64{
65 std::map<DWORD, IATBlock*>::const_iterator iats_itr;
66
67 size_t maxIATSize = 0;
68 for (iats_itr = foundIATs.cbegin(); iats_itr != foundIATs.cend(); ++iats_itr) {
69 const IATBlock* iblock = iats_itr->second;
70 const size_t currCount = iblock->countThunks();
71
72 if (!iblock->isInMain // is it a dynamic IAT
73 && (iblock->isTerminated == isIatTerminated))
74 {
75 if (currCount > maxIATSize) {
77 }
78 }
79 }
80 return maxIATSize;
81}
82
83pesieve::ImpReconstructor::t_imprec_res pesieve::ImpReconstructor::_recreateImportTableFiltered(const IN peconv::ExportsMapper* exportsMap, IN const pesieve::t_imprec_mode& imprec_mode)
84{
85 // convert to filter:
86
87 int filter = IMP_REC0;
88 switch (imprec_mode) {
90 filter = IMP_REC0; break;
92 filter = IMP_REC1; break;
94 filter = IMP_REC2; break;
95 }
96
97 // in AUTO mode: chose higher filter if the unterminated IAT is bigger than the main IAT, or any terminated:
98
99 if (imprec_mode == PE_IMPREC_AUTO) {
100
101 const size_t untermIATSize = getMaxDynamicIATSize(false);
103 const size_t mainIATSize = getMainIATSize();
104 const size_t termIATSize = getMaxDynamicIATSize(true);
105
107 filter = IMP_REC1;
108 }
109 }
110 }
111
112 // Try to rebuild ImportTable for module
113
114 while (!findIATsCoverage(exportsMap, (t_imprec_filter)filter)) {
115 if (imprec_mode != PE_IMPREC_AUTO) {
116 // no autodetect: don't try different modes
117 return IMP_RECOVERY_ERROR;
118 }
119 // try next filter:
120 filter++;
121 //limit exceeded, quit with error:
122 if (filter == IMP_REC_COUNT) {
123 return IMP_RECOVERY_ERROR;
124 }
125 }
126
127 //coverage found, try to rebuild:
128 bool isOk = false;
129 ImportTableBuffer* impBuf = constructImportTable();
130 if (impBuf) {
131 if (appendImportTable(*impBuf)) {
132 isOk = true;
133 }
134 }
135 delete impBuf;
136
137 if (!isOk) {
138 return IMP_RECOVERY_ERROR;
139 }
140 // convert results:
141 switch (filter) {
142 case IMP_REC0:
143 return IMP_RECREATED_FILTER0;
144 case IMP_REC1:
145 return IMP_RECREATED_FILTER1;
146 case IMP_REC2:
147 return IMP_RECREATED_FILTER2;
148 }
149 return IMP_RECREATED_FILTER0;
150}
151
153{
154 if (!exportsMap || imprec_mode == pesieve::PE_IMPREC_NONE) {
155 return IMP_RECOVERY_SKIPPED;
156 }
157
158 if (!collectIATs(exportsMap)) {
159 return IMP_NOT_FOUND;
160 }
161
162 if (!peBuffer.isValidPe()) {
163 // this is possibly a shellcode, stop after collecting the IATs
164 return IMP_RECOVERY_NOT_APPLICABLE;
165 }
166 if (!peconv::is_pe_raw_eq_virtual(peBuffer.vBuf, peBuffer.vBufSize)
167 && peconv::is_pe_raw(peBuffer.vBuf, peBuffer.vBufSize))
168 {
169 // Do not proceed, the PE is in a raw format
170 return IMP_RECOVERY_NOT_APPLICABLE;
171 }
172
173 if (imprec_mode == PE_IMPREC_UNERASE ||
174 (imprec_mode == PE_IMPREC_AUTO && !this->hasDynamicIAT()))
175 {
176 const bool is_default_valid = this->isDefaultImportValid(exportsMap);
177 if (is_default_valid) {
178 // Valid Import Table already set
180 }
181 if (findImportTable(exportsMap)) {
182 // ImportTable found and set:
184 }
185 }
186 const bool isDotnet = peconv::is_dot_net(peBuffer.vBuf, peBuffer.vBufSize);
187 if (isDotnet && imprec_mode == PE_IMPREC_AUTO) {
188 // Valid Import Table already set
190 }
191
192 // Try to rebuild ImportTable for module
193 if ((imprec_mode == PE_IMPREC_REBUILD0 || imprec_mode == PE_IMPREC_REBUILD1 || imprec_mode == PE_IMPREC_REBUILD2)
194 || imprec_mode == PE_IMPREC_AUTO)
195 {
196 return _recreateImportTableFiltered(exportsMap, imprec_mode);
197 }
198 return IMP_RECOVERY_ERROR;
199}
200
202{
203 if (!foundIATs.size()) {
204 return false;
205 }
206 std::ofstream report;
207 report.open(reportPath);
208 if (report.is_open() == false) {
209 return false;
210 }
211
212 std::map<DWORD, IATBlock*>::iterator itr;
213 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
214 report << itr->second->toString();
215 }
216 report.close();
217 return true;
218}
219
220bool pesieve::ImpReconstructor::isDefaultImportValid(IN const peconv::ExportsMapper* exportsMap)
221{
222 BYTE *vBuf = this->peBuffer.vBuf;
223 const size_t vBufSize = this->peBuffer.vBufSize;
224 if (!vBuf || !vBufSize) return false;
225
226 IMAGE_DATA_DIRECTORY *iat_dir = peconv::get_directory_entry(vBuf, IMAGE_DIRECTORY_ENTRY_IAT, true);
227 if (!iat_dir) return false;
228
229 IMAGE_DATA_DIRECTORY *imp_dir = peconv::get_directory_entry(vBuf, IMAGE_DIRECTORY_ENTRY_IMPORT, true);
230 if (!imp_dir) return false;
231
232 if (imp_dir->VirtualAddress == 0 && imp_dir->Size == 0
233 && iat_dir->VirtualAddress == 0 && iat_dir->Size == 0)
234 {
235 // the PE has no Import Table, and no artefacts indicating that it was erased. Probably legit no-import PE.
236 return false;
237 }
238
239 if (iat_dir->VirtualAddress != 0 && imp_dir->VirtualAddress == 0) {
240 // the PE has IAT, but no Import Table. Import Table Address was probably erased.
241 return false;
242 }
243
244 // verify if the Import Table that is currently set is fine:
245
246 DWORD iat_offset = iat_dir->VirtualAddress;
247 IATBlock* iat_block = this->findIATBlock(exportsMap, iat_offset);
248 if (!iat_block) {
249 //could not find any IAT Block at this IAT offset. The IAT offset may be incorrect.
250 return false;
251 }
252 const size_t start_offset = peconv::get_hdrs_size(vBuf);
253 const bool is64bit = peconv::is64bit(vBuf);
254 size_t table_size = 0;
256 is64bit,
257 vBuf,
258 vBufSize,
259 exportsMap,
263 );
264 if (!import_table) {
265 // could not find Import Table for this IAT offset
266 return false;
267 }
268 // Import Table found and it fits the address that was already set
270 if (imp_dir->VirtualAddress == imp_table_offset) {
271 return true;
272 }
273 return false;
274}
275
276IATBlock* pesieve::ImpReconstructor::findIATBlock(IN const peconv::ExportsMapper* exportsMap, size_t start_offset)
277{
278 if (!exportsMap) return nullptr;
279
280 // filter calls to the own exports
282 {
283 public:
286 {
287 }
288
289 virtual bool shouldProcessVA(ULONGLONG va)
290 {
291 if (va >= startAddr && va < endAddr) {
292 // the address is in the current module: this may be a call to module's own function
293 return false;
294 }
295 return true;
296 }
297
298 virtual bool shouldAcceptExport(ULONGLONG va, const peconv::ExportedFunc &exp)
299 {
300 // accept any
301 return true;
302 }
303
304 protected:
305 const ULONGLONG startAddr;
306 const ULONGLONG endAddr;
307 };
308 //---
309 ThunkFilterSelfCallback filter = ThunkFilterSelfCallback(peBuffer.moduleBase, peBuffer.getBufferSize());
310
311 IATBlock* iat_block = nullptr;
312 if (this->is64bit) {
313 iat_block = find_iat<ULONGLONG>(this->peBuffer.vBuf, this->peBuffer.vBufSize, exportsMap, start_offset, &filter);
314 }
315 else {
316 iat_block = find_iat<DWORD>(this->peBuffer.vBuf, this->peBuffer.vBufSize, exportsMap, start_offset, &filter);
317 }
318 return iat_block;
319}
320
321
322void pesieve::ImpReconstructor::collectMainIatData()
323{
324 BYTE* vBuf = this->peBuffer.vBuf;
325 const size_t vBufSize = this->peBuffer.vBufSize;
326 if (!vBuf) return;
327
328 if (!peconv::has_valid_import_table(vBuf, vBufSize)) {
329 // No import table
330 return;
331 }
332 peconv::collect_thunks(vBuf, vBufSize, mainIatThunks);
333}
334
335IATBlock* pesieve::ImpReconstructor::findIAT(IN const peconv::ExportsMapper* exportsMap, size_t start_offset)
336{
337 BYTE *vBuf = this->peBuffer.vBuf;
338 const size_t vBufSize = this->peBuffer.vBufSize;
339 if (!vBuf) return nullptr;
340
341 IATBlock* iat_block = findIATBlock(exportsMap, start_offset);
342 if (!iat_block) {
343 return nullptr;
344 }
345 DWORD mainIatRVA = 0;
346 DWORD mainIatSize = 0;
347 IMAGE_DATA_DIRECTORY* dir = peconv::get_directory_entry(vBuf, IMAGE_DIRECTORY_ENTRY_IAT, true);
348 if (dir) {
349 mainIatRVA = dir->VirtualAddress;
350 mainIatSize = dir->Size;
351 }
352 if ( (mainIatRVA != 0 && iat_block->iatOffset >= mainIatRVA && iat_block->iatOffset < (mainIatRVA + mainIatSize) )
353 || mainIatThunks.find(iat_block->iatOffset) != mainIatThunks.end() )
354 {
355 iat_block->isInMain = true;
356 }
357 return iat_block;
358}
359
360size_t pesieve::ImpReconstructor::collectIATs(IN const peconv::ExportsMapper* exportsMap)
361{
362 BYTE *vBuf = this->peBuffer.vBuf;
363 const size_t vBufSize = this->peBuffer.vBufSize;
364 if (!vBuf) return 0;
365
366 size_t found = 0;
367 const size_t pe_hdr_size = peconv::get_hdrs_size(vBuf); //if the buffer is not a valid PE, it will be 0
368
369 for (size_t search_offset = pe_hdr_size; search_offset < vBufSize;) {
370
371 IATBlock *currIAT = findIAT(exportsMap, search_offset);
372 if (!currIAT) {
373 //can't find any more IAT
374 break;
375 }
376 found++;
377 const DWORD iat_offset = currIAT->iatOffset;
378 const size_t iat_end = iat_offset + currIAT->iatSize;
379 if (!appendFoundIAT(iat_offset, currIAT)) {
380 delete currIAT; //this IAT already exist in the map
381 }
382 // next search should be after thie current IAT:
383 if (iat_end <= search_offset) {
384 break; //this should never happen
385 }
387 }
388 return found;
389}
390
391bool pesieve::ImpReconstructor::findImportTable(IN const peconv::ExportsMapper* exportsMap)
392{
393 BYTE *vBuf = this->peBuffer.vBuf;
394 const size_t vBufSize = this->peBuffer.vBufSize;
395 if (!vBuf) return false;
396
397 IMAGE_DATA_DIRECTORY* imp_dir = peconv::get_directory_entry(vBuf, IMAGE_DIRECTORY_ENTRY_IMPORT, true);
398 if (!imp_dir) {
399 return false;
400 }
401 IMAGE_DATA_DIRECTORY *iat_dir = peconv::get_directory_entry(vBuf, IMAGE_DIRECTORY_ENTRY_IAT, true);
402 if (!iat_dir) {
403 return false;
404 }
406 size_t table_size = 0;
407
408 const size_t start_offset = peconv::get_hdrs_size(vBuf);
409
410 std::map<DWORD, IATBlock*>::iterator itr;
411 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
412 IATBlock *currIAT = itr->second;
413
414 const DWORD iat_offset = currIAT->iatOffset;
415#ifdef _DEBUG
416 std::cout << "[*] Searching import table for IAT: " << std::hex << iat_offset << ", size: " << currIAT->iatSize << std::endl;
417#endif
418 bool is64bit = peconv::is64bit(vBuf);
420 is64bit,
421 vBuf,
422 vBufSize,
423 exportsMap,
427 );
428 if (import_table) {
429 //import table found, set it in the IATBlock:
430 currIAT->importTableOffset = DWORD((ULONG_PTR)import_table - (ULONG_PTR)vBuf);
431 //overwrite the Data Directory:
432 iat_dir->VirtualAddress = iat_offset;
433 iat_dir->Size = MASK_TO_DWORD(currIAT->iatSize);
434 break;
435 }
436 }
437
438 if (!import_table) return false;
439
441 if (imp_dir->VirtualAddress == imp_offset && imp_dir->Size == table_size) {
442 //std::cout << "[*] Validated Imports offset!\n";
443 return true;
444 }
445#ifdef _DEBUG
446 if (imp_dir->Size == table_size) {
447 std::cout << "[*] Validated Imports size!\n";
448 }
449#endif
450 //overwrite the Data Directory:
451 imp_dir->VirtualAddress = imp_offset;
453 return true;
454}
455
456bool pesieve::ImpReconstructor::findIATsCoverage(IN const peconv::ExportsMapper* exportsMap, t_imprec_filter filter)
457{
458 size_t neededIATs = 0;
459 size_t covered = 0;
460 std::map<DWORD, IATBlock*>::iterator itr;
461 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
462 IATBlock* iat = itr->second;
463
464 switch (filter) {
465 case IMP_REC0:
466 if (!iat->isInMain && !iat->isTerminated) {
467 continue;
468 }
469 case IMP_REC1:
470 if (!iat->isInMain && !iat->isTerminated && iat->countThunks() < MIN_THUNKS_COUNT) {
471 continue;
472 }
473 }
474 neededIATs++;
475
476 if (iat->makeCoverage(exportsMap)) {
477 covered++;
478 }
479 else {
480 std::cout << "[-] Failed covering block: " << std::hex << itr->first << " series: " << iat->thunkSeries.size() << "\n";
481 }
482 }
483 if (neededIATs == 0) {
484 return false;
485 }
486 return (covered == neededIATs);
487}
488
489ImportTableBuffer* pesieve::ImpReconstructor::constructImportTable()
490{
491 BYTE *vBuf = this->peBuffer.vBuf;
492 const size_t vBufSize = this->peBuffer.vBufSize;
493 if (!vBuf || !vBufSize) return nullptr;
494
495 size_t ready_blocks = 0;
496 std::map<DWORD, IATBlock*>::iterator itr;
497 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
498 IATBlock* iat = itr->second;
499 if (iat->isValid()) {
500 ready_blocks += iat->thunkSeries.size();
501 }
502 }
503 if (ready_blocks == 0) {
504 return nullptr;
505 }
506 const DWORD end_rva = MASK_TO_DWORD(vBufSize);
508 if (!importTableBuffer) {
509 return nullptr;
510 }
512
513 const DWORD names_start_rva = MASK_TO_DWORD(importTableBuffer->getRVA() + importTableBuffer->getDescriptorsSize());
515 size_t names_space = 0;
516 size_t i = 0;
517 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
518 IATBlock* iat = itr->second;
519 if (!iat->isValid()) {
520 continue;
521 }
522 IATThunksSeriesSet::iterator sItr;
523 for (sItr = iat->thunkSeries.begin(); sItr != iat->thunkSeries.end(); ++sItr, ++i) {
525 importTableBuffer->descriptors[i].FirstThunk = series->startOffset;
526 importTableBuffer->descriptors[i].OriginalFirstThunk = orig_thunk_rva;
527 //calculate size for names
528 const DWORD names_space_size = MASK_TO_DWORD(series->sizeOfNamesSpace(this->is64bit));
531 }
532 }
533 //fill functions' names:
536 size_t dlls_area_size = 0;
537 i = 0;
538 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
539 IATBlock* iat = itr->second;
540 if (!iat->isValid()) {
541 continue;
542 }
543 IATThunksSeriesSet::iterator sItr;
544 for (sItr = iat->thunkSeries.begin(); sItr != iat->thunkSeries.end(); ++sItr++, ++i) {
546 DWORD name_rva = importTableBuffer->descriptors[i].OriginalFirstThunk;
547 const size_t names_space_size = series->sizeOfNamesSpace(this->is64bit);
548 BYTE *buf = importTableBuffer->getNamesSpaceAt(name_rva, names_space_size);
549 if (!buf) {
550 continue;
551 }
552 series->fillNamesSpace(buf, names_space_size, name_rva, this->is64bit);
553 }
554 dlls_area_size += iat->sizeOfDllsSpace();
555 }
556 //fill DLLs' names:
557 importTableBuffer->allocDllsSpace(dlls_rva, dlls_area_size);
559 i = 0;
560 //TODO: optimize it: write the repeating DLL names only once
561 for (itr = foundIATs.begin(); itr != foundIATs.end(); ++itr) {
562 IATBlock* iat = itr->second;
563 if (!iat->isValid()) {
564 continue;
565 }
566 DWORD max_dll_name = MASK_TO_DWORD(iat->maxDllLen());
567 IATThunksSeriesSet::iterator sItr;
568 for (sItr = iat->thunkSeries.begin(); sItr != iat->thunkSeries.end(); ++sItr, ++i) {
570 importTableBuffer->descriptors[i].Name = dll_name_rva;
572 if (buf) {
573 //fill the name:
574 memcpy(buf, series->getDllName().c_str(), series->getDllName().length() + 1);
575 }
577 }
578 }
579 return importTableBuffer;
580}
581
582bool pesieve::ImpReconstructor::appendImportTable(ImportTableBuffer &importTable)
583{
584 const size_t import_table_size = importTable.getDescriptorsSize() + importTable.getNamesSize() + importTable.getDllNamesSize();
585 const size_t new_size = peBuffer.vBufSize + import_table_size;
586
587 if (!peBuffer.resizeBuffer(new_size)) {
588 return false;
589 }
590
591 const DWORD imports_start_rva = importTable.getRVA();
592 peBuffer.resizeLastSection(imports_start_rva + import_table_size);
593 return importTable.setTableInPe(peBuffer.vBuf, peBuffer.vBufSize);
594}
size_t countThunks() const
Definition iat_block.h:125
t_imprec_res rebuildImportTable(const IN peconv::ExportsMapper *exportsMap, IN const pesieve::t_imprec_mode &imprec_mode)
enum pesieve::ImpReconstructor::imprec_res t_imprec_res
bool printFoundIATs(std::string reportPath)
BYTE * getDllSpaceAt(const DWORD rva, size_t required_size)
BYTE * getNamesSpaceAt(const DWORD rva, size_t required_size)
bool allocDesciptors(size_t descriptors_count)
A class containing callbacks for functions: find_iat, fill_iat.
Definition iat_finder.h:16
#define MASK_TO_DWORD(val)
Definition iat_finder.h:9
#define MIN_THUNKS_COUNT
iat
Definition demo.py:20
DWORD(__stdcall *_PssCaptureSnapshot)(HANDLE ProcessHandle
BYTE * get_buffer_space_at(IN BYTE *buffer, IN const size_t buffer_size, IN const DWORD buffer_rva, IN const DWORD required_rva, IN const size_t required_size)
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
IMAGE_IMPORT_DESCRIPTOR * find_import_table(IN bool is64bit, IN BYTE *vBuf, IN size_t vBufSize, IN const peconv::ExportsMapper *exportsMap, IN DWORD iat_offset, OUT size_t &table_size, IN OPTIONAL size_t search_offset)
@ PE_IMPREC_AUTO
try to autodetect the most suitable mode
@ PE_IMPREC_REBUILD0
build the import table from the scratch, basing on the found IAT(s): use only terminated blocks (rest...
@ PE_IMPREC_UNERASE
recover erased parts of the partialy damaged import table
@ PE_IMPREC_REBUILD2
build the import table from the scratch, basing on the found IAT(s): use all found blocks (aggressive...
@ PE_IMPREC_REBUILD1
build the import table from the scratch, basing on the found IAT(s): use terminated blocks,...
Final summary about the scanned process.