Files
MemProcFS/vmm/vmmwinthread.c
2026-02-24 19:39:58 +01:00

999 lines
52 KiB
C
Executable File

// vmmwinthread.c : implementations related to windows threading.
//
// (c) Ulf Frisk, 2024-2026
// Author: Ulf Frisk, pcileech@frizk.net
//
#include "vmmwinthread.h"
#include "vmm.h"
#include "vmmwin.h"
#include "charutil.h"
#include "pdb.h"
#include "pe.h"
// ----------------------------------------------------------------------------
// THREADING FUNCTIONALITY BELOW:
//
// The threading subsystem is dependent on loaded kernel pdb symbols and being
// initialized asynchronously at startup. i.e. it may not be immediately avail-
// able at startup time or not available at all. Loading threads may be slow
// the first time if many threads exist in a process since a list have to be
// traversed - hence functionality exists to start a load asynchronously.
// ----------------------------------------------------------------------------
typedef struct tdVMMWIN_INITIALIZETHREAD_CONTEXT {
POB_MAP pmThread;
POB_SET psObTeb;
POB_SET psObTrapFrame;
PVMM_PROCESS pProcess;
} VMMWIN_INITIALIZETHREAD_CONTEXT, *PVMMWIN_INITIALIZETHREAD_CONTEXT;
int VmmWinThread_Initialize_CmpThreadEntry(PVMM_MAP_THREADENTRY v1, PVMM_MAP_THREADENTRY v2)
{
return
(v1->dwTID < v2->dwTID) ? -1 :
(v1->dwTID > v2->dwTID) ? 1 : 0;
}
VOID VmmWinThread_Initialize_DoWork_Pre(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pSystemProcess, _In_opt_ PVMMWIN_INITIALIZETHREAD_CONTEXT ctx, _In_ QWORD va, _In_ PBYTE pb, _In_ DWORD cb, _In_ QWORD vaFLink, _In_ QWORD vaBLink, _In_ POB_SET pVSetAddress, _Inout_ PBOOL pfValidEntry, _Inout_ PBOOL pfValidFLink, _Inout_ PBOOL pfValidBLink)
{
BOOL f, f32 = H->vmm.f32;
DWORD dwTID;
PVMM_MAP_THREADENTRY e;
PVMM_OFFSET_ETHREAD ot = &H->vmm.offset.ETHREAD;
// 1: sanity check
f = ctx &&
(f32 ? VMM_KADDR32_4(vaFLink) : VMM_KADDR64_8(vaFLink)) &&
(f32 ? VMM_KADDR32_4(vaBLink) : VMM_KADDR64_8(vaBLink)) &&
(!ot->oProcessOpt || (VMM_PTR_OFFSET(f32, pb, ot->oProcessOpt) == ctx->pProcess->win.EPROCESS.va)) &&
(dwTID = (DWORD)VMM_PTR_OFFSET(f32, pb, ot->oCid + (f32 ? 4ULL : 8ULL)));
if(!f) { return; }
*pfValidEntry = *pfValidFLink = *pfValidBLink = TRUE;
// 2: allocate and populate thread entry with info.
if(!(e = LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_MAP_THREADENTRY)))) { return; }
e->vaETHREAD = va;
e->dwTID = dwTID;
e->dwPID = (DWORD)VMM_PTR_OFFSET(f32, pb, ot->oCid);
e->dwExitStatus = *(PDWORD)(pb + ot->oExitStatus);
e->bState = *(PUCHAR)(pb + ot->oState);
e->bSuspendCount = *(PUCHAR)(pb + ot->oSuspendCount);
if(ot->oRunningOpt) { e->bRunning = *(PUCHAR)(pb + ot->oRunningOpt); }
e->bPriority = *(PUCHAR)(pb + ot->oPriority);
e->bBasePriority = *(PUCHAR)(pb + ot->oBasePriority);
e->bWaitReason = *(PUCHAR)(pb + ot->oWaitReason);
e->vaTeb = VMM_PTR_OFFSET(f32, pb, ot->oTeb);
e->ftCreateTime = *(PQWORD)(pb + ot->oCreateTime);
e->ftExitTime = *(PQWORD)(pb + ot->oExitTime);
e->vaStartAddress = VMM_PTR_OFFSET(f32, pb, ot->oStartAddress);
e->vaImpersonationToken = ot->oClientSecurityOpt ? VMM_PTR_EX_FAST_REF(f32, VMM_PTR_OFFSET(f32, pb, ot->oClientSecurityOpt)) : 0;
e->vaWin32StartAddress = VMM_PTR_OFFSET(f32, pb, ot->oWin32StartAddress);
e->vaStackBaseKernel = VMM_PTR_OFFSET(f32, pb, ot->oStackBase);
e->vaStackLimitKernel = VMM_PTR_OFFSET(f32, pb, ot->oStackLimit);
e->vaTrapFrame = VMM_PTR_OFFSET(f32, pb, ot->oTrapFrame);
e->qwAffinity = VMM_PTR_OFFSET(f32, pb, ot->oAffinity);
e->dwKernelTime = *(PDWORD)(pb + ot->oKernelTime);
e->dwUserTime = *(PDWORD)(pb + ot->oUserTime);
if(e->ftExitTime > 0x0200000000000000) { e->ftExitTime = 0; }
ObSet_Push(ctx->psObTeb, e->vaTeb);
ObSet_Push(ctx->psObTrapFrame, e->vaTrapFrame);
ObMap_Push(ctx->pmThread, e->dwTID, e); // map will free allocation when cleared
}
VOID VmmWinThread_Initialize_DoWork(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess)
{
BOOL f, f32 = H->vmm.f32;
BYTE pb[0x200];
DWORD i, cMap, cbTrapFrame = 0;
QWORD va, vaThreadListEntry;
POB_SET psObTeb = NULL, psObTrapFrame = NULL;
POB_MAP pmObThreads = NULL;
PVMMOB_MAP_THREAD pObThreadMap = NULL;
PVMM_MAP_THREADENTRY pThreadEntry;
PVMM_PROCESS pObSystemProcess = NULL;
VMMWIN_INITIALIZETHREAD_CONTEXT ctx = { 0 };
PVMM_OFFSET_ETHREAD ot = &H->vmm.offset.ETHREAD;
PVMMOB_MAP_HANDLE pObHandleMap = NULL;
PVMM_MAP_HANDLEENTRY peHandle;
POB_SET psObPrefetch = NULL;
// 0: 1st run optimization: pre-populate prefetch candidates from handle table.
// i.e. speed up linked list _ETHREAD walking.
if(!ObContainer_Exists(pProcess->pObPersistent->pObCMapThreadPrefetch)) {
if(VmmMap_GetHandle(H, pProcess, &pObHandleMap, VMM_HANDLE_FLAG_CORE)) {
psObPrefetch = ObSet_New(H);
for(i = 0; i < pObHandleMap->cMap; i++) {
peHandle = pObHandleMap->pMap + i;
// threads (and processes) usually have access 0x1fffff -> use this as a fast heuristic.
if(peHandle->dwGrantedAccess == 0x1fffff) {
ObSet_Push(psObPrefetch, peHandle->vaObject);
}
}
peHandle = NULL;
ObContainer_SetOb(pProcess->pObPersistent->pObCMapThreadPrefetch, psObPrefetch);
Ob_DECREF_NULL(&psObPrefetch);
Ob_DECREF_NULL(&pObHandleMap);
}
}
// 1: set up and perform list traversal call.
vaThreadListEntry = VMM_PTR_OFFSET(f32, pProcess->win.EPROCESS.pb, H->vmm.offset.ETHREAD.oThreadListHeadKP);
if(f32 ? !VMM_KADDR32_4(vaThreadListEntry) : !VMM_KADDR64_8(vaThreadListEntry)) { goto fail; }
if(!(pObSystemProcess = VmmProcessGet(H, 4))) { goto fail; }
if(!(psObTeb = ObSet_New(H))) { goto fail; }
if(!(psObTrapFrame = ObSet_New(H))) { goto fail; }
if(!(pmObThreads = ObMap_New(H, OB_MAP_FLAGS_OBJECT_LOCALFREE))) { goto fail; }
ctx.pmThread = pmObThreads;
ctx.psObTeb = psObTeb;
ctx.psObTrapFrame = psObTrapFrame;
ctx.pProcess = pProcess;
va = vaThreadListEntry - H->vmm.offset.ETHREAD.oThreadListEntry;
VmmWin_ListTraversePrefetch(
H,
pObSystemProcess,
f32,
&ctx,
1,
&va,
H->vmm.offset.ETHREAD.oThreadListEntry,
H->vmm.offset.ETHREAD.oMax,
(VMMWIN_LISTTRAVERSE_PRE_CB)VmmWinThread_Initialize_DoWork_Pre,
NULL,
pProcess->pObPersistent->pObCMapThreadPrefetch);
// 2: transfer result from generic map into PVMMOB_MAP_THREAD
if(!(cMap = ObMap_Size(pmObThreads))) { goto fail; }
if(!(pObThreadMap = Ob_AllocEx(H, OB_TAG_MAP_THREAD, 0, sizeof(VMMOB_MAP_THREAD) + cMap * sizeof(VMM_MAP_THREADENTRY), NULL, NULL))) { goto fail; }
pObThreadMap->cMap = cMap;
cbTrapFrame = ((ot->oTrapRsp < 0x200 - 8) && (ot->oTrapRip < 0x200 - 8)) ? 8 + max(ot->oTrapRsp, ot->oTrapRip) : 0;
VmmCachePrefetchPages3(H, pObSystemProcess, psObTrapFrame, cbTrapFrame, 0);
VmmCachePrefetchPages3(H, pProcess, psObTeb, 0x20, 0);
for(i = 0; i < cMap; i++) {
pThreadEntry = (PVMM_MAP_THREADENTRY)ObMap_GetByIndex(pmObThreads, i);
// fetch Teb
if(VmmRead2(H, pProcess, pThreadEntry->vaTeb, pb, 0x20, VMM_FLAG_FORCECACHE_READ)) {
pThreadEntry->vaStackBaseUser = VMM_PTR_OFFSET_DUAL(f32, pb, 4, 8);
pThreadEntry->vaStackLimitUser = VMM_PTR_OFFSET_DUAL(f32, pb, 8, 16);
}
// fetch TrapFrame (RSP/RIP)
if(cbTrapFrame && VmmRead2(H, pObSystemProcess, pThreadEntry->vaTrapFrame, pb, cbTrapFrame, VMM_FLAG_FORCECACHE_READ)) {
pThreadEntry->vaRIP = VMM_PTR_OFFSET(f32, pb, ot->oTrapRip);
pThreadEntry->vaRSP = VMM_PTR_OFFSET(f32, pb, ot->oTrapRsp);
f = ((pThreadEntry->vaStackBaseUser > pThreadEntry->vaRSP) && (pThreadEntry->vaStackLimitUser < pThreadEntry->vaRSP)) ||
((pThreadEntry->vaStackBaseKernel > pThreadEntry->vaRSP) && (pThreadEntry->vaStackLimitKernel < pThreadEntry->vaRSP));
if(!f) {
pThreadEntry->vaRIP = 0;
pThreadEntry->vaRSP = 0;
}
}
// commit
memcpy(pObThreadMap->pMap + i, pThreadEntry, sizeof(VMM_MAP_THREADENTRY));
}
// 3: sort on thread id (TID) and assign result to process object.
qsort(pObThreadMap->pMap, cMap, sizeof(VMM_MAP_THREADENTRY), (int(*)(const void *, const void *))VmmWinThread_Initialize_CmpThreadEntry);
pProcess->Map.pObThread = pObThreadMap; // pProcess take reference responsibility
fail:
Ob_DECREF(psObTeb);
Ob_DECREF(psObTrapFrame);
Ob_DECREF(pmObThreads);
Ob_DECREF(pObSystemProcess);
}
/*
* Initialize the thread map for a specific process.
* NB! The threading sub-system is dependent on pdb symbols and may take a small
* amount of time before it's available after system startup.
* -- H
* -- pProcess
* -- return
*/
_Success_(return)
BOOL VmmWinThread_Initialize(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess)
{
if(pProcess->Map.pObThread) { return TRUE; }
if(!H->vmm.fThreadMapEnabled) { return FALSE; }
VmmTlbSpider(H, pProcess);
EnterCriticalSection(&pProcess->LockUpdate);
if(!pProcess->Map.pObThread) {
VmmWinThread_Initialize_DoWork(H, pProcess);
if(!pProcess->Map.pObThread) {
pProcess->Map.pObThread = Ob_AllocEx(H, OB_TAG_MAP_THREAD, LMEM_ZEROINIT, sizeof(VMMOB_MAP_THREAD), NULL, NULL);
}
}
LeaveCriticalSection(&pProcess->LockUpdate);
return pProcess->Map.pObThread ? TRUE : FALSE;
}
// ----------------------------------------------------------------------------
// CallStack unwinding features for threads in memory dumps
//
// Contributed under BSD 0-Clause License (0BSD)
// Author: MattCore71
// ----------------------------------------------------------------------------
#define UWOP_PUSH_NONVOL 0x0
#define UWOP_ALLOC_LARGE 0x01
#define UWOP_ALLOC_SMALL 0x02
#define UWOP_SET_FPREG 0x03
#define UWOP_SAVE_NONVOL 0x04
#define UWOP_SAVE_NONVOL_FAR 0x05
#define UWOP_SAVE_XMM128 0x08
#define UWOP_SAVE_XMM128_FAR 0x09
#define UWOP_PUSH_MACHFRAME 0x0a
#define UNW_FLAG_NHANDLER 0x0
#define UNW_FLAG_EHANDLER 0x1
#define UNW_FLAG_UHANDLER 0x2
#define UNW_FLAG_CHAININFO 0x4
#define VMMWINTHREADCS_MAX_DEPTH 0x80
typedef struct tdVMMWINTHREAD_SYMBOL {
CHAR szModule[MAX_PATH];
CHAR szFunction[MAX_PATH];
BOOL fSymbolLookupFailed;
DWORD displacement;
QWORD retaddress;
} VMMWINTHREAD_SYMBOL, *PVMMWINTHREAD_SYMBOL;
typedef struct tdVMMWINTHREAD_MODULE_SECTION {
CHAR uszModuleName[MAX_PATH]; // TODO: remove
QWORD vaModuleBase;
DWORD size_vad;
struct {
CHAR szSectionName[IMAGE_SIZEOF_SHORT_NAME];
DWORD dwZERO;
DWORD Address;
DWORD Size;
} text;
struct {
DWORD Address;
DWORD Size;
} pdata;
} VMMWINTHREAD_MODULE_SECTION, *PVMMWINTHREAD_MODULE_SECTION;
typedef struct tdVMMWINTHREAD_FRAME {
BOOL fRegPresent;
QWORD vaRetAddr;
QWORD vaRSP;
QWORD vaBaseSP;
} VMMWINTHREAD_FRAME, *PVMMWINTHREAD_FRAME;
typedef struct tdVMMWINTHREAD_RSP_UNWINDER {
QWORD unwind_address;
QWORD RSP_in;
QWORD RSP_out;
BOOL chained;
DWORD nb_slot_chained;
} VMMWINTHREAD_RSP_UNWINDER, *PVMMWINTHREAD_RSP_UNWINDER;
typedef struct _FRAME_OFFSET_SH {
USHORT FrameOffset;
} FRAME_OFFSET_SH, *PFRAME_OFFSET_SH;
typedef struct _FRAME_OFFSET_L {
DWORD FrameOffset;
} FRAME_OFFSET_L, *PFRAME_OFFSET_L;
typedef struct _UNWIND_INFO {
BYTE Version : 3;
BYTE Flags : 5;
BYTE SizeOfProlog;
BYTE CountOfCodes;
BYTE FrameRegister_offset;
} UNWIND_INFO, *PUNWIND_INFO;
typedef union _UNWIND_CODE {
struct {
BYTE CodeOffset;
BYTE UnwindOp : 4;
BYTE OpInfo : 4;
};
} UNWIND_CODE, *PUNWIND_CODE;
// RUNTIME_FUNCTION_X64
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY_X64 {
DWORD BeginAddress;
DWORD EndAddress;
union {
DWORD UnwindInfoAddress;
DWORD UnwindData;
};
} _IMAGE_RUNTIME_FUNCTION_ENTRY_X64, *_PIMAGE_RUNTIME_FUNCTION_ENTRY_X64;
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY_X64 RUNTIME_FUNCTION_X64, *PRUNTIME_FUNCTION_X64;
// Forward declarations:
DWORD VmmWinThreadCs_GetModuleSectionFromAddress(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ QWORD va, _Out_ PVMMWINTHREAD_MODULE_SECTION pModuleSection);
_Success_(return) BOOL VmmWinThreadCs_GetSymbolFromAddr(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ QWORD qwReturnAddress, _Out_ PVMMWINTHREAD_SYMBOL pSymbol);
_Success_(return) BOOL VmmWinThreadCs_HeuristicScanForFrame(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _In_ PVMMWINTHREAD_FRAME pCurrentFrame, _Out_ PVMMWINTHREAD_FRAME pReturnScanFrame);
_Success_(return) BOOL VmmWinThreadCs_PopReturnAddress(VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ QWORD va, _Out_ PQWORD pqwBufferCandidate, _Out_opt_ PVMMWINTHREAD_MODULE_SECTION pModuleSectionOpt);
_Success_(return) BOOL VmmWinThreadCs_RspUnwinder(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _Inout_ PVMMWINTHREAD_RSP_UNWINDER pInRSPOut);
_Success_(return) BOOL VmmWinThreadCs_ValidateCandidate(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _In_ QWORD vaCandidate, _In_ PVMMWINTHREAD_FRAME pCurrentFrame, _Out_ PVMMWINTHREAD_FRAME pValidationTempFrame);
_Success_(return) BOOL VmmWinThreadCs_ValidateThreadBeforeUnwind(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread);
_Success_(return)
BOOL VmmWinThreadCs_UnwindFrame(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _In_ PVMMWINTHREAD_FRAME pCurrentFrame, _Out_ PVMMWINTHREAD_FRAME pFrameOut)
{
BOOL fResult = FALSE;
QWORD qwCurrentAddress;
PBYTE pbPdata = NULL;
VMMWINTHREAD_MODULE_SECTION sModuleSectionInfo;
QWORD qwCurrentRSP = 0, vaModuleBase = 0, qwRvaAddress = 0, qwRetAddress = 0;
DWORD dwResultFromAddress, dwPdataSize, cbPdataRead;
DWORD iRuntime, cRuntimeFunctions;
PRUNTIME_FUNCTION_X64 pRuntimeIter;
BYTE pbUnwindInfoRead[4];
UNWIND_INFO* pUnwindInfo = NULL;
VMMWINTHREAD_RSP_UNWINDER sInRSPOut, sInRSPOutChained;
DWORD dwUnwindAddress = 0;
// initial sanity checks:
// unwinding from metadata is unavailable if not PE or if function fails, exiting
if(!pCurrentFrame) { goto end; }
qwCurrentAddress = pCurrentFrame->vaRetAddr;
dwResultFromAddress = VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, qwCurrentAddress, &sModuleSectionInfo);
if(!dwResultFromAddress) { goto end; }
// reading all the pdata section containing the RUNTIME_FUNCTION_X64
vaModuleBase = sModuleSectionInfo.vaModuleBase;
dwPdataSize = sModuleSectionInfo.pdata.Size;
pbPdata = LocalAlloc(0, dwPdataSize);
if(!pbPdata) { goto end; }
VmmReadEx(H, pProcess, vaModuleBase + sModuleSectionInfo.pdata.Address, pbPdata, dwPdataSize, &cbPdataRead, VMM_FLAG_ZEROPAD_ON_FAIL);
if(!cbPdataRead) { goto end; }
pRuntimeIter = (PRUNTIME_FUNCTION_X64)pbPdata;
qwRvaAddress = qwCurrentAddress - vaModuleBase ;
cRuntimeFunctions = dwPdataSize / sizeof(RUNTIME_FUNCTION_X64);
// finding where the previous Return address is located among runtime functions and getting the unwindInfo structure address
int dwMaxCountRuntime = 0;
for(iRuntime = 0; iRuntime < cRuntimeFunctions; iRuntime++) {
if((qwRvaAddress >= pRuntimeIter[iRuntime].BeginAddress) && (qwRvaAddress < pRuntimeIter[iRuntime].EndAddress) && pRuntimeIter[iRuntime].BeginAddress) {
dwUnwindAddress = pRuntimeIter[iRuntime].UnwindInfoAddress;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND RUNTIME STRUCT: BEGIN:[%08x] END:[%08x] UNWIND_INFO:[%08x] UNWIND_INFO_ADDR:[%016llx] PID:[%u] TID:[%u]", pRuntimeIter[iRuntime].BeginAddress, pRuntimeIter[iRuntime].EndAddress, (DWORD)dwUnwindAddress, vaModuleBase + dwUnwindAddress, pProcess->dwPID, pThread->dwTID);
// issue if finding multiple runtime functions corresponding, exiting..
if (dwMaxCountRuntime > 1) { goto end; }
dwMaxCountRuntime++;
}
}
if(dwUnwindAddress == 0) { goto end; }
// Reading UNWIND INFO structure
pUnwindInfo = (PUNWIND_INFO)pbUnwindInfoRead;
if(!VmmRead(H, pProcess, vaModuleBase + dwUnwindAddress, pbUnwindInfoRead, 4)) { goto end; }
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND INFO: VERSION:[%02x] FLAGS:[%02x] PID:[%u] TID:[%u]", pUnwindInfo->Version, pUnwindInfo->Flags, pProcess->dwPID, pThread->dwTID);
// retreiving the number of slot for the current UNWIND_INFO
DWORD dwNbslot = pUnwindInfo->CountOfCodes;
// finding out if UNWIND INFO is not conventional, exiting if not.
if((pUnwindInfo->Version != 0x01 && pUnwindInfo->Version != 0x02) || (pUnwindInfo->Flags > 0x04)) {
goto end;
}
// If the CountOfCodes is NULL and the vaBaseSP is null we are at the beginning of the unwind process, therefore the returnAddress is on top of the stack, no need to restore anything
if((pUnwindInfo->CountOfCodes == 0x00) && (pCurrentFrame->vaBaseSP == 0) && (pUnwindInfo->Flags != 0x04)) {
// We can get the return address of top by passing pThread->vaRSP
if(!VmmWinThreadCs_PopReturnAddress(H, pProcess, pThread->vaRSP, &qwRetAddress, NULL)) {
pFrameOut->vaRSP = pThread->vaRSP;
pFrameOut->vaRetAddr = 0;
goto end;
}
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND SUCCESS #1: RETADDR:[%016llx] PID:[%u] TID:[%u]", qwRetAddress, pProcess->dwPID, pThread->dwTID);
pFrameOut->vaRetAddr = qwRetAddress;
pFrameOut->vaRSP = pThread->vaRSP;
pFrameOut->vaBaseSP = pThread->vaRSP + 8;
fResult = TRUE; goto end;
}
// if we find a leaf function without being at the beginning
if((pUnwindInfo->CountOfCodes == 0x00) && (pCurrentFrame->vaBaseSP != 0) && (pUnwindInfo->Flags != 0x04)) {
qwCurrentRSP = pCurrentFrame->vaBaseSP;
//printf("No Prolog is present, it is a leaf function, the return address is on top of stack but we are not at the beginning\n");
if (!VmmWinThreadCs_PopReturnAddress(H, pProcess, qwCurrentRSP, &qwRetAddress, NULL)) { goto end; }
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND SUCCESS #2: RETADDR:[%016llx] PID:[%u] TID:[%u]", qwRetAddress, pProcess->dwPID, pThread->dwTID);
pFrameOut->vaRetAddr = qwRetAddress;
pFrameOut->vaBaseSP = qwCurrentRSP + 8;
pFrameOut->vaRSP = qwCurrentRSP;
fResult = TRUE; goto end;
}
// we need to unwind each code to restore RSP and pop the return address
{
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND Normal function, Continuing... PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
sInRSPOut.unwind_address = vaModuleBase + dwUnwindAddress;
pFrameOut->vaRSP = qwCurrentRSP;
sInRSPOut.RSP_in = pCurrentFrame->vaBaseSP;
if(!VmmWinThreadCs_RspUnwinder(H, pProcess, pThread, &sInRSPOut)) { goto end; }
qwCurrentRSP = sInRSPOut.RSP_out;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND New RSP:[%016llx] PID:[%u] TID:[%u]", qwCurrentRSP, pProcess->dwPID, pThread->dwTID);
if(pUnwindInfo->Flags == 0x04) {
QWORD qwRuntimeChainAddr;
RUNTIME_FUNCTION_X64* pRuntimeChained;
BYTE pbReadRtime[sizeof(RUNTIME_FUNCTION_X64)];
QWORD qwUwdChainedAddr;
chain:
pRuntimeChained = NULL;
// RUNTIME struct for chained is after previous unwind structure + 4 + 2bytes for each UNWIND_CODES
// we read RUNTIME_FUNCTION_X64 for chained steps
qwRuntimeChainAddr = vaModuleBase + dwUnwindAddress + 4 + (2 * dwNbslot);
if(!VmmRead(H, pProcess, qwRuntimeChainAddr, pbReadRtime, sizeof(RUNTIME_FUNCTION_X64))) { goto end; }
pRuntimeChained = (RUNTIME_FUNCTION_X64 *)pbReadRtime;
qwUwdChainedAddr = pRuntimeChained[0].UnwindInfoAddress;
if(qwUwdChainedAddr == 0) { goto end; }
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND CHAINED CHAINED_RUNTIME:[%016llx] UNWIND_INFO_ADDR:[%016llx] PID:[%u] TID:[%u]", qwRuntimeChainAddr, vaModuleBase + qwUwdChainedAddr, pProcess->dwPID, pThread->dwTID);
// preparing second structure pInRSPOutChained for new call to VmmWinThreadCs_RspUnwinder in order to resolve chain.
ZeroMemory(&sInRSPOutChained, sizeof(VMMWINTHREAD_RSP_UNWINDER));
sInRSPOutChained.unwind_address = vaModuleBase+qwUwdChainedAddr;
sInRSPOutChained.RSP_in = qwCurrentRSP;
if(!VmmWinThreadCs_RspUnwinder(H, pProcess, pThread, &sInRSPOutChained)) { goto end; }
// updating current RSP
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND New RSP:[%016llx] PID:[%u] TID:[%u]", sInRSPOutChained.RSP_out, pProcess->dwPID, pThread->dwTID);
qwCurrentRSP = sInRSPOutChained.RSP_out;
// if chained function was also chained, redoing chained step (goto chain)
if(sInRSPOutChained.chained) {
// updating current UnwindAddress before jumping
dwUnwindAddress = (DWORD)qwUwdChainedAddr;
dwNbslot = sInRSPOutChained.nb_slot_chained;
goto chain;
}
}
if(!VmmWinThreadCs_PopReturnAddress(H, pProcess, qwCurrentRSP, &qwRetAddress, NULL)) { goto end; }
qwCurrentRSP = qwCurrentRSP + 8;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNWIND FINAL_RSP:[%016llx] RETURN_ADDR:[%016llx] PID:[%u] TID:[%u]", qwCurrentRSP, qwRetAddress, pProcess->dwPID, pThread->dwTID);
//preparing return argument structure
pFrameOut->vaRetAddr = qwRetAddress;
pFrameOut->vaBaseSP = qwCurrentRSP;
pFrameOut->vaRSP = pCurrentFrame->vaBaseSP;
fResult = TRUE; goto end;
}
end:
LocalFree(pbPdata);
return fResult;
}
_Success_(return)
BOOL VmmWinThreadCs_RspUnwinder(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _Inout_ PVMMWINTHREAD_RSP_UNWINDER pInRSPOut)
{
BOOL fResult = FALSE;
BYTE pUnwindInfoRead[4];
PUNWIND_INFO pUnwindInfo = (PUNWIND_INFO)pUnwindInfoRead;
DWORD dwUnwdIter, dwNbslot;
QWORD qwCurrentRSP, pReadUnwind = pInRSPOut->unwind_address + 4;
USHORT FrameOffset;
PUNWIND_CODE pUnwindCodes = NULL;
PFRAME_OFFSET_L offset_l;
PFRAME_OFFSET_SH offset;
if(!pInRSPOut) { goto end; }
qwCurrentRSP = pInRSPOut->RSP_in;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: Reading UNWIND_INFO for unwind address passed in structure at %016llx PID:[%u] TID:[%u]", pInRSPOut->unwind_address, pProcess->dwPID, pThread->dwTID);
if(!VmmRead(H, pProcess, pInRSPOut->unwind_address, pUnwindInfoRead, 4)) { goto end; }
// reading UNWIND CODES for dwNbslot
dwNbslot = pUnwindInfo->CountOfCodes;
pUnwindCodes = LocalAlloc(LMEM_ZEROINIT, dwNbslot * sizeof(UNWIND_CODE));
if(!pUnwindCodes) { goto end; }
// detecting multiple chained function
if(pUnwindInfo->Flags == 0x04) {
pInRSPOut->chained = TRUE;
pInRSPOut->nb_slot_chained = dwNbslot;
}
if(pUnwindInfo->CountOfCodes == 0x00) {
pInRSPOut->RSP_out = pInRSPOut->RSP_in;
goto end;
}
if(!VmmRead(H, pProcess, pReadUnwind, (PBYTE)pUnwindCodes, 2 * dwNbslot)) { goto end; }
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: Enumerating each UNWIND CODES - RSP at begenning is %016llx PID:[%u] TID:[%u]", pInRSPOut->RSP_in, pProcess->dwPID, pThread->dwTID);
// for each slot, testing type of OpInfo
for(dwUnwdIter = 0; dwUnwdIter < dwNbslot; dwUnwdIter++) {
switch (pUnwindCodes[dwUnwdIter].UnwindOp) {
//we pop a registry, the stack need to grow down
case UWOP_PUSH_NONVOL:
qwCurrentRSP = qwCurrentRSP + 8;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: New RSP: %016llx CODE:[%02x] PID:[%u] TID:[%u]", qwCurrentRSP, UWOP_PUSH_NONVOL, pProcess->dwPID, pThread->dwTID);
break;
//Restoring former stack allocation, (the number of bytes is given by new slot and carried by FrameOffset)
case UWOP_ALLOC_LARGE:
if(pUnwindCodes[dwUnwdIter].OpInfo == 0x00) {
offset = (PFRAME_OFFSET_SH)&pUnwindCodes[dwUnwdIter + 1];
FrameOffset = (offset->FrameOffset)*8;
qwCurrentRSP = qwCurrentRSP + FrameOffset;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: New RSP: %016llx FRAME_OFFSET:[%02x] CODE:[%02x] PID:[%u] TID:[%u]", qwCurrentRSP, FrameOffset, UWOP_ALLOC_LARGE, pProcess->dwPID, pThread->dwTID);
dwUnwdIter++;
break;
}
else if(pUnwindCodes[dwUnwdIter].OpInfo == 0x01) {
offset_l = (PFRAME_OFFSET_L)&pUnwindCodes[dwUnwdIter + 1];
FrameOffset = (USHORT)offset_l->FrameOffset;
printf("Frame offset is %02x\n", FrameOffset);
qwCurrentRSP = qwCurrentRSP + FrameOffset;
printf("NEW RSP : %016llx\n", qwCurrentRSP);
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: New RSP: %016llx FRAME_OFFSET:[%02x] CODE:[%02x] PID:[%u] TID:[%u]", qwCurrentRSP, FrameOffset, UWOP_ALLOC_LARGE, pProcess->dwPID, pThread->dwTID);
dwUnwdIter=dwUnwdIter + 2;
break;
}
break;
case UWOP_ALLOC_SMALL:
FrameOffset = (pUnwindCodes[dwUnwdIter].OpInfo)*8 +8;
qwCurrentRSP = qwCurrentRSP + FrameOffset;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: New RSP: %016llx FRAME_OFFSET:[%02x] CODE:[%02x] PID:[%u] TID:[%u]", qwCurrentRSP, FrameOffset, UWOP_ALLOC_SMALL, pProcess->dwPID, pThread->dwTID);
break;
// vaRSP is left untouched
case UWOP_SET_FPREG:
break;
// the save is only made on stack space already allocated, RSP is left untouched but next slot is used for this register so we go over it
case UWOP_SAVE_NONVOL:
dwUnwdIter++;
break;
// the save is only made on stack already allocated, RSP is left untouched
case UWOP_SAVE_NONVOL_FAR:
break;
// the save is only made on stack already allocated, RSP is left untouched
case UWOP_SAVE_XMM128:
break;
// the save is only made on stack already allocated, RSP is left untouched
case UWOP_SAVE_XMM128_FAR:
break;
case UWOP_PUSH_MACHFRAME:
if(pUnwindCodes[dwUnwdIter].OpInfo == 0x00) {
qwCurrentRSP = qwCurrentRSP + 40;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: New RSP: %016llx CODE:[%02x] PID:[%u] TID:[%u]", qwCurrentRSP, UWOP_PUSH_MACHFRAME, pProcess->dwPID, pThread->dwTID);
break;
}
else if(pUnwindCodes[dwUnwdIter].OpInfo == 0x01) {
qwCurrentRSP = qwCurrentRSP + 48;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: New RSP: %016llx CODE:[%02x] PID:[%u] TID:[%u]", qwCurrentRSP, UWOP_PUSH_MACHFRAME, pProcess->dwPID, pThread->dwTID);
break;
}
break;
default:
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RSPUnwinder: Unknown Code CODE:[%02x] PID:[%u] TID:[%u]", pUnwindCodes[dwUnwdIter].UnwindOp, pProcess->dwPID, pThread->dwTID);
break;
}
pInRSPOut->RSP_out = qwCurrentRSP;
}
fResult = TRUE;
end:
LocalFree(pUnwindCodes);
return fResult;
}
VOID VmmWinThreadCs_CleanupCB(PVMMOB_MAP_THREADCALLSTACK pOb)
{
LocalFree(pOb->pbMultiText);
}
#define VMMWINTHREADCS_BUFFER_USERTEXT 0x10000
/*
* Retrieve a new callstack object for the specified thread.
* CALLER DECREF: return
* -- H
* -- pProcess
* -- pThread
* -- return
*/
PVMMOB_MAP_THREADCALLSTACK VmmWinThreadCs_UnwindScanCallstack(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread)
{
VMMWINTHREAD_FRAME sFrameInit, sCurrentFrame = { 0 };
DWORD i, dwIterFrame, cboText = 0;
PVMMWINTHREAD_FRAME peFrame, peSrc, pFullCallStack = NULL;
BOOL fResultDisplay = FALSE;
QWORD qwLimitKernel = 0x00007FFFFFFF0000;
PVMMOB_MAP_THREADCALLSTACK pObCS = NULL;
PVMM_MAP_THREADCALLSTACKENTRY peDst;
POB_STRMAP psmOb = NULL;
VMMWINTHREAD_SYMBOL sCurrentSymbol;
LPSTR uszText = NULL;
if(H->vmm.tpMemoryModel != VMM_MEMORYMODEL_X64) { return NULL; }
pFullCallStack = LocalAlloc(LMEM_ZEROINIT, VMMWINTHREADCS_MAX_DEPTH * sizeof(VMMWINTHREAD_FRAME));
if(!pFullCallStack) { return NULL; }
// checking condition before starting to unwind
if(!VmmWinThreadCs_ValidateThreadBeforeUnwind(H, pProcess, pThread)) { goto end; }
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " START: RIP:[%016llx] PID:[%u] TID:[%u]", pThread->vaRIP, pProcess->dwPID, pThread->dwTID);
sFrameInit.vaRetAddr = pThread->vaRIP;
// setting RSP as 0 as we are not unwinding kernel stack
sFrameInit.vaRSP = 0;
sFrameInit.vaBaseSP = 0;
pFullCallStack[0] = sFrameInit;
for(dwIterFrame = 0; dwIterFrame < VMMWINTHREADCS_MAX_DEPTH - 2; dwIterFrame++) {
peFrame = &pFullCallStack[dwIterFrame];
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " START: %02u: RIP:[%016llx] RSP:[%016llx] ADDR:[%016llx] PID:[%u] TID:[%u]", dwIterFrame, peFrame->vaRetAddr, peFrame->vaBaseSP, pFullCallStack[dwIterFrame].vaRetAddr, pProcess->dwPID, pThread->dwTID);
if(pFullCallStack[dwIterFrame].vaRetAddr > qwLimitKernel) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " END: (kernel address not supported) RIP:[%016llx] ADDR:[%016llx] PID:[%u] TID:[%u]", pThread->vaRIP, pFullCallStack[dwIterFrame].vaRetAddr, pProcess->dwPID, pThread->dwTID);
break;
}
// try to unwind frame:
if(VmmWinThreadCs_UnwindFrame(H, pProcess, pThread, &pFullCallStack[dwIterFrame], &sCurrentFrame)) {
pFullCallStack[dwIterFrame + 1] = sCurrentFrame;
continue;
}
// unwind frame failed, trying heuristic technique:
if(VmmWinThreadCs_HeuristicScanForFrame(H, pProcess, pThread, &pFullCallStack[dwIterFrame], &sCurrentFrame)) {
pFullCallStack[dwIterFrame + 1] = sCurrentFrame;
continue;
}
// both techniques failed, stopping
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " END: (unwind+heuristics fail) RIP:[%016llx] ADDR:[%016llx] PID:[%u] TID:[%u]", pThread->vaRIP, pFullCallStack[dwIterFrame].vaRetAddr, pProcess->dwPID, pThread->dwTID);
break;
}
// setting the last frame before display
if(pFullCallStack[dwIterFrame].vaRetAddr) {
dwIterFrame = dwIterFrame + 1;
pFullCallStack[dwIterFrame].vaRSP = pFullCallStack[dwIterFrame - 1].vaBaseSP;
pFullCallStack[dwIterFrame].vaRetAddr = 0;
}
dwIterFrame++;
// create ob object:
if(!(psmOb = ObStrMap_New(H, OB_STRMAP_FLAGS_CASE_SENSITIVE))) { goto end; }
if(!(pObCS = Ob_AllocEx(H, OB_TAG_THREAD_CALLSTACK, LMEM_ZEROINIT, sizeof(VMMOB_MAP_THREADCALLSTACK) + dwIterFrame * sizeof(VMM_MAP_THREADCALLSTACKENTRY), (OB_CLEANUP_CB)VmmWinThreadCs_CleanupCB, NULL))) { goto end; }
if(!(uszText = LocalAlloc(LMEM_ZEROINIT, VMMWINTHREADCS_BUFFER_USERTEXT))) { goto end; }
if(H->cfg.fFileInfoHeader) {
cboText += (DWORD)_snprintf_s(uszText + cboText, VMMWINTHREADCS_BUFFER_USERTEXT - cboText, _TRUNCATE, "Index RSP RetAddr Module!Function+Displacement\n==================================================================\n");
}
for(i = 0; i < dwIterFrame; i++) {
peSrc = &pFullCallStack[i];
peDst = &pObCS->pMap[i];
peDst->i = i;
peDst->fRegPresent = peSrc->fRegPresent;
peDst->vaRetAddr = peSrc->vaRetAddr;
peDst->vaRSP = peSrc->vaRSP;
peDst->vaBaseSP = peSrc->vaBaseSP;
if(i && pFullCallStack[i - 1].vaRetAddr && VmmWinThreadCs_GetSymbolFromAddr(H, pProcess, pFullCallStack[i - 1].vaRetAddr, &sCurrentSymbol)) {
peDst->cbDisplacement = sCurrentSymbol.displacement;
ObStrMap_PushPtrUU(psmOb, sCurrentSymbol.szFunction, &peDst->uszFunction, NULL);
ObStrMap_PushPtrUU(psmOb, sCurrentSymbol.szModule, &peDst->uszModule, NULL);
cboText += (DWORD)_snprintf_s(uszText + cboText, VMMWINTHREADCS_BUFFER_USERTEXT - cboText, _TRUNCATE, "%02u: %016llx %016llx %s!%s+%x\n", peDst->i, peDst->vaRSP, peDst->vaRetAddr, sCurrentSymbol.szModule, sCurrentSymbol.szFunction, sCurrentSymbol.displacement);
} else {
ObStrMap_PushPtrUU(psmOb, "", &peDst->uszFunction, NULL);
ObStrMap_PushPtrUU(psmOb, "", &peDst->uszModule, NULL);
cboText += (DWORD)_snprintf_s(uszText + cboText, VMMWINTHREADCS_BUFFER_USERTEXT - cboText, _TRUNCATE, "%02u: %016llx %016llx\n", peDst->i, peDst->vaRSP, peDst->vaRetAddr);
}
}
ObStrMap_PushPtrUU(psmOb, uszText, &pObCS->uszText, NULL);
pObCS->cbText = cboText;
pObCS->dwPID = pProcess->dwPID;
pObCS->dwTID = pThread->dwTID;
pObCS->cMap = dwIterFrame;
ObStrMap_FinalizeAllocU_DECREF_NULL(&psmOb, &pObCS->pbMultiText, &pObCS->cbMultiText);
if(VmmLogIsActive(H, MID_THREADCS, LOGLEVEL_5_DEBUG)) {
VmmLog(H, MID_THREADCS, LOGLEVEL_5_DEBUG, "CALLSTACK PRINTOUT PID:[%u] TID:[%u]", pObCS->dwPID, pObCS->dwTID);
for(i = 0; i < pObCS->cMap; i++) {
peDst = &pObCS->pMap[i];
VmmLog(H, MID_THREADCS, LOGLEVEL_5_DEBUG, " [%02u] RSP:[%016llx] RET:[%016llx] SITE:[%s!%s+%x]", peDst->i, peDst->vaRSP, peDst->vaRetAddr, peDst->uszModule, peDst->uszFunction, peDst->cbDisplacement);
}
VmmLog(H, MID_THREADCS, LOGLEVEL_5_DEBUG, "\n%s", uszText);
}
end:
LocalFree(pFullCallStack);
LocalFree(uszText);
return pObCS;
}
_Success_(return)
BOOL VmmWinThreadCs_PopReturnAddress(VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ QWORD va, _Out_ PQWORD pqwBufferCandidate, _Out_opt_ PVMMWINTHREAD_MODULE_SECTION pModuleSectionOpt)
{
VMMWINTHREAD_MODULE_SECTION ModuleSection;
if(!pModuleSectionOpt) { pModuleSectionOpt = &ModuleSection; }
if(!VmmRead(H, pProcess, va, (PBYTE)pqwBufferCandidate, sizeof(QWORD))) { return FALSE; }
if(!VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, *pqwBufferCandidate, pModuleSectionOpt)) { return FALSE; }
return TRUE;
}
#define VMMWINTHREADCS_HEURISTICSCAN_LOOPPROTECT_MAX 50
_Success_(return)
BOOL VmmWinThreadCs_HeuristicScanForFrame(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _In_ PVMMWINTHREAD_FRAME pCurrentFrame, _Out_ PVMMWINTHREAD_FRAME pReturnScanFrame)
{
VMMWINTHREAD_MODULE_SECTION sModuleSection;
VMMWINTHREAD_FRAME sValidationTempFrame, sRegistryTempFrame = { 0 };
QWORD qwAddressCandidate, qwCurrentRSP = pCurrentFrame->vaBaseSP;
DWORD dwCounterReg = 0, dwLoopProtect = 0;
QWORD aqwAddressCandidates[VMMWINTHREADCS_HEURISTICSCAN_LOOPPROTECT_MAX];
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Heuristic scan START. RSP:[%016llx] PID:[%u] TID:[%u]", qwCurrentRSP, pProcess->dwPID, pThread->dwTID);
// Getting previous RSP and ret address as input for ValidateCandidate:
pReturnScanFrame->vaRetAddr = pCurrentFrame->vaRetAddr;
pReturnScanFrame->vaRSP = pCurrentFrame->vaBaseSP;
sRegistryTempFrame.fRegPresent = FALSE;
// Reading 8 bytes by 8 bytes and decreasing RSP at the same time (which increase addresses)
if(!VmmRead2(H, pProcess, qwCurrentRSP, (PBYTE)aqwAddressCandidates, VMMWINTHREADCS_HEURISTICSCAN_LOOPPROTECT_MAX * sizeof(QWORD), VMM_FLAG_ZEROPAD_ON_FAIL)) { return FALSE; }
for(qwCurrentRSP = pCurrentFrame->vaBaseSP; qwCurrentRSP != pThread->vaStackBaseUser && dwLoopProtect < VMMWINTHREADCS_HEURISTICSCAN_LOOPPROTECT_MAX; qwCurrentRSP = qwCurrentRSP + 8) {
qwAddressCandidate = aqwAddressCandidates[dwLoopProtect];
dwLoopProtect++;
if(!VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, qwAddressCandidate, &sModuleSection)) { continue; }
if(VmmWinThreadCs_ValidateCandidate(H, pProcess, pThread, qwAddressCandidate, pReturnScanFrame, &sValidationTempFrame)) {
// Reserving call registry in case we do not find another candidate
if(sValidationTempFrame.fRegPresent == TRUE && dwCounterReg == 0) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Heuristic scan (reserving candidate). RSP:[%016llx] RET:[%016llx] PID:[%u] TID:[%u]", qwCurrentRSP, sRegistryTempFrame.vaRetAddr, pProcess->dwPID, pThread->dwTID);
sRegistryTempFrame.vaBaseSP = qwCurrentRSP + 8;
sRegistryTempFrame.vaRetAddr = sValidationTempFrame.vaRetAddr;
sRegistryTempFrame.fRegPresent = TRUE;
dwCounterReg = dwCounterReg+1;
continue;
}
// Not a call by registry we found a candidate, we can update the structure and return
else{
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Heuristic scan SUCCESS. RSP:[%016llx] (direct candidate) RET:[%016llx] PID:[%u] TID:[%u]", pReturnScanFrame->vaBaseSP, pReturnScanFrame->vaRetAddr, pProcess->dwPID, pThread->dwTID);
pReturnScanFrame->vaBaseSP = qwCurrentRSP + 8;
pReturnScanFrame->vaRetAddr = sValidationTempFrame.vaRetAddr;
return TRUE;
}
}
}
if(sRegistryTempFrame.fRegPresent) {
// Updating return structure pReturnScanFrame with values
pReturnScanFrame->vaBaseSP = sRegistryTempFrame.vaBaseSP;
pReturnScanFrame->vaRetAddr = sRegistryTempFrame.vaRetAddr;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Heuristic scan SUCCESS. RSP:[%016llx] (reserved candidate) RET:[%016llx] PID:[%u] TID:[%u]", pReturnScanFrame->vaBaseSP, pReturnScanFrame->vaRetAddr, pProcess->dwPID, pThread->dwTID);
return TRUE;
}
// We reached the end and did not find any candidate
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Heuristic scan FAIL. PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
return FALSE;
}
//FF D0 : call rax
//FF D3 : call rbx
//FF 15 : call [RIP+x]
//E8 : call xxxxxx
//FF 90 : call[RAX+x]
_Success_(return)
BOOL VmmWinThreadCs_ValidateCandidate(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _In_ QWORD vaCandidate, _In_ PVMMWINTHREAD_FRAME pCurrentFrame, _Out_ PVMMWINTHREAD_FRAME pValidationTempFrame)
{
static BYTE DCALL = 0xe8;
static BYTE ICALL = 0xff;
static BYTE MRIPI = 0x15;
static BYTE MRAXI = 0x90;
static BYTE RAXI = 0xD0;
static BYTE RBXI = 0xD3;
BYTE pbOpcodesIndirectRead[6];
PBYTE pbOpcodesDirectRead = pbOpcodesIndirectRead + 1;
QWORD qwDirectCallAddress, qwIndirectCallAddress, qwIndirectStoredAddress, qwCurrentRIP = pCurrentFrame->vaRetAddr;
VMMWINTHREAD_MODULE_SECTION sModuleSectionRIP, sModuleSectionTargetCall;
DWORD dwOffset;
// 1: read opcode:
pValidationTempFrame->fRegPresent = FALSE;
if(!VmmRead(H, pProcess, vaCandidate - 6, pbOpcodesIndirectRead, 6)) { return FALSE; }
// 2: finding zone for RIP:
if(!VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, qwCurrentRIP, &sModuleSectionRIP)) { return FALSE; }
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RIP %016llx is zone starting at %016llx PID:[%u] TID:[%u]", qwCurrentRIP, sModuleSectionRIP.vaModuleBase, pProcess->dwPID, pThread->dwTID);
// 3: direct call:
if(DCALL == pbOpcodesDirectRead[0]) {
dwOffset = *(PDWORD)(pbOpcodesDirectRead + 1);
qwDirectCallAddress = vaCandidate + dwOffset;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " DIRECT CALL instruction at %016llx OFF:[%08x] TARGET:[%016llx] PID:[%u] TID:[%u]", vaCandidate - 5, dwOffset, qwDirectCallAddress, pProcess->dwPID, pThread->dwTID);
// we retreive the module info for the target of the call and store it
if(!VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, qwDirectCallAddress, &sModuleSectionTargetCall)) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Could not find appropriate module for the direct adress PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
return FALSE;
}
// we check that the target for the direct call is in the same VAD as the previous return address i.e RIP
if((sModuleSectionRIP.vaModuleBase != 0) && (sModuleSectionRIP.vaModuleBase == sModuleSectionTargetCall.vaModuleBase)) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RIP %016llx is in range with %016llx PID:[%u] TID:[%u]", vaCandidate, sModuleSectionTargetCall.vaModuleBase, pProcess->dwPID, pThread->dwTID);
pValidationTempFrame->vaRetAddr = vaCandidate;
return TRUE;
}
return FALSE;
}
// 4: indirect call:
if(ICALL == pbOpcodesIndirectRead[0]) {
// FF 15 : call [RIP+x]
if(MRIPI == pbOpcodesIndirectRead[1]) {
dwOffset = *(PDWORD)(pbOpcodesIndirectRead + 2);
qwIndirectCallAddress = vaCandidate + dwOffset;
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " INDIRECT CALL instruction at %016llx OFF:[%08x] INDIRECT:[%016llx] PID:[%u] TID:[%u]", vaCandidate - 6, dwOffset, qwIndirectCallAddress, pProcess->dwPID, pThread->dwTID);
if(!VmmWinThreadCs_PopReturnAddress(H, pProcess, qwIndirectCallAddress, &qwIndirectStoredAddress, &sModuleSectionTargetCall)) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " Could not read indirect target address PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
return FALSE;
};
if(sModuleSectionRIP.vaModuleBase && (sModuleSectionRIP.vaModuleBase == sModuleSectionTargetCall.vaModuleBase)) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RIP %016llx is in same module as indirect jump %016llx MODULE_BASE:[%016llx] PID:[%u] TID:[%u]", vaCandidate - 6, qwIndirectCallAddress, sModuleSectionRIP.vaModuleBase, pProcess->dwPID, pThread->dwTID);
pValidationTempFrame->vaRetAddr = vaCandidate;
return TRUE;
}
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " RIP %016llx is NOT in same module as indirect jump %016llx PID:[%u] TID:[%u]", vaCandidate - 6, qwIndirectCallAddress, pProcess->dwPID, pThread->dwTID);
return FALSE;
}
// Not able to check jump address but storing it in case we don't have anything else
if(MRAXI == pbOpcodesIndirectRead[1]) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " INDIRECT CALL via memory RAX PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
pValidationTempFrame->fRegPresent = TRUE;
pValidationTempFrame->vaRetAddr = vaCandidate;
return TRUE;
}
if(RAXI == pbOpcodesIndirectRead[1]) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " INDIRECT CALL via RAX PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
pValidationTempFrame->fRegPresent = TRUE;
pValidationTempFrame->vaRetAddr = vaCandidate;
return TRUE;
}
if(RBXI == pbOpcodesIndirectRead[1]) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " INDIRECT CALL via RBX PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
pValidationTempFrame->fRegPresent = TRUE;
pValidationTempFrame->vaRetAddr = vaCandidate;
return TRUE;
}
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " INDIRECT CALL unknown PID:[%u] TID:[%u]", pProcess->dwPID, pThread->dwTID);
return FALSE;
}
// 5: unknown opcode:
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " UNKNOWN OPCODE:[%02X%02X%02X%02X%02X%02X] PID:[%u] TID:[%u]", pbOpcodesIndirectRead[0], pbOpcodesIndirectRead[1], pbOpcodesIndirectRead[2], pbOpcodesIndirectRead[3], pbOpcodesIndirectRead[4], pbOpcodesIndirectRead[5], pProcess->dwPID, pThread->dwTID);
return FALSE;
}
_Success_(return)
BOOL VmmWinThreadCs_ValidateThreadBeforeUnwind(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread)
{
QWORD vaCurrentRIP;
VMMWINTHREAD_MODULE_SECTION ModuleSection;
vaCurrentRIP = pThread->vaRIP;
if((pThread->vaRSP > pThread->vaStackBaseUser) || (pThread->vaRSP < pThread->vaStackLimitUser)) {
VmmLog(H, MID_THREADCS, LOGLEVEL_6_TRACE, " SP for thread is invalid. RSP:[%016llx] PID:[%u] TID:[%u]", pThread->vaRSP, pProcess->dwPID, pThread->dwTID);
return FALSE;
}
return VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, vaCurrentRIP, &ModuleSection);
}
// Issues with some modules PDB loading such as KERNELBASE, win32u and some others.. address are left without symbols for these.
_Success_(return)
BOOL VmmWinThreadCs_GetSymbolFromAddr(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ QWORD qwReturnAddress, _Out_ PVMMWINTHREAD_SYMBOL pSymbol)
{
PDB_HANDLE hPDB;
VMMWINTHREAD_MODULE_SECTION sModule;
if(!qwReturnAddress || !VmmWinThreadCs_GetModuleSectionFromAddress(H, pProcess, qwReturnAddress, &sModule)) { return FALSE; }
// load the PDB for the module:
if(!(hPDB = PDB_GetHandleFromModuleAddress(H, pProcess, sModule.vaModuleBase))) { goto fail; }
if(!PDB_LoadEnsure(H, hPDB) || !PDB_GetModuleInfo(H, hPDB, pSymbol->szModule, NULL, NULL)) { goto fail; }
// lookup the symbol:
if(!PDB_GetSymbolFromOffset(H, hPDB, (DWORD)(qwReturnAddress - sModule.vaModuleBase), pSymbol->szFunction, &pSymbol->displacement)) { goto fail; }
// return the symbol:
pSymbol->retaddress = qwReturnAddress;
return TRUE;
fail:
// PDB symbol lookup failed, but we still have the module & section name:
_snprintf_s(pSymbol->szModule, sizeof(pSymbol->szModule), _TRUNCATE, "[%s]", sModule.uszModuleName);
_snprintf_s(pSymbol->szFunction, sizeof(pSymbol->szFunction), _TRUNCATE, "[%s]", sModule.text.szSectionName);
pSymbol->retaddress = qwReturnAddress;
pSymbol->displacement = (DWORD)(qwReturnAddress - sModule.vaModuleBase);
return TRUE;
}
DWORD VmmWinThreadCs_GetModuleSectionFromAddress(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ QWORD va, _Out_ PVMMWINTHREAD_MODULE_SECTION pModuleSection)
{
DWORD i, dwResult = 0;
QWORD vaBase;
PVMMOB_MAP_VAD pObVadMap = NULL;
PVMM_MAP_VADENTRY peVad;
VMM_MEMORY_SEARCH_CONTEXT_SEARCHENTRY SearchEntry[1] = {0};
VMM_MEMORY_SEARCH_CONTEXT ctxSearch = { 0 };
PVMMOB_MAP_MODULE pObModuleMap = NULL;
PVMM_MAP_MODULEENTRY peModule;
IMAGE_SECTION_HEADER Section = { 0 };
POB_DATA pObDataSearchResult = NULL;
ZeroMemory(pModuleSection, sizeof(VMMWINTHREAD_MODULE_SECTION));
if(!va) { dwResult = 0; goto end; }
// try module map first:
if(VmmMap_GetModule(H, pProcess, VMM_MODULE_FLAG_NORMAL, &pObModuleMap) && (peModule = VmmMap_GetModuleEntryEx2(H, pObModuleMap, va))) {
// ".text":
if(!PE_SectionGetFromAddressOffset(H, pProcess, peModule->vaBase, (DWORD)(va - peModule->vaBase), &Section)) { dwResult = 0; goto end; }
*(PQWORD)pModuleSection->text.szSectionName = *(PQWORD)Section.Name;
pModuleSection->text.Address = Section.VirtualAddress;
pModuleSection->text.Size = Section.Misc.VirtualSize;
// .pdata:
if(!PE_SectionGetFromName(H, pProcess, peModule->vaBase, ".pdata", &Section)) { dwResult = 0; goto end; }
pModuleSection->pdata.Address = Section.VirtualAddress;
pModuleSection->pdata.Size = Section.Misc.VirtualSize;
// module info:
strncpy_s(pModuleSection->uszModuleName, sizeof(pModuleSection->uszModuleName), peModule->uszText, _TRUNCATE);
CharUtil_ReplaceAllA(pModuleSection->uszModuleName, '.', '\0');
pModuleSection->vaModuleBase = peModule->vaBase;
dwResult = 1; goto end;
}
// try vad map secondly:
if(VmmMap_GetVad(H, pProcess, &pObVadMap, VMM_VADMAP_TP_FULL)) {
for(i = 0; i < pObVadMap->cMap; i++) {
peVad = pObVadMap->pMap + i;
if((va >= peVad->vaStart) && (va < peVad->vaEnd)) {
// we prepare structure for searching MZ in VAD
ctxSearch.pSearch = SearchEntry;
ctxSearch.pSearch[0].cb = 4;
memcpy(ctxSearch.pSearch[0].pb, (BYTE[4]) { 0x4d, 0x5a, 0x90, 0x00 }, 4);
memcpy(ctxSearch.pSearch[0].pbSkipMask, (BYTE[4]) { 0x00, 0x00, 0xff, 0x00 }, 4);
ctxSearch.pSearch[0].cbAlign = 0x1000;
ctxSearch.cSearch++;
ctxSearch.vaMin = peVad->vaStart;
ctxSearch.vaMax = peVad->vaEnd;
// MZ header found in VAD
if(VmmSearch(H, pProcess, &ctxSearch, &pObDataSearchResult) && ctxSearch.cResult) {
vaBase = pObDataSearchResult->pqw[0];
// ".text":
if(!PE_SectionGetFromAddressOffset(H, pProcess, vaBase, (DWORD)(va - vaBase), &Section)) { dwResult = 0; goto end; }
*(PQWORD)pModuleSection->text.szSectionName = *(PQWORD)Section.Name;
pModuleSection->text.Address = Section.VirtualAddress;
pModuleSection->text.Size = Section.Misc.VirtualSize;
// .pdata:
if(!PE_SectionGetFromName(H, pProcess, vaBase, ".pdata", &Section)) { dwResult = 0; goto end; }
pModuleSection->pdata.Address = Section.VirtualAddress;
pModuleSection->pdata.Size = Section.Misc.VirtualSize;
// module info:
strncpy_s(pModuleSection->uszModuleName, sizeof(pModuleSection->uszModuleName), CharUtil_PathSplitLast(peVad->uszText), _TRUNCATE);
CharUtil_ReplaceAllA(pModuleSection->uszModuleName, '.', '\0');
pModuleSection->vaModuleBase = peVad->vaStart;
pModuleSection->size_vad = (DWORD)(peVad->vaEnd - peVad->vaStart);
dwResult = 2;
goto end;
}
break;
}
}
}
dwResult = 0;
pModuleSection->vaModuleBase = 0;
pModuleSection->size_vad = 0;
end:
Ob_DECREF(pObDataSearchResult);
Ob_DECREF(pObModuleMap);
Ob_DECREF(pObVadMap);
return dwResult;
}
/*
* Refresh the callstack cache.
* -- H
*/
VOID VmmWinThreadCs_Refresh(_In_ VMM_HANDLE H)
{
ObMap_Clear(H->vmm.pmObThreadCallback);
}
/*
* Retrieve the callstack for the specified thread.
* Callback parsing is only supported for x64 user-mode threads.
* Callback parsing is best-effort and is very resource intense since it may
* download a large amounts of PDB symbol data from the Microsoft symbol server.
* Use with caution!
* CALLER DECREF: *ppObCS
* -- H
* -- pProcess
* -- pThread
* -- flags = VMM_FLAG_NOCACHE (do not use cache) or VMM_FLAG_FORCECACHE_READ (require cache)
* -- ppObCS
* -- return
*/
_Success_(return)
BOOL VmmWinThreadCs_GetCallstack(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVMM_MAP_THREADENTRY pThread, _In_ DWORD flags, _Out_ PVMMOB_MAP_THREADCALLSTACK *ppObCS)
{
PVMMOB_MAP_THREADCALLSTACK pObCS = NULL;
BOOL fNoCache = (flags & VMM_FLAG_NOCACHE) ? TRUE : FALSE;
BOOL fRequireCache = (flags & VMM_FLAG_FORCECACHE_READ) ? TRUE : FALSE;
QWORD qwKey = ((QWORD)pProcess->dwPID << 32) | pThread->dwTID;
if(fNoCache || !(pObCS = ObMap_GetByKey(H->vmm.pmObThreadCallback, qwKey)) || fRequireCache) {
AcquireSRWLockExclusive(&H->vmm.LockSRW.ThreadCallback);
if(fNoCache || !(pObCS = ObMap_GetByKey(H->vmm.pmObThreadCallback, qwKey))) {
pObCS = VmmWinThreadCs_UnwindScanCallstack(H, pProcess, pThread); // fetch the callstack
if(!pObCS && (pObCS = Ob_AllocEx(H, OB_TAG_THREAD_CALLSTACK, LMEM_ZEROINIT, sizeof(VMMOB_MAP_THREADCALLSTACK), NULL, NULL))) {
pObCS->dwPID = pProcess->dwPID;
pObCS->dwTID = pThread->dwTID;
}
if(pObCS) {
ObMap_Push(H->vmm.pmObThreadCallback, qwKey, pObCS);
}
}
ReleaseSRWLockExclusive(&H->vmm.LockSRW.ThreadCallback);
}
*ppObCS = pObCS;
return pObCS ? TRUE : FALSE;
}