|
14 | 14 | #include <unistd.h> |
15 | 15 | #endif |
16 | 16 |
|
| 17 | +#if (CLI_WINDOWS) |
| 18 | + #include <dbghelp.h> |
| 19 | + |
| 20 | +static void print_thread_stacktrace(HANDLE hThread, const std::string& label) { |
| 21 | + // Freeze the thread so GetThreadContext returns a consistent snapshot. |
| 22 | + SuspendThread(hThread); |
| 23 | + |
| 24 | + CONTEXT ctx = {}; |
| 25 | + ctx.ContextFlags = CONTEXT_FULL; |
| 26 | + if (!GetThreadContext(hThread, &ctx)) { |
| 27 | + std::cerr << "[STACKTRACE] GetThreadContext failed (err " << GetLastError() << ")\n" << std::flush; |
| 28 | + return; // caller will TerminateThread; no Resume needed |
| 29 | + } |
| 30 | + |
| 31 | + HANDLE hProc = GetCurrentProcess(); |
| 32 | + |
| 33 | + // SymInitialize must be called once per process. |
| 34 | + static bool sym_ready = false; |
| 35 | + if (!sym_ready) { |
| 36 | + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); |
| 37 | + sym_ready = SymInitialize(hProc, nullptr, TRUE) != FALSE; |
| 38 | + } |
| 39 | + |
| 40 | + STACKFRAME64 sf = {}; |
| 41 | + DWORD machine; |
| 42 | + |
| 43 | +#if defined(_M_X64) |
| 44 | + machine = IMAGE_FILE_MACHINE_AMD64; |
| 45 | + sf.AddrPC.Offset = ctx.Rip; sf.AddrPC.Mode = AddrModeFlat; |
| 46 | + sf.AddrFrame.Offset = ctx.Rbp; sf.AddrFrame.Mode = AddrModeFlat; |
| 47 | + sf.AddrStack.Offset = ctx.Rsp; sf.AddrStack.Mode = AddrModeFlat; |
| 48 | +#elif defined(_M_IX86) |
| 49 | + machine = IMAGE_FILE_MACHINE_I386; |
| 50 | + sf.AddrPC.Offset = ctx.Eip; sf.AddrPC.Mode = AddrModeFlat; |
| 51 | + sf.AddrFrame.Offset = ctx.Ebp; sf.AddrFrame.Mode = AddrModeFlat; |
| 52 | + sf.AddrStack.Offset = ctx.Esp; sf.AddrStack.Mode = AddrModeFlat; |
| 53 | +#else |
| 54 | + std::cerr << "[STACKTRACE] unsupported architecture\n" << std::flush; |
| 55 | + return; |
| 56 | +#endif |
| 57 | + |
| 58 | + std::cerr << "[STACKTRACE] hung in " << label << ":\n"; |
| 59 | + |
| 60 | + alignas(SYMBOL_INFO) char sym_buf[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; |
| 61 | + SYMBOL_INFO* sym = reinterpret_cast<SYMBOL_INFO*>(sym_buf); |
| 62 | + |
| 63 | + IMAGEHLP_LINE64 line_info = {}; |
| 64 | + line_info.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| 65 | + |
| 66 | + for (int n = 0; n < 32; ++n) { |
| 67 | + if (!StackWalk64(machine, hProc, hThread, &sf, &ctx, |
| 68 | + nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) { |
| 69 | + break; |
| 70 | + } |
| 71 | + if (sf.AddrPC.Offset == 0) { break; } |
| 72 | + |
| 73 | + std::cerr << " #" << std::dec << n |
| 74 | + << " 0x" << std::hex << std::setw(16) << std::setfill('0') << sf.AddrPC.Offset; |
| 75 | + |
| 76 | + sym->SizeOfStruct = sizeof(SYMBOL_INFO); |
| 77 | + sym->MaxNameLen = MAX_SYM_NAME; |
| 78 | + DWORD64 sym_disp = 0; |
| 79 | + if (sym_ready && SymFromAddr(hProc, sf.AddrPC.Offset, &sym_disp, sym)) { |
| 80 | + std::cerr << " " << sym->Name << "+" << std::dec << sym_disp; |
| 81 | + DWORD line_disp = 0; |
| 82 | + if (SymGetLineFromAddr64(hProc, sf.AddrPC.Offset, &line_disp, &line_info)) { |
| 83 | + std::cerr << " (" << line_info.FileName << ":" << line_info.LineNumber << ")"; |
| 84 | + } |
| 85 | + } |
| 86 | + std::cerr << "\n"; |
| 87 | + } |
| 88 | + std::cerr << std::dec << std::setfill(' ') << std::flush; |
| 89 | + // Caller TerminateThread; intentionally not ResumeThread here. |
| 90 | +} |
| 91 | +#endif |
| 92 | + |
17 | 93 | const char* color(const u8 score, const bool is_hardened) { |
18 | 94 | if (arg_bitset.test(NO_ANSI)) { |
19 | 95 | return ""; |
@@ -218,6 +294,60 @@ const char* get_vm_description(const std::string& vm_brand) { |
218 | 294 | return ""; |
219 | 295 | } |
220 | 296 |
|
| 297 | +#if (CLI_WINDOWS) |
| 298 | +// Run a single technique in a worker thread and wait up to TECHNIQUE_TIMEOUT_MS. |
| 299 | +// Returns {result, timed_out}. On timeout the flag is pushed to |
| 300 | +// VM::disabled_techniques so the VM::vmaware constructor won't re-run it. |
| 301 | +static constexpr DWORD TECHNIQUE_TIMEOUT_MS = 3000; |
| 302 | + |
| 303 | +struct win_run_result { bool result; bool timed_out; }; |
| 304 | + |
| 305 | +static win_run_result win_run_technique(const VM::enum_flags flag) { |
| 306 | + struct runner_t { |
| 307 | + VM::enum_flags flag; |
| 308 | + bool result = false; |
| 309 | + static DWORD WINAPI run(LPVOID lp) noexcept { |
| 310 | + auto* self = static_cast<runner_t*>(lp); |
| 311 | + self->result = VM::check(self->flag); |
| 312 | + return 0; |
| 313 | + } |
| 314 | + }; |
| 315 | + |
| 316 | + runner_t runner { |
| 317 | + flag, |
| 318 | + false |
| 319 | + }; |
| 320 | + |
| 321 | + HANDLE h = CreateThread(nullptr, 0, runner_t::run, &runner, 0, nullptr); |
| 322 | + if (!h) { |
| 323 | + return { |
| 324 | + VM::check(flag), |
| 325 | + false |
| 326 | + }; |
| 327 | + } |
| 328 | + |
| 329 | + if (WaitForSingleObject(h, TECHNIQUE_TIMEOUT_MS) == WAIT_TIMEOUT) { |
| 330 | + TerminateThread(h, 0); |
| 331 | + CloseHandle(h); |
| 332 | + VM::disabled_techniques.push_back(flag); |
| 333 | + std::cerr << "[TIMEOUT] VM::" << VM::flag_to_string(flag) |
| 334 | + << " did not finish within " << (TECHNIQUE_TIMEOUT_MS / 1000) |
| 335 | + << "s, skipping\n" << std::flush; |
| 336 | + |
| 337 | + return { |
| 338 | + false, |
| 339 | + true |
| 340 | + }; |
| 341 | + } |
| 342 | + |
| 343 | + CloseHandle(h); |
| 344 | + return { |
| 345 | + runner.result, |
| 346 | + false |
| 347 | + }; |
| 348 | +} |
| 349 | +#endif |
| 350 | + |
221 | 351 | static void checker(const VM::enum_flags flag, const char* message) { |
222 | 352 | std::string enum_name; |
223 | 353 |
|
@@ -245,43 +375,9 @@ static void checker(const VM::enum_flags flag, const char* message) { |
245 | 375 | auto start_time = std::chrono::high_resolution_clock::now(); |
246 | 376 |
|
247 | 377 | #if (CLI_WINDOWS) |
248 | | - // Some Windows API calls (e.g. SetupDiGetClassDevsW device enumeration) can |
249 | | - // stall indefinitely on certain hypervisors. Run each technique in a worker |
250 | | - // thread so we can abort it and continue if it takes too long. |
251 | | - constexpr DWORD TECHNIQUE_TIMEOUT_MS = 3000; |
252 | | - |
253 | | - struct technique_runner { |
254 | | - VM::enum_flags flag; |
255 | | - bool result = false; |
256 | | - |
257 | | - static DWORD WINAPI run(LPVOID lp) noexcept { |
258 | | - auto* self = static_cast<technique_runner*>(lp); |
259 | | - self->result = VM::check(self->flag); |
260 | | - return 0; |
261 | | - } |
262 | | - }; |
263 | | - |
264 | | - technique_runner runner{ flag, false }; |
265 | | - HANDLE hThread = CreateThread(nullptr, 0, technique_runner::run, &runner, 0, nullptr); |
266 | | - |
267 | | - bool result = false; |
268 | | - |
269 | | - if (hThread) { |
270 | | - if (WaitForSingleObject(hThread, TECHNIQUE_TIMEOUT_MS) == WAIT_TIMEOUT) { |
271 | | - TerminateThread(hThread, 0); |
272 | | - CloseHandle(hThread); |
273 | | - // Prevent VM::vmaware constructor from re-running this technique. |
274 | | - VM::disabled_techniques.push_back(flag); |
275 | | - std::cerr << "[TIMEOUT] \"" << message << "\" (VM::" << VM::flag_to_string(flag) |
276 | | - << ") did not finish within " << (TECHNIQUE_TIMEOUT_MS / 1000) |
277 | | - << "s, skipping\n" << std::flush; |
278 | | - return; |
279 | | - } |
280 | | - CloseHandle(hThread); |
281 | | - result = runner.result; |
282 | | - } else { |
283 | | - result = VM::check(flag); |
284 | | - } |
| 378 | + const win_run_result wres = win_run_technique(flag); |
| 379 | + if (wres.timed_out) { return; } |
| 380 | + const bool result = wres.result; |
285 | 381 | #else |
286 | 382 | const bool result = VM::check(flag); |
287 | 383 | #endif |
@@ -360,6 +456,19 @@ bool parse_disable_token(const char* token) { |
360 | 456 | } |
361 | 457 |
|
362 | 458 | void generate_json(const char* output) { |
| 459 | +#if (CLI_WINDOWS) |
| 460 | + // Warm the memo cache with per-technique timeouts before constructing |
| 461 | + // VM::vmaware, so that any technique that hangs (e.g. CPU_HEURISTIC on |
| 462 | + // Azure Hyper-V) is aborted and disabled rather than blocking indefinitely. |
| 463 | + for (u8 i = VM::technique_begin; i < static_cast<u8>(VM::technique_end); ++i) { |
| 464 | + const VM::enum_flags flag = static_cast<VM::enum_flags>(i); |
| 465 | + if (is_disabled(flag) || is_unsupported(flag)) { |
| 466 | + continue; |
| 467 | + } |
| 468 | + win_run_technique(flag); |
| 469 | + } |
| 470 | +#endif |
| 471 | + |
363 | 472 | const VM::vmaware vm(VM::MULTIPLE); |
364 | 473 |
|
365 | 474 | std::vector<std::string> json; |
|
0 commit comments