From cfb99d9c8070842f21d81700ed32863b1fa9e26c Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 01:13:20 +0100 Subject: [PATCH 1/8] [REFACT] Keep all the information about the callstack in the main report --- scanners/thread_scanner.cpp | 104 +++++++++++++++++++----------------- scanners/thread_scanner.h | 89 ++++++++++++++++-------------- 2 files changed, 105 insertions(+), 88 deletions(-) diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index 3d096856..797949ab 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -306,7 +306,7 @@ bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vector &call_stack, IN OUT ctx_details& cDetails) +size_t pesieve::ThreadScanner::_analyzeCallStack(IN const std::vector &call_stack, IN OUT ctx_details& cDetails, OUT IN std::set &shcCandidates) { size_t processedCntr = 0; @@ -334,7 +334,7 @@ size_t pesieve::ThreadScanner::analyzeCallStack(IN const std::vector if (mod_name.length() == 0) { if (!cDetails.is_managed) { is_curr_shc = true; - cDetails.shcCandidates.insert(next_return); + shcCandidates.insert(next_return); #ifdef _SHOW_THREAD_INFO std::cout << "\t" << std::hex << next_return << " <=== SHELLCODE\n"; #endif //_SHOW_THREAD_INFO @@ -362,10 +362,10 @@ size_t pesieve::ThreadScanner::analyzeCallStack(IN const std::vector return processedCntr; } -size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ctx_details& cDetails) +size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport& my_report) { // do it in a new thread to prevent stucking... - t_stack_enum_params args(hProcess, hThread, ctx, &cDetails); + t_stack_enum_params args(hProcess, hThread, ctx, &my_report.cDetails); const size_t max_wait = 1000; { @@ -391,12 +391,15 @@ size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE h if (!args.is_ok) { return 0; } -#ifdef _SHOW_THREAD_INFO - std::cout << "\n=== TID " << std::dec << GetThreadId(hThread) << " ===\n"; -#endif //_SHOW_THREAD_INFO - const size_t analyzedCount = analyzeCallStack(args.callStack, cDetails); - if (!cDetails.is_managed) { - cDetails.is_ret_as_syscall = checkReturnAddrIntegrity(args.callStack); + my_report.callStack = args.callStack; + return args.callStack.size(); +} + +size_t pesieve::ThreadScanner::analyzeCallStackInfo(IN OUT ThreadScanReport& my_report) +{ + const size_t analyzedCount = _analyzeCallStack(my_report.callStack, my_report.cDetails, my_report.shcCandidates); + if (!my_report.cDetails.is_managed) { + my_report.cDetails.is_ret_as_syscall = checkReturnAddrIntegrity(my_report.callStack); } return analyzedCount; } @@ -412,7 +415,9 @@ bool read_return_ptr(IN HANDLE hProcess, IN OUT ctx_details& cDetails) { return false; } -bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ctx_details& cDetails) + + +bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report) { bool is_ok = false; BOOL is_wow64 = FALSE; @@ -425,9 +430,9 @@ bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; if (pesieve::util::wow64_get_thread_context(hThread, &ctx)) { is_ok = true; - cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp); - read_return_ptr(hProcess, cDetails); - retrieved = fillCallStackInfo(hProcess, hThread, &ctx, cDetails); + my_report.cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp); + read_return_ptr(hProcess, my_report.cDetails); + retrieved = fillCallStackInfo(hProcess, hThread, &ctx, my_report); } } #endif @@ -438,13 +443,13 @@ bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE if (GetThreadContext(hThread, &ctx)) { is_ok = true; #ifdef _WIN64 - cDetails.init(true, ctx.Rip, ctx.Rsp, ctx.Rbp); - read_return_ptr(hProcess, cDetails); + my_report.cDetails.init(true, ctx.Rip, ctx.Rsp, ctx.Rbp); + read_return_ptr(hProcess, my_report.cDetails); #else - cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp); - read_return_ptr(hProcess, cDetails); + my_report.cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp); + read_return_ptr(hProcess, my_report.cDetails); #endif - retrieved = fillCallStackInfo(hProcess, hThread, &ctx, cDetails); + retrieved = fillCallStackInfo(hProcess, hThread, &ctx, my_report); } } if (!retrieved) is_ok = false; @@ -595,47 +600,50 @@ bool should_scan_context(const util::thread_info& info) return false; } -bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport* my_report) +bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport& my_report) { const DWORD tid = info.tid; - ctx_details cDetails; - const bool is_ok = fetchThreadCtxDetails(processHandle, hThread, cDetails); + ctx_details &cDetails = my_report.cDetails; + + const bool is_ok = fetchThreadCtxDetails(processHandle, hThread, my_report); if (!pesieve::is_thread_running(hThread)) { - my_report->status = SCAN_NOT_SUSPICIOUS; + my_report.status = SCAN_NOT_SUSPICIOUS; return false; } if (!is_ok) { // could not fetch the thread context and information - my_report->status = SCAN_ERROR; + my_report.status = SCAN_ERROR; return false; } - my_report->frames_count = cDetails.stackFramesCount; - my_report->stack_ptr = cDetails.rsp; + + analyzeCallStackInfo(my_report); + + my_report.stack_ptr = cDetails.rsp; bool isModified = false; bool is_unnamed = !isAddrInNamedModule(cDetails.rip); if (is_unnamed) { - my_report->indicators.insert(THI_SUS_IP); - if (reportSuspiciousAddr(my_report, cDetails.rip)) { - if (my_report->status == SCAN_SUSPICIOUS) { - my_report->indicators.insert(THI_SUS_CALLSTACK_SHC); + my_report.indicators.insert(THI_SUS_IP); + if (reportSuspiciousAddr(&my_report, cDetails.rip)) { + if (my_report.status == SCAN_SUSPICIOUS) { + my_report.indicators.insert(THI_SUS_CALLSTACK_SHC); isModified = true; } } } - for (auto itr = cDetails.shcCandidates.begin(); itr != cDetails.shcCandidates.end(); ++itr) { + for (auto itr = my_report.shcCandidates.begin(); itr != my_report.shcCandidates.end(); ++itr) { const ULONGLONG addr = *itr; #ifdef _SHOW_THREAD_INFO std::cout << "Checking shc candidate: " << std::hex << addr << "\n"; #endif //_SHOW_THREAD_INFO //automatically verifies if the address is legit: - if (reportSuspiciousAddr(my_report, addr)) { - if (my_report->status == SCAN_SUSPICIOUS) { - my_report->indicators.insert(THI_SUS_CALLSTACK_SHC); + if (reportSuspiciousAddr(&my_report, addr)) { + if (my_report.status == SCAN_SUSPICIOUS) { + my_report.indicators.insert(THI_SUS_CALLSTACK_SHC); #ifdef _DEBUG std::cout << "[@]" << std::dec << tid << " : " << "Suspicious, possible shc: " << std::hex << addr << " Entropy: " << std::dec << my_report->stats.entropy << " : " << my_report->is_code << std::endl; #endif //_DEBUG - if (my_report->is_code) { + if (my_report.is_code) { break; } #ifdef _SHOW_THREAD_INFO @@ -657,17 +665,17 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor std::cout << "Return addr: " << std::hex << ret_addr << "\n"; printResolvedAddr(ret_addr); #endif //_SHOW_THREAD_INFO - if (is_unnamed && reportSuspiciousAddr(my_report, (ULONGLONG)ret_addr)) { + if (is_unnamed && reportSuspiciousAddr(&my_report, (ULONGLONG)ret_addr)) { isModified = true; - my_report->indicators.insert(THI_SUS_RET); - if (my_report->status == SCAN_SUSPICIOUS) { - my_report->indicators.insert(THI_SUS_CALLSTACK_SHC); + my_report.indicators.insert(THI_SUS_RET); + if (my_report.status == SCAN_SUSPICIOUS) { + my_report.indicators.insert(THI_SUS_CALLSTACK_SHC); } else { - my_report->status = SCAN_SUSPICIOUS; - if (my_report->stats.entropy < 1) { // discard, do not dump - my_report->module = 0; - my_report->moduleSize = 0; + my_report.status = SCAN_SUSPICIOUS; + if (my_report.stats.entropy < 1) { // discard, do not dump + my_report.module = 0; + my_report.moduleSize = 0; } } } @@ -677,9 +685,9 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor bool isStackCorrupt = false; - if (this->info.is_extended && !cDetails.is_managed && !cDetails.is_ret_as_syscall) + if (this->info.is_extended && !my_report.cDetails.is_managed && !my_report.cDetails.is_ret_as_syscall) { - my_report->indicators.insert(THI_SUS_CALLS_INTEGRITY); + my_report.indicators.insert(THI_SUS_CALLS_INTEGRITY); isStackCorrupt = true; } @@ -687,12 +695,12 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor cDetails.stackFramesCount == 1 && this->info.is_extended && info.ext.state == Waiting && info.ext.wait_reason == UserRequest) { - my_report->indicators.insert(THI_SUS_CALLSTACK_CORRUPT); + my_report.indicators.insert(THI_SUS_CALLSTACK_CORRUPT); isStackCorrupt = true; } if (isStackCorrupt) { - my_report->status = SCAN_SUSPICIOUS; + my_report.status = SCAN_SUSPICIOUS; } return isModified; } @@ -756,7 +764,7 @@ ThreadScanReport* pesieve::ThreadScanner::scanRemote() my_report->status = SCAN_ERROR; return my_report; } - scanRemoteThreadCtx(hThread, my_report); + scanRemoteThreadCtx(hThread, *my_report); CloseHandle(hThread); filterDotNet(*my_report); diff --git a/scanners/thread_scanner.h b/scanners/thread_scanner.h index 338df84a..fc5c8784 100644 --- a/scanners/thread_scanner.h +++ b/scanners/thread_scanner.h @@ -35,6 +35,37 @@ namespace pesieve { return ""; } + //! A custom structure keeping a fragment of a thread context + typedef struct _ctx_details { + bool is64b; + ULONGLONG rip; + ULONGLONG rsp; + ULONGLONG rbp; + ULONGLONG last_ret; // the last return address on the stack + ULONGLONG ret_on_stack; // the last return address stored on the stack + bool is_ret_as_syscall; + bool is_ret_in_frame; + bool is_managed; // does it contain .NET modules + size_t stackFramesCount; + + _ctx_details(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0) + : is64b(_is64b), rip(_rip), rsp(_rsp), rbp(_rbp), last_ret(_ret_addr), ret_on_stack(0), is_ret_as_syscall(false), is_ret_in_frame(false), + stackFramesCount(0), + is_managed(false) + { + } + + void init(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0) + { + this->is64b = _is64b; + this->rip = _rip; + this->rsp = _rsp; + this->rbp = _rbp; + this->last_ret = _ret_addr; + } + + } ctx_details; + //! A report from the thread scan, generated by ThreadScanner class ThreadScanReport : public ModuleScanReport { @@ -50,7 +81,7 @@ namespace pesieve { ThreadScanReport(DWORD _tid) : ModuleScanReport(0, 0), tid(_tid), - susp_addr(0), protection(0), stack_ptr(0), frames_count(0), + susp_addr(0), protection(0), stack_ptr(0), thread_state(THREAD_STATE_UNKNOWN), thread_wait_reason(0), thread_wait_time(0), is_code(false) { @@ -77,10 +108,10 @@ namespace pesieve { OUT_PADDED(outs, level, "\"stack_ptr\" : "); outs << "\"" << std::hex << stack_ptr << "\""; } - if (frames_count) { + if (callStack.size()) { outs << ",\n"; OUT_PADDED(outs, level, "\"frames_count\" : "); - outs << std::dec << frames_count; + outs << std::dec << callStack.size(); } if (thread_state != THREAD_STATE_UNKNOWN) { outs << ",\n"; @@ -127,47 +158,20 @@ namespace pesieve { ULONGLONG susp_addr; DWORD protection; ULONGLONG stack_ptr; - size_t frames_count; + //size_t frames_count; DWORD thread_state; DWORD thread_wait_reason; DWORD thread_wait_time; + + ctx_details cDetails; + std::vector callStack; + std::set shcCandidates; std::set indicators; + AreaEntropyStats stats; bool is_code; }; - //! A custom structure keeping a fragment of a thread context - typedef struct _ctx_details { - bool is64b; - ULONGLONG rip; - ULONGLONG rsp; - ULONGLONG rbp; - ULONGLONG last_ret; // the last return address on the stack - ULONGLONG ret_on_stack; // the last return address stored on the stack - bool is_ret_as_syscall; - bool is_ret_in_frame; - bool is_managed; // does it contain .NET modules - size_t stackFramesCount; - std::set shcCandidates; - - _ctx_details(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0) - : is64b(_is64b), rip(_rip), rsp(_rsp), rbp(_rbp), last_ret(_ret_addr), ret_on_stack(0), is_ret_as_syscall(false), is_ret_in_frame(false), - stackFramesCount(0), - is_managed(false) - { - } - - void init(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0) - { - this->is64b = _is64b; - this->rip = _rip; - this->rsp = _rsp; - this->rbp = _rbp; - this->last_ret = _ret_addr; - } - - } ctx_details; - //! A scanner for threads //! Stack-scan inspired by the idea presented here: https://github.com/thefLink/Hunt-Sleeping-Beacons class ThreadScanner : public ProcessFeatureScanner { @@ -183,15 +187,20 @@ namespace pesieve { protected: static std::string choosePreferredFunctionName(const std::string& dbgSymbol, const std::string& manualSymbol); - bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport* my_report); + bool scanRemoteThreadCtx(HANDLE hThread, ThreadScanReport& my_report); + bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report); + bool isAddrInNamedModule(ULONGLONG addr); void printThreadInfo(const util::thread_info& threadi); std::string resolveLowLevelFuncName(const ULONGLONG addr, size_t maxDisp=25); bool printResolvedAddr(ULONGLONG addr); - bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ctx_details& c); - size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ctx_details& cDetails); - size_t analyzeCallStack(IN const std::vector &stack_frame, IN OUT ctx_details& cDetails); + + size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport& my_report); + size_t analyzeCallStackInfo(IN OUT ThreadScanReport& my_report); + size_t _analyzeCallStack(IN const std::vector& call_stack, IN OUT ctx_details& cDetails, OUT IN std::set& shcCandidates); + bool checkReturnAddrIntegrity(IN const std::vector& callStack); + bool fillAreaStats(ThreadScanReport* my_report); bool reportSuspiciousAddr(ThreadScanReport* my_report, ULONGLONG susp_addr); bool filterDotNet(ThreadScanReport& my_report); From 1d8419d9ad3099538bf00e1e9146f3daf3d36cee Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 02:04:02 +0100 Subject: [PATCH 2/8] [FEATURE] Allow to print full callstack in the report --- params_info/pe_sieve_params_info.cpp | 6 +-- scanners/thread_scanner.cpp | 61 +++++++++++++--------------- scanners/thread_scanner.h | 42 +++++++++++++------ 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/params_info/pe_sieve_params_info.cpp b/params_info/pe_sieve_params_info.cpp index d291ddf9..47e4b6b1 100644 --- a/params_info/pe_sieve_params_info.cpp +++ b/params_info/pe_sieve_params_info.cpp @@ -153,11 +153,11 @@ std::string pesieve::translate_json_level(const pesieve::t_json_level &mode) { switch (mode) { case pesieve::JSON_BASIC: - return "basic"; + return "basic (default)"; case pesieve::JSON_DETAILS: - return "details #1 (list patches)"; + return "details #1 (medium)"; case pesieve::JSON_DETAILS2: - return "details #2 (list patches: extended)"; + return "details #2 (verbose)"; } return ""; } diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index 797949ab..bd4e17b5 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -306,17 +306,16 @@ bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vector &call_stack, IN OUT ctx_details& cDetails, OUT IN std::set &shcCandidates) +size_t pesieve::ThreadScanner::_analyzeCallStack(IN OUT ctx_details& cDetails, OUT IN std::set &shcCandidates) { size_t processedCntr = 0; cDetails.is_managed = false; - cDetails.stackFramesCount = call_stack.size(); cDetails.is_ret_in_frame = false; #ifdef _SHOW_THREAD_INFO - std::cout << "\n" << "Stack frame Size: " << std::dec << call_stack.size() << "\n===\n"; + std::cout << "\n" << "Stack frame Size: " << std::dec << cDetails.callStack.size() << "\n===\n"; #endif //_SHOW_THREAD_INFO - for (auto itr = call_stack.rbegin(); itr != call_stack.rend() ;++itr, ++processedCntr) { + for (auto itr = cDetails.callStack.rbegin(); itr != cDetails.callStack.rend() ;++itr, ++processedCntr) { const ULONGLONG next_return = *itr; if (cDetails.ret_on_stack == next_return) { cDetails.is_ret_in_frame = true; @@ -362,6 +361,15 @@ size_t pesieve::ThreadScanner::_analyzeCallStack(IN const std::vector return processedCntr; } +size_t pesieve::ThreadScanner::analyzeCallStackInfo(IN OUT ThreadScanReport& my_report) +{ + const size_t analyzedCount = _analyzeCallStack(my_report.cDetails, my_report.shcCandidates); + if (!my_report.cDetails.is_managed) { + my_report.cDetails.is_ret_as_syscall = checkReturnAddrIntegrity(my_report.cDetails.callStack); + } + return analyzedCount; +} + size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport& my_report) { // do it in a new thread to prevent stucking... @@ -391,31 +399,22 @@ size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE h if (!args.is_ok) { return 0; } - my_report.callStack = args.callStack; + my_report.cDetails.callStack = args.callStack; return args.callStack.size(); } -size_t pesieve::ThreadScanner::analyzeCallStackInfo(IN OUT ThreadScanReport& my_report) -{ - const size_t analyzedCount = _analyzeCallStack(my_report.callStack, my_report.cDetails, my_report.shcCandidates); - if (!my_report.cDetails.is_managed) { - my_report.cDetails.is_ret_as_syscall = checkReturnAddrIntegrity(my_report.callStack); - } - return analyzedCount; -} - -template -bool read_return_ptr(IN HANDLE hProcess, IN OUT ctx_details& cDetails) { - PTR_T ret_addr = 0; - cDetails.ret_on_stack = 0; - if (peconv::read_remote_memory(hProcess, (LPVOID)cDetails.rsp, (BYTE*)&ret_addr, sizeof(ret_addr)) == sizeof(ret_addr)) { - cDetails.ret_on_stack = (ULONGLONG)ret_addr; - return true; +namespace pesieve { + template + bool read_return_ptr(IN HANDLE hProcess, IN OUT ctx_details& cDetails) { + PTR_T ret_addr = 0; + cDetails.ret_on_stack = 0; + if (peconv::read_remote_memory(hProcess, (LPVOID)cDetails.rsp, (BYTE*)&ret_addr, sizeof(ret_addr)) == sizeof(ret_addr)) { + cDetails.ret_on_stack = (ULONGLONG)ret_addr; + return true; + } + return false; } - return false; -} - - +}; //namespace pesieve bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report) { @@ -610,14 +609,12 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor my_report.status = SCAN_NOT_SUSPICIOUS; return false; } - if (!is_ok) { + if (!is_ok || !analyzeCallStackInfo(my_report)) { // could not fetch the thread context and information my_report.status = SCAN_ERROR; return false; } - analyzeCallStackInfo(my_report); - my_report.stack_ptr = cDetails.rsp; bool isModified = false; bool is_unnamed = !isAddrInNamedModule(cDetails.rip); @@ -692,7 +689,7 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor } if (hasEmptyGUI && - cDetails.stackFramesCount == 1 + cDetails.callStack.size() == 1 && this->info.is_extended && info.ext.state == Waiting && info.ext.wait_reason == UserRequest) { my_report.indicators.insert(THI_SUS_CALLSTACK_CORRUPT); @@ -743,14 +740,14 @@ ThreadScanReport* pesieve::ThreadScanner::scanRemote() } } } - if (!should_scan_context(info)) { - return my_report; - } if (this->info.is_extended) { my_report->thread_state = info.ext.state; my_report->thread_wait_reason = info.ext.wait_reason; my_report->thread_wait_time = info.ext.wait_time; } + if (!should_scan_context(info)) { + return my_report; + } // proceed with detailed checks: HANDLE hThread = OpenThread( THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | SYNCHRONIZE, diff --git a/scanners/thread_scanner.h b/scanners/thread_scanner.h index fc5c8784..f21aac06 100644 --- a/scanners/thread_scanner.h +++ b/scanners/thread_scanner.h @@ -7,6 +7,7 @@ #include "../utils/process_symbols.h" #include "../stats/stats.h" #include "../stats/entropy_stats.h" +#include namespace pesieve { @@ -46,11 +47,10 @@ namespace pesieve { bool is_ret_as_syscall; bool is_ret_in_frame; bool is_managed; // does it contain .NET modules - size_t stackFramesCount; + std::vector callStack; _ctx_details(bool _is64b = false, ULONGLONG _rip = 0, ULONGLONG _rsp = 0, ULONGLONG _rbp = 0, ULONGLONG _ret_addr = 0) : is64b(_is64b), rip(_rip), rsp(_rsp), rbp(_rbp), last_ret(_ret_addr), ret_on_stack(0), is_ret_as_syscall(false), is_ret_in_frame(false), - stackFramesCount(0), is_managed(false) { } @@ -87,6 +87,29 @@ namespace pesieve { { } + const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) + { + OUT_PADDED(outs, level, "\"stack_ptr\" : "); + outs << "\"" << std::hex << stack_ptr << "\""; + if (cDetails.callStack.size()) { + outs << ",\n"; + OUT_PADDED(outs, level, "\"frames_count\" : "); + outs << std::dec << cDetails.callStack.size(); + if (jdetails >= JSON_DETAILS) { + outs << ",\n"; + OUT_PADDED(outs, level, "\"frames\" : ["); + for (auto itr = cDetails.callStack.begin(); itr != cDetails.callStack.end(); ++itr) { + if (itr != cDetails.callStack.begin()) { + outs << ", "; + } + const ULONGLONG addr = *itr; + outs << "\"" << std::hex << addr << "\""; + } + outs << "]"; + } + } + } + const virtual void fieldsToJSON(std::stringstream &outs, size_t level, const pesieve::t_json_level &jdetails) { ModuleScanReport::_toJSON(outs, level); @@ -105,13 +128,10 @@ namespace pesieve { outs << "]"; if (stack_ptr) { outs << ",\n"; - OUT_PADDED(outs, level, "\"stack_ptr\" : "); - outs << "\"" << std::hex << stack_ptr << "\""; - } - if (callStack.size()) { - outs << ",\n"; - OUT_PADDED(outs, level, "\"frames_count\" : "); - outs << std::dec << callStack.size(); + OUT_PADDED(outs, level, "\"callstack\" : {\n"); + callstackToJSON(outs, level + 1, jdetails); + outs << "\n"; + OUT_PADDED(outs, level, "}"); } if (thread_state != THREAD_STATE_UNKNOWN) { outs << ",\n"; @@ -158,13 +178,11 @@ namespace pesieve { ULONGLONG susp_addr; DWORD protection; ULONGLONG stack_ptr; - //size_t frames_count; DWORD thread_state; DWORD thread_wait_reason; DWORD thread_wait_time; ctx_details cDetails; - std::vector callStack; std::set shcCandidates; std::set indicators; @@ -197,7 +215,7 @@ namespace pesieve { size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT ThreadScanReport& my_report); size_t analyzeCallStackInfo(IN OUT ThreadScanReport& my_report); - size_t _analyzeCallStack(IN const std::vector& call_stack, IN OUT ctx_details& cDetails, OUT IN std::set& shcCandidates); + size_t _analyzeCallStack(IN OUT ctx_details& cDetails, OUT IN std::set& shcCandidates); bool checkReturnAddrIntegrity(IN const std::vector& callStack); From 9c15f8646e3a2e22f9966088f0e2f5bf68d6aa53 Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 02:05:51 +0100 Subject: [PATCH 3/8] [FEATURE] Added one more exclusion in the callstack integrity checks --- scanners/thread_scanner.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index bd4e17b5..1cda5ee3 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -269,6 +269,9 @@ bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vectorinfo.ext.wait_reason == WrQueue) { From 3edb424c82d959eb8c2cb084ca9310f74cfdc38b Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 02:12:41 +0100 Subject: [PATCH 4/8] [FEATURE] Always print callstack if corrupt indicator set --- scanners/thread_scanner.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scanners/thread_scanner.h b/scanners/thread_scanner.h index f21aac06..3558216a 100644 --- a/scanners/thread_scanner.h +++ b/scanners/thread_scanner.h @@ -89,13 +89,17 @@ namespace pesieve { const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) { + bool printCallstack = (jdetails >= JSON_DETAILS) ? true : false; + if (this->indicators.find(THI_SUS_CALLSTACK_CORRUPT) != this->indicators.end()) { + printCallstack = true; + } OUT_PADDED(outs, level, "\"stack_ptr\" : "); outs << "\"" << std::hex << stack_ptr << "\""; if (cDetails.callStack.size()) { outs << ",\n"; OUT_PADDED(outs, level, "\"frames_count\" : "); outs << std::dec << cDetails.callStack.size(); - if (jdetails >= JSON_DETAILS) { + if (printCallstack) { outs << ",\n"; OUT_PADDED(outs, level, "\"frames\" : ["); for (auto itr = cDetails.callStack.begin(); itr != cDetails.callStack.end(); ++itr) { From 7d449b7baeacebd92f1bcafd4f0ad9d46c91a5d1 Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 02:24:43 +0100 Subject: [PATCH 5/8] [BUGFIX] Fixed variable referenced in a debug string --- scanners/thread_scanner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index 1cda5ee3..f47ed4c2 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -641,7 +641,7 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor if (my_report.status == SCAN_SUSPICIOUS) { my_report.indicators.insert(THI_SUS_CALLSTACK_SHC); #ifdef _DEBUG - std::cout << "[@]" << std::dec << tid << " : " << "Suspicious, possible shc: " << std::hex << addr << " Entropy: " << std::dec << my_report->stats.entropy << " : " << my_report->is_code << std::endl; + std::cout << "[@]" << std::dec << tid << " : " << "Suspicious, possible shc: " << std::hex << addr << " Entropy: " << std::dec << my_report.stats.entropy << " : " << my_report.is_code << std::endl; #endif //_DEBUG if (my_report.is_code) { break; From 293922e4132c10e95d5b689b2bbf6e9ad7ed2cdb Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 02:55:08 +0100 Subject: [PATCH 6/8] [BUGFIX] Fixed checking callstack integrity --- scanners/thread_scanner.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index f47ed4c2..d4bb17cc 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -617,7 +617,7 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor my_report.status = SCAN_ERROR; return false; } - + my_report.stack_ptr = cDetails.rsp; bool isModified = false; bool is_unnamed = !isAddrInNamedModule(cDetails.rip); @@ -654,8 +654,6 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor } } - const bool hasEmptyGUI = has_empty_gui_info(tid); - if (this->info.is_extended && info.ext.state == Waiting && this->info.ext.wait_reason != Suspended && !cDetails.is_ret_in_frame) { @@ -691,8 +689,7 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor isStackCorrupt = true; } - if (hasEmptyGUI && - cDetails.callStack.size() == 1 + if (cDetails.callStack.size() == 1 && this->info.is_extended && info.ext.state == Waiting && info.ext.wait_reason == UserRequest) { my_report.indicators.insert(THI_SUS_CALLSTACK_CORRUPT); From fe4426b5bd5ccb50e32e4e3a58b6394169a5731c Mon Sep 17 00:00:00 2001 From: hasherezade Date: Sun, 9 Feb 2025 03:03:20 +0100 Subject: [PATCH 7/8] [FEATURE] In thread_scanner: one more fuction mapping --- scanners/thread_scanner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index d4bb17cc..f03b0bde 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -279,7 +279,7 @@ bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vector Date: Sun, 9 Feb 2025 03:09:23 +0100 Subject: [PATCH 8/8] [BUGFIX] Do not report the reloc base if not filled --- scanners/module_scan_report.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/module_scan_report.h b/scanners/module_scan_report.h index 3d11168a..fadee36f 100644 --- a/scanners/module_scan_report.h +++ b/scanners/module_scan_report.h @@ -77,7 +77,7 @@ namespace pesieve { outs << std::hex << "\"" << origBase << "\"" << ",\n"; } #endif //_DEBUG - if (relocBase != (ULONGLONG)module) { + if (relocBase && relocBase != (ULONGLONG)module) { OUT_PADDED(outs, level, "\"reloc_base\" : "); outs << std::hex << "\"" << relocBase << "\"" << ",\n"; }