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.cpp
Go to the documentation of this file.
1#include "thread_scanner.h"
2#include <peconv.h>
3#include "mempage_data.h"
5#include "../utils/ntddk.h"
6#include "../stats/stats.h"
10
12
13#define ENTROPY_TRESHOLD 3.0
14//#define NO_ENTROPY_CHECK
15
16#ifdef _DEBUG
17#define _SHOW_THREAD_INFO
18#endif
19
20using namespace pesieve;
21
22typedef struct _t_stack_enum_params {
23 bool is_ok;
24 HANDLE hProcess;
25 HANDLE hThread;
26 LPVOID ctx;
28 std::vector<ULONGLONG> callStack;
29
30 _t_stack_enum_params(IN HANDLE _hProcess = NULL, IN HANDLE _hThread = NULL, IN LPVOID _ctx = NULL, IN const pesieve::ctx_details* _cDetails = NULL)
31 : is_ok(false),
32 hProcess(_hProcess), hThread(_hThread), ctx(_ctx), cDetails(_cDetails)
33 {
34 }
35
37
38//---
39
40namespace pesieve {
41
42 bool is_thread_running(HANDLE hThread)
43 {
44 DWORD exit_code = 0;
45 if (GetExitCodeThread(hThread, &exit_code)) {
46 if (exit_code != STILL_ACTIVE) {
47#ifdef _DEBUG
48 std::cout << " Thread ExitCode: " << std::dec << exit_code << "\n";
49#endif
50 return false;
51 }
52 }
53 return true;
54 }
55
56};
57
58bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION& page_info)
59{
60 size_t page_info_size = sizeof(MEMORY_BASIC_INFORMATION);
61 const SIZE_T out = VirtualQueryEx(processHandle, (LPCVOID)start_va, &page_info, page_info_size);
62 const bool is_read = (out == page_info_size) ? true : false;
63 const DWORD error = is_read ? ERROR_SUCCESS : GetLastError();
64 if (error != ERROR_SUCCESS) {
65 //nothing to read
66 return false;
67 }
68 return true;
69}
70
71DWORD WINAPI enum_stack_thread(LPVOID lpParam)
72{
73 t_stack_enum_params* args = static_cast<t_stack_enum_params*>(lpParam);
74 if (!args || !args->cDetails || !args->ctx) {
75 return STATUS_INVALID_PARAMETER;
76 }
77 size_t fetched = 0;
78 const pesieve::ctx_details& cDetails = *(args->cDetails);
79#ifdef _WIN64
80 if (cDetails.is64b) {
81 STACKFRAME64 frame = { 0 };
82
83 frame.AddrPC.Offset = cDetails.rip;
84 frame.AddrPC.Mode = AddrModeFlat;
85 frame.AddrStack.Offset = cDetails.rsp;
86 frame.AddrStack.Mode = AddrModeFlat;
87 frame.AddrFrame.Offset = cDetails.rbp;
88 frame.AddrFrame.Mode = AddrModeFlat;
89
90 while (StackWalk64(IMAGE_FILE_MACHINE_AMD64, args->hProcess, args->hThread, &frame, args->ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
91 const ULONGLONG next_addr = frame.AddrPC.Offset;
92 args->callStack.push_back(next_addr);
93 fetched++;
94 }
95 }
96#endif
97 if (!cDetails.is64b) {
98 STACKFRAME frame = { 0 };
99
100 frame.AddrPC.Offset = cDetails.rip;
101 frame.AddrPC.Mode = AddrModeFlat;
102 frame.AddrStack.Offset = cDetails.rsp;
103 frame.AddrStack.Mode = AddrModeFlat;
104 frame.AddrFrame.Offset = cDetails.rbp;
105 frame.AddrFrame.Mode = AddrModeFlat;
106
107 while (StackWalk(IMAGE_FILE_MACHINE_I386, args->hProcess, args->hThread, &frame, args->ctx, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL)) {
108 const ULONGLONG next_return = frame.AddrPC.Offset;
109 args->callStack.push_back(next_return);
110 fetched++;
111 }
112 }
113 if (fetched) {
114 args->is_ok = true;
115 return STATUS_SUCCESS;
116 }
117 return STATUS_UNSUCCESSFUL;
118}
119
120//---
121
123{
124 switch (thread_wait_reason) {
125 case DelayExecution: return "DelayExecution";
126 case Suspended: return "Suspended";
127 case Executive: return "Executive";
128 case UserRequest: return "UserRequest";
129 case WrUserRequest: return "WrUserRequest";
130 case WrEventPair: return "WrEventPair";
131 case WrQueue: return "WrQueue";
132 case WrAlertByThreadId: return "WrAlertByThreadId";
133 }
134 std::stringstream ss;
135 ss << "Other: " << std::dec << thread_wait_reason;
136 return ss.str();
137}
138
140{
141 switch (thread_state) {
142 case Initialized: return "Initialized";
143 case Ready: return "Ready";
144 case Running: return "Running";
145 case Standby: return "Standby";
146 case Terminated: return "Terminated";
147 case Waiting: return "Waiting";
148 case Transition: return "Transition";
149 case DeferredReady: return "DeferredReady";
150 case GateWaitObsolete: return "GateWaitObsolete";
151 case WaitingForProcessInSwap: return "WaitingForProcessInSwap";
152 }
153 std::stringstream ss;
154 ss << "Other: " << std::dec << thread_state;
155 return ss.str();
156}
157
158//---
159
160std::string pesieve::ThreadScanner::choosePreferredFunctionName(const std::string& dbgSymbol, const std::string& manualSymbol)
161{
162 if (dbgSymbol.empty()) {
163 if (manualSymbol.empty()) {
164 return "";
165 }
166 // Give priority to the manual symbol if the debug symbol is empty
167 return manualSymbol;
168 }
169 // Give priority to the manual symbol if it denotes the actual syscall
170 if (!SyscallTable::isSyscallFunc(dbgSymbol)) {
171 if (SyscallTable::isSyscallFunc(manualSymbol)) {
172 return manualSymbol;
173 }
174 }
175 //oterwise use the debug symbol
176 return dbgSymbol;
177}
178
179bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vector<ULONGLONG>& callStack, IN OUT ThreadScanReport& my_report)
180{
181 if (this->info.last_syscall == INVALID_SYSCALL || !symbols || !callStack.size() || !info.is_extended || !g_SyscallTable.isReady()) {
182 return true; // skip the check
183 }
184 const ULONGLONG lastCalled = *(callStack.begin());
185 const std::string debugFuncName = symbols->funcNameFromAddr(lastCalled);
186 const std::string manualSymbol = exportsMap ? resolveLowLevelFuncName(lastCalled) : "";
187 if (debugFuncName.empty() && !exportsMap) {
188 return true; // skip the check
189 }
190 const std::string lastFuncCalled = choosePreferredFunctionName(debugFuncName, manualSymbol);
191 my_report.lastFunction = lastFuncCalled;
192 if (lastFuncCalled.empty()) {
193#ifdef _DEBUG
194 std::cout << "ERR: Can't fetch the name of the last function called!\n";
195#endif
196 return false;
197 }
198
199 if (callStack.size() == 1) {
200 if (this->info.ext.wait_reason == Suspended && lastFuncCalled == "RtlUserThreadStart" && this->info.last_syscall == 0) {
201 return true; //normal for suspended threads
202 }
203 return false; // otherwise it is an anomaly
204 }
205 // Proceed to check if the last syscall matches the last function called...
206
207#ifndef _WIN64
208 static bool isWow64 = util::is_current_wow64();
209 if (!isWow64 && lastFuncCalled == "KiFastSystemCallRet") {
210 return true;
211 }
212#endif
213 const std::string syscallFuncName = my_report.lastSyscall;
214 if (syscallFuncName.empty()) {
215 return true; // skip the check
216 }
217
218 if (SyscallTable::isSameSyscallFunc(syscallFuncName, lastFuncCalled)) {
219 return true; // valid
220 }
221
222 const ScannedModule* mod = modulesInfo.findModuleContaining(lastCalled);
223 const std::string lastModName = mod ? mod->getModName() : "";
224
225 if (syscallFuncName == "NtCallbackReturn") {
226 if (lastModName == "win32u.dll"
227 || lastModName == "user32.dll" || lastModName == "winsrv.dll") // for Windows7
228 {
229 return true;
230 }
231 }
232 if (!SyscallTable::isSyscallDll(lastModName)) {
233#ifdef _DEBUG
234 std::cout << "[@]" << std::dec << info.tid << " : " << "LastSyscall: " << syscallFuncName << " VS LastCalledAddr: " << std::hex << lastCalled
235 << " : " << lastFuncCalled << "(" << lastModName << "." << manualSymbol << " )" << " DIFFERENT!"
236 << " WaitReason: " << std::dec << ThreadScanReport::translate_wait_reason(this->info.ext.wait_reason) << std::endl;
237#endif //_DEBUG
238 return false;
239 }
240
241 if (this->info.ext.wait_reason == WrUserRequest ||
242 this->info.ext.wait_reason == UserRequest)
243 {
244 if (syscallFuncName.rfind("NtUser", 0) == 0 ) {
245 if (lastFuncCalled.rfind("NtUser", 0) == 0) return true;
246 if (lastFuncCalled.rfind("NtGdi", 0) == 0) return true;
247 }
248 if (syscallFuncName.rfind("NtGdi", 0) == 0) {
249 if (lastFuncCalled.rfind("NtGdi", 0) == 0) return true;
250 if (lastFuncCalled.rfind("NtUser", 0) == 0) return true;
251 }
252 }
253
254 if (this->info.ext.wait_reason == UserRequest) {
255 if (syscallFuncName.find("WaitFor", 0) != std::string::npos &&
256 (lastFuncCalled.find("WaitFor", 0) != std::string::npos))
257 {
258 return true;
259 }
260 if (syscallFuncName == "NtWaitForSingleObject") {
261 if ((lastFuncCalled.rfind("NtQuery", 0) == 0) || lastFuncCalled == "NtDelayExecution") return true;
262 }
263 if (syscallFuncName.rfind("NtUser", 0) == 0 && lastFuncCalled == "NtWaitForWorkViaWorkerFactory") {
264 return true;
265 }
266 if (syscallFuncName.rfind("NtUserModify", 0) == 0 && lastFuncCalled == "NtDeviceIoControlFile") {
267 return true;
268 }
269 }
270
271 if (this->info.ext.wait_reason == WrQueue) {
272 if (syscallFuncName.rfind("NtWaitFor", 0) == 0 && lastFuncCalled == "NtWaitForWorkViaWorkerFactory") {
273 return true;
274 }
275 if (syscallFuncName == "NtWaitForWorkViaWorkerFactory") {
276 if (lastFuncCalled.rfind("NtWaitFor", 0) == 0 || lastFuncCalled.rfind("NtUserMsgWaitFor", 0) == 0 || lastFuncCalled.rfind("NtUserCreate", 0) == 0) {
277 return true;
278 }
279 }
280 }
281
282 if (this->info.ext.wait_reason == DelayExecution) {
283 if (syscallFuncName == "NtDelayExecution") {
284 if ((lastFuncCalled.rfind("NtUserMsgWaitFor", 0) == 0) || (lastFuncCalled.rfind("NtWaitFor", 0) == 0)) return true;
285 }
286 }
287#ifdef _DEBUG
288 std::cout << "[@]" << std::dec << info.tid << " : " << "LastSyscall: " << syscallFuncName << " VS LastCalledAddr: " << std::hex << lastCalled
289 << " : " << lastFuncCalled << "(" << lastModName << "." << manualSymbol << " )" << " DIFFERENT!"
290 << " WaitReason: " << std::dec << ThreadScanReport::translate_wait_reason(this->info.ext.wait_reason) << std::endl;
291#endif //_DEBUG
292#ifdef _SHOW_THREAD_INFO
294 std::cout << "STACK:\n";
295 for (auto itr = callStack.rbegin(); itr != callStack.rend(); ++itr) {
296 ULONGLONG next_return = *itr;
297 symbols->dumpSymbolInfo(next_return);
298 std::cout << "\t";
299 printResolvedAddr(next_return);
300 }
301 std::cout << std::endl;
302#endif //_SHOW_THREAD_INFO
303 return false;
304}
305
306size_t pesieve::ThreadScanner::_analyzeCallStack(IN OUT ctx_details& cDetails, OUT IN std::set<ULONGLONG> &shcCandidates)
307{
308 size_t processedCntr = 0;
309
310 cDetails.is_managed = false;
311 cDetails.is_ret_in_frame = false;
312#ifdef _SHOW_THREAD_INFO
313 std::cout << "\n" << "Stack frame Size: " << std::dec << cDetails.callStack.size() << "\n===\n";
314#endif //_SHOW_THREAD_INFO
315 for (auto itr = cDetails.callStack.rbegin(); itr != cDetails.callStack.rend() ;++itr, ++processedCntr) {
316 const ULONGLONG next_return = *itr;
317 if (cDetails.ret_on_stack == next_return) {
318 cDetails.is_ret_in_frame = true;
319 }
320#ifdef _SHOW_THREAD_INFO
321 if (symbols) {
322 symbols->dumpSymbolInfo(next_return);
323 }
324 std::cout << "\t";
325 printResolvedAddr(next_return);
326#endif //_SHOW_THREAD_INFO
327 bool is_curr_shc = false;
328 const ScannedModule* mod = modulesInfo.findModuleContaining(next_return);
329 const std::string mod_name = mod ? mod->getModName() : "";
330 if (mod_name.length() == 0) {
331 if (!cDetails.is_managed) {
332 is_curr_shc = true;
333 shcCandidates.insert(next_return);
334#ifdef _SHOW_THREAD_INFO
335 std::cout << "\t" << std::hex << next_return << " <=== SHELLCODE\n";
336#endif //_SHOW_THREAD_INFO
337 } else {
338#ifdef _SHOW_THREAD_INFO
339 std::cout << "\t" << std::hex << next_return << " <=== .NET JIT\n";
340#endif //_SHOW_THREAD_INFO
341 }
342 }
343 if (!is_curr_shc) {
344 // store the last address, till the first called shellcode:
345 cDetails.last_ret = next_return;
346 }
347 // check if the found shellcode is a .NET JIT:
348 if (mod_name == "clr.dll" || mod_name == "coreclr.dll") {
349 cDetails.is_managed = true;
350#ifdef _SHOW_THREAD_INFO
351 std::cout << "\t" << std::hex << next_return << " <--- .NET\n";
352#endif //_SHOW_THREAD_INFO
353 }
354 }
355#ifdef _SHOW_THREAD_INFO
356 std::cout << "\n===\n";
357#endif //_SHOW_THREAD_INFO
358 return processedCntr;
359}
360
362{
363 const size_t analyzedCount = _analyzeCallStack(my_report.cDetails, my_report.shcCandidates);
364
365 bool checkCalls = true;
366 if (my_report.cDetails.is_managed) {
367 checkCalls = false;
368 }
369 if (info.ext.wait_reason > WrQueue ||
370 info.ext.wait_reason == WrFreePage || info.ext.wait_reason == WrPageIn || info.ext.wait_reason == WrPoolAllocation ||
371 info.ext.wait_reason == FreePage || info.ext.wait_reason == PageIn || info.ext.wait_reason == PoolAllocation ||
372 info.ext.wait_reason == Suspended)// there can be last func. vs last syscall mismatch in case of suspended threads
373 {
374 checkCalls = false;
375 }
376 if (checkCalls) {
377 my_report.cDetails.is_ret_as_syscall = checkReturnAddrIntegrity(my_report.cDetails.callStack, my_report);
378 }
379 return analyzedCount;
380}
381
382size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport& my_report)
383{
384 // do it in a new thread to prevent stucking...
385 t_stack_enum_params args(hProcess, hThread, ctx, &my_report.cDetails);
386
387 const size_t max_wait = 1000;
388 {
389 HANDLE enumThread = CreateThread(
390 NULL, // default security attributes
391 0, // use default stack size
392 enum_stack_thread, // thread function name
393 &args, // argument to thread function
394 0, // use default creation flags
395 0); // returns the thread identifiee
396
397 if (enumThread) {
398 DWORD wait_result = WaitForSingleObject(enumThread, max_wait);
399 if (wait_result == WAIT_TIMEOUT) {
400 std::cerr << "[!] Cannot retrieve stack frame: timeout passed!\n";
401 TerminateThread(enumThread, 0);
402 CloseHandle(enumThread);
403 return 0;
404 }
405 CloseHandle(enumThread);
406 }
407 }
408 if (!args.is_ok) {
409 return 0;
410 }
411 my_report.cDetails.callStack = args.callStack;
412 return args.callStack.size();
413}
414
415namespace pesieve {
416 template <typename PTR_T>
417 bool read_return_ptr(IN HANDLE hProcess, IN OUT ctx_details& cDetails) {
418 PTR_T ret_addr = 0;
419 cDetails.ret_on_stack = 0;
420 if (peconv::read_remote_memory(hProcess, (LPVOID)cDetails.rsp, (BYTE*)&ret_addr, sizeof(ret_addr)) == sizeof(ret_addr)) {
421 cDetails.ret_on_stack = (ULONGLONG)ret_addr;
422 return true;
423 }
424 return false;
425 }
426}; //namespace pesieve
427
428bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report)
429{
430 bool is_ok = false;
431 BOOL is_wow64 = FALSE;
432 size_t retrieved = 0;
433#ifdef _WIN64
434 pesieve::util::is_process_wow64(hProcess, &is_wow64);
435
436 if (is_wow64) {
437 WOW64_CONTEXT ctx = { 0 };
438 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
439 if (pesieve::util::wow64_get_thread_context(hThread, &ctx)) {
440 is_ok = true;
441 my_report.cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp);
442 read_return_ptr<DWORD>(hProcess, my_report.cDetails);
443 retrieved = fillCallStackInfo(hProcess, hThread, &ctx, my_report);
444 }
445 }
446#endif
447 if (!is_ok) {
448
449 CONTEXT ctx = { 0 };
450 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
451 if (GetThreadContext(hThread, &ctx)) {
452 is_ok = true;
453#ifdef _WIN64
454 my_report.cDetails.init(true, ctx.Rip, ctx.Rsp, ctx.Rbp);
455 read_return_ptr<ULONGLONG>(hProcess, my_report.cDetails);
456#else
457 my_report.cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp);
458 read_return_ptr<DWORD>(hProcess, my_report.cDetails);
459#endif
460 retrieved = fillCallStackInfo(hProcess, hThread, &ctx, my_report);
461 }
462 }
463 if (!retrieved) is_ok = false;
464 return is_ok;
465}
466
468{
469 ScannedModule* mod = modulesInfo.findModuleContaining(addr);
470 if (!mod) return false;
471
472 //the module is named
473 if (mod->getModName().length() > 0) {
474 return true;
475 }
476 return false;
477}
478
479std::string pesieve::ThreadScanner::resolveLowLevelFuncName(IN const ULONGLONG addr, OUT OPTIONAL size_t* displacement)
480{
481 size_t MAX_DISP = 25;
482 if (!exportsMap) {
483 return "";
484 }
485 ScannedModule* mod = modulesInfo.findModuleContaining(addr);
486 if (!mod) {
487 return "";
488 }
490 // not a DLL containing syscalls
491 return "";
492 }
493 size_t _displacement = 0;
494 std::string expName;
495 for (size_t disp = 0; disp < MAX_DISP; disp++) {
496 _displacement = disp;
497 const ULONGLONG va = addr - disp;
498
499 const std::set<peconv::ExportedFunc>* exp_set = exportsMap->find_exports_by_va(va);
500 if (!exp_set) {
501 continue; // no exports at this VA
502 }
503 // walk through the export candicates, and find the most suitable one:
504 for (auto it1 = exp_set->begin(); it1 != exp_set->end(); ++it1) {
505 const peconv::ExportedFunc& exp = *it1;
506 const std::string libName = exp.libName;
507 if (!SyscallTable::isSyscallDll(libName)) {
508 // it is not a low-level export
509 continue;
510 }
511 expName = exp.nameToString();
512 // give preference to the functions naming syscalls:
513 if (SyscallTable::isSyscallFunc(expName)) {
514 if (displacement) {
515 (*displacement) = _displacement;
516 }
517 return expName;
518 }
519 }
520 }
521 // otherwise, return any found
522 if (displacement) {
523 (*displacement) = _displacement;
524 }
525 return expName;
526}
527
528std::string pesieve::ThreadScanner::resolveAddrToString(IN const ULONGLONG addr)
529{
530 ScannedModule* mod = modulesInfo.findModuleContaining(addr);
531 ULONGLONG modBase = 0;
532 if (!mod) {
533 return "";
534 }
535 std::stringstream ss;
536 modBase = mod->getStart();
537 ss << mod->getModName();
538
539 char SEPARATOR = '!';
540 bool is_resolved = false;
541 size_t disp = 0;
542 if (symbols && symbols->IsInitialized()) {
543 std::string funcName = symbols->funcNameFromAddr(addr, &disp);
544 if (!funcName.empty()) {
545 is_resolved = true;
546 ss << SEPARATOR << funcName;
547 }
548 }
549 if (!is_resolved) {
550 std::string funcName = resolveLowLevelFuncName(addr, &disp);
551 if (!funcName.empty()) {
552 is_resolved = true;
553 ss << SEPARATOR << funcName;
554 }
555 }
556 if (is_resolved) {
557 if (disp) {
558 ss << "+0x" << std::hex << disp;
559 }
560 }
561 else {
562 ULONGLONG diff = addr - modBase;
563 ss << "+0x" << std::hex << diff;
564 }
565 return ss.str();
566}
567
569{
570 bool is_resolved = false;
571 std::cout << std::hex << addr;
572 ScannedModule* mod = modulesInfo.findModuleContaining(addr);
573 if (mod) {
574 std::cout << " : " << mod->getModName();
575 is_resolved = true;
576 }
577 std::cout << " : " << resolveLowLevelFuncName(addr);
578 std::cout << std::endl;
579 return is_resolved;
580}
581
583{
584 std::cout << std::dec << "TID: " << threadi.tid << "\n";
585 std::cout << std::hex << "\tStart : ";
587
588 if (threadi.is_extended) {
589 std::cout << std::hex << "\tSysStart: ";
591 if (threadi.last_syscall != INVALID_SYSCALL) {
592 std::cout << "\tLast Syscall: " << std::hex << threadi.last_syscall << " Func: " << g_SyscallTable.getSyscallName(threadi.last_syscall) << std::endl;
593 }
594 std::cout << "\tState: [" << ThreadScanReport::translate_thread_state(threadi.ext.state) << "]";
595 if (threadi.ext.state == Waiting) {
596 std::cout << " Reason: [" << ThreadScanReport::translate_wait_reason(threadi.ext.wait_reason) << "] Time: " << threadi.ext.wait_time;
597 }
598 std::cout << "\n";
599 }
600 std::cout << "\n";
601}
602
604{
605 if (!my_report) return false;
606
607 ULONG_PTR end_va = (ULONG_PTR)my_report->module + my_report->moduleSize;
608 MemPageData mem(this->processHandle, this->isReflection, (ULONG_PTR)my_report->module, end_va);
609 if (!mem.fillInfo() || !mem.load()) {
610 return false;
611 }
612 my_report->is_code = util::is_code(mem.loadedData.getData(true), mem.loadedData.getDataSize(true));
614 return calc.fill(my_report->stats, nullptr);
615}
616
618{
619 MEMORY_BASIC_INFORMATION page_info = { 0 };
620 if (!get_page_details(processHandle, (LPVOID)susp_addr, page_info)) {
621 return false;
622 }
623 if (page_info.State & MEM_FREE) {
624 return false;
625 }
626 ULONGLONG base = (ULONGLONG)page_info.BaseAddress;
627 my_report->module = (HMODULE)base;
628 my_report->moduleSize = page_info.RegionSize;
629 my_report->protection = page_info.AllocationProtect;
630 my_report->susp_addr = susp_addr;
631 my_report->status = SCAN_SUSPICIOUS;
632 const bool isStatFilled = fillAreaStats(my_report);
633#ifndef NO_ENTROPY_CHECK
634 if (isStatFilled && (my_report->stats.entropy < ENTROPY_TRESHOLD)) {
635 my_report->status = SCAN_NOT_SUSPICIOUS;
636 }
637#endif
638 return true;
639}
640
641// if extended info given, allow to filter out from the scan basing on the thread state and conditions
643{
644 if (!info.is_extended) {
645 return true;
646 }
647 const KTHREAD_STATE state = (KTHREAD_STATE)info.ext.state;
648 if (state == Ready) {
649 return true;
650 }
651 if (state == Terminated) {
652 return false;
653 }
654 if (state == Waiting && info.ext.wait_reason <= WrQueue) {
655 return true;
656 }
657 return false;
658}
659
661{
662 const DWORD tid = info.tid;
663 ctx_details &cDetails = my_report.cDetails;
664
665 const bool is_ok = fetchThreadCtxDetails(processHandle, hThread, my_report);
666 if (!pesieve::is_thread_running(hThread)) {
667 my_report.status = SCAN_NOT_SUSPICIOUS;
668 return false;
669 }
670 if (!is_ok || !analyzeCallStackInfo(my_report)) {
671 // could not fetch the thread context and information
672 my_report.status = SCAN_ERROR;
673 return false;
674 }
675
676 my_report.stack_ptr = cDetails.rsp;
677 bool is_unnamed = !isAddrInNamedModule(cDetails.rip);
678 if (is_unnamed) {
679 my_report.indicators.insert(THI_SUS_IP);
680 if (reportSuspiciousAddr(&my_report, cDetails.rip)) {
681 if (my_report.status == SCAN_SUSPICIOUS) {
682 my_report.indicators.insert(THI_SUS_CALLSTACK_SHC);
683 }
684 }
685 }
686
687 for (auto itr = my_report.shcCandidates.begin(); itr != my_report.shcCandidates.end(); ++itr) {
688 const ULONGLONG addr = *itr;
689#ifdef _SHOW_THREAD_INFO
690 std::cout << "Checking shc candidate: " << std::hex << addr << "\n";
691#endif //_SHOW_THREAD_INFO
692 //automatically verifies if the address is legit:
693 if (reportSuspiciousAddr(&my_report, addr)) {
694 if (my_report.status == SCAN_SUSPICIOUS) {
695 my_report.indicators.insert(THI_SUS_CALLSTACK_SHC);
696#ifdef _DEBUG
697 std::cout << "[@]" << std::dec << tid << " : " << "Suspicious, possible shc: " << std::hex << addr << " Entropy: " << std::dec << my_report.stats.entropy << " : " << my_report.is_code << std::endl;
698#endif //_DEBUG
699 if (my_report.is_code) {
700 break;
701 }
702#ifdef _SHOW_THREAD_INFO
703 std::cout << "Found! " << std::hex << addr << "\n";
704#endif //_SHOW_THREAD_INFO
705 }
706 }
707 }
708
709 if (this->info.is_extended && info.ext.state == Waiting && this->info.ext.wait_reason != Suspended
710 && !cDetails.is_ret_in_frame)
711 {
712 const ULONGLONG ret_addr = cDetails.ret_on_stack;
713 is_unnamed = !isAddrInNamedModule(ret_addr);
714#ifdef _SHOW_THREAD_INFO
715 std::cout << "Return addr: " << std::hex << ret_addr << "\n";
716 printResolvedAddr(ret_addr);
717#endif //_SHOW_THREAD_INFO
718 if (is_unnamed && reportSuspiciousAddr(&my_report, (ULONGLONG)ret_addr)) {
719 my_report.indicators.insert(THI_SUS_RET);
720 if (my_report.status == SCAN_SUSPICIOUS) {
721 my_report.indicators.insert(THI_SUS_CALLSTACK_SHC);
722 }
723 else {
724 my_report.status = SCAN_SUSPICIOUS;
725 if (my_report.stats.entropy < 1) { // discard, do not dump
726 my_report.module = 0;
727 my_report.moduleSize = 0;
728 }
729 }
730 }
731 }
732
733 // other indicators of stack being corrupt:
734
735 if (this->info.is_extended && !my_report.cDetails.is_managed && !my_report.cDetails.is_ret_as_syscall)
736 {
737 my_report.indicators.insert(THI_SUS_CALLS_INTEGRITY);
738 my_report.status = SCAN_SUSPICIOUS;
739 }
740
741 if (cDetails.callStack.size() == 1
742 && this->info.is_extended && info.ext.state == Waiting && info.ext.wait_reason == UserRequest)
743 {
744 my_report.indicators.insert(THI_SUS_CALLSTACK_CORRUPT);
745 my_report.status = SCAN_SUSPICIOUS;
746 }
747 return (my_report.status == SCAN_SUSPICIOUS) ? true : false;
748}
749
751{
752 if (!isManaged) return false;
753
754 const size_t count = my_report.indicators.size();
755 if (count > 1) return false;
756
757 auto itr = my_report.indicators.begin();
758 if (itr == my_report.indicators.end()) return false;
759
760 if (*itr == THI_SUS_CALLSTACK_SHC) {
761 my_report.status = SCAN_NOT_SUSPICIOUS;
762 return true;
763 }
764 return false;
765}
766
768{
769 if (!this->info.is_extended) {
770 return;
771 }
772 my_report.thread_state = info.ext.state;
773 my_report.thread_wait_reason = info.ext.wait_reason;
774 my_report.thread_wait_time = info.ext.wait_time;
775 if (this->info.last_syscall != INVALID_SYSCALL) {
776 my_report.lastSyscall = g_SyscallTable.getSyscallName(this->info.last_syscall);
777 }
778}
779
781{
782 for (auto itr = my_report.cDetails.callStack.begin(); itr != my_report.cDetails.callStack.end(); ++itr) {
783 ULONGLONG addr = *itr;
784 my_report.addrToSymbol[addr] = this->resolveAddrToString(addr);
785 }
786}
787
789{
790 if (GetCurrentThreadId() == info.tid) {
791 return nullptr; // do not scan your own thread
792 }
793 ThreadScanReport* my_report = new (std::nothrow) ThreadScanReport(info.tid);
794 if (!my_report) {
795 return nullptr;
796 }
797 initReport(*my_report);
798
799#ifdef _SHOW_THREAD_INFO
801#endif // _SHOW_THREAD_INFO
802 // initialize the report with the collected info:
803
804 bool is_unnamed = !isAddrInNamedModule(info.start_addr);
805 if (is_unnamed) {
806 if (reportSuspiciousAddr(my_report, info.start_addr)) {
807 if (my_report->status == SCAN_SUSPICIOUS) {
808 my_report->indicators.insert(THI_SUS_START);
809 }
810 }
811 }
813 return my_report;
814 }
815 // proceed with detailed checks:
816 HANDLE hThread = OpenThread(
817 THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | SYNCHRONIZE,
818 FALSE,
819 info.tid
820 );
821 if (!hThread) {
822#ifdef _DEBUG
823 std::cerr << "[-] Could not OpenThread. Error: " << GetLastError() << std::endl;
824#endif
825 my_report->status = SCAN_ERROR;
826 return my_report;
827 }
828 scanRemoteThreadCtx(hThread, *my_report);
829 CloseHandle(hThread);
830
831 filterDotNet(*my_report);
832 reportResolvedCallstack(*my_report);
833
834 return my_report;
835}
A class responsible for filling in the statistics with the data from the particular buffer.
Definition stats.h:73
bool fill(AreaStats &stats, StatsSettings *settings)
Definition stats.h:80
util::ByteBuffer loadedData
Represents a basic info about the scanned module, such as its base offset, size, and the status.
std::string getModName() const
ULONGLONG getStart() const
A report from the thread scan, generated by ThreadScanner.
std::set< ThSusIndicator > indicators
std::map< ULONGLONG, std::string > addrToSymbol
static std::string translate_wait_reason(DWORD thread_wait_reason)
std::set< ULONGLONG > shcCandidates
static std::string translate_thread_state(DWORD thread_state)
bool checkReturnAddrIntegrity(IN const std::vector< ULONGLONG > &callStack, IN OUT ThreadScanReport &my_report)
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)
void printThreadInfo(const util::thread_info &threadi)
bool fillAreaStats(ThreadScanReport *my_report)
void reportResolvedCallstack(ThreadScanReport &my_report)
bool printResolvedAddr(const ULONGLONG addr)
bool filterDotNet(ThreadScanReport &my_report)
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)
ProcessSymbolsManager * symbols
bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport &my_report)
bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport &my_report)
size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport &my_report)
bool is_code(const BYTE *loadedData, size_t loadedSize)
BOOL is_process_wow64(IN HANDLE processHandle, OUT BOOL *isProcWow64)
bool is_current_wow64()
struct pesieve::util::_thread_info thread_info
BOOL wow64_get_thread_context(IN HANDLE hThread, IN OUT PWOW64_CONTEXT lpContext)
bool read_return_ptr(IN HANDLE hProcess, IN OUT ctx_details &cDetails)
struct pesieve::_ctx_details ctx_details
A custom structure keeping a fragment of a thread context.
bool is_thread_running(HANDLE hThread)
std::string info()
The string with the basic information about the scanner.
Definition pe_sieve.cpp:274
@ THI_SUS_CALLSTACK_SHC
@ THI_SUS_CALLSTACK_CORRUPT
@ THI_SUS_CALLS_INTEGRITY
pesieve::SyscallTable g_SyscallTable
Definition pe_sieve.cpp:24
_t_stack_enum_params(IN HANDLE _hProcess=NULL, IN HANDLE _hThread=NULL, IN LPVOID _ctx=NULL, IN const pesieve::ctx_details *_cDetails=NULL)
const pesieve::ctx_details * cDetails
std::vector< ULONGLONG > callStack
std::vector< ULONGLONG > callStack
static bool isSyscallFunc(const std::string &funcName, bool NtOnly=false)
static bool isSameSyscallFunc(const std::string &func1, const std::string &func2)
static bool isSyscallDll(const std::string &libName)
size_t getDataSize(bool trimmed=false) const
Definition byte_buffer.h:55
const BYTE * getData(bool trimmed=false) const
Definition byte_buffer.h:65
struct _t_stack_enum_params t_stack_enum_params
bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION &page_info)
DWORD WINAPI enum_stack_thread(LPVOID lpParam)
#define ENTROPY_TRESHOLD
bool should_scan_context(const util::thread_info &info)
#define INVALID_SYSCALL
Definition threads_util.h:6