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
iat_scanner.cpp
Go to the documentation of this file.
1#include "iat_scanner.h"
2
3#include <peconv.h>
4
5#include <string>
6#include <fstream>
7#include <iostream>
8
9using namespace pesieve;
10
11const bool IATScanReport::hooksToJSON(std::stringstream &outs, size_t level)
12{
13 if (notCovered.count() == 0) {
14 return false;
15 }
16 bool is_first = true;
17 OUT_PADDED(outs, level, "\"hooks_list\" : [\n");
18
19 std::map<DWORD, ULONGLONG>::iterator itr;
20 for (itr = notCovered.thunkToAddr.begin(); itr != notCovered.thunkToAddr.end(); ++itr) {
21 const DWORD thunk_rva = itr->first;
22 const ULONGLONG addr = itr->second;
23 if (!is_first) {
24 outs << ",\n";
25 }
26 is_first = false;
27 OUT_PADDED(outs, level, "{\n");
28
29 OUT_PADDED(outs, (level + 1), "\"thunk_rva\" : ");
30 outs << "\"" << std::hex << thunk_rva << "\"" << ",\n";
31
32 std::map<DWORD, peconv::ExportedFunc*>::const_iterator found = storedFunc.thunkToFunc.find(thunk_rva);
33 if (found != storedFunc.thunkToFunc.end()) {
34 const peconv::ExportedFunc *func = found->second;
35 if (func) {
36 OUT_PADDED(outs, (level + 1), "\"func_name\" : ");
37 outs << "\"" << func->toString() << "\"" << ",\n";
38 }
39 }
40 OUT_PADDED(outs, (level + 1), "\"target_va\" : ");
41 outs << "\"" << std::hex << (ULONGLONG)addr << "\"";
42 outs << "\n";
43 OUT_PADDED(outs, level, "}");
44 }
45 outs << "\n";
46 OUT_PADDED(outs, level, "]");
47 return true;
48}
49
50std::string IATScanReport::formatTargetName(IN const peconv::ExportsMapper* exportsMap, IN const ModulesInfo& modulesInfo, IN const ULONGLONG module_start, IN ULONGLONG addr)
51{
52 if (addr == 0) {
53 return "(invalid)";
54 }
55 if (!exportsMap) {
56 return "";
57 }
58 const peconv::ExportedFunc* func = exportsMap->find_export_by_va(addr);
59 if (func) {
60 return func->toString();
61 }
62 const ScannedModule* modExp = modulesInfo.findModuleContaining(addr);
63 if (!modExp) {
64 if (module_start == 0) {
65 return "(invalid)";
66 }
67 return "(unknown)";
68 }
69 std::stringstream report;
70 report << peconv::get_dll_shortname(modExp->getModName());
71 report << ".(unknown_func)";
72 return report.str();
73}
74
75std::string IATScanReport::formatHookedFuncName(IN peconv::ImportsCollection* storedFunc, DWORD thunk_rva)
76{
77 if (!storedFunc) {
78 return "(unknown)";
79 }
80 std::map<DWORD, peconv::ExportedFunc*>::const_iterator found = storedFunc->thunkToFunc.find(thunk_rva);
81 if (found != storedFunc->thunkToFunc.end()) {
82 const peconv::ExportedFunc* func = found->second;
83 if (!func) {
84 return ""; //this should not happen
85 }
86 return func->toString();
87 }
88 return "(unknown)";
89
90}
91
92bool IATScanReport::saveNotRecovered(IN const std::string& fileName,
93 IN HANDLE hProcess,
94 IN peconv::ImportsCollection *storedFunc,
95 IN peconv::ImpsNotCovered &notCovered,
96 IN const ModulesInfo &modulesInfo,
97 IN const peconv::ExportsMapper *exportsMap)
98{
99 const char delim = ';';
100 const char internal_delim = ':';
101
102 if (notCovered.count() == 0) {
103 return false;
104 }
105 std::ofstream report;
106 report.open(fileName);
107 if (report.is_open() == false) {
108 return false;
109 }
110
111 std::map<DWORD,ULONGLONG>::iterator itr;
112 for (itr = notCovered.thunkToAddr.begin(); itr != notCovered.thunkToAddr.end(); ++itr)
113 {
114 const DWORD thunk_rva = itr->first;
115 const ULONGLONG addr = itr->second;
116 report << std::hex << thunk_rva << delim;
117 if (storedFunc) {
118 report << "[" << formatHookedFuncName(storedFunc, thunk_rva) << "]";
119 report << "->";
120 }
121 const ScannedModule* modExp = modulesInfo.findModuleContaining(addr);
122 const ULONGLONG module_start = (modExp) ? modExp->getStart() : peconv::fetch_alloc_base(hProcess, (BYTE*)addr);
123 const ULONGLONG offset = addr - module_start;
124 report << std::hex << addr;
125 report << "["
126 << std::hex << module_start << "+" << offset
127 << internal_delim
128 << formatTargetName(exportsMap, modulesInfo, module_start, addr);
129 report << internal_delim;
130 if (modExp) {
131 report << modExp->isSuspicious();
132 }
133 else {
134 report << true; // module not found, assume suspicious
135 }
136 report << "]";
137 report << std::endl;
138 }
139 report.close();
140 return true;
141}
142
143bool IATScanReport::generateList(IN const std::string &fileName, IN HANDLE hProcess, IN const ModulesInfo &modulesInfo, IN const peconv::ExportsMapper *exportsMap)
144{
145 return saveNotRecovered(fileName,
146 hProcess,
147 &storedFunc,
149 modulesInfo,
150 exportsMap);
151}
152
153
154template <typename FIELD_T>
155FIELD_T get_thunk_at_rva(BYTE *mod_buf, size_t mod_size, DWORD rva)
156{
157 if (!mod_buf || !mod_size) {
158 return 0;
159 }
160 if (!peconv::validate_ptr(mod_buf, mod_size, (BYTE*)((ULONG_PTR)mod_buf + rva), sizeof(FIELD_T))) {
161 return 0;
162 }
163
164 FIELD_T* field_ptr = (FIELD_T*)((ULONG_PTR)mod_buf + rva);
165 return (*field_ptr);
166}
167
168bool pesieve::IATScanner::isValidFuncFilled(ULONGLONG filled_val, const peconv::ExportedFunc& definedFunc, const peconv::ExportedFunc &possibleFunc)
169{
170 if (!peconv::ExportedFunc::isTheSameFuncName(possibleFunc, definedFunc)) {
171 return false;
172 }
173 if (peconv::ExportedFunc::isTheSameDllName(possibleFunc, definedFunc)) {
174 return true;
175 }
176 ULONGLONG dll_base = this->exportsMap.find_dll_base_by_func_va(filled_val);
177 if (!dll_base) {
178 return false; //could not find a DLL by this function value
179 }
180 // check for a common redirection to another system DLL:
181 const std::string fullName = exportsMap.get_dll_path(dll_base);
182 //std::cout << std::hex << filled_val << " : " << dll_base << " : " << fullName << " : " << definedFunc.toString() << " : " << defined_short << " vs " << possible_short << "\n";
183 if (isInSystemDir(fullName)) {
184 return true;
185 }
186 return false;
187}
188
189bool pesieve::IATScanner::scanByOriginalTable(peconv::ImpsNotCovered &not_covered)
190{
191 if (!remoteModData.isInitialized()) {
192 std::cerr << "[-] Failed to initialize remote module header" << std::endl;
193 return false;
194 }
195 if (!moduleData.isInitialized() && !moduleData.loadOriginal()) {
196 std::cerr << "[-] Failed to initialize module data: " << moduleData.szModName << std::endl;
197 return false;
198 }
199
200 // first try to find by the Import Table in the original file:
201 peconv::ImportsCollection collection;
202 if (!listAllImports(collection)) {
203 return false;
204 }
205 if (collection.size() == 0) {
206 return true; //nothing to scan...
207 }
208
209 // load full remote for the IAT scan:
210 if (!remoteModData.loadFullImage()) {
211 std::cerr << "[-] Failed to initialize remote module" << std::endl;
212 return false;
213 }
214 std::map<DWORD, peconv::ExportedFunc*>::iterator itr;
215 // get filled thunks from the mapped module (remote):
216
217 for (itr = collection.thunkToFunc.begin(); itr != collection.thunkToFunc.end(); ++itr) {
218 DWORD thunk_rva = itr->first;
219
220 //std::cout << "Thunk: " << std::hex << *itr << "\n";
221 ULONGLONG filled_val = 0;
222 if (moduleData.is64bit()) {
223 filled_val = get_thunk_at_rva<ULONGLONG>(remoteModData.imgBuffer, remoteModData.imgBufferSize, thunk_rva);
224 }
225 else {
226 filled_val = get_thunk_at_rva<DWORD>(remoteModData.imgBuffer, remoteModData.imgBufferSize, thunk_rva);
227 }
228 peconv::ExportedFunc* defined_func = itr->second;
229 if (!defined_func) {
230 // cannot retrieve the origial import
231 continue;
232 }
233
234 const std::set<peconv::ExportedFunc>* possibleExports = exportsMap.find_exports_by_va(filled_val);
235 // no export at this thunk:
236 if (!possibleExports || possibleExports->size() == 0) {
237
238 //filter out .NET: mscoree._CorExeMain
239 const std::string dShortName = peconv::get_dll_shortname(defined_func->libName);
240 if (dShortName == "mscoree"
241 && (defined_func->funcName == "_CorExeMain" || defined_func->funcName == "_CorDllMain") )
242 {
243 continue; //this is normal, skip it
244 }
245
246 not_covered.insert(thunk_rva, filled_val);
247#ifdef _DEBUG
248 std::cout << "Function not covered: " << std::hex << thunk_rva << " [" << dShortName << "] func: [" << defined_func->funcName << "] val: " << std::hex << filled_val << "\n";
249#endif
250 continue;
251 }
252
253 // check if the defined import matches the possible ones:
254 bool is_covered = false;
255 std::set<peconv::ExportedFunc>::const_iterator cItr;
256 for (cItr = possibleExports->begin(); cItr != possibleExports->end(); ++cItr) {
257 const peconv::ExportedFunc possibleFunc = *cItr;
258 if (isValidFuncFilled(filled_val, *defined_func, possibleFunc)){
259 is_covered = true;
260 break;
261 }
262 }
263
264 if (!is_covered) {
265 not_covered.insert(thunk_rva, filled_val);
266#ifdef _DEBUG
267 std::cout << "Mismatch at RVA: " << std::hex << thunk_rva << " " << defined_func->libName<< " func: " << defined_func->toString() << "\n";
268
269 for (cItr = possibleExports->begin(); cItr != possibleExports->end(); ++cItr) {
270 const peconv::ExportedFunc possibleFunc = *cItr;
271 std::cout << "\t proposed: " << possibleFunc.libName << " : " << possibleFunc.toString() << "\n";
272 }
273#endif
274 }
275 }
276 return true;
277}
278
280{
281 if (!remoteModData.isInitialized()) {
282 std::cerr << "[-] Failed to initialize remote module header" << std::endl;
283 return nullptr;
284 }
285
286 peconv::ImpsNotCovered not_covered;
288
289 if (!scanByOriginalTable(not_covered)) {
290 // IAT scan failed:
291 status = SCAN_ERROR;
292 }
293
294 if (not_covered.count() > 0) {
295#ifdef _DEBUG
296 std::cout << "[*] IAT: " << moduleData.szModName << " hooked: " << not_covered.count() << "\n";
297#endif
298 status = SCAN_SUSPICIOUS;
299 }
300
301 IATScanReport *report = new(std::nothrow) IATScanReport(remoteModData.modBaseAddr, remoteModData.getModuleSize(), moduleData.szModName);
302 if (!report) {
303 return nullptr;
304 }
305
306 if (not_covered.count()) {
307 listAllImports(report->storedFunc);
308 }
309 if (this->hooksFilter != PE_IATS_UNFILTERED) {
310 filterResults(not_covered, *report);
311 }
312 else {
313 report->notCovered = not_covered;
314 }
315 report->status = status;
316 if (report->countHooked() == 0) {
317 report->status = SCAN_NOT_SUSPICIOUS;
318 }
319 return report;
320}
322
323void pesieve::IATScanner::initExcludedPaths()
324{
325 char sysWow64Path[MAX_PATH] = { 0 };
326 ExpandEnvironmentStringsA("%SystemRoot%\\SysWoW64", sysWow64Path, MAX_PATH);
327 this->m_sysWow64Path_str = sysWow64Path;
328 std::transform(m_sysWow64Path_str.begin(), m_sysWow64Path_str.end(), m_sysWow64Path_str.begin(), tolower);
329
330 char system32Path[MAX_PATH] = { 0 };
331 ExpandEnvironmentStringsA("%SystemRoot%\\system32", system32Path, MAX_PATH);
332 this->m_system32Path_str = system32Path;
333 std::transform(m_system32Path_str.begin(), m_system32Path_str.end(), m_system32Path_str.begin(), tolower);
334}
335
336bool pesieve::IATScanner::isInSystemDir(const std::string &moduleName)
337{
338 std::string dirName = peconv::get_directory_name(moduleName);
339 std::transform(dirName.begin(), dirName.end(), dirName.begin(), tolower);
340
341 if (dirName == m_system32Path_str || dirName == m_sysWow64Path_str) {
342 return true;
343 }
344 return false;
345}
346
347bool pesieve::IATScanner::filterResults(peconv::ImpsNotCovered &notCovered, IATScanReport &report)
348{
349 std::map<DWORD, ULONGLONG>::iterator itr;
350 for (itr = notCovered.thunkToAddr.begin(); itr != notCovered.thunkToAddr.end(); ++itr)
351 {
352 const DWORD thunk = itr->first;
353 const ULONGLONG addr = itr->second;
354
355 ScannedModule *modExp = modulesInfo.findModuleContaining(addr);
356 ULONGLONG module_start = (modExp) ? modExp->getStart() : peconv::fetch_alloc_base(this->processHandle, (BYTE*)addr);
357 if (module_start == 0) {
358 // invalid address of the hook
359 report.notCovered.insert(thunk, addr);
360 continue;
361 }
362 if (this->hooksFilter == PE_IATS_CLEAN_SYS_FILTERED) {
363 // insert hooks leading to suspicious modules:
364 if (modExp && modExp->isSuspicious()) {
365 report.notCovered.insert(thunk, addr);
366 continue;
367 }
368 }
369 // filter out hooks leading to system DLLs
370 std::string moduleName = this->exportsMap.get_dll_path(module_start);
371 if (isInSystemDir(moduleName)) {
372#ifdef _DEBUG
373 std::cout << "Skipped: " << moduleName << "\n";
374#endif
375 continue;
376 }
377 // insert hooks leading to non-system modules:
378 report.notCovered.insert(thunk, addr);
379 }
380 return true;
381}
382
383bool pesieve::IATScanner::listAllImports(peconv::ImportsCollection &_storedFunc)
384{
385 return moduleData.loadImportsList(_storedFunc);
386}
387
A report from an IAT scan, generated by IATScanner.
Definition iat_scanner.h:12
const bool hooksToJSON(std::stringstream &outs, size_t level)
static std::string formatHookedFuncName(IN peconv::ImportsCollection *storedFunc, DWORD thunk_rva)
bool generateList(IN const std::string &fileName, IN HANDLE hProcess, IN const ModulesInfo &modulesInfo, IN const peconv::ExportsMapper *exportsMap)
peconv::ImportsCollection storedFunc
Definition iat_scanner.h:51
static std::string formatTargetName(IN const peconv::ExportsMapper *exportsMap, IN const ModulesInfo &modulesInfo, IN const ULONGLONG module_start, IN ULONGLONG addr)
static bool saveNotRecovered(IN const std::string &fileName, IN HANDLE hProcess, IN peconv::ImportsCollection *storedFunc, IN peconv::ImpsNotCovered &notCovered, IN const ModulesInfo &modulesInfo, IN const peconv::ExportsMapper *exportsMap)
peconv::ImpsNotCovered notCovered
Definition iat_scanner.h:52
virtual IATScanReport * scanRemote()
A container of all the process modules that were scanned.
Represents a basic info about the scanned module, such as its base offset, size, and the status.
std::string getModName() const
ULONGLONG getStart() const
#define OUT_PADDED(stream, field_size, str)
Definition format_util.h:12
FIELD_T get_thunk_at_rva(BYTE *mod_buf, size_t mod_size, DWORD rva)
DWORD(__stdcall *_PssCaptureSnapshot)(HANDLE ProcessHandle
int MAX_PATH
Definition pesieve.py:10
enum pesieve::module_scan_status t_scan_status
@ PE_IATS_CLEAN_SYS_FILTERED
scan IAT, filter hooks if they lead to unpatched system module
@ PE_IATS_UNFILTERED
scan IAT, unfiltered
Final summary about the scanned process.