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
thread_scanner.h
Go to the documentation of this file.
1#pragma once
2
3#include <windows.h>
4
5#include "module_scanner.h"
8#include "../stats/stats.h"
10#include <vector>
11
12namespace pesieve {
13
24
25 inline std::string indicator_to_str(const ThSusIndicator& indicator)
26 {
27 switch (indicator) {
28 case THI_NONE: return "NONE";
29 case THI_SUS_START: return "SUS_START";
30 case THI_SUS_IP: return "SUS_IP";
31 case THI_SUS_RET: return "SUS_RET";
32 case THI_SUS_CALLSTACK_SHC: return "SUS_CALLSTACK_SHC";
33 case THI_SUS_CALLS_INTEGRITY: return "SUS_CALLS_INTEGRITY";
34 case THI_SUS_CALLSTACK_CORRUPT: return "SUS_CALLSTACK_CORRUPT";
35 }
36 return "";
37 }
38
40 typedef struct _ctx_details {
41 bool is64b;
42 ULONGLONG rip;
43 ULONGLONG rsp;
44 ULONGLONG rbp;
45 ULONGLONG last_ret; // the last return address on the stack
46 ULONGLONG ret_on_stack; // the last return address stored on the stack
49 bool is_managed; // does it contain .NET modules
50 std::vector<ULONGLONG> callStack;
51
52 _ctx_details(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0)
53 {
54 init(_is64b, _rip, _rsp, _rbp, _ret_addr);
55 }
56
57 void init(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0)
58 {
59 this->is64b = _is64b;
60 this->rip = _rip;
61 this->rsp = _rsp;
62 this->rbp = _rbp;
63 this->last_ret = _ret_addr;
64 this->ret_on_stack = 0;
65 this->is_ret_as_syscall = true;
66 this->is_ret_in_frame = true;
67 this->is_managed = false;
68 this->callStack.clear();
69 }
70
72
74 public:
75 SuspAddrReport(ULONGLONG _module = 0 , size_t _moduleSize = 0, DWORD _allocProtection = 0)
76 : module(_module), moduleSize(_moduleSize),
77 alloc_protection(_allocProtection), curr_protection(0),
78 is_code(false)
79 {
80 }
81
82 void print()
83 {
84 std::cout << __FUNCTION__ << " : " << std::hex << module << " : " << moduleSize
85 << " alloc_protect: " << alloc_protection
86 << " curr_protect: " << curr_protection
87 << " is_code: " << is_code
88 << " entropy: " << stats.entropy
89 << " susp_addr [";
90 for (auto itr = suspAddresses.begin(); itr != suspAddresses.end(); ++itr) {
91 ULONGLONG susp_addr = *itr;
92 std::cout << " " << susp_addr;
93 }
94 std::cout << " ]" << std::endl;
95 }
96
97 const bool toJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) const
98 {
99 if (addressesToJSON(outs, level, jdetails)) {
100 outs << ",\n";
101 }
102 moduleInfoToJSON(outs, level, jdetails);
103 return true;
104 }
105
106 void addSuspAddr(ULONGLONG addr)
107 {
108 this->suspAddresses.insert(addr);
109 }
110
111 bool addressesToJSON(std::stringstream& outs,size_t level,const pesieve::t_json_level& jdetails) const
112 {
113 if (!suspAddresses.size()) {
114 return false;
115 }
116
117 std::string addrTypeStr = "susp_return_addr";
118 if (this->module && this->moduleSize) {
119 addrTypeStr = "susp_addr";
120 }
121
122 OUT_PADDED(outs, level, "\"" << addrTypeStr << "\" : [");
123
124 bool isFirst = true;
125 for (auto itr = suspAddresses.begin(); itr != suspAddresses.end(); ++itr) {
126 const ULONGLONG susp_addr = *itr;
127 if (!susp_addr) {
128 continue;
129 }
130 if (!isFirst) {
131 outs << ", ";
132 }
133 outs << "\"" << std::hex << susp_addr << "\"";
134 isFirst = false;
135 }
136 outs << "]";
137 return true;
138 }
139
140 bool moduleInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) const
141 {
142 if (!this->module) {
143 return false;
144 }
145 OUT_PADDED(outs, level, "\"module\" : ");
146 outs << "\"" << std::hex << (ULONGLONG)module << "\"";
147 if (moduleSize) {
148 outs << ",\n";
149 OUT_PADDED(outs, level, "\"module_size\" : ");
150 outs << "\"" << std::hex << (ULONGLONG)moduleSize << "\"";
151 }
152 outs << ",\n";
154 OUT_PADDED(outs, level, "\"alloc_protection\" : ");
155 outs << "\"" << std::hex << alloc_protection << "\"";
156 outs << ",\n";
157 OUT_PADDED(outs, level, "\"curr_protection\" : ");
158 outs << "\"" << std::hex << curr_protection << "\"";
159 }
160 else {
161 OUT_PADDED(outs, level, "\"protection\" : ");
162 outs << "\"" << std::hex << curr_protection << "\"";
163 }
164 outs << ",\n";
165 OUT_PADDED(outs, level, "\"is_code\" : ");
166 outs << "\"" << std::dec << is_code << "\"";
167 if (stats.isFilled()) {
168 outs << ",\n";
169 stats.toJSON(outs, level);
170 }
171 outs << "\n";
172 return true;
173 }
174
175 ULONG_PTR module;
177 std::set<ULONGLONG> suspAddresses;
182 };
183
186 {
187 public:
188 static const DWORD THREAD_STATE_UNKNOWN = (-1);
189 static const DWORD THREAD_STATE_WAITING = 5;
190
191 static std::string translate_thread_state(DWORD thread_state);
192 static std::string translate_wait_reason(DWORD thread_wait_reason);
193
194 //---
195
204
206 {
207 for (auto itr = this->suspAreaReports.begin(); itr != this->suspAreaReports.end(); ++itr) {
208 SuspAddrReport* rep = itr->second;
209 delete rep;
210 }
211 suspAreaReports.clear();
212 }
213
214 SuspAddrReport* findAreaForAddress(const ULONGLONG &susp_addr)
215 {
216 auto found = this->suspAreaReports.find(susp_addr);
217 if (found != this->suspAreaReports.end()) {
218 return found->second;
219 }
220 for (auto itr = this->suspAreaReports.begin(); itr != this->suspAreaReports.end(); ++itr) {
221 SuspAddrReport* rep = itr->second;
222 if (!rep) continue;
223 if (susp_addr >= rep->module && susp_addr < (rep->module + rep->moduleSize)) {
224 return rep;
225 }
226 }
227 return nullptr;
228 }
229
230 const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails, const ctx_details& details)
231 {
232 bool printCallstack = (jdetails >= JSON_DETAILS) ? true : false;
233 if (this->indicators.find(THI_SUS_CALLSTACK_CORRUPT) != this->indicators.end()) {
234 printCallstack = true;
235 }
236 if (this->indicators.find(THI_SUS_CALLSTACK_SHC) != this->indicators.end()) {
237 printCallstack = true;
238 }
239 OUT_PADDED(outs, level, "\"stack_ptr\" : ");
240 outs << "\"" << std::hex << details.rsp << "\"";
241 if (details.callStack.size()) {
242 outs << ",\n";
243 OUT_PADDED(outs, level, "\"frames_count\" : ");
244 outs << std::dec << details.callStack.size();
245 if (printCallstack) {
246 outs << ",\n";
247 OUT_PADDED(outs, level, "\"frames\" : [");
248 for (auto itr = details.callStack.rbegin(); itr != details.callStack.rend(); ++itr) {
249 if (itr != details.callStack.rbegin()) {
250 outs << ", ";
251 }
252 const ULONGLONG addr = *itr;
253 outs << "\"" << std::hex << addr;
254 auto sItr = this->addrToSymbol.find(addr);
255 if (sItr != this->addrToSymbol.end()) {
256 const std::string &sym = sItr->second;
257 if (!sym.empty()) {
258 outs << ";" << sym;
259 }
260 }
261 outs << "\"";
262 }
263 outs << "]";
264 }
265 }
266 }
267
268 const bool threadInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
269 {
270 OUT_PADDED(outs, level, "\"state\" : ");
272 outs << "\"" << "UNKNOWN" << "\"";
273 }
274 else {
275 outs << "\"" << translate_thread_state(thread_state) << "\"";
276 }
278 outs << ",\n";
279 OUT_PADDED(outs, level, "\"wait_reason\" : ");
280 outs << "\"" << translate_wait_reason(thread_wait_reason) << "\"";
281 }
282 if (stack_ptr) {
283 outs << ",\n";
284 OUT_PADDED(outs, level, "\"callstack\" : {\n");
285 callstackToJSON(outs, level + 1, jdetails, cDetails);
286 outs << "\n";
287 OUT_PADDED(outs, level, "}");
288 }
290 outs << ",\n";
291 OUT_PADDED(outs, level, "\"native_callstack\" : {\n");
292 callstackToJSON(outs, level + 1, jdetails, nativeWow64Details);
293 outs << "\n";
294 OUT_PADDED(outs, level, "}");
295 }
296 bool showLastCall = (jdetails >= JSON_DETAILS) ? true : false;
297 if ((this->indicators.find(THI_SUS_CALLS_INTEGRITY) != this->indicators.end()) ||
298 (this->indicators.find(THI_SUS_CALLSTACK_CORRUPT) != this->indicators.end()) )
299 {
300 showLastCall = true;
301 }
302 if (showLastCall) {
303 if (!this->lastSyscall.empty()) {
304 outs << ",\n";
305 OUT_PADDED(outs, level, "\"last_sysc\" : ");
306 outs << "\"" << this->lastSyscall << "\"";
307 }
308 if (!this->lastFunction.empty() && (this->lastFunction != this->lastSyscall)) {
309 outs << ",\n";
310 OUT_PADDED(outs, level, "\"last_func\" : ");
311 outs << "\"" << this->lastFunction << "\"";
312 }
313 }
314 outs << "\n";
315 return true;
316 }
317
318 const bool indicatorsToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
319 {
320 OUT_PADDED(outs, level, "\"indicators\" : [");
321 for (auto itr = indicators.begin(); itr != indicators.end(); ++itr) {
322 if (itr != indicators.begin()) {
323 outs << ", ";
324 }
325 outs << "\"" << indicator_to_str(*itr) << "\"";
326 }
327 outs << "]";
328 return true;
329 }
330
331 const virtual void fieldsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
332 {
333 ElementScanReport::_toJSON(outs, level);
334 outs << ",\n";
335 OUT_PADDED(outs, level, "\"thread_id\" : ");
336 outs << std::dec << tid;
337 outs << ",\n";
338 OUT_PADDED(outs, level, "\"thread_info\" : {\n");
339 threadInfoToJSON(outs, level + 1, jdetails);
340 OUT_PADDED(outs, level, "}");
341 outs << ",\n";
342 indicatorsToJSON(outs, level, jdetails);
343
344 if (!suspAreaReports.empty()) {
345 outs << ",\n";
346 OUT_PADDED(outs, level, "\"susp_areas\" : [\n");
347
348 bool isFirst = true;
349 for (const auto& entry : suspAreaReports) {
350 const SuspAddrReport* suspr = entry.second;
351 if (!suspr) {
352 continue;
353 }
354 if (!isFirst) {
355 outs << ",\n";
356 }
357 OUT_PADDED(outs, level + 1, "{\n");
358 suspr->toJSON(outs, level + 2, jdetails);
359 OUT_PADDED(outs, level + 1, "}");
360 isFirst = false;
361 }
362 outs << "\n";
363 OUT_PADDED(outs, level, "]");
364 }
365 }
366
367 const virtual bool toJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level &jdetails)
368 {
369 OUT_PADDED(outs, level, "\"thread_scan\" : {\n");
370 fieldsToJSON(outs, level + 1, jdetails);
371 outs << "\n";
372 OUT_PADDED(outs, level, "}");
373 return true;
374 }
375
376 DWORD tid;
377 ULONGLONG stack_ptr;
381
382 std::string lastSyscall;
383 std::string lastFunction;
384
385 // Primary context used by the existing analysis logic. For a WOW64 target
386 // scanned by a 64-bit build, this remains the 32-bit guest context.
388
389 // Additional native AMD64 context exposed for WOW64 targets. It is report-only
390 // in this narrow refactor; indicator semantics remain unchanged.
393
394 std::map<ULONGLONG, std::string> addrToSymbol;
395 std::set<ULONGLONG> shcCandidates;
396 std::set<ThSusIndicator> indicators;
397 std::map<ULONGLONG, SuspAddrReport*> suspAreaReports;
398 };
399
403 public:
404 ThreadScanner(HANDLE hProc, bool _isReflection, bool _isManaged, const util::thread_info& _info, ModulesInfo& _modulesInfo, peconv::ExportsMapper* _exportsMap, ProcessSymbolsManager* _symbols)
405 : ProcessFeatureScanner(hProc), isReflection(_isReflection), isManaged(_isManaged),
406 info(_info), modulesInfo(_modulesInfo), exportsMap(_exportsMap), symbols(_symbols)
407 {
408 }
409
410 virtual ThreadScanReport* scanRemote();
411
412 protected:
413 void initReport(ThreadScanReport& my_report);
415 static std::string choosePreferredFunctionName(const std::string& dbgSymbol, const std::string& manualSymbol);
416
417 bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport& my_report);
418 bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report);
419
420 bool isAddrInNamedModule(ULONGLONG addr);
421 void printThreadInfo(const util::thread_info& threadi);
422 std::string resolveLowLevelFuncName(IN const ULONGLONG addr, OUT OPTIONAL size_t* disp = nullptr);
423 std::string resolveAddrToString(IN ULONGLONG addr);
424 bool printResolvedAddr(const ULONGLONG addr);
425 size_t fillCallStackInfo(IN HANDLE hThread, const IN LPVOID ctx, IN OUT ctx_details& cDetails);
426 bool fetchNativeThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details& cDetails);
427#ifdef _WIN64
428 bool fetchWow64ThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details& cDetails);
429#endif
430 size_t analyzeCallStackInfo(IN OUT ThreadScanReport& my_report);
431 size_t _analyzeCallStack(IN OUT ctx_details& cDetails, OUT IN std::set<ULONGLONG>& shcCandidates);
432
433 bool checkReturnAddrIntegrity(IN const std::vector<ULONGLONG>& callStack, IN OUT ThreadScanReport& my_report);
434
435 bool fillAreaStats(SuspAddrReport* my_report);
436 bool reportSuspiciousAddr(ThreadScanReport* my_report, ULONGLONG susp_addr);
437 bool filterDotNet(ThreadScanReport& my_report);
438
439 // Assigns the final verdict basing on collected indicators. Returns true if the report was modified.
440 bool assessIndicators(ThreadScanReport& my_report);
441
446 peconv::ExportsMapper* exportsMap;
448 };
449
450}; //namespace pesieve
virtual const bool _toJSON(std::stringstream &outs, size_t level=JSON_LEVEL, const pesieve::t_json_level &jdetails=JSON_BASIC)
ModuleScanReport(HMODULE _module, size_t _moduleSize, t_scan_status _status=SCAN_NOT_SUSPICIOUS)
A container of all the process modules that were scanned.
ProcessFeatureScanner(HANDLE _processHandle)
void addSuspAddr(ULONGLONG addr)
SuspAddrReport(ULONGLONG _module=0, size_t _moduleSize=0, DWORD _allocProtection=0)
const bool toJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails) const
std::set< ULONGLONG > suspAddresses
bool addressesToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails) const
bool moduleInfoToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails) const
A report from the thread scan, generated by ThreadScanner.
virtual const void callstackToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails, const ctx_details &details)
std::map< ULONGLONG, SuspAddrReport * > suspAreaReports
std::set< ThSusIndicator > indicators
std::map< ULONGLONG, std::string > addrToSymbol
virtual const void fieldsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static std::string translate_wait_reason(DWORD thread_wait_reason)
const bool threadInfoToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static const DWORD THREAD_STATE_UNKNOWN
const bool indicatorsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static const DWORD THREAD_STATE_WAITING
SuspAddrReport * findAreaForAddress(const ULONGLONG &susp_addr)
std::set< ULONGLONG > shcCandidates
virtual const bool toJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails)
static std::string translate_thread_state(DWORD thread_state)
virtual ThreadScanReport * scanRemote()
size_t analyzeCallStackInfo(IN OUT ThreadScanReport &my_report)
bool reportSuspiciousAddr(ThreadScanReport *my_report, ULONGLONG susp_addr)
std::string resolveAddrToString(IN ULONGLONG addr)
std::string resolveLowLevelFuncName(IN const ULONGLONG addr, OUT OPTIONAL size_t *disp=nullptr)
size_t _analyzeCallStack(IN OUT ctx_details &cDetails, OUT IN std::set< ULONGLONG > &shcCandidates)
bool checkReturnAddrIntegrity(IN const std::vector< ULONGLONG > &callStack, IN OUT ThreadScanReport &my_report)
void printThreadInfo(const util::thread_info &threadi)
void reportResolvedCallstack(ThreadScanReport &my_report)
ThreadScanner(HANDLE hProc, bool _isReflection, bool _isManaged, const util::thread_info &_info, ModulesInfo &_modulesInfo, peconv::ExportsMapper *_exportsMap, ProcessSymbolsManager *_symbols)
bool printResolvedAddr(const ULONGLONG addr)
bool assessIndicators(ThreadScanReport &my_report)
bool fillAreaStats(SuspAddrReport *my_report)
bool filterDotNet(ThreadScanReport &my_report)
bool fetchNativeThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details &cDetails)
void initReport(ThreadScanReport &my_report)
peconv::ExportsMapper * exportsMap
const util::thread_info & info
bool isAddrInNamedModule(ULONGLONG addr)
static std::string choosePreferredFunctionName(const std::string &dbgSymbol, const std::string &manualSymbol)
size_t fillCallStackInfo(IN HANDLE hThread, const IN LPVOID ctx, IN OUT ctx_details &cDetails)
ProcessSymbolsManager * symbols
bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport &my_report)
bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport &my_report)
#define OUT_PADDED(stream, field_size, str)
Definition format_util.h:12
struct pesieve::util::_thread_info thread_info
struct pesieve::_ctx_details ctx_details
A custom structure keeping a fragment of a thread context.
std::string indicator_to_str(const ThSusIndicator &indicator)
enum pesieve::ThSusIndicator _ThSusIndicator
@ THI_SUS_CALLSTACK_SHC
@ THI_SUS_CALLSTACK_CORRUPT
@ THI_SUS_CALLS_INTEGRITY
@ JSON_DETAILS
include the basic list patches in the main JSON report
void init(bool _is64b=false, ULONGLONG _rip=0, ULONGLONG _rsp=0, ULONGLONG _rbp=0, ULONGLONG _ret_addr=0)
std::vector< ULONGLONG > callStack
_ctx_details(bool _is64b=false, ULONGLONG _rip=0, ULONGLONG _rsp=0, ULONGLONG _rbp=0, ULONGLONG _ret_addr=0)