diff --git a/scanners/thread_scanner.cpp b/scanners/thread_scanner.cpp index fb32977a..7a7e5436 100644 --- a/scanners/thread_scanner.cpp +++ b/scanners/thread_scanner.cpp @@ -11,6 +11,7 @@ extern pesieve::SyscallTable g_SyscallTable; #define ENTROPY_THRESHOLD 3.0 +#define ENTROPY_ENC_THRESHOLD 6.0 //#define NO_ENTROPY_CHECK #ifdef _DEBUG @@ -185,6 +186,11 @@ bool pesieve::ThreadScanner::checkReturnAddrIntegrity(IN const std::vectorinfo.ext.wait_reason == UserRequest) { + if (syscallFuncName.rfind("NtDxgkGet", 0) == 0 && + lastFuncCalled.rfind("NtWaitFor", 0) == 0) + { + return true; + } if (syscallFuncName.find("WaitFor", 0) != std::string::npos && (lastFuncCalled.find("WaitFor", 0) != std::string::npos)) { @@ -568,37 +574,45 @@ void pesieve::ThreadScanner::printThreadInfo(const pesieve::util::thread_info& t std::cout << "\n"; } -bool pesieve::ThreadScanner::fillAreaStats(ThreadScanReport* my_report) +bool pesieve::ThreadScanner::fillAreaStats(SuspAddrReport* report) { - if (!my_report) return false; + if (!report) return false; - ULONG_PTR end_va = (ULONG_PTR)my_report->module + my_report->moduleSize; - MemPageData mem(this->processHandle, this->isReflection, (ULONG_PTR)my_report->module, end_va); + ULONG_PTR end_va = (ULONG_PTR)report->module + report->moduleSize; + MemPageData mem(this->processHandle, this->isReflection, (ULONG_PTR)report->module, end_va); if (!mem.fillInfo() || !mem.load()) { return false; } - my_report->is_code = util::is_code(mem.loadedData.getData(true), mem.loadedData.getDataSize(true)); + report->is_code = util::is_code(mem.loadedData.getData(true), mem.loadedData.getDataSize(true)); AreaStatsCalculator calc(mem.loadedData); - return calc.fill(my_report->stats, nullptr); + return calc.fill(report->stats, nullptr); } bool pesieve::ThreadScanner::reportSuspiciousAddr(ThreadScanReport* my_report, ULONGLONG susp_addr) { - MEMORY_BASIC_INFORMATION page_info = { 0 }; - if (!get_page_details(processHandle, (LPVOID)susp_addr, page_info)) { + SuspAddrReport* susRep = my_report->findAreaForAddress(susp_addr); + if (!susRep) { + MEMORY_BASIC_INFORMATION page_info = { 0 }; + if (!get_page_details(processHandle, (LPVOID)susp_addr, page_info)) { + return false; + } + if (!(page_info.State & MEM_COMMIT)) { + return false; + } + ULONGLONG base = (ULONGLONG)page_info.BaseAddress; + susRep = new SuspAddrReport(base, page_info.RegionSize, page_info.AllocationProtect); + susRep->curr_protection = page_info.Protect; + my_report->suspAreaReports[base] = susRep; + fillAreaStats(susRep); + } + if (!susRep) { return false; } - if (!(page_info.State & MEM_COMMIT)) { - return false; - } - ULONGLONG base = (ULONGLONG)page_info.BaseAddress; - my_report->module = (HMODULE)base; - my_report->moduleSize = page_info.RegionSize; - my_report->alloc_protection = page_info.AllocationProtect; - my_report->curr_protection = page_info.Protect; - my_report->susp_addr = susp_addr; + susRep->addSuspAddr(susp_addr); +#ifdef _DEBUG + susRep->print(); +#endif //_DEBUG my_report->status = SCAN_SUSPICIOUS; - fillAreaStats(my_report); return true; } @@ -640,9 +654,7 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor 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_CALLSTACK_SHC); } } @@ -653,18 +665,10 @@ bool pesieve::ThreadScanner::scanRemoteThreadCtx(HANDLE hThread, ThreadScanRepor #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); + 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 << std::endl; #endif //_DEBUG - if (my_report.is_code) { - break; - } -#ifdef _SHOW_THREAD_INFO - std::cout << "Found! " << std::hex << addr << "\n"; -#endif //_SHOW_THREAD_INFO - } } } @@ -677,18 +681,11 @@ 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) { my_report.indicators.insert(THI_SUS_RET); - if (my_report.status == SCAN_SUSPICIOUS) { + if (reportSuspiciousAddr(&my_report, (ULONGLONG)ret_addr)) { 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; - } - } } } @@ -725,7 +722,7 @@ bool pesieve::ThreadScanner::filterDotNet(ThreadScanReport& my_report) switch (indicator) { case THI_SUS_CALLSTACK_SHC: case THI_SUS_IP: - case THI_SUS_RET: + case THI_SUS_START: dnet_common++; break; default: @@ -739,6 +736,25 @@ bool pesieve::ThreadScanner::filterDotNet(ThreadScanReport& my_report) return false; } +static int scoreArea(const SuspAddrReport& area) +{ + int score = 0; + + if (area.is_code) { + score += 10; + } + + if (area.stats.isFilled()) { + if (area.stats.entropy >= ENTROPY_THRESHOLD) { + score += 10; + } + if (area.stats.entropy >= ENTROPY_ENC_THRESHOLD) { + score += 10; + } + } + return score; +} + bool pesieve::ThreadScanner::assessIndicators(HANDLE hThread, ThreadScanReport& my_report) { if (my_report.status == SCAN_NOT_SUSPICIOUS) { @@ -748,9 +764,31 @@ bool pesieve::ThreadScanner::assessIndicators(HANDLE hThread, ThreadScanReport& my_report.status = SCAN_NOT_SUSPICIOUS; return true; } + if (filterDotNet(my_report)) { return true; } + ULONGLONG best_base = 0; + size_t best_module_size = 0; + int best_score = 0; + double best_entropy = 0; + for (auto itr1 = my_report.suspAreaReports.begin(); itr1 != my_report.suspAreaReports.end(); ++itr1) { + ULONGLONG base = itr1->first; + const SuspAddrReport* rep = itr1->second; + if (!rep) continue; + + const int score = scoreArea(*rep); + if (score == 0) continue; + + if (score > best_score + || (score == best_score && rep->stats.entropy > best_entropy)) + { + best_base = rep->module; + best_module_size = rep->moduleSize; + best_score = score; + best_entropy = rep->stats.entropy; + } + } std::set< ThSusIndicator> validatedIndicators; for (auto itr = my_report.indicators.begin(); @@ -758,23 +796,25 @@ bool pesieve::ThreadScanner::assessIndicators(HANDLE hThread, ThreadScanReport& ++itr) { const ThSusIndicator& indicator = *itr; - - if (!my_report.susp_addr) { - validatedIndicators.insert(indicator); - continue; - } - - if (my_report.stats.isFilled() - && my_report.stats.entropy >= ENTROPY_THRESHOLD) - { + switch (indicator) { + case THI_SUS_START: + case THI_SUS_CALLSTACK_SHC: + if (best_base) { + validatedIndicators.insert(indicator); + } + break; + default: validatedIndicators.insert(indicator); + break; } } if (!validatedIndicators.size()) { my_report.status = SCAN_NOT_SUSPICIOUS; return true; } - return false; + my_report.module = (HMODULE)best_base; + my_report.moduleSize = best_module_size; + return true; } void pesieve::ThreadScanner::initReport(ThreadScanReport& my_report) @@ -817,10 +857,9 @@ ThreadScanReport* pesieve::ThreadScanner::scanRemote() bool is_unnamed = !isAddrInNamedModule(info.start_addr); if (is_unnamed) { + my_report->indicators.insert(THI_SUS_START); if (reportSuspiciousAddr(my_report, info.start_addr)) { - if (my_report->status == SCAN_SUSPICIOUS) { - my_report->indicators.insert(THI_SUS_START); - } + my_report->indicators.insert(THI_SUS_CALLSTACK_SHC); } } if (!should_scan_context(info)) { diff --git a/scanners/thread_scanner.h b/scanners/thread_scanner.h index c616d5e0..6e15fcb9 100644 --- a/scanners/thread_scanner.h +++ b/scanners/thread_scanner.h @@ -66,6 +66,114 @@ namespace pesieve { } ctx_details; + class SuspAddrReport { + public: + SuspAddrReport(ULONGLONG _module = 0 , size_t _moduleSize = 0, DWORD _allocProtection = 0) + : module(_module), moduleSize(_moduleSize), + alloc_protection(_allocProtection), curr_protection(0), + is_code(false) + { + } + + void print() + { + std::cout << __FUNCTION__ << " : " << std::hex << module << " : " << moduleSize + << " alloc_protect: " << alloc_protection + << " curr_protect: " << curr_protection + << " is_code: " << is_code + << " entropy: " << stats.entropy + << " susp_addr ["; + for (auto itr = suspAddresses.begin(); itr != suspAddresses.end(); ++itr) { + ULONGLONG susp_addr = *itr; + std::cout << " " << susp_addr; + } + std::cout << " ]" << std::endl; + } + + const bool toJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) const + { + if (addressesToJSON(outs, level, jdetails)) { + outs << ",\n"; + } + moduleInfoToJSON(outs, level, jdetails); + return true; + } + + void addSuspAddr(ULONGLONG addr) + { + this->suspAddresses.insert(addr); + } + + bool addressesToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) const + { + if (!suspAddresses.size()) { + return false; + } + std::string addrTypeStr = "susp_return_addr"; + if (this->module && this->moduleSize) { + addrTypeStr = "susp_addr"; + } + OUT_PADDED(outs, level, "\"" << addrTypeStr << "\" : ["); + OUT_PADDED(outs, level, "\"" << addrTypeStr << "\" : ["); + bool isFirst = true; + for (auto itr = suspAddresses.begin(); itr != suspAddresses.end(); ++itr) { + ULONGLONG susp_addr = *itr; + if (!susp_addr) continue; + + if (!isFirst) { + outs << ", "; + } + outs << "\"" << std::hex << susp_addr << "\""; + isFirst = false; + } + outs << "]"; + return true; + } + + bool moduleInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) const + { + if (!this->module) { + return false; + } + OUT_PADDED(outs, level, "\"module\" : "); + outs << "\"" << std::hex << (ULONGLONG)module << "\""; + if (moduleSize) { + outs << ",\n"; + OUT_PADDED(outs, level, "\"module_size\" : "); + outs << "\"" << std::hex << (ULONGLONG)moduleSize << "\""; + } + outs << ",\n"; + if (alloc_protection != curr_protection) { + OUT_PADDED(outs, level, "\"alloc_protection\" : "); + outs << "\"" << std::hex << alloc_protection << "\""; + outs << ",\n"; + OUT_PADDED(outs, level, "\"curr_protection\" : "); + outs << "\"" << std::hex << curr_protection << "\""; + } + else { + OUT_PADDED(outs, level, "\"protection\" : "); + outs << "\"" << std::hex << curr_protection << "\""; + } + outs << ",\n"; + OUT_PADDED(outs, level, "\"is_code\" : "); + outs << "\"" << std::dec << is_code << "\""; + if (stats.isFilled()) { + outs << ",\n"; + stats.toJSON(outs, level); + } + outs << "\n"; + return true; + } + + ULONG_PTR module; + size_t moduleSize; + std::set suspAddresses; + DWORD alloc_protection; + DWORD curr_protection; + AreaEntropyStats stats; + bool is_code; + }; + //! A report from the thread scan, generated by ThreadScanner class ThreadScanReport : public ModuleScanReport { @@ -80,13 +188,37 @@ namespace pesieve { ThreadScanReport(DWORD _tid) : ModuleScanReport(0, 0), - tid(_tid), - susp_addr(0), alloc_protection(0), curr_protection(0), stack_ptr(0), + tid(_tid), stack_ptr(0), thread_state(THREAD_STATE_UNKNOWN), - thread_wait_reason(0), thread_wait_time(0), is_code(false) + thread_wait_reason(0), thread_wait_time(0) { } + ~ThreadScanReport() + { + for (auto itr = this->suspAreaReports.begin(); itr != this->suspAreaReports.end(); ++itr) { + SuspAddrReport* rep = itr->second; + delete rep; + } + suspAreaReports.clear(); + } + + SuspAddrReport* findAreaForAddress(const ULONGLONG &susp_addr) + { + auto found = this->suspAreaReports.find(susp_addr); + if (found != this->suspAreaReports.end()) { + return found->second; + } + for (auto itr = this->suspAreaReports.begin(); itr != this->suspAreaReports.end(); ++itr) { + SuspAddrReport* rep = itr->second; + if (!rep) continue; + if (susp_addr >= rep->module && susp_addr < (rep->module + rep->moduleSize)) { + return rep; + } + } + return nullptr; + } + const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) { bool printCallstack = (jdetails >= JSON_DETAILS) ? true : false; @@ -113,42 +245,18 @@ namespace pesieve { outs << "\"" << std::hex << addr; auto sItr = this->addrToSymbol.find(addr); if (sItr != this->addrToSymbol.end()) { - outs << ";" << sItr->second; + const std::string &sym = sItr->second; + if (!sym.empty()) { + outs << ";" << sym; + } } outs << "\""; - } outs << "]"; } } } - const bool moduleInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) - { - if (!this->module) { - return false; - } - outs << ",\n"; - OUT_PADDED(outs, level, "\"module\" : "); - outs << "\"" << std::hex << (ULONGLONG)module << "\""; - if (moduleSize) { - outs << ",\n"; - OUT_PADDED(outs, level, "\"module_size\" : "); - outs << "\"" << std::hex << (ULONGLONG)moduleSize << "\""; - } - outs << ",\n"; - OUT_PADDED(outs, level, "\"alloc_protection\" : "); - outs << "\"" << std::hex << alloc_protection << "\""; - outs << ",\n"; - OUT_PADDED(outs, level, "\"curr_protection\" : "); - outs << "\"" << std::hex << curr_protection << "\""; - if (stats.isFilled()) { - outs << ",\n"; - stats.toJSON(outs, level); - } - return true; - } - const bool threadInfoToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails) { OUT_PADDED(outs, level, "\"state\" : "); @@ -218,17 +326,27 @@ namespace pesieve { outs << ",\n"; indicatorsToJSON(outs, level, jdetails); - if (susp_addr) { + if (!suspAreaReports.empty()) { outs << ",\n"; - if (this->module && this->moduleSize) { - OUT_PADDED(outs, level, "\"susp_addr\" : "); + OUT_PADDED(outs, level, "\"susp_areas\" : [\n"); + + bool isFirst = true; + for (const auto& entry : suspAreaReports) { + const SuspAddrReport* suspr = entry.second; + if (!suspr) { + continue; + } + if (!isFirst) { + outs << ",\n"; + } + OUT_PADDED(outs, level + 1, "{\n"); + suspr->toJSON(outs, level + 2, jdetails); + OUT_PADDED(outs, level + 1, "}"); + isFirst = false; } - else { - OUT_PADDED(outs, level, "\"susp_return_addr\" : "); - } - outs << "\"" << std::hex << susp_addr << "\""; + outs << "\n"; + OUT_PADDED(outs, level, "]"); } - moduleInfoToJSON(outs, level, jdetails); } const virtual bool toJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level &jdetails) @@ -241,9 +359,6 @@ namespace pesieve { } DWORD tid; - ULONGLONG susp_addr; - DWORD alloc_protection; - DWORD curr_protection; ULONGLONG stack_ptr; DWORD thread_state; DWORD thread_wait_reason; @@ -256,9 +371,7 @@ namespace pesieve { std::map addrToSymbol; std::set shcCandidates; std::set indicators; - - AreaEntropyStats stats; - bool is_code; + std::map suspAreaReports; }; //! A scanner for threads @@ -292,7 +405,7 @@ namespace pesieve { bool checkReturnAddrIntegrity(IN const std::vector& callStack, IN OUT ThreadScanReport& my_report); - bool fillAreaStats(ThreadScanReport* my_report); + bool fillAreaStats(SuspAddrReport* my_report); bool reportSuspiciousAddr(ThreadScanReport* my_report, ULONGLONG susp_addr); bool filterDotNet(ThreadScanReport& my_report); diff --git a/stats/entropy_stats.h b/stats/entropy_stats.h index 60ca58ba..5433f971 100644 --- a/stats/entropy_stats.h +++ b/stats/entropy_stats.h @@ -35,7 +35,7 @@ namespace pesieve { double entropy; protected: - const virtual void fieldsToJSON(std::stringstream& outs, size_t level) + virtual void fieldsToJSON(std::stringstream& outs, size_t level) const { OUT_PADDED(outs, level, "\"area_start\" : "); outs << "\"" << std::hex << area_start << "\""; diff --git a/stats/multi_stats.h b/stats/multi_stats.h index ae5b8b6d..f2d253bd 100644 --- a/stats/multi_stats.h +++ b/stats/multi_stats.h @@ -129,7 +129,7 @@ namespace pesieve { lastStr.clear(); } - const virtual void fieldsToJSON(std::stringstream& outs, size_t level) + virtual void fieldsToJSON(std::stringstream& outs, size_t level) const { OUT_PADDED(outs, level, "\"offset\" : "); outs << std::hex << "\"" << offset << "\""; @@ -208,7 +208,7 @@ namespace pesieve { return true; } - const virtual void fieldsToJSON(std::stringstream& outs, size_t level) + virtual void fieldsToJSON(std::stringstream& outs, size_t level) const { OUT_PADDED(outs, level, "\"full_area\" : {\n"); currArea.fieldsToJSON(outs, level + 1); diff --git a/stats/stats.h b/stats/stats.h index 5ec29723..a4f0282e 100644 --- a/stats/stats.h +++ b/stats/stats.h @@ -35,7 +35,7 @@ namespace pesieve { area_size++; } - const virtual void fieldsToJSON(std::stringstream& outs, size_t level) = 0; + virtual void fieldsToJSON(std::stringstream& outs, size_t level) const = 0; bool isFilled() const { @@ -46,7 +46,7 @@ namespace pesieve { virtual bool fillSettings(StatsSettings* _settings) { return false; } - const virtual bool toJSON(std::stringstream& outs, size_t level) + virtual bool toJSON(std::stringstream& outs, size_t level) const { if (!isFilled()) { return false;