// m_sys_syscall.c : implementation related to the Sys/Syscall built-in module.
//
// The '/sys/syscall' module is responsible for displaying the Windows syscall table
// a.k.a. the System Service Dispatch Table (SSDT).
//
// (c) Ulf Frisk, 2020-2025
// Author: Ulf Frisk, pcileech@frizk.net
//

#include "modules.h"

typedef struct tdKSERVICE_DESCRIPTOR_TABLE32 {
    DWORD ServiceTableBase;
    DWORD ServiceCounterTableBase;
    DWORD NumberOfServices;
    DWORD ParamTableBase;
} KSERVICE_DESCRIPTOR_TABLE32, *PKSERVICE_DESCRIPTOR_TABLE32;

typedef struct tdKSERVICE_DESCRIPTOR_TABLE64 {
    QWORD ServiceTableBase;
    QWORD ServiceCounterTableBase;
    QWORD NumberOfServices;
    QWORD ParamTableBase;
} KSERVICE_DESCRIPTOR_TABLE64, *PKSERVICE_DESCRIPTOR_TABLE64;

typedef struct tdMSYSCALL_CONTEXT {
    BOOL fInit;
    QWORD vaKeServiceDescriptorTable;
    QWORD vaKeServiceDescriptorTableShadow;
    union {
        BYTE pbServiceDescriptorTable[1];
        // [0] = TableNt, [1] = TableNtShadow, [2] = TableWin32kShadow
        KSERVICE_DESCRIPTOR_TABLE32 ServiceDescriptorTable32[3];
        KSERVICE_DESCRIPTOR_TABLE64 ServiceDescriptorTable64[3];
    };
    POB_COMPRESSED pCompressedData[3];
} MSYSCALL_CONTEXT, *PMSYSCALL_CONTEXT;

/*
* Retrieve csrss.exe process. It's a specially treated process with both
* user/kernel space mapped for win32k reasons.
* CALLER DECREF: return
* -- H
* -- return
*/
PVMM_PROCESS MSyscall_GetProcessCsrss(_In_ VMM_HANDLE H)
{
    PVMM_PROCESS pObProcess = NULL;
    while((pObProcess = VmmProcessGetNext(H, pObProcess, 0))) {
        if(!strcmp(pObProcess->szName, "csrss.exe")) {
            return pObProcess;
        }
    }
    return NULL;
}

VOID MSyscall_Initialize_BuildText(_In_ VMM_HANDLE H, PMSYSCALL_CONTEXT ctxM, PVMM_PROCESS pProcess, DWORD iTable, PDB_HANDLE hPDB, QWORD vaBase)
{
    BOOL f, f32 = H->vmm.f32;
    DWORD i, c, dwSymbolDisplacement, oBuffer = 0, cbBuffer;
    PBYTE pbBuffer = NULL;
    PDWORD pdwTable = NULL;
    QWORD va, vaServiceTableBase;
    CHAR szSymbolName[MAX_PATH];
    c = f32 ? ctxM->ServiceDescriptorTable32[iTable].NumberOfServices : (DWORD)ctxM->ServiceDescriptorTable64[iTable].NumberOfServices;
    vaServiceTableBase = f32 ? ctxM->ServiceDescriptorTable32[iTable].ServiceTableBase : ctxM->ServiceDescriptorTable64[iTable].ServiceTableBase;
    if(!(pdwTable = LocalAlloc(0, c * sizeof(DWORD)))) { goto fail; }
    if(!VmmRead(H, pProcess, vaServiceTableBase, (PBYTE)pdwTable, c * sizeof(DWORD))) { goto fail; }
    cbBuffer = c * (64 + MAX_PATH);
    if(!(pbBuffer = LocalAlloc(0, cbBuffer))) { goto fail; }
    for(i = 0; i < c; i++) {
        va = 0;
        f = pdwTable[i] &&
            (va = f32 ? pdwTable[i] : vaServiceTableBase + (((LONG)pdwTable[i]) >> 4)) &&
            PDB_GetSymbolFromOffset(H, hPDB, (DWORD)(va - vaBase), szSymbolName, &dwSymbolDisplacement) &&
            (dwSymbolDisplacement == 0);
        oBuffer += f ?
            snprintf(pbBuffer + oBuffer, cbBuffer - oBuffer, "%04x %08x +%06x %llx %s %s\n", (i + 0x1000 * iTable), pdwTable[i], (DWORD)(va - vaBase), va, (iTable == 2 ? "win32k" : "nt    "), szSymbolName) :
            snprintf(pbBuffer + oBuffer, cbBuffer - oBuffer, "%04x %08x +%06x %*llx %s %s\n", (i + 0x1000 * iTable), pdwTable[i], 0, (f32 ? 8 : 16), va, (iTable == 2 ? "win32k" : "nt    "), "---");
    }
    ctxM->pCompressedData[iTable] = ObCompressed_NewFromByte(H, H->vmm.pObCacheMapObCompressedShared, pbBuffer, oBuffer);
fail:
    LocalFree(pbBuffer);
    LocalFree(pdwTable);
}

VOID MSyscall_Initialize(_In_ VMM_HANDLE H, PMSYSCALL_CONTEXT ctxM)
{
    BOOL f, f32 = H->vmm.f32;
    DWORD i, cbRead;
    PDB_HANDLE hPdbWin32k;
    PVMM_MAP_MODULEENTRY peModuleWin32k;
    PVMMOB_MAP_MODULE pObModuleMap = NULL;
    PE_CODEVIEW_INFO CVInfoWin32k = { 0 };
    PVMM_PROCESS pObProcessCsrss = NULL, pObSystemProcess = NULL;
    if(!(pObSystemProcess = VmmProcessGet(H, 4))) { goto fail; }
    // retrieve nt!KeServiceDescriptorTable & nt!KeServiceDescriptorTableShadow
    if(!PDB_GetSymbolAddress(H, PDB_HANDLE_KERNEL, "KeServiceDescriptorTable", &ctxM->vaKeServiceDescriptorTable)) { goto fail; }
    if(!PDB_GetSymbolAddress(H, PDB_HANDLE_KERNEL, "KeServiceDescriptorTableShadow", &ctxM->vaKeServiceDescriptorTableShadow)) { goto fail; }
    if(!VMM_KADDR_8_16(f32, ctxM->vaKeServiceDescriptorTable)) { goto fail; }
    if(!VMM_KADDR_8_16(f32, ctxM->vaKeServiceDescriptorTableShadow)) { goto fail; }
    cbRead = (f32 ? sizeof(KSERVICE_DESCRIPTOR_TABLE32) : sizeof(KSERVICE_DESCRIPTOR_TABLE64));
    if(!VmmRead(H, pObSystemProcess, ctxM->vaKeServiceDescriptorTable, ctxM->pbServiceDescriptorTable, cbRead)) { goto fail; }                     // Table
    if(!VmmRead(H, pObSystemProcess, ctxM->vaKeServiceDescriptorTableShadow, ctxM->pbServiceDescriptorTable + cbRead, 2 * cbRead)) { goto fail; }  // TableShadow
    // validate nt!KeServiceDescriptorTable / nt!KeServiceDescriptorTableShadow
    for(i = 0; i < 3; i++) {
        if(f32) {
            f = VMM_KADDR32_4(ctxM->ServiceDescriptorTable32[i].ServiceTableBase) &&
                (ctxM->ServiceDescriptorTable32[i].ServiceCounterTableBase == 0) &&
                (ctxM->ServiceDescriptorTable32[i].NumberOfServices < 0x800) &&
                VMM_KADDR32(ctxM->ServiceDescriptorTable32[i].ParamTableBase) &&
                (ctxM->ServiceDescriptorTable32[i].ServiceTableBase < ctxM->ServiceDescriptorTable32[i].ParamTableBase);
            if(!f) { return; }
        } else {
            f = VMM_KADDR64_8(ctxM->ServiceDescriptorTable64[i].ServiceTableBase) &&
                (ctxM->ServiceDescriptorTable64[i].ServiceCounterTableBase == 0) &&
                (ctxM->ServiceDescriptorTable64[i].NumberOfServices < 0x800) &&
                VMM_KADDR64(ctxM->ServiceDescriptorTable64[i].ParamTableBase) &&
                (ctxM->ServiceDescriptorTable64[i].ServiceTableBase < ctxM->ServiceDescriptorTable64[i].ParamTableBase);
            if(!f) { return; }
        }
    }
    // fetch win32k infos
    if(!(pObProcessCsrss = MSyscall_GetProcessCsrss(H))) { goto fail_win32k; }
    if(!VmmMap_GetModuleEntryEx(H, pObSystemProcess, 0, "win32k.sys", 0, &pObModuleMap, &peModuleWin32k)) { goto fail_win32k; }
    if(!(hPdbWin32k = PDB_GetHandleFromModuleAddress(H, pObProcessCsrss, peModuleWin32k->vaBase))) { goto fail_win32k; }
    // build text files in-memory (win32k)
    MSyscall_Initialize_BuildText(H, ctxM, pObProcessCsrss, 2, hPdbWin32k, peModuleWin32k->vaBase);
fail_win32k:
    // build text files in-memory (nt)
    MSyscall_Initialize_BuildText(H, ctxM, pObSystemProcess, 0, PDB_HANDLE_KERNEL, H->vmm.kernel.vaBase);
    MSyscall_Initialize_BuildText(H, ctxM, pObSystemProcess, 1, PDB_HANDLE_KERNEL, H->vmm.kernel.vaBase);
fail:
    Ob_DECREF(pObModuleMap);
    Ob_DECREF(pObSystemProcess);
    Ob_DECREF(pObProcessCsrss);
    ctxM->fInit = TRUE;
}

PMSYSCALL_CONTEXT MSyscall_GetContext(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
    PMSYSCALL_CONTEXT ctxM = (PMSYSCALL_CONTEXT)ctxP->ctxM;
    if(ctxM->fInit) { return ctxM; }
    EnterCriticalSection(&H->vmm.LockPlugin);
    if(ctxM->fInit) { goto finish; }
    PDB_Initialize_WaitComplete(H);
    MSyscall_Initialize(H, ctxM);
finish:
    LeaveCriticalSection(&H->vmm.LockPlugin);
    return ctxM;
}

NTSTATUS MSyscall_Read(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _Out_writes_to_(cb, *pcbRead) PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset)
{
    PMSYSCALL_CONTEXT ctxM = MSyscall_GetContext(H, ctxP);
    if(!_stricmp(ctxP->uszPath, "syscall_nt.txt")) {
        return Util_VfsReadFile_FromObCompressed(ctxM->pCompressedData[0], pb, cb, pcbRead, cbOffset);
    }
    if(!_stricmp(ctxP->uszPath, "syscall_nt_shadow.txt")) {
        return Util_VfsReadFile_FromObCompressed(ctxM->pCompressedData[1], pb, cb, pcbRead, cbOffset);
    }
    if(!_stricmp(ctxP->uszPath, "syscall_win32k.txt")) {
        return Util_VfsReadFile_FromObCompressed(ctxM->pCompressedData[2], pb, cb, pcbRead, cbOffset);
    }
    return VMMDLL_STATUS_FILE_INVALID;
}

BOOL MSyscall_List(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _Inout_ PHANDLE pFileList)
{
    PMSYSCALL_CONTEXT ctxM = MSyscall_GetContext(H, ctxP);
    if(ctxP->uszPath[0]) { return FALSE; }
    VMMDLL_VfsList_AddFile(pFileList, "syscall_nt.txt", ObCompress_Size(ctxM->pCompressedData[0]), NULL);
    VMMDLL_VfsList_AddFile(pFileList, "syscall_nt_shadow.txt", ObCompress_Size(ctxM->pCompressedData[1]), NULL);
    VMMDLL_VfsList_AddFile(pFileList, "syscall_win32k.txt", ObCompress_Size(ctxM->pCompressedData[2]), NULL);
    return TRUE;
}

VOID MSyscall_Close(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
    PMSYSCALL_CONTEXT ctxM = (PMSYSCALL_CONTEXT)ctxP->ctxM;
    Ob_DECREF(ctxM->pCompressedData[0]);
    Ob_DECREF(ctxM->pCompressedData[1]);
    Ob_DECREF(ctxM->pCompressedData[2]);
    LocalFree(ctxM);
}

VOID M_SysSyscall_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; }
    if(!(pRI->reg_info.ctxM = LocalAlloc(LMEM_ZEROINIT, sizeof(MSYSCALL_CONTEXT)))) { return; }     // internal module context
    strcpy_s(pRI->reg_info.uszPathName, 128, "\\sys\\syscall");     // module name
    pRI->reg_info.fRootModule = TRUE;                               // module shows in root directory
    pRI->reg_fn.pfnList = MSyscall_List;                            // List function supported
    pRI->reg_fn.pfnRead = MSyscall_Read;                            // Read function supported
    pRI->reg_fn.pfnClose = MSyscall_Close;                          // Close function supported
    pRI->pfnPluginManager_Register(H, pRI);
}
