[FEATURE] In ThreadScanner: added native callstack for WoW64 processes

This commit is contained in:
hasherezade
2026-05-31 11:36:09 -08:00
parent 33bea937b2
commit 3aebdf8dd5
4 changed files with 107 additions and 69 deletions
+64 -46
View File
@@ -331,8 +331,7 @@ size_t pesieve::ThreadScanner::analyzeCallStackInfo(IN OUT ThreadScanReport& my_
return analyzedCount;
}
template<typename T_STACKFRAME>
size_t enum_callstack(IN ProcessSymbolsManager* symbols, const pesieve::ctx_details& cDetails,IN HANDLE hThread, IN LPVOID ctx, DWORD MachineType, std::vector<ULONGLONG> &callStack)
size_t enum_callstack(IN ProcessSymbolsManager* symbols, const pesieve::ctx_details& cDetails, IN HANDLE hThread, IN LPVOID ctx, DWORD MachineType, std::vector<ULONGLONG>& callStack)
{
if (!ctx || !symbols || !symbols->IsInitialized()) {
return 0;
@@ -340,7 +339,7 @@ size_t enum_callstack(IN ProcessSymbolsManager* symbols, const pesieve::ctx_deta
const size_t max_frames = 128;
size_t fetched = 0;
T_STACKFRAME frame = { 0 };
STACKFRAME64 frame = { 0 };
frame.AddrPC.Offset = cDetails.rip;
frame.AddrPC.Mode = AddrModeFlat;
@@ -348,11 +347,11 @@ size_t enum_callstack(IN ProcessSymbolsManager* symbols, const pesieve::ctx_deta
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Offset = cDetails.rbp;
frame.AddrFrame.Mode = AddrModeFlat;
DWORD64 prevPc = 0;
size_t frameCount = 0;
while (symbols->RunStackWalk(MachineType, hThread, &frame, ctx, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL)) {
while (symbols->RunStackWalk64(MachineType, hThread, &frame, ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
DWORD64 next_return = frame.AddrPC.Offset;
if (next_return == 0) break;
@@ -368,7 +367,7 @@ size_t enum_callstack(IN ProcessSymbolsManager* symbols, const pesieve::ctx_deta
return fetched;
}
size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, IN const LPVOID ctx, IN OUT ThreadScanReport& my_report)
size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hThread, IN const LPVOID ctx, IN OUT ctx_details& cDetails)
{
if (!ctx ||
!this->symbols || !this->symbols->IsInitialized())
@@ -376,21 +375,12 @@ size_t pesieve::ThreadScanner::fillCallStackInfo(IN HANDLE hProcess, IN HANDLE h
return 0;
}
std::vector<ULONGLONG> callStack;
size_t fetched = 0;
const pesieve::ctx_details& cDetails = my_report.cDetails;
#ifdef _WIN64
if (cDetails.is64b) {
fetched = enum_callstack<STACKFRAME64>(this->symbols, cDetails, hThread, ctx, IMAGE_FILE_MACHINE_AMD64, callStack);
}
#endif
if (!cDetails.is64b) {
fetched = enum_callstack<STACKFRAME>(this->symbols, cDetails, hThread, ctx, IMAGE_FILE_MACHINE_I386, callStack);
}
const DWORD machineType = cDetails.is64b ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_MACHINE_I386;
const size_t fetched = enum_callstack(this->symbols, cDetails, hThread, ctx, machineType, callStack);
if (!fetched) {
return 0;
}
my_report.cDetails.callStack = callStack;
cDetails.callStack = callStack;
return callStack.size();
}
@@ -407,43 +397,67 @@ namespace pesieve {
}
}; //namespace pesieve
bool pesieve::ThreadScanner::fetchNativeThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details& cDetails)
{
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
if (!GetThreadContext(hThread, &ctx)) {
return false;
}
#ifdef _WIN64
cDetails.init(true, ctx.Rip, ctx.Rsp, ctx.Rbp);
read_return_ptr<ULONGLONG>(hProcess, cDetails);
#else
cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp);
read_return_ptr<DWORD>(hProcess, cDetails);
#endif
fillCallStackInfo(hThread, &ctx, cDetails);
return true;
}
#ifdef _WIN64
bool pesieve::ThreadScanner::fetchWow64ThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details& cDetails)
{
WOW64_CONTEXT ctx = { 0 };
ctx.ContextFlags = WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_CONTROL;
if (!pesieve::util::wow64_get_thread_context(hThread, &ctx)) {
return false;
}
cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp);
read_return_ptr<DWORD>(hProcess, cDetails);
fillCallStackInfo(hThread, &ctx, cDetails);
return true;
}
#endif
bool pesieve::ThreadScanner::fetchThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, OUT ThreadScanReport& my_report)
{
bool is_ok = false;
BOOL is_wow64 = FALSE;
size_t retrieved = 0;
#ifdef _WIN64
BOOL is_wow64 = FALSE;
pesieve::util::is_process_wow64(hProcess, &is_wow64);
if (is_wow64) {
WOW64_CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
if (pesieve::util::wow64_get_thread_context(hThread, &ctx)) {
is_ok = true;
my_report.cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp);
read_return_ptr<DWORD>(hProcess, my_report.cDetails);
retrieved = fillCallStackInfo(hProcess, hThread, &ctx, my_report);
}
}
#endif
if (!is_ok) {
const bool native_ok = fetchNativeThreadCtxDetails(hProcess, hThread, my_report.nativeWow64Details);
my_report.has_native_wow64_context = native_ok;
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
if (GetThreadContext(hThread, &ctx)) {
is_ok = true;
#ifdef _WIN64
my_report.cDetails.init(true, ctx.Rip, ctx.Rsp, ctx.Rbp);
read_return_ptr<ULONGLONG>(hProcess, my_report.cDetails);
#else
my_report.cDetails.init(false, ctx.Eip, ctx.Esp, ctx.Ebp);
read_return_ptr<DWORD>(hProcess, my_report.cDetails);
#endif
retrieved = fillCallStackInfo(hProcess, hThread, &ctx, my_report);
const bool wow64_ok = fetchWow64ThreadCtxDetails(hProcess, hThread, my_report.cDetails);
if (!wow64_ok && native_ok) {
// Preserve a usable primary stack if the WOW64 guest context could not be fetched.
// Avoid reporting the same native stack twice.
my_report.cDetails = my_report.nativeWow64Details;
my_report.has_native_wow64_context = false;
}
}
#ifdef _DEBUG
std::cout << std::dec << "[" << GetThreadId(hThread) << "] Retrieved callstack: " << retrieved << std::endl;
std::cout << std::dec << "[" << GetThreadId(hThread) << "] Retrieved WOW64 guest callstack: " << my_report.cDetails.callStack.size()
<< ", native callstack: " << my_report.nativeWow64Details.callStack.size() << std::endl;
#endif //_DEBUG
return wow64_ok || native_ok;
}
#endif
const bool is_ok = fetchNativeThreadCtxDetails(hProcess, hThread, my_report.cDetails);
#ifdef _DEBUG
std::cout << std::dec << "[" << GetThreadId(hThread) << "] Retrieved callstack: " << my_report.cDetails.callStack.size() << std::endl;
#endif //_DEBUG
return is_ok;
}
@@ -836,6 +850,10 @@ void pesieve::ThreadScanner::reportResolvedCallstack(ThreadScanReport& my_report
ULONGLONG addr = *itr;
my_report.addrToSymbol[addr] = this->resolveAddrToString(addr);
}
for (auto itr = my_report.nativeWow64Details.callStack.begin(); itr != my_report.nativeWow64Details.callStack.end(); ++itr) {
ULONGLONG addr = *itr;
my_report.addrToSymbol[addr] = this->resolveAddrToString(addr);
}
}
ThreadScanReport* pesieve::ThreadScanner::scanRemote()
+29 -9
View File
@@ -197,7 +197,8 @@ namespace pesieve {
: ModuleScanReport(0, 0),
tid(_tid), stack_ptr(0),
thread_state(THREAD_STATE_UNKNOWN),
thread_wait_reason(0), thread_wait_time(0)
thread_wait_reason(0), thread_wait_time(0),
has_native_wow64_context(false)
{
}
@@ -226,7 +227,7 @@ namespace pesieve {
return nullptr;
}
const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails)
const virtual void callstackToJSON(std::stringstream& outs, size_t level, const pesieve::t_json_level& jdetails, const ctx_details& details)
{
bool printCallstack = (jdetails >= JSON_DETAILS) ? true : false;
if (this->indicators.find(THI_SUS_CALLSTACK_CORRUPT) != this->indicators.end()) {
@@ -236,16 +237,16 @@ namespace pesieve {
printCallstack = true;
}
OUT_PADDED(outs, level, "\"stack_ptr\" : ");
outs << "\"" << std::hex << stack_ptr << "\"";
if (cDetails.callStack.size()) {
outs << "\"" << std::hex << details.rsp << "\"";
if (details.callStack.size()) {
outs << ",\n";
OUT_PADDED(outs, level, "\"frames_count\" : ");
outs << std::dec << cDetails.callStack.size();
outs << std::dec << details.callStack.size();
if (printCallstack) {
outs << ",\n";
OUT_PADDED(outs, level, "\"frames\" : [");
for (auto itr = cDetails.callStack.rbegin(); itr != cDetails.callStack.rend(); ++itr) {
if (itr != cDetails.callStack.rbegin()) {
for (auto itr = details.callStack.rbegin(); itr != details.callStack.rend(); ++itr) {
if (itr != details.callStack.rbegin()) {
outs << ", ";
}
const ULONGLONG addr = *itr;
@@ -281,7 +282,14 @@ namespace pesieve {
if (stack_ptr) {
outs << ",\n";
OUT_PADDED(outs, level, "\"callstack\" : {\n");
callstackToJSON(outs, level + 1, jdetails);
callstackToJSON(outs, level + 1, jdetails, cDetails);
outs << "\n";
OUT_PADDED(outs, level, "}");
}
if (has_native_wow64_context && nativeWow64Details.rsp) {
outs << ",\n";
OUT_PADDED(outs, level, "\"native_callstack\" : {\n");
callstackToJSON(outs, level + 1, jdetails, nativeWow64Details);
outs << "\n";
OUT_PADDED(outs, level, "}");
}
@@ -374,7 +382,15 @@ namespace pesieve {
std::string lastSyscall;
std::string lastFunction;
// Primary context used by the existing analysis logic. For a WOW64 target
// scanned by a 64-bit build, this remains the 32-bit guest context.
ctx_details cDetails;
// Additional native AMD64 context exposed for WOW64 targets. It is report-only
// in this narrow refactor; indicator semantics remain unchanged.
bool has_native_wow64_context;
ctx_details nativeWow64Details;
std::map<ULONGLONG, std::string> addrToSymbol;
std::set<ULONGLONG> shcCandidates;
std::set<ThSusIndicator> indicators;
@@ -406,7 +422,11 @@ namespace pesieve {
std::string resolveLowLevelFuncName(IN const ULONGLONG addr, OUT OPTIONAL size_t* disp = nullptr);
std::string resolveAddrToString(IN ULONGLONG addr);
bool printResolvedAddr(const ULONGLONG addr);
size_t fillCallStackInfo(IN HANDLE hProcess, IN HANDLE hThread, const IN LPVOID ctx, IN OUT ThreadScanReport& my_report);
size_t fillCallStackInfo(IN HANDLE hThread, const IN LPVOID ctx, IN OUT ctx_details& cDetails);
bool fetchNativeThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details& cDetails);
#ifdef _WIN64
bool fetchWow64ThreadCtxDetails(IN HANDLE hProcess, IN HANDLE hThread, IN OUT ctx_details& cDetails);
#endif
size_t analyzeCallStackInfo(IN OUT ThreadScanReport& my_report);
size_t _analyzeCallStack(IN OUT ctx_details& cDetails, OUT IN std::set<ULONGLONG>& shcCandidates);
+7 -7
View File
@@ -78,20 +78,20 @@ public:
return true;
}
static bool RunStackWalk(
static bool RunStackWalk64(
_In_ DWORD MachineType,
_In_ HANDLE hProcess,
_In_ HANDLE hThread,
_Inout_ LPSTACKFRAME StackFrame,
_Inout_ LPSTACKFRAME64 StackFrame,
_Inout_ PVOID ContextRecord,
_In_opt_ PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
_In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
_In_opt_ PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
_In_opt_ PTRANSLATE_ADDRESS_ROUTINE TranslateAddress
_In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
_In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
_In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
_In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
std::lock_guard<std::mutex> guard(m_Mutex);
if (StackWalk(MachineType, hProcess, hThread, StackFrame, ContextRecord, ReadMemoryRoutine, FunctionTableAccessRoutine, GetModuleBaseRoutine, TranslateAddress)) {
if (StackWalk64(MachineType, hProcess, hThread, StackFrame, ContextRecord, ReadMemoryRoutine, FunctionTableAccessRoutine, GetModuleBaseRoutine, TranslateAddress)) {
return true;
}
return false;
+7 -7
View File
@@ -204,21 +204,21 @@ public:
return DbgHelpWrapper::RefreshModuleList(hProcess);
}
bool RunStackWalk(
bool RunStackWalk64(
_In_ DWORD MachineType,
_In_ HANDLE hThread,
_Inout_ LPSTACKFRAME StackFrame,
_Inout_ LPSTACKFRAME64 StackFrame,
_Inout_ PVOID ContextRecord,
_In_opt_ PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
_In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
_In_opt_ PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
_In_opt_ PTRANSLATE_ADDRESS_ROUTINE TranslateAddress
_In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
_In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
_In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
_In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
)
{
if (!isInit) {
return false;
}
return DbgHelpWrapper::RunStackWalk(MachineType,
return DbgHelpWrapper::RunStackWalk64(MachineType,
this->hProcess,
hThread,
StackFrame,