Files
MemProcFS/vmm/modules/m_fc_thread.c
2026-01-04 15:00:03 +01:00

219 lines
9.8 KiB
C

// m_fc_thread.c : thread forensic module.
//
// REQUIRE: FORENSIC SUB-SYSTEM INIT.
//
// NB! module generate forensic data only - no file system presence!
//
// (c) Ulf Frisk, 2020-2026
// Author: Ulf Frisk, pcileech@frizk.net
//
#include "modules.h"
static LPSTR MFCPROC_CSV_THREAD = "PID,TID,ETHREAD,State,WaitReason,CreateTime,ExitTime,Running,BasePriority,Priority,ExitStatus,StartAddress,Win32StartAddress,IP,SP,TEB,StackBaseUser,StackLimitUser,StackBaseKernel,StackLimitKernel,TrapFrame,ImpersonationToken\n";
static LPSTR FC_SQL_SCHEMA_THREAD =
"DROP TABLE IF EXISTS thread; " \
"CREATE TABLE thread(id INTEGER PRIMARY KEY AUTOINCREMENT, id_str INTEGER, pid INT, tid INT, ethread INTEGER, teb INTEGER, state INT, exitstatus INT, running INT, prio INT, priobase INT, waitreason INT, startaddr INTEGER, stackbase_u INTEGER, stacklimit_u INTEGER, stackbase_k INTEGER, stacklimit_k INTEGER, trapframe INTEGER, sp INTEGER, ip INTEGER, time_create INTEGER, time_exit INTEGER); " \
"DROP VIEW IF EXISTS v_thread; " \
"CREATE VIEW v_thread AS SELECT t.*, str.* FROM thread t, str WHERE t.id_str = str.id; ";
#define MFCTHREAD_GET_STR_STATE(pe) ((pe->bState < (sizeof(_KTHREAD_STATE_STR) / sizeof(LPCSTR))) ? _KTHREAD_STATE_STR[pe->bState] : "Unknown")
#define MFCTHREAD_GET_STR_WAIT_REASON(pe) ((pe->bWaitReason < (sizeof(_KWAIT_REASON_STR) / sizeof(LPCSTR))) ? _KWAIT_REASON_STR[pe->bWaitReason] : "Unknown")
VOID M_FcThread_FcInitialize_ThreadProc(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _In_ PVOID pv)
{
int rc;
sqlite3 *hSql = NULL;
sqlite3_stmt *hStmt = NULL, *hStmtStr = NULL;
DWORD i;
PVMMOB_MAP_THREAD pObThreadMap = NULL;
PVMM_MAP_THREADENTRY pe;
CHAR szStr[MAX_PATH];
FCSQL_INSERTSTRTABLE SqlStrInsert;
if(!VmmMap_GetThread(H, pProcess, &pObThreadMap)) { goto fail; }
if(!(hSql = Fc_SqlReserve(H))) { goto fail; }
if(SQLITE_OK != sqlite3_prepare_v2(hSql, "INSERT INTO thread (id_str, pid, tid, ethread, teb, state, exitstatus, running, prio, priobase, waitreason, startaddr, stackbase_u, stacklimit_u, stackbase_k, stacklimit_k, trapframe, sp, ip, time_create, time_exit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", -1, &hStmt, NULL)) { goto fail; }
if(SQLITE_OK != sqlite3_prepare_v2(hSql, szFC_SQL_STR_INSERT, -1, &hStmtStr, NULL)) { goto fail; }
sqlite3_exec(hSql, "BEGIN TRANSACTION", NULL, NULL, NULL);
for(i = 0; i < pObThreadMap->cMap; i++) {
pe = pObThreadMap->pMap + i;
snprintf(szStr, _countof(szStr), "TID: %i", pe->dwTID);
if(!Fc_SqlInsertStr(H, hStmtStr, szStr, &SqlStrInsert)) { goto fail_transact; }
sqlite3_reset(hStmt);
rc = Fc_SqlBindMultiInt64(hStmt, 1, 20,
SqlStrInsert.id,
(QWORD)pe->dwPID,
(QWORD)pe->dwTID,
pe->vaETHREAD,
pe->vaTeb,
(QWORD)pe->bState,
(QWORD)pe->dwExitStatus,
(QWORD)pe->bRunning,
(QWORD)pe->bPriority,
(QWORD)pe->bBasePriority,
(QWORD)pe->bWaitReason,
pe->vaStartAddress,
pe->vaStackBaseUser,
pe->vaStackLimitUser,
pe->vaStackBaseKernel,
pe->vaStackLimitKernel,
pe->vaTrapFrame,
pe->vaRSP,
pe->vaRIP,
pe->ftCreateTime,
pe->ftExitTime
);
if(SQLITE_OK != rc) { goto fail_transact; }
sqlite3_step(hStmt);
}
fail_transact:
sqlite3_exec(hSql, "COMMIT TRANSACTION", NULL, NULL, NULL);
fail:
sqlite3_finalize(hStmt);
sqlite3_finalize(hStmtStr);
Fc_SqlReserveReturn(H, hSql);
Ob_DECREF(pObThreadMap);
return;
}
/*
* Forensic initialization function called when the forensic sub-system is initializing.
*/
PVOID M_FcThread_FcInitialize(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
// 1: initialize csv
FcFileAppend(H, "threads.csv", MFCPROC_CSV_THREAD);
// 2: initialize timelining (sql)
if(SQLITE_OK != Fc_SqlExec(H, FC_SQL_SCHEMA_THREAD)) { return NULL; }
VmmWork_ProcessActionForeachParallel_Void(H, 0, NULL, VmmWork_ProcessActionForeachParallel_CriteriaActiveOnly, M_FcThread_FcInitialize_ThreadProc);
return NULL;
}
/*
* Timeline data by executing a partial SQL query on pre-existing data.
* -- H
* -- ctxfc
* -- hTimeline
* -- pfnAddEntry
* -- pfnEntryAddBySql
*/
VOID M_FcThread_FcTimeline(
_In_ VMM_HANDLE H,
_In_opt_ PVOID ctxfc,
_In_ HANDLE hTimeline,
_In_ VOID(*pfnAddEntry)(_In_ VMM_HANDLE H, _In_ HANDLE hTimeline, _In_ QWORD ft, _In_ DWORD dwAction, _In_ DWORD dwPID, _In_ DWORD dwData32, _In_ QWORD qwData64, _In_ LPCSTR uszText),
_In_ VOID(*pfnEntryAddBySql)(_In_ VMM_HANDLE H, _In_ HANDLE hTimeline, _In_ DWORD cEntrySql, _In_ LPCSTR *pszEntrySql)
) {
LPCSTR pszSql[] = {
"id_str, time_create, "STRINGIZE(FC_TIMELINE_ACTION_CREATE)", pid, tid, ethread FROM thread WHERE time_create > 0;",
"id_str, time_exit, "STRINGIZE(FC_TIMELINE_ACTION_DELETE)", pid, tid, ethread FROM thread WHERE time_exit > 0;",
};
pfnEntryAddBySql(H, hTimeline, sizeof(pszSql) / sizeof(LPSTR), pszSql);
}
VOID M_FcThread_FcLogJSON(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _In_ VOID(*pfnLogJSON)(_In_ VMM_HANDLE H, _In_ PVMMDLL_FORENSIC_JSONDATA pData))
{
PVMM_PROCESS pProcess = ctxP->pProcess;
PVMMDLL_FORENSIC_JSONDATA pd;
PVMMOB_MAP_THREAD pObThreadMap = NULL;
PVMM_MAP_THREADENTRY pe;
DWORD i, o;
CHAR szTime[24];
CHAR usz[MAX_PATH] = { 0 };
if(!pProcess || !(pd = LocalAlloc(LMEM_ZEROINIT, sizeof(VMMDLL_FORENSIC_JSONDATA)))) { return; }
pd->dwVersion = VMMDLL_FORENSIC_JSONDATA_VERSION;
pd->dwPID = pProcess->dwPID;
pd->szjType = "thread";
if(VmmMap_GetThread(H, pProcess, &pObThreadMap)) {
for(i = 0; i < pObThreadMap->cMap; i++) {
pe = pObThreadMap->pMap + i;
Util_FileTime2String(pe->ftCreateTime, szTime);
o = snprintf(usz, _countof(usz), "state:[%i %s] wait:[%i %s] status:[%x %x] prio:[%x %x] start-addr:[%llx %llx] impersonation:[%llx] start:[%s]",
pe->bState, MFCTHREAD_GET_STR_STATE(pe),
pe->bWaitReason, MFCTHREAD_GET_STR_WAIT_REASON(pe),
pe->bRunning, pe->dwExitStatus,
pe->bBasePriority, pe->bPriority,
pe->vaStartAddress, pe->vaWin32StartAddress,
pe->vaImpersonationToken,
szTime
);
if(pe->ftExitTime) {
Util_FileTime2String(pe->ftExitTime, szTime);
snprintf(usz + o, _countof(usz) - 0, " stop:[%s]", szTime);
}
// assign:
pd->i = i;
pd->vaObj = pe->vaETHREAD;
pd->qwHex[0] = pe->dwTID;
pd->qwHex[1] = pe->vaTeb;
pd->va[0] = pe->vaStackBaseUser ? pe->vaStackBaseUser : pe->vaStackBaseKernel;
pd->va[1] = pe->vaStackBaseUser ? pe->vaStackLimitUser : pe->vaStackLimitKernel;
pd->usz[0] = usz;
pfnLogJSON(H, pd);
}
}
Ob_DECREF(pObThreadMap);
LocalFree(pd);
}
VOID M_FcThread_FcLogCSV(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _In_ VMMDLL_CSV_HANDLE hCSV)
{
PVMM_PROCESS pProcess = ctxP->pProcess;
PVMMOB_MAP_THREAD pObThreadMap = NULL;
PVMM_MAP_THREADENTRY pe;
DWORD i;
if(pProcess && VmmMap_GetThread(H, pProcess, &pObThreadMap)) {
for(i = 0; i < pObThreadMap->cMap; i++) {
pe = pObThreadMap->pMap + i;
FcCsv_Reset(hCSV);
FcFileAppend(H, "threads.csv", "%i,%i,%llx,%s,%s,%s,%s,%x,%x,%x,%x,%llx,%llx,%llx,%llx,%llx,%llx,%llx,%llx,%llx,%llx,%llx\n",
pe->dwPID,
pe->dwTID,
pe->vaETHREAD,
MFCTHREAD_GET_STR_STATE(pe),
MFCTHREAD_GET_STR_WAIT_REASON(pe),
FcCsv_FileTime(hCSV, pe->ftCreateTime),
FcCsv_FileTime(hCSV, pe->ftExitTime),
pe->bRunning,
pe->bBasePriority,
pe->bPriority,
pe->dwExitStatus,
pe->vaStartAddress,
pe->vaWin32StartAddress,
pe->vaRIP,
pe->vaRSP,
pe->vaTeb,
pe->vaStackBaseUser,
pe->vaStackLimitUser,
pe->vaStackBaseKernel,
pe->vaStackLimitKernel,
pe->vaTrapFrame,
pe->vaImpersonationToken
);
}
}
Ob_DECREF(pObThreadMap);
}
/*
* Plugin initialization / registration function called by the plugin manager.
* -- H
* -- pRI
*/
VOID M_FcThread_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pRI)
{
if((pRI->magic != VMMDLL_PLUGIN_REGINFO_MAGIC) || (pRI->wVersion != VMMDLL_PLUGIN_REGINFO_VERSION)) { return; }
if((pRI->tpSystem != VMMDLL_SYSTEM_WINDOWS_64) && (pRI->tpSystem != VMMDLL_SYSTEM_WINDOWS_32)) { return; }
strcpy_s(pRI->reg_info.uszPathName, 128, "\\forensic\\hidden\\thread"); // module name
pRI->reg_info.fRootModule = TRUE; // module shows in root directory
pRI->reg_info.fRootModuleHidden = TRUE; // module hidden by default
pRI->reg_fnfc.pfnInitialize = M_FcThread_FcInitialize; // Forensic initialize function supported
pRI->reg_fnfc.pfnTimeline = M_FcThread_FcTimeline; // Forensic timelining supported
pRI->reg_fnfc.pfnLogCSV = M_FcThread_FcLogCSV; // CSV log function supported
pRI->reg_fnfc.pfnLogJSON = M_FcThread_FcLogJSON; // JSON log function supported
memcpy(pRI->reg_info.sTimelineNameShort, "THREAD", 6);
strncpy_s(pRI->reg_info.uszTimelineFile, 32, "timeline_thread", _TRUNCATE);
pRI->pfnPluginManager_Register(H, pRI);
}