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>
4#include "../utils/ntddk.h"
5#include "../stats/stats.h"
6#include "mempage_data.h"
7
8#include <dbghelp.h>
9#pragma comment(lib, "dbghelp")
10
11#define ENTROPY_TRESHOLD 3.0
12//#define NO_ENTROPY_CHECK
13using namespace pesieve;
14
15typedef struct _t_stack_enum_params {
20 std::vector<ULONGLONG> stack_frame;
21 bool is_ok;
22
27
29 {
30 this->hProcess = hProcess;
31 this->hThread = hThread;
32 this->ctx = ctx;
33 this->c = &c;
34 this->is_ok = false;
35 }
37
38
40{
41 t_stack_enum_params* args = static_cast<t_stack_enum_params*>(lpParam);
42 if (!args || !args->c || !args->ctx) {
44 }
45 size_t fetched = 0;
46 bool in_shc = false;
47 const pesieve::thread_ctx& c = *(args->c);
48#ifdef _WIN64
49 if (c.is64b) {
50 STACKFRAME64 frame = { 0 };
51
52 frame.AddrPC.Offset = c.rip;
53 frame.AddrPC.Mode = AddrModeFlat;
54 frame.AddrStack.Offset = c.rsp;
55 frame.AddrStack.Mode = AddrModeFlat;
56 frame.AddrFrame.Offset = c.rbp;
57 frame.AddrFrame.Mode = AddrModeFlat;
58
60 //std::cout << "Next Frame start:" << std::hex << frame.AddrPC.Offset << "\n";
61 const ULONGLONG next_addr = frame.AddrPC.Offset;
62 args->stack_frame.push_back(next_addr);
63 fetched++;
64 }
65 }
66#endif
67 if (!c.is64b) {
68 STACKFRAME frame = { 0 };
69
70 frame.AddrPC.Offset = c.rip;
71 frame.AddrPC.Mode = AddrModeFlat;
72 frame.AddrStack.Offset = c.rsp;
73 frame.AddrStack.Mode = AddrModeFlat;
74 frame.AddrFrame.Offset = c.rbp;
75 frame.AddrFrame.Mode = AddrModeFlat;
76
78 const ULONGLONG next_addr = frame.AddrPC.Offset;
79 args->stack_frame.push_back(next_addr);
80 fetched++;
81 }
82 }
83 if (fetched) {
84 args->is_ok = true;
85 return STATUS_SUCCESS;
86 }
88}
89
90//---
91
92std::string ThreadScanReport::translate_wait_reason(DWORD thread_wait_reason)
93{
94 switch (thread_wait_reason) {
95 case DelayExecution: return "DelayExecution";
96 case Suspended: return "Suspended";
97 case Executive: return "Executive";
98 case UserRequest: return "UserRequest";
99 case WrUserRequest: return "WrUserRequest";
100 }
101 std::stringstream ss;
102 ss << "Other: " << std::dec << thread_wait_reason;
103 return ss.str();
104}
105
106std::string ThreadScanReport::translate_thread_state(DWORD thread_state)
107{
108 switch (thread_state) {
109 case Initialized: return "Initialized";
110 case Ready: return "Ready";
111 case Running: return "Running";
112 case Standby: return "Standby";
113 case Terminated: return "Terminated";
114 case Waiting: return "Waiting";
115 case Transition: return "Transition";
116 case DeferredReady: return "DeferredReady";
117 case GateWaitObsolete: return "GateWaitObsolete";
118 case WaitingForProcessInSwap: return "WaitingForProcessInSwap";
119 }
120 std::stringstream ss;
121 ss << "Other: " << std::dec << thread_state;
122 return ss.str();
123}
124
125//---
126
128{
129 // do it in a new thread to prevent stucking...
130 t_stack_enum_params args(hProcess, hThread, ctx, c);
131 const size_t max_wait = 1000;
132 {
134 NULL, // default security attributes
135 0, // use default stack size
136 enum_stack_thread, // thread function name
137 &args, // argument to thread function
138 0, // use default creation flags
139 0); // returns the thread identifiee
140
141 if (enumThread) {
143 if (wait_result == WAIT_TIMEOUT) {
144 std::cerr << "[!] Cannot retrieve stack frame: timeout passed!\n";
147 return 0;
148 }
150 }
151 }
152
153 if (!args.is_ok) {
154 return 0;
155 }
156
157 // filter:
158 size_t cntr = 0;
159 bool has_shellcode = false;
160 c.is_managed = false;
161 std::vector<ULONGLONG>::iterator itr;
162 for (itr = args.stack_frame.begin(); itr != args.stack_frame.end(); ++itr) {
163 cntr++;
165#ifdef _DEBUG
166 resolveAddr(next_addr);
167#endif
168 bool is_curr_shc = false;
169 const ScannedModule* mod = modulesInfo.findModuleContaining(next_addr);
170 const std::string mod_name = mod ? mod->getModName() : "";
171 if (mod_name.length() == 0) {
172 has_shellcode = is_curr_shc = true;
173#ifdef _DEBUG
174 std::cout << std::hex << next_addr << " <=== SHELLCODE\n";
175#endif
176 }
177 if (!has_shellcode || is_curr_shc) {
178 // store the last address, till the first called shellcode:
179 c.ret_addr = next_addr;
180 }
181 // check if the found shellcode is a .NET JIT:
182 if (mod_name == "clr.dll") {
183 c.is_managed = true;
184#ifdef _DEBUG
185 std::cout << std::hex << next_addr << " <--- .NET\n";
186#endif
187 if (has_shellcode) break;
188 }
189 }
190 return cntr;
191}
192
194{
195 bool is_ok = false;
196 BOOL is_wow64 = FALSE;
197#ifdef _WIN64
199
200 if (is_wow64) {
201 WOW64_CONTEXT ctx = { 0 };
202 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
203 if (pesieve::util::wow64_get_thread_context(hThread, &ctx)) {
204 is_ok = true;
205
206 c.rip = ctx.Eip;
207 c.rsp = ctx.Esp;
208 c.rbp = ctx.Ebp;
209 c.is64b = false;
210 enumStackFrames(hProcess, hThread, &ctx, c);
211 }
212 }
213#endif
214 if (!is_ok) {
215
216 CONTEXT ctx = { 0 };
217 ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
218 if (GetThreadContext(hThread, &ctx)) {
219 is_ok = true;
220#ifdef _WIN64
221 c.rip = ctx.Rip;
222 c.rsp = ctx.Rsp;
223 c.rbp = ctx.Rbp;
224 c.is64b = true;
225
226
227#else
228 c.rip = ctx.Eip;
229 c.rsp = ctx.Esp;
230 c.is64b = false;
231#endif
232 enumStackFrames(hProcess, hThread, &ctx, c);
233 }
234 }
235 return is_ok;
236}
237
239{
240 ScannedModule* mod = modulesInfo.findModuleContaining(addr);
241 if (!mod) return true;
242
243 //the module is named
244 if (mod->getModName().length() > 0) {
245 return false;
246 }
247 return true;
248}
249
251{
252 bool is_resolved = false;
253 ScannedModule* mod = modulesInfo.findModuleContaining(addr);
254 std::cout << " > " << std::hex << addr;
255 if (mod) {
256 std::cout << " : " << mod->getModName();
257 is_resolved = true;
258 }
259 if (exportsMap && is_resolved) {
260 bool search_name = false;
261 if (mod->getModName() == "ntdll.dll" || mod->getModName() == "win32u.dll") {
262 search_name = true;
263 }
264 for (size_t i = 0; i < 25; i++) {
265 const peconv::ExportedFunc* exp = exportsMap->find_export_by_va(addr - i);
266 if (exp) {
267 std::cout << " : " << exp->toString();
268 is_resolved = true;
269 break;
270 }
271 if (!search_name) {
272 break;
273 }
274 }
275 }
276 std::cout << std::endl;
277 return is_resolved;
278}
279
281{
283 const SIZE_T out = VirtualQueryEx(processHandle, (LPCVOID)start_va, &page_info, page_info_size);
284 const bool is_read = (out == page_info_size) ? true : false;
285 const DWORD error = is_read ? ERROR_SUCCESS : GetLastError();
286 if (error != ERROR_SUCCESS) {
287 //nothing to read
288 return false;
289 }
290 return true;
291}
292
294{
295 if (!my_report) return false;
296
297 ULONG_PTR end_va = (ULONG_PTR)my_report->module + my_report->moduleSize;
298 MemPageData mem(this->processHandle, this->isReflection, (ULONG_PTR)my_report->module, end_va);
299 if (!mem.fillInfo() || !mem.load()) {
300 return false;
301 }
302 AreaStatsCalculator calc(mem.loadedData);
303 return calc.fill(my_report->stats, nullptr);
304}
305
307{
309 if (!get_page_details(processHandle, (LPVOID)susp_addr, page_info)) {
310 return false;
311 }
312 if (page_info.State & MEM_FREE) {
313 return false;
314 }
315 ULONGLONG base = (ULONGLONG)page_info.BaseAddress;
316 if (this->info.is_extended) {
317 my_report->thread_state = info.ext.state;
318 my_report->thread_wait_reason = info.ext.wait_reason;
319 }
320 my_report->module = (HMODULE)base;
321 my_report->moduleSize = page_info.RegionSize;
322 my_report->protection = page_info.AllocationProtect;
323
324 my_report->thread_ip = susp_addr;
325 my_report->status = SCAN_SUSPICIOUS;
326 const bool isStatFilled = fillAreaStats(my_report);
327#ifndef NO_ENTROPY_CHECK
328 if (isStatFilled && (my_report->stats.entropy < ENTROPY_TRESHOLD)) {
330 }
331#endif
332 return true;
333}
334
335
337{
338 if (SymInitialize(hProc, NULL, TRUE)) {
339 return true;
340 }
341 return false;
342}
343
345{
346 if (SymCleanup(hProc)) {
347 return true;
348 }
349 return false;
350}
351
352// if extended info given, allow to filter out from the scan basing on the thread state and conditions
354{
355 if (!info.is_extended) {
356 return true;
357 }
358 const KTHREAD_STATE state = (KTHREAD_STATE)info.ext.state;
359 if (state == Running || state == Ready) {
360 return true;
361 }
362 if (state == Terminated) {
363 return false;
364 }
365 if (state == Waiting) {
366 if (info.ext.start_addr == 0) {
367 return true;
368 }
369 if (info.ext.wait_reason == DelayExecution
370 || info.ext.wait_reason == Suspended
371 || info.ext.wait_reason == Executive // the thread is waiting got the scheduler
372 || info.ext.wait_reason == UserRequest // i.e. WaitForSingleObject/WaitForMultipleObjects
373 || info.ext.wait_reason == WrUserRequest // i.e. when the thread calls GetMessage
374 )
375 {
376 return true;
377 }
378 }
379 return false;
380}
381
383{
384 HANDLE hThread = OpenThread(
386 FALSE,
387 info.tid
388 );
389 if (!hThread) {
390#ifdef _DEBUG
391 std::cerr << "[-] Could not OpenThread. Error: " << GetLastError() << std::endl;
392#endif
393 return nullptr;
394 }
395#ifdef _DEBUG
396 std::cout << std::dec << "---\nTid: " << info.tid << "\n";
397 if (info.is_extended) {
398 std::cout << " Start: " << std::hex << info.ext.start_addr << std::dec << " State: " << info.ext.state;
399 if (info.ext.state == Waiting) {
400 std::cout << " WaitReason: " << info.ext.wait_reason
401 << " WaitTime: " << info.ext.wait_time;
402 }
403 std::cout << "\n";
404 resolveAddr(info.ext.start_addr);
405 }
406#endif
408#ifndef _DEBUG
409 // if NOT compiled in a debug mode, make this check BEFORE scan
410 if (!should_scan(info)) {
411 CloseHandle(hThread); // close the opened thread
413 return my_report;
414 }
415#endif
416 thread_ctx c = { 0 };
417 const bool is_ok = fetchThreadCtx(processHandle, hThread, c);
418
419 DWORD exit_code = 0;
420 GetExitCodeThread(hThread, &exit_code);
421 CloseHandle(hThread);
422
423 if (!is_ok) {
424 // could not fetch the thread context and information
425 my_report->status = SCAN_ERROR;
426 return my_report;
427 }
428#ifdef _DEBUG
429 std::cout << " b:" << c.is64b << std::hex << " Rip: " << c.rip << " Rsp: " << c.rsp;
430 if (exit_code != STILL_ACTIVE)
431 std::cout << " ExitCode: " << exit_code;
432
433 if (c.ret_addr != 0) {
434 std::cout << std::hex << " Ret: " << c.ret_addr;
435 }
436 std::cout << "\n";
437#endif
438
439 if (exit_code != STILL_ACTIVE) {
441 return my_report;
442 }
443#ifdef _DEBUG
444 // if compiled in a debug mode, make this check AFTER scan
445 // (so that we can see first what was skipped)
446 if (!should_scan(info)) {
448 return my_report;
449 }
450#endif
451 bool is_shc = isAddrInShellcode(c.rip);
452 if (is_shc) {
453 if (reportSuspiciousAddr(my_report, c.rip, c)) {
454 return my_report;
455 }
456 }
457 if ((c.ret_addr != 0) && (c.is_managed == false)) {
458 is_shc = isAddrInShellcode(c.ret_addr);
459 if (is_shc) {
460 if (reportSuspiciousAddr(my_report, c.ret_addr, c)) {
461 return my_report;
462 }
463 }
464 }
465 return my_report;
466}
A class responsible for filling in the statistics with the data from the particular buffer.
Definition stats.h:73
Represents a basic info about the scanned module, such as its base offset, size, and the status.
std::string getModName() const
A report from the thread scan, generated by ThreadScanner.
static std::string translate_wait_reason(DWORD thread_wait_reason)
static std::string translate_thread_state(DWORD thread_state)
virtual ThreadScanReport * scanRemote()
bool reportSuspiciousAddr(ThreadScanReport *my_report, ULONGLONG susp_addr, thread_ctx &c)
size_t enumStackFrames(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN OUT thread_ctx &c)
bool resolveAddr(ULONGLONG addr)
static bool FreeSymbols(HANDLE hProc)
bool fillAreaStats(ThreadScanReport *my_report)
bool isAddrInShellcode(ULONGLONG addr)
static bool InitSymbols(HANDLE hProc)
bool fetchThreadCtx(IN HANDLE hProcess, IN HANDLE hThread, OUT thread_ctx &c)
BOOL is_process_wow64(IN HANDLE processHandle, OUT BOOL *isProcWow64)
BOOL wow64_get_thread_context(IN HANDLE hThread, IN OUT PWOW64_CONTEXT lpContext)
size_t fill_iat(BYTE *vBuf, size_t vBufSize, IN const peconv::ExportsMapper *exportsMap, IN OUT IATBlock &iat, IN ThunkFoundCallback *callback)
Definition iat_finder.h:31
std::string info()
The string with the basic information about the scanner.
Definition pe_sieve.cpp:268
const pesieve::thread_ctx * c
_t_stack_enum_params(IN HANDLE hProcess, IN HANDLE hThread, IN LPVOID ctx, IN const pesieve::thread_ctx &c)
std::vector< ULONGLONG > stack_frame
A custom structure keeping a fragment of a thread context.
bool should_scan(const util::thread_info &info)
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