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

260 lines
12 KiB
C

// m_fc_proc.c : process 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"
#include "../vmmwin.h"
static LPSTR MFCPROC_CSV_PROCESS = "PID,PPID,State,ShortName,Name,IntegrityLevel,User,CreateTime,ExitTime,Wow64,EPROCESS,PEB,PEB32,DTB,UserDTB,UserPath,KernelPath,CommandLine,Flag\n";
static LPSTR FC_SQL_SCHEMA_PROCESS =
"DROP TABLE IF EXISTS process; " \
"CREATE TABLE process(id INTEGER PRIMARY KEY AUTOINCREMENT, id_str_name INTEGER, id_str_path INTEGER, id_str_user INTEGER, id_str_all INTEGER, pid INT, ppid INT, eprocess INTEGER, dtb INTEGER, dtb_user INTEGER, state INTEGER, wow64 INT, peb INTEGER, peb32 INTEGER, time_create INTEGER, time_exit INTEGER); " \
"DROP VIEW IF EXISTS v_process; " \
"CREATE VIEW v_process AS SELECT p.*, sn.cbu AS cbu_name, sn.cbj AS cbj_name, sn.sz AS sz_name, sp.cbu AS cbu_path, sp.cbj AS cbj_path, sp.sz AS sz_path, su.cbu AS cbu_user, su.cbj AS cbj_user, su.sz AS sz_user, sa.cbu AS cbu_all, sa.cbj AS cbj_all, sa.sz AS sz_all FROM process p, str sn, str sp, str su, str sa WHERE p.id_str_name = sn.id AND p.id_str_path = sp.id AND p.id_str_user = su.id AND p.id_str_all = sa.id; ";
/*
* Forensic initialization function called when the forensic sub-system is initializing.
*/
PVOID MFcProc_FcInitialize(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
int rc;
BOOL fWellKnownAccount = FALSE;
PVMM_PROCESS pObProcess = NULL;
sqlite3 *hSql = NULL;
sqlite3_stmt *hStmt = NULL, *hStmtStr = NULL;
FCSQL_INSERTSTRTABLE SqlStrInsert[4];
CHAR uszUserName[MAX_PATH], uszFullInfo[2048];
// 1: initialize csv
FcFileAppend(H, "process.csv", MFCPROC_CSV_PROCESS);
// 2: initialize timelining (sql)
if(SQLITE_OK != Fc_SqlExec(H, FC_SQL_SCHEMA_PROCESS)) { goto fail; }
if(!(hSql = Fc_SqlReserve(H))) { goto fail; }
if(SQLITE_OK != sqlite3_prepare_v2(hSql, "INSERT INTO process (id_str_name, id_str_path, id_str_user, id_str_all, pid, ppid, eprocess, dtb, dtb_user, state, wow64, peb, peb32, 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);
while((pObProcess = VmmProcessGetNext(H, pObProcess, VMM_FLAG_PROCESS_TOKEN | VMM_FLAG_PROCESS_SHOW_TERMINATED))) {
// build and insert string data into 'str' table.
if(!Fc_SqlInsertStr(H, hStmtStr, pObProcess->pObPersistent->uszNameLong, &SqlStrInsert[0])) { goto fail_transact; }
if(!Fc_SqlInsertStr(H, hStmtStr, pObProcess->pObPersistent->uszPathKernel, &SqlStrInsert[1])) { goto fail_transact; }
if(!pObProcess->win.Token || !pObProcess->win.Token->fSidUserValid || !VmmWinUser_GetName(H, &pObProcess->win.Token->SidUser.SID, uszUserName, MAX_PATH, &fWellKnownAccount)) { uszUserName[0] = 0; }
if(!Fc_SqlInsertStr(H, hStmtStr, uszUserName, &SqlStrInsert[2])) { goto fail_transact; }
_snprintf_s(uszFullInfo, 2048 - 2, 2048 - 3, "%s [%s%s] %s", pObProcess->pObPersistent->uszNameLong, (fWellKnownAccount ? "*" : ""), uszUserName, pObProcess->pObPersistent->uszPathKernel);
if(!Fc_SqlInsertStr(H, hStmtStr, uszFullInfo, &SqlStrInsert[3])) { goto fail_transact; }
// insert into 'process' table.
sqlite3_reset(hStmt);
rc = Fc_SqlBindMultiInt64(hStmt, 1, 15,
SqlStrInsert[0].id,
SqlStrInsert[1].id,
SqlStrInsert[2].id,
SqlStrInsert[3].id,
(QWORD)pObProcess->dwPID,
(QWORD)pObProcess->dwPPID,
pObProcess->win.EPROCESS.va,
pObProcess->paDTB,
pObProcess->paDTB_UserOpt,
(QWORD)pObProcess->dwState,
(QWORD)(pObProcess->win.fWow64 ? 1 : 0),
pObProcess->win.vaPEB,
(QWORD)pObProcess->win.vaPEB32,
VmmProcess_GetCreateTimeOpt(H, pObProcess),
VmmProcess_GetExitTimeOpt(H, pObProcess)
);
if(SQLITE_OK != rc) { goto fail_transact; }
sqlite3_step(hStmt);
}
sqlite3_exec(hSql, "COMMIT TRANSACTION", NULL, NULL, NULL);
fail:
Ob_DECREF(pObProcess);
sqlite3_finalize(hStmt);
sqlite3_finalize(hStmtStr);
Fc_SqlReserveReturn(H, hSql);
return NULL;
fail_transact:
sqlite3_exec(hSql, "COMMIT TRANSACTION", NULL, NULL, NULL);
goto fail;
}
/*
* Timeline data by executing a partial SQL query on pre-existing data.
* -- H
* -- ctxfc
* -- hTimeline
* -- pfnAddEntry
* -- pfnEntryAddBySql
*/
VOID MFcProc_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_all, time_create, "STRINGIZE(FC_TIMELINE_ACTION_CREATE)", pid, ppid, eprocess FROM process WHERE time_create > 0;",
"id_str_all, time_exit, "STRINGIZE(FC_TIMELINE_ACTION_DELETE)", pid, ppid, eprocess FROM process WHERE time_exit > 0;"
};
pfnEntryAddBySql(H, hTimeline, sizeof(pszSql) / sizeof(LPSTR), pszSql);
}
VOID MFcProc_LogHeap(_In_ VMM_HANDLE H, _In_ PVMMDLL_FORENSIC_JSONDATA pd, _In_ VOID(*pfnLogJSON)(_In_ VMM_HANDLE H, _In_ PVMMDLL_FORENSIC_JSONDATA pData), _In_ PVMMOB_MAP_HEAP pMap)
{
DWORD i;
PVMM_MAP_HEAP_SEGMENTENTRY peR;
for(i = 0; i < pMap->cSegments; i++) {
peR = pMap->pSegments + i;
pd->i = i;
pd->va[0] = peR->va;
pd->va[1] = pMap->pMap[peR->iHeap].va;
pd->qwNum[0] = peR->cb;
pfnLogJSON(H, pd);
}
}
VOID MFcProc_LogProcess_GetUserName(_In_ VMM_HANDLE H, _In_ PVMM_PROCESS pProcess, _Out_writes_(17) LPSTR uszUserName, _Out_ PBOOL fAccountUser)
{
BOOL f, fWellKnownAccount = FALSE;
uszUserName[0] = 0;
f = pProcess->win.Token &&
pProcess->win.Token->fSidUserValid &&
VmmWinUser_GetName(H, &pProcess->win.Token->SidUser.SID, uszUserName, 17, &fWellKnownAccount);
*fAccountUser = f && !fWellKnownAccount;
}
VOID MFcProc_LogProcess(_In_ VMM_HANDLE H, _In_ PVMMDLL_FORENSIC_JSONDATA pd, _In_ VOID(*pfnLogJSON)(_In_ VMM_HANDLE H, _In_ PVMMDLL_FORENSIC_JSONDATA pData), PVMM_PROCESS pProcess)
{
SIZE_T o;
BOOL fStateTerminated, fAccountUser = FALSE;
CHAR uszBuffer[1024], szUserName[17], szTimeCRE[24], szTimeEXIT[24];
PVMMWIN_USER_PROCESS_PARAMETERS pu = VmmWin_UserProcessParameters_Get(H, pProcess);
SIZE_T cbu = sizeof(uszBuffer);
LPSTR usz = uszBuffer;
if((SIZE_T)pu->cbuImagePathName + pu->cbuCommandLine > sizeof(uszBuffer) - 512) {
cbu = (SIZE_T)pu->cbuImagePathName + pu->cbuCommandLine + 512;
usz = LocalAlloc(0, cbu);
if(!usz) { return; }
}
pd->i = pProcess->dwPID;
pd->vaObj = pProcess->win.EPROCESS.va;
pd->qwNum[1] = pProcess->dwPPID;
pd->qwHex[0] = pProcess->paDTB;
pd->qwHex[1] = pProcess->paDTB_UserOpt;
pd->usz[0] = pProcess->pObPersistent->uszPathKernel;
fStateTerminated = (pProcess->dwState != 0);
MFcProc_LogProcess_GetUserName(H, pProcess, szUserName, &fAccountUser);
Util_FileTime2String(VmmProcess_GetCreateTimeOpt(H, pProcess), szTimeCRE);
o = _snprintf_s(usz, cbu, _TRUNCATE, "flags:[%s%c%c%c] user:[%s] upath:[%s] cmd:[%s] createtime:[%s]",
pProcess->win.fWow64 ? "32" : " ",
pProcess->win.EPROCESS.fNoLink ? 'E' : ' ',
fStateTerminated ? 'T' : ' ',
fAccountUser ? 'U' : ' ',
szUserName,
pu->uszImagePathName ? pu->uszImagePathName : "",
pu->uszCommandLine ? pu->uszCommandLine : "",
szTimeCRE
);
if(VmmProcess_GetExitTimeOpt(H, pProcess)) {
Util_FileTime2String(VmmProcess_GetExitTimeOpt(H, pProcess), szTimeEXIT);
o += _snprintf_s(usz + o, cbu - o, _TRUNCATE, " exittime:[%s]", szTimeEXIT);
}
if(pProcess->win.Token && pProcess->win.Token->IntegrityLevel) {
o += _snprintf_s(usz + o, cbu - o, _TRUNCATE, " integrity:[%s]", VMM_TOKEN_INTEGRITY_LEVEL_STR[pProcess->win.Token->IntegrityLevel]);
}
pd->usz[1] = usz;
pfnLogJSON(H, pd);
if(usz != uszBuffer) { LocalFree(usz); }
}
VOID MFcProc_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_HEAP pObHeapMap = NULL;
if(!pProcess || !(pd = LocalAlloc(LMEM_ZEROINIT, sizeof(VMMDLL_FORENSIC_JSONDATA)))) { return; }
// process:
FC_JSONDATA_INIT_PIDTYPE(pd, pProcess->dwPID, "process");
MFcProc_LogProcess(H, pd, pfnLogJSON, pProcess);
// heap:
FC_JSONDATA_INIT_PIDTYPE(pd, pProcess->dwPID, "heap");
if(VmmMap_GetHeap(H, pProcess, &pObHeapMap)) {
MFcProc_LogHeap(H, pd, pfnLogJSON, pObHeapMap);
}
Ob_DECREF(pObHeapMap);
LocalFree(pd);
}
static VOID MFcProc_BuildFlagString(_In_ PVMM_PROCESS pProcess, _In_ BOOL fAccountUser, _Out_writes_(8) LPSTR szFlag)
{
SIZE_T o = 0;
if(pProcess->win.fWow64) { szFlag[o++] = '3'; szFlag[o++] = '2'; }
if(pProcess->win.EPROCESS.fNoLink) { szFlag[o++] = 'E'; }
if(pProcess->dwState != 0) { szFlag[o++] = 'T'; }
if(fAccountUser) { szFlag[o++] = 'U'; }
if(o == 0) {
szFlag[o++] = '-';
}
szFlag[o] = '\0';
}
VOID MFcProc_FcLogCSV(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _In_ VMMDLL_CSV_HANDLE hCSV)
{
PVMM_PROCESS pProcess = ctxP->pProcess;
PVMMWIN_USER_PROCESS_PARAMETERS pu;
BOOL fAccountUser = FALSE;
CHAR szUserName[17];
CHAR szFlag[8];
if(!pProcess) { return; }
pu = VmmWin_UserProcessParameters_Get(H, pProcess);
MFcProc_BuildFlagString(pProcess, fAccountUser, szFlag);
MFcProc_LogProcess_GetUserName(H, pProcess, szUserName, &fAccountUser);
FcCsv_Reset(hCSV);
FcFileAppend(H, "process.csv", "%i,%i,%i,%s,%s,%s,%s,%s,%s,%i,0x%llx,0x%llx,0x%x,0x%llx,0x%llx,%s,%s,%s,%s\n",
pProcess->dwPID,
pProcess->dwPPID,
pProcess->dwState,
FcCsv_String(hCSV, pProcess->szName),
FcCsv_String(hCSV, pProcess->pObPersistent->uszNameLong),
FcCsv_String(hCSV, (LPSTR)VMM_TOKEN_INTEGRITY_LEVEL_STR[pProcess->win.Token ? pProcess->win.Token->IntegrityLevel : 0]),
FcCsv_String(hCSV, szUserName),
FcCsv_FileTime(hCSV, VmmProcess_GetCreateTimeOpt(H, pProcess)),
FcCsv_FileTime(hCSV, VmmProcess_GetExitTimeOpt(H, pProcess)),
pProcess->win.fWow64 ? 1 : 0,
pProcess->win.EPROCESS.va,
pProcess->win.vaPEB,
pProcess->win.vaPEB32,
pProcess->paDTB,
pProcess->paDTB_UserOpt,
FcCsv_String(hCSV, pu->uszImagePathName),
FcCsv_String(hCSV, pProcess->pObPersistent->uszPathKernel),
FcCsv_String(hCSV, pu->uszCommandLine),
FcCsv_String(hCSV, szFlag)
);
}
/*
* Plugin initialization / registration function called by the plugin manager.
* -- H
* -- pRI
*/
VOID M_FcProc_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\\proc"); // 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 = MFcProc_FcInitialize; // Forensic initialize function supported
pRI->reg_fnfc.pfnTimeline = MFcProc_FcTimeline; // Forensic timelining supported
pRI->reg_fnfc.pfnLogCSV = MFcProc_FcLogCSV; // CSV log function supported
pRI->reg_fnfc.pfnLogJSON = MFcProc_FcLogJSON; // JSON log function supported
memcpy(pRI->reg_info.sTimelineNameShort, "PROC", 5);
strncpy_s(pRI->reg_info.uszTimelineFile, 32, "timeline_process", _TRUNCATE);
pRI->pfnPluginManager_Register(H, pRI);
}