5#include "../utils/ntddk.h"
13#define ENTROPY_THRESHOLD 3.0
14#define ENTROPY_ENC_THRESHOLD 6.0
18#define _SHOW_THREAD_INFO
30 if (GetExitCodeThread(hThread, &exit_code)) {
31 if (exit_code != STILL_ACTIVE) {
33 std::cout <<
" Thread ExitCode: " << std::dec << exit_code <<
"\n";
43bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION& page_info)
45 size_t page_info_size =
sizeof(MEMORY_BASIC_INFORMATION);
46 const SIZE_T out = VirtualQueryEx(processHandle, (LPCVOID)start_va, &page_info, page_info_size);
47 const bool is_read = (out == page_info_size) ?
true :
false;
48 const DWORD error = is_read ? ERROR_SUCCESS : GetLastError();
49 if (error != ERROR_SUCCESS) {
59 case DelayExecution:
return "DelayExecution";
60 case Suspended:
return "Suspended";
61 case Executive:
return "Executive";
62 case UserRequest:
return "UserRequest";
63 case WrUserRequest:
return "WrUserRequest";
64 case WrEventPair:
return "WrEventPair";
65 case WrQueue:
return "WrQueue";
66 case WrAlertByThreadId:
return "WrAlertByThreadId";
76 case Initialized:
return "Initialized";
77 case Ready:
return "Ready";
78 case Running:
return "Running";
79 case Standby:
return "Standby";
80 case Terminated:
return "Terminated";
81 case Waiting:
return "Waiting";
82 case Transition:
return "Transition";
83 case DeferredReady:
return "DeferredReady";
84 case GateWaitObsolete:
return "GateWaitObsolete";
85 case WaitingForProcessInSwap:
return "WaitingForProcessInSwap";
96 if (dbgSymbol.empty()) {
97 if (manualSymbol.empty()) {
118 const ULONGLONG lastCalled = *(callStack.begin());
119 const std::string debugFuncName = (
symbols) ?
symbols->funcNameFromAddr(lastCalled) :
"";
125 my_report.lastFunction = lastFuncCalled;
126 if (this->
info.last_syscall == 0) {
127 if (this->
info.ext.wait_reason == Suspended && callStack.size() == 1 && lastFuncCalled ==
"RtlUserThreadStart") {
138 if (lastFuncCalled.empty()) {
140 std::cout <<
"ERR: Can't fetch the name of the last function called!\n";
149 if (!isWow64 && lastFuncCalled ==
"KiFastSystemCallRet") {
153 const std::string syscallFuncName = my_report.lastSyscall;
154 if (syscallFuncName.empty()) {
163 const std::string lastModName = mod ? mod->
getModName() :
"";
165 if (syscallFuncName ==
"NtCallbackReturn") {
166 if (lastModName ==
"win32u.dll"
167 || lastModName ==
"user32.dll" || lastModName ==
"winsrv.dll")
174 std::cout <<
"[@]" << std::dec <<
info.tid <<
" : " <<
"LastSyscall: " << syscallFuncName <<
" VS LastCalledAddr: " << std::hex << lastCalled
175 <<
" : " << lastFuncCalled <<
"(" << lastModName <<
"." << manualSymbol <<
" )" <<
" DIFFERENT!"
181 if (this->
info.ext.wait_reason == WrUserRequest ||
182 this->info.ext.wait_reason == UserRequest)
184 if (syscallFuncName.rfind(
"NtUser", 0) == 0) {
185 if (lastFuncCalled.rfind(
"NtUser", 0) == 0)
return true;
186 if (lastFuncCalled.rfind(
"NtGdi", 0) == 0)
return true;
188 if (syscallFuncName.rfind(
"NtGdi", 0) == 0) {
189 if (lastFuncCalled.rfind(
"NtGdi", 0) == 0)
return true;
190 if (lastFuncCalled.rfind(
"NtUser", 0) == 0)
return true;
194 if (this->
info.ext.wait_reason == UserRequest) {
195 if (syscallFuncName.rfind(
"NtDxgkGet", 0) == 0 &&
196 lastFuncCalled.rfind(
"NtWaitFor", 0) == 0)
200 if (syscallFuncName.find(
"WaitFor", 0) != std::string::npos &&
201 (lastFuncCalled.find(
"WaitFor", 0) != std::string::npos))
205 if (syscallFuncName ==
"NtWaitForSingleObject") {
206 if ((lastFuncCalled.rfind(
"NtQuery", 0) == 0) || lastFuncCalled ==
"NtDelayExecution")
return true;
208 if (syscallFuncName.rfind(
"NtUser", 0) == 0 && lastFuncCalled ==
"NtWaitForWorkViaWorkerFactory") {
211 if (syscallFuncName.rfind(
"NtUserModify", 0) == 0 && lastFuncCalled ==
"NtDeviceIoControlFile") {
216 if (this->
info.ext.wait_reason == WrQueue) {
217 if (syscallFuncName.rfind(
"NtWaitFor", 0) == 0 && lastFuncCalled ==
"NtWaitForWorkViaWorkerFactory") {
220 if (syscallFuncName ==
"NtWaitForWorkViaWorkerFactory") {
221 if (lastFuncCalled.rfind(
"NtWaitFor", 0) == 0
222 || lastFuncCalled.rfind(
"NtUserMsgWaitFor", 0) == 0
223 || lastFuncCalled.rfind(
"NtUserCreate", 0) == 0)
230 if (this->
info.ext.wait_reason == DelayExecution) {
231 if (syscallFuncName ==
"NtDelayExecution") {
232 if ((lastFuncCalled.rfind(
"NtUserMsgWaitFor", 0) == 0) || (lastFuncCalled.rfind(
"NtWaitFor", 0) == 0))
return true;
237 std::cout <<
"[@]" << std::dec <<
info.tid <<
" : " <<
"LastSyscall: " << syscallFuncName <<
" VS LastCalledAddr: " << std::hex << lastCalled
238 <<
" : " << lastFuncCalled <<
"(" << lastModName <<
"." << manualSymbol <<
" )" <<
" DIFFERENT!"
241#ifdef _SHOW_THREAD_INFO
243 std::cout <<
"STACK:\n";
244 for (
auto itr = callStack.rbegin(); itr != callStack.rend(); ++itr) {
245 ULONGLONG next_return = *itr;
246 symbols->dumpSymbolInfo(next_return);
250 std::cout << std::endl;
257 size_t processedCntr = 0;
259 cDetails.is_managed =
false;
260 cDetails.is_ret_in_frame =
false;
261#ifdef _SHOW_THREAD_INFO
262 std::cout <<
"\n" <<
"Stack frame Size: " << std::dec << cDetails.callStack.size() <<
"\n===\n";
264 for (
auto itr = cDetails.callStack.rbegin(); itr != cDetails.callStack.rend() ;++itr, ++processedCntr) {
265 const ULONGLONG next_return = *itr;
266 if (cDetails.ret_on_stack == next_return) {
267 cDetails.is_ret_in_frame =
true;
270#ifdef _SHOW_THREAD_INFO
272 symbols->dumpSymbolInfo(next_return);
277 bool is_curr_shc =
false;
279 const std::string mod_name = mod ? mod->
getModName() :
"";
280 if (mod_name.length() == 0) {
281 if (!cDetails.is_managed) {
283 shcCandidates.insert(next_return);
284#ifdef _SHOW_THREAD_INFO
285 std::cout <<
"\t" << std::hex << next_return <<
" <=== SHELLCODE\n";
288#ifdef _SHOW_THREAD_INFO
289 std::cout <<
"\t" << std::hex << next_return <<
" <=== .NET JIT\n";
295 cDetails.last_ret = next_return;
298 if (mod_name ==
"clr.dll" || mod_name ==
"coreclr.dll") {
299 cDetails.is_managed =
true;
300#ifdef _SHOW_THREAD_INFO
301 std::cout <<
"\t" << std::hex << next_return <<
" <--- .NET\n";
305#ifdef _SHOW_THREAD_INFO
306 std::cout <<
"\n===\n";
308 return processedCntr;
313 const size_t analyzedCount =
_analyzeCallStack(my_report.cDetails, my_report.shcCandidates);
315 bool checkCalls =
true;
316 if (my_report.cDetails.is_managed) {
319 if (
info.is_extended) {
320 if (
info.ext.wait_reason > WrQueue ||
321 info.ext.wait_reason == WrFreePage ||
info.ext.wait_reason == WrPageIn ||
info.ext.wait_reason == WrPoolAllocation ||
322 info.ext.wait_reason == FreePage ||
info.ext.wait_reason == PageIn ||
info.ext.wait_reason == PoolAllocation ||
323 info.ext.wait_reason == Suspended)
331 return analyzedCount;
336 if (!ctx || !symbols || !symbols->IsInitialized()) {
339 const size_t max_frames = 128;
342 STACKFRAME64 frame = { 0 };
344 frame.AddrPC.Offset = cDetails.
rip;
345 frame.AddrPC.Mode = AddrModeFlat;
346 frame.AddrStack.Offset = cDetails.
rsp;
347 frame.AddrStack.Mode = AddrModeFlat;
348 frame.AddrFrame.Offset = cDetails.
rbp;
349 frame.AddrFrame.Mode = AddrModeFlat;
352 size_t frameCount = 0;
354 while (symbols->RunStackWalk64(MachineType, hThread, &frame, ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
356 DWORD64 next_return = frame.AddrPC.Offset;
357 if (next_return == 0)
break;
359 if (next_return == prevPc)
break;
361 prevPc = next_return;
362 if (++frameCount > max_frames)
break;
364 callStack.push_back(next_return);
377 std::vector<ULONGLONG> callStack;
378 const DWORD machineType = cDetails.is64b ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_MACHINE_I386;
379 const size_t fetched =
enum_callstack(this->
symbols, cDetails, hThread, ctx, machineType, callStack);
383 cDetails.callStack = callStack;
384 return callStack.size();
388 template <
typename PTR_T>
391 cDetails.ret_on_stack = 0;
392 if (peconv::read_remote_memory(hProcess, (LPVOID)cDetails.rsp, (BYTE*)&ret_addr,
sizeof(ret_addr)) ==
sizeof(ret_addr)) {
393 cDetails.ret_on_stack = (ULONGLONG)ret_addr;
403 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
404 if (!GetThreadContext(hThread, &ctx)) {
408 cDetails.init(
true, ctx.Rip, ctx.Rsp, ctx.Rbp);
411 cDetails.init(
false, ctx.Eip, ctx.Esp, ctx.Ebp);
419bool pesieve::ThreadScanner::fetchWow64ThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT
ctx_details& cDetails)
421 WOW64_CONTEXT ctx = { 0 };
422 ctx.ContextFlags = WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_CONTROL;
426 cDetails.init(
false, ctx.Eip, ctx.Esp, ctx.Ebp);
428 fillCallStackInfo(hThread, &ctx, cDetails);
436 BOOL is_wow64 = FALSE;
441 my_report.has_native_wow64_context = native_ok;
443 const bool wow64_ok = fetchWow64ThreadCtxDetails(hProcess, hThread, my_report.cDetails);
444 if (!wow64_ok && native_ok) {
447 my_report.cDetails = my_report.nativeWow64Details;
448 my_report.has_native_wow64_context =
false;
451 std::cout << std::dec <<
"[" << GetThreadId(hThread) <<
"] Retrieved WOW64 guest callstack: " << my_report.cDetails.callStack.size()
452 <<
", native callstack: " << my_report.nativeWow64Details.callStack.size() << std::endl;
454 return wow64_ok || native_ok;
460 std::cout << std::dec <<
"[" << GetThreadId(hThread) <<
"] Retrieved callstack: " << my_report.cDetails.callStack.size() << std::endl;
468 if (!mod)
return false;
479 size_t MAX_DISP = 25;
491 size_t _displacement = 0;
493 for (
size_t disp = 0; disp < MAX_DISP; disp++) {
494 _displacement = disp;
495 const ULONGLONG va = addr - disp;
497 const std::set<peconv::ExportedFunc>* exp_set =
exportsMap->find_exports_by_va(va);
502 for (
auto it1 = exp_set->begin(); it1 != exp_set->end(); ++it1) {
503 const peconv::ExportedFunc& exp = *it1;
504 const std::string libName = exp.libName;
509 expName = exp.nameToString();
513 (*displacement) = _displacement;
521 (*displacement) = _displacement;
529 ULONGLONG modBase = 0;
533 std::stringstream ss;
537 char SEPARATOR =
'!';
538 bool is_resolved =
false;
541 std::string funcName =
symbols->funcNameFromAddr(addr, &disp);
542 if (!funcName.empty()) {
544 ss << SEPARATOR << funcName;
549 if (!funcName.empty()) {
551 ss << SEPARATOR << funcName;
556 ss <<
"+0x" << std::hex << disp;
560 ULONGLONG diff = addr - modBase;
561 ss <<
"+0x" << std::hex << diff;
568 bool is_resolved =
false;
569 std::cout << std::hex << addr;
576 std::cout << std::endl;
582 std::cout << std::dec <<
"TID: " << threadi.
tid <<
"\n";
583 std::cout << std::hex <<
"\tStart : ";
587 std::cout << std::hex <<
"\tSysStart: ";
603 if (!
report)
return false;
605 ULONG_PTR end_va = (ULONG_PTR)
report->module +
report->moduleSize;
619 MEMORY_BASIC_INFORMATION page_info = { 0 };
623 if (!(page_info.State & MEM_COMMIT)) {
626 ULONGLONG base = (ULONGLONG)page_info.BaseAddress;
627 susRep =
new SuspAddrReport(base, page_info.RegionSize, page_info.AllocationProtect);
646 if (!
info.is_extended) {
649 const KTHREAD_STATE state = (KTHREAD_STATE)
info.ext.state;
650 if (state == Ready) {
656 if (state == Terminated) {
659 if (state == Waiting &&
info.ext.wait_reason < MaximumWaitReason) {
667 const DWORD tid =
info.tid;
689 const ULONGLONG addr = *itr;
690#ifdef _SHOW_THREAD_INFO
691 std::cout <<
"Checking shc candidate: " << std::hex << addr <<
"\n";
697 std::cout <<
"[@]" << std::dec << tid <<
" : " <<
"Suspicious, possible shc: " << std::hex << addr << std::endl;
702 if (this->
info.is_extended &&
info.ext.state == Waiting && this->info.ext.wait_reason != Suspended
707#ifdef _SHOW_THREAD_INFO
708 std::cout <<
"Return addr: " << std::hex << ret_addr <<
"\n";
735 size_t dnet_common = 0;
752 if (dnet_common == my_report.
indicators.size()) {
786 ULONGLONG best_base = 0;
787 size_t best_module_size = 0;
789 double best_entropy = 0;
791 ULONGLONG base = itr1->first;
795 const int score = scoreArea(*rep);
796 if (score == 0)
continue;
798 if (score > best_score
799 || (score == best_score && rep->
stats.
entropy > best_entropy))
808 std::set< ThSusIndicator> validatedIndicators;
820 validatedIndicators.insert(indicator);
824 validatedIndicators.insert(indicator);
828 if (!validatedIndicators.size()) {
832 my_report.
module = (HMODULE)best_base;
839 if (!this->
info.is_extended) {
853 ULONGLONG addr = *itr;
857 ULONGLONG addr = *itr;
864 if (GetCurrentThreadId() ==
info.tid) {
874#ifdef _SHOW_THREAD_INFO
888 HANDLE hThread = OpenThread(
889 THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | SYNCHRONIZE,
895 CloseHandle(hThread);
899 std::cerr <<
"[-] Could not OpenThread. Error: " << GetLastError() << std::endl;
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
void addSuspAddr(ULONGLONG addr)
A report from the thread scan, generated by ThreadScanner.
ctx_details nativeWow64Details
std::map< ULONGLONG, SuspAddrReport * > suspAreaReports
std::set< ThSusIndicator > indicators
std::map< ULONGLONG, std::string > addrToSymbol
static std::string translate_wait_reason(DWORD thread_wait_reason)
SuspAddrReport * findAreaForAddress(const ULONGLONG &susp_addr)
std::set< ULONGLONG > shcCandidates
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)
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)
ModulesInfo & modulesInfo
bool fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, 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_CALLS_INTEGRITY
pesieve::SyscallTable g_SyscallTable
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
Final summary about the scanned process.
#define ENTROPY_ENC_THRESHOLD
bool get_page_details(HANDLE processHandle, LPVOID start_va, MEMORY_BASIC_INFORMATION &page_info)
#define ENTROPY_THRESHOLD
size_t enum_callstack(IN ProcessSymbolsManager *symbols, const pesieve::ctx_details &cDetails, IN HANDLE hThread, IN LPVOID ctx, DWORD MachineType, std::vector< ULONGLONG > &callStack)
bool should_scan_context(const util::thread_info &info)