5#include "../utils/ntddk.h"
13#define ENTROPY_TRESHOLD 3.0
17#define _SHOW_THREAD_INFO
45 if (GetExitCodeThread(hThread, &exit_code)) {
46 if (exit_code != STILL_ACTIVE) {
48 std::cout <<
" Thread ExitCode: " << std::dec << exit_code <<
"\n";
58bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION& page_info)
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) {
75 return STATUS_INVALID_PARAMETER;
81 STACKFRAME64 frame = { 0 };
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;
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;
97 if (!cDetails.
is64b) {
98 STACKFRAME frame = { 0 };
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;
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;
115 return STATUS_SUCCESS;
117 return STATUS_UNSUCCESSFUL;
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";
134 std::stringstream ss;
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";
153 std::stringstream ss;
162 if (dbgSymbol.empty()) {
163 if (manualSymbol.empty()) {
184 const ULONGLONG lastCalled = *(callStack.begin());
185 const std::string debugFuncName =
symbols->funcNameFromAddr(lastCalled);
191 my_report.lastFunction = lastFuncCalled;
192 if (lastFuncCalled.empty()) {
194 std::cout <<
"ERR: Can't fetch the name of the last function called!\n";
199 if (callStack.size() == 1) {
200 if (this->
info.ext.wait_reason == Suspended && lastFuncCalled ==
"RtlUserThreadStart" && this->info.last_syscall == 0) {
209 if (!isWow64 && lastFuncCalled ==
"KiFastSystemCallRet") {
213 const std::string syscallFuncName = my_report.lastSyscall;
214 if (syscallFuncName.empty()) {
223 const std::string lastModName = mod ? mod->
getModName() :
"";
225 if (syscallFuncName ==
"NtCallbackReturn") {
226 if (lastModName ==
"win32u.dll"
227 || lastModName ==
"user32.dll" || lastModName ==
"winsrv.dll")
234 std::cout <<
"[@]" << std::dec <<
info.tid <<
" : " <<
"LastSyscall: " << syscallFuncName <<
" VS LastCalledAddr: " << std::hex << lastCalled
235 <<
" : " << lastFuncCalled <<
"(" << lastModName <<
"." << manualSymbol <<
" )" <<
" DIFFERENT!"
241 if (this->
info.ext.wait_reason == WrUserRequest ||
242 this->info.ext.wait_reason == UserRequest)
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;
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;
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))
260 if (syscallFuncName ==
"NtWaitForSingleObject") {
261 if ((lastFuncCalled.rfind(
"NtQuery", 0) == 0) || lastFuncCalled ==
"NtDelayExecution")
return true;
263 if (syscallFuncName.rfind(
"NtUser", 0) == 0 && lastFuncCalled ==
"NtWaitForWorkViaWorkerFactory") {
266 if (syscallFuncName.rfind(
"NtUserModify", 0) == 0 && lastFuncCalled ==
"NtDeviceIoControlFile") {
271 if (this->
info.ext.wait_reason == WrQueue) {
272 if (syscallFuncName.rfind(
"NtWaitFor", 0) == 0 && lastFuncCalled ==
"NtWaitForWorkViaWorkerFactory") {
275 if (syscallFuncName ==
"NtWaitForWorkViaWorkerFactory") {
276 if (lastFuncCalled.rfind(
"NtWaitFor", 0) == 0 || lastFuncCalled.rfind(
"NtUserMsgWaitFor", 0) == 0 || lastFuncCalled.rfind(
"NtUserCreate", 0) == 0) {
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;
288 std::cout <<
"[@]" << std::dec <<
info.tid <<
" : " <<
"LastSyscall: " << syscallFuncName <<
" VS LastCalledAddr: " << std::hex << lastCalled
289 <<
" : " << lastFuncCalled <<
"(" << lastModName <<
"." << manualSymbol <<
" )" <<
" DIFFERENT!"
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);
301 std::cout << std::endl;
308 size_t processedCntr = 0;
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";
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;
320#ifdef _SHOW_THREAD_INFO
322 symbols->dumpSymbolInfo(next_return);
327 bool is_curr_shc =
false;
329 const std::string mod_name = mod ? mod->
getModName() :
"";
330 if (mod_name.length() == 0) {
331 if (!cDetails.is_managed) {
333 shcCandidates.insert(next_return);
334#ifdef _SHOW_THREAD_INFO
335 std::cout <<
"\t" << std::hex << next_return <<
" <=== SHELLCODE\n";
338#ifdef _SHOW_THREAD_INFO
339 std::cout <<
"\t" << std::hex << next_return <<
" <=== .NET JIT\n";
345 cDetails.last_ret = next_return;
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";
355#ifdef _SHOW_THREAD_INFO
356 std::cout <<
"\n===\n";
358 return processedCntr;
363 const size_t analyzedCount =
_analyzeCallStack(my_report.cDetails, my_report.shcCandidates);
365 bool checkCalls =
true;
366 if (my_report.cDetails.is_managed) {
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)
379 return analyzedCount;
387 const size_t max_wait = 1000;
389 HANDLE enumThread = CreateThread(
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);
405 CloseHandle(enumThread);
411 my_report.cDetails.callStack = args.
callStack;
416 template <
typename PTR_T>
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;
431 BOOL is_wow64 = FALSE;
432 size_t retrieved = 0;
437 WOW64_CONTEXT ctx = { 0 };
438 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
441 my_report.cDetails.init(
false, ctx.Eip, ctx.Esp, ctx.Ebp);
450 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
451 if (GetThreadContext(hThread, &ctx)) {
454 my_report.cDetails.init(
true, ctx.Rip, ctx.Rsp, ctx.Rbp);
457 my_report.cDetails.init(
false, ctx.Eip, ctx.Esp, ctx.Ebp);
463 if (!retrieved) is_ok =
false;
470 if (!mod)
return false;
481 size_t MAX_DISP = 25;
493 size_t _displacement = 0;
495 for (
size_t disp = 0; disp < MAX_DISP; disp++) {
496 _displacement = disp;
497 const ULONGLONG va = addr - disp;
499 const std::set<peconv::ExportedFunc>* exp_set =
exportsMap->find_exports_by_va(va);
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;
511 expName = exp.nameToString();
515 (*displacement) = _displacement;
523 (*displacement) = _displacement;
531 ULONGLONG modBase = 0;
535 std::stringstream ss;
539 char SEPARATOR =
'!';
540 bool is_resolved =
false;
543 std::string funcName =
symbols->funcNameFromAddr(addr, &disp);
544 if (!funcName.empty()) {
546 ss << SEPARATOR << funcName;
551 if (!funcName.empty()) {
553 ss << SEPARATOR << funcName;
558 ss <<
"+0x" << std::hex << disp;
562 ULONGLONG diff = addr - modBase;
563 ss <<
"+0x" << std::hex << diff;
570 bool is_resolved =
false;
571 std::cout << std::hex << addr;
578 std::cout << std::endl;
584 std::cout << std::dec <<
"TID: " << threadi.
tid <<
"\n";
585 std::cout << std::hex <<
"\tStart : ";
589 std::cout << std::hex <<
"\tSysStart: ";
605 if (!my_report)
return false;
614 return calc.
fill(my_report->
stats,
nullptr);
619 MEMORY_BASIC_INFORMATION page_info = { 0 };
623 if (page_info.State & MEM_FREE) {
626 ULONGLONG base = (ULONGLONG)page_info.BaseAddress;
627 my_report->
module = (HMODULE)base;
629 my_report->
protection = page_info.AllocationProtect;
633#ifndef NO_ENTROPY_CHECK
644 if (!
info.is_extended) {
647 const KTHREAD_STATE state = (KTHREAD_STATE)
info.ext.state;
648 if (state == Ready) {
651 if (state == Terminated) {
654 if (state == Waiting &&
info.ext.wait_reason <= WrQueue) {
662 const DWORD tid =
info.tid;
688 const ULONGLONG addr = *itr;
689#ifdef _SHOW_THREAD_INFO
690 std::cout <<
"Checking shc candidate: " << std::hex << addr <<
"\n";
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;
702#ifdef _SHOW_THREAD_INFO
703 std::cout <<
"Found! " << std::hex << addr <<
"\n";
709 if (this->
info.is_extended &&
info.ext.state == Waiting && this->info.ext.wait_reason != Suspended
714#ifdef _SHOW_THREAD_INFO
715 std::cout <<
"Return addr: " << std::hex << ret_addr <<
"\n";
742 && this->info.is_extended &&
info.ext.state == Waiting &&
info.ext.wait_reason == UserRequest)
754 const size_t count = my_report.
indicators.size();
755 if (count > 1)
return false;
758 if (itr == my_report.
indicators.end())
return false;
769 if (!this->
info.is_extended) {
783 ULONGLONG addr = *itr;
790 if (GetCurrentThreadId() ==
info.tid) {
799#ifdef _SHOW_THREAD_INFO
816 HANDLE hThread = OpenThread(
817 THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | SYNCHRONIZE,
823 std::cerr <<
"[-] Could not OpenThread. Error: " << GetLastError() << std::endl;
829 CloseHandle(hThread);
A class responsible for filling in the statistics with the data from the particular buffer.
bool fill(AreaStats &stats, StatsSettings *settings)
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)
ModulesInfo & modulesInfo
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)
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.
@ THI_SUS_CALLSTACK_CORRUPT
@ THI_SUS_CALLS_INTEGRITY
pesieve::SyscallTable g_SyscallTable
_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
const BYTE * getData(bool trimmed=false) const
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)
bool should_scan_context(const util::thread_info &info)