/*
 * Copyright 2004-2020 Sandboxie Holdings, LLC 
 * Copyright 2020-2021 David Xanatos, xanasoft.com
 *
 * This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

//---------------------------------------------------------------------------
// Syscall Management
//---------------------------------------------------------------------------


#include "syscall.h"
#include "dll.h"
#include "process.h"
#include "thread.h"
#include "obj.h"
#include "api.h"
#include "util.h"
#include "session.h"
#include "conf.h"
#include "common/pattern.h"
#include "core/low/lowdata.h"


//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------


static BOOLEAN Syscall_Init_List(void);

static BOOLEAN Syscall_Init_Table(void);

static BOOLEAN Syscall_Init_ServiceData(void);

static void Syscall_ErrorForAsciiName(const UCHAR *name_a);

void Syscall_Update_Lockdown();


//---------------------------------------------------------------------------


static NTSTATUS Syscall_OpenHandle(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args);

static NTSTATUS Syscall_GetNextProcess(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args);

static NTSTATUS Syscall_GetNextThread(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args);

static NTSTATUS Syscall_DeviceIoControlFile(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args);

static NTSTATUS Syscall_DuplicateHandle(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args);

static BOOLEAN Syscall_QuerySystemInfo_SupportProcmonStack(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args);


//---------------------------------------------------------------------------


static NTSTATUS Syscall_Api_Query(PROCESS *proc, ULONG64 *parms);

static NTSTATUS Syscall_Api_Invoke(PROCESS *proc, ULONG64 *parms);


//---------------------------------------------------------------------------


static ULONG Syscall_GetIndexFromNtdll(UCHAR *code);

static BOOLEAN Syscall_GetKernelAddr(
    ULONG index, void **pKernelAddr, ULONG *pParamCount);


//---------------------------------------------------------------------------


#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, Syscall_Init)
#pragma alloc_text (INIT, Syscall_Init_List)
#pragma alloc_text (INIT, Syscall_Init_Table)
#pragma alloc_text (INIT, Syscall_Init_ServiceData)
#pragma alloc_text (INIT, Syscall_Set1)
#pragma alloc_text (INIT, Syscall_Set2)
#pragma alloc_text (INIT, Syscall_ErrorForAsciiName)
#pragma alloc_text (INIT, Syscall_GetIndexFromNtdll)
#pragma alloc_text (INIT, Syscall_GetKernelAddr)
#pragma alloc_text (INIT, Syscall_GetServiceTable)
#endif // ALLOC_PRAGMA

#include "syscall_util.c"

#ifdef HOOK_WIN32K
#include "syscall_win32.c"
#endif


//---------------------------------------------------------------------------
// Variables
//---------------------------------------------------------------------------


static LIST Syscall_List;

static SYSCALL_ENTRY **Syscall_Table = NULL;

static ULONG Syscall_MaxIndex = 0;

static UCHAR *Syscall_NtdllSavedCode = NULL;

static SYSCALL_ENTRY *Syscall_SetInformationThread = NULL;


//---------------------------------------------------------------------------
// Syscall_Init
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_Init(void)
{
    if (! Syscall_Init_List())
        return FALSE;

    if (! Syscall_Init_Table())
        return FALSE;

    if (! Syscall_Init_ServiceData())
        return FALSE;

#ifdef HOOK_WIN32K
    if (Driver_OsBuild >= 10041 && Conf_Get_Boolean(NULL, L"EnableWin32kHooks", 0, TRUE)) {

        if (!Syscall_Init_List32())
            return FALSE;

        if (!Syscall_Init_Table32())
            return FALSE;
    }
#endif

    if (! Syscall_Set1("DuplicateObject", Syscall_DuplicateHandle))
        return FALSE;

    if (Driver_OsVersion >= DRIVER_WINDOWS_VISTA) {
        if (!Syscall_Set1("GetNextProcess", Syscall_GetNextProcess))
            return FALSE;

        if (!Syscall_Set1("GetNextThread", Syscall_GetNextThread))
            return FALSE;
    }

    if (!Syscall_Set1("DeviceIoControlFile", Syscall_DeviceIoControlFile))
        return FALSE;

    if (!Syscall_Set3("QuerySystemInformation", Syscall_QuerySystemInfo_SupportProcmonStack))
        return FALSE;

    //
    // set API handlers
    //

    Api_SetFunction(API_QUERY_SYSCALLS,     Syscall_Api_Query);
    Api_SetFunction(API_INVOKE_SYSCALL,     Syscall_Api_Invoke);

    //
    // finish
    //

    return TRUE;
}


//---------------------------------------------------------------------------
// Syscall_Init_List
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_Init_List(void)
{
    BOOLEAN success = FALSE;
    UCHAR *name, *ntdll_code;
    void *ntos_addr;
    DLL_ENTRY *dll;
    SYSCALL_ENTRY *entry;
    ULONG proc_index, proc_offset, syscall_index, param_count;
    ULONG name_len, entry_len;

    List_Init(&Syscall_List);

    //
    // prepare the approve and disabled lists
    //

    LIST disabled_hooks;
    Syscall_LoadHookMap(L"DisableWinNtHook", &disabled_hooks);

    LIST approved_syscalls;
    Syscall_LoadHookMap(L"ApproveWinNtSysCall", &approved_syscalls);

    //
    // scan each ZwXxx export in NTDLL
    //

    dll = Dll_Load(Dll_NTDLL);
    if (! dll)
        goto finish;

    proc_offset = Dll_GetNextProc(dll, "Zw", &name, &proc_index);
    if (! proc_offset)
        goto finish;

    while (proc_offset) {

        if (name[0] != 'Z' || name[1] != 'w')
            break;
        name += 2;                  // skip Zw prefix
        for (name_len = 0; (name_len < 64) && name[name_len]; ++name_len)
            ;

        //DbgPrint("    Found SysCall %s\n", name);

        entry = NULL;

        //
        // we don't hook ZwXxx services which do not or may not return to
        // caller. this is because the invocation of Syscall_Api_Invoke
        // goes through IopXxxControlFile (called by NtDeviceIoControlFile)
        // which takes a reference on the file object for our API device
        // object, and no return means there is no corresponding dereference
        // for the file object.  there could be other nasty side effects.
        //
        // ZwXxx services:   ZwContinue, ZwCallbackReturn, ZwRaiseException,
        // NtTerminateJobObject, NtTerminateProcess, NtTerminateThread
        //

#define IS_PROC_NAME(ln,nm) (name_len == ln && memcmp(name, nm, ln) == 0)

        if (    IS_PROC_NAME(8,  "Continue")
            ||  IS_PROC_NAME(10, "ContinueEx")
            ||  IS_PROC_NAME(14, "CallbackReturn")
            ||  IS_PROC_NAME(14, "RaiseException")
            ||  IS_PROC_NAME(18, "TerminateJobObject")
            ||  IS_PROC_NAME(16, "TerminateProcess")
            ||  IS_PROC_NAME(15, "TerminateThread")

                                                            ) {
            goto next_zwxxx;
        }

        //
        // on 64-bit Windows, some syscalls are fake, and should be skipped
        //

        if (    IS_PROC_NAME(15, "QuerySystemTime"))
              goto next_zwxxx;


        // ICD-10607 - McAfee uses it to pass its own data in the stack. The call is not important to us. 
        //if (    IS_PROC_NAME(14, "YieldExecution")) // $Workaround$ - 3rd party fix
        //    goto next_zwxxx;

        //
        // the Google Chrome "wow_helper" process expects NtMapViewOfSection
        // to not be already hooked.  although this is needed only on 64-bit
        // Vista, this particular syscall is not very important to us, so
        // for sake of consistency, we skip hooking it on all platforms
        //
        //if (    IS_PROC_NAME(16,  "MapViewOfSection")) // $Workaround$ - 3rd party fix
        //    goto next_zwxxx;

        if(Syscall_HookMapMatch(name, name_len, &disabled_hooks))
            goto next_zwxxx;

#undef IS_PROC_NAME

        //
        // analyze each ZwXxx export to find the service index number
        //

        ntos_addr = NULL;
        param_count = 0;

        ntdll_code = Dll_RvaToAddr(dll, proc_offset);
        if (ntdll_code) {

            syscall_index = Syscall_GetIndexFromNtdll(ntdll_code);

            if (syscall_index == -2) {
                //
                // if ZwXxx export is not a real syscall, then skip it
                //
                goto next_zwxxx;
            }

            if (syscall_index != -1) {
                Syscall_GetKernelAddr(
                            syscall_index, &ntos_addr, &param_count);

                //DbgPrint("    Found SysCall: %s, pcnt %d; idx: %d\r\n", name, param_count, syscall_index);
            }
        }

        if (! ntos_addr) {

            Syscall_ErrorForAsciiName(name);
            goto finish;
        }

        //
        // store an entry for the syscall in our list of syscalls
        //

        entry_len = sizeof(SYSCALL_ENTRY) + name_len + 1;
        entry = Mem_AllocEx(Driver_Pool, entry_len, TRUE);
        if (! entry)
            goto finish;

        entry->syscall_index = (USHORT)syscall_index;
        entry->param_count = (USHORT)param_count;
        entry->ntdll_offset = proc_offset;
        entry->ntos_func = ntos_addr;
        entry->handler1_func = NULL;
        entry->handler2_func = NULL;
        entry->handler3_func_support_procmon = NULL;
        entry->approved = (Syscall_HookMapMatch(name, name_len, &approved_syscalls) != 0);
        entry->name_len = (USHORT)name_len;
        memcpy(entry->name, name, name_len);
        entry->name[name_len] = '\0';

        List_Insert_After(&Syscall_List, NULL, entry);

        if (syscall_index > Syscall_MaxIndex)
            Syscall_MaxIndex = syscall_index;

        //
        // process next ZwXxx export
        //

next_zwxxx:

        proc_offset = Dll_GetNextProc(dll, NULL, &name, &proc_index);
    }

    success = TRUE;

    //
    // report an error if we did not find a reasonable number of services
    //

    if (Syscall_MaxIndex < 100) {
        Log_Msg1(MSG_1113, L"100");
        success = FALSE;
    }

    if (Syscall_MaxIndex >= 500) {
        Log_Msg1(MSG_1113, L"500");
        success = FALSE;
    }

finish:

    if(!success)
        Syscall_MaxIndex = 0;

    Syscall_FreeHookMap(&disabled_hooks);

    Syscall_FreeHookMap(&approved_syscalls);

    return success;
}


//---------------------------------------------------------------------------
// Syscall_Init_Table
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_Init_Table(void)
{
    SYSCALL_ENTRY *entry;
    ULONG len, i;

    //
    // build the table which maps syscall index numbers to entries
    //

    len = sizeof(SYSCALL_ENTRY *) * (Syscall_MaxIndex + 1);
    Syscall_Table = Mem_AllocEx(Driver_Pool, len, TRUE);
    if (! Syscall_Table)
        return FALSE;

    for (i = 0; i <= Syscall_MaxIndex; ++i) {

        entry = List_Head(&Syscall_List);
        while (entry) {
            if (entry->syscall_index == i)
                break;
            entry = List_Next(entry);
        }

        Syscall_Table[i] = entry;
    }

    return TRUE;
}


//---------------------------------------------------------------------------
// Syscall_Init_ServiceData
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_Init_ServiceData(void)
{
    UCHAR *NtdllExports[] = NATIVE_FUNCTION_NAMES;
    SYSCALL_ENTRY *entry;
    DLL_ENTRY *dll;
    UCHAR *ntdll_code;
    ULONG i;

    //
    // allocate some space to save code from ntdll
    //

    Syscall_NtdllSavedCode = Mem_AllocEx(Driver_Pool, (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT), TRUE);
    if (! Syscall_NtdllSavedCode)
        return FALSE;

    dll = Dll_Load(Dll_NTDLL);
    if (! dll)
        return FALSE;

    //
    // save a copy of some syscall functions in ntdll, we will pass
    // these to SbieSvc, which in turn will pass them to SbieLow
    // (see core/svc/DriverAssistInject.cpp and core/low/lowdata.h)
    //

    for (i = 0; i < NATIVE_FUNCTION_COUNT; ++i) {

        entry = Syscall_GetByName(NtdllExports[i] + 2); // +2 skip Nt prefix
        if (! entry)
            return FALSE;

        ntdll_code = Dll_RvaToAddr(dll, entry->ntdll_offset);
        if (! ntdll_code) {
            Syscall_ErrorForAsciiName(entry->name);
            return FALSE;
        }

        memcpy(Syscall_NtdllSavedCode + (i * NATIVE_FUNCTION_SIZE), ntdll_code, NATIVE_FUNCTION_SIZE);
    }

    //
    // get the syscall for NtSetInformationThread (see Syscall_Api_Invoke)
    //

    Syscall_SetInformationThread = Syscall_GetByName("SetInformationThread");

    //
    // finish
    //

    return TRUE;
}


//---------------------------------------------------------------------------
// Syscall_GetByName
//---------------------------------------------------------------------------


_FX SYSCALL_ENTRY *Syscall_GetByName(const UCHAR *name)
{
    ULONG name_len = strlen(name);

    SYSCALL_ENTRY *entry = List_Head(&Syscall_List);
    while (entry) {

        if (entry->name_len == name_len
                    && memcmp(entry->name, name, name_len) == 0) {

            return entry;
        }

        entry = List_Next(entry);
    }

    Syscall_ErrorForAsciiName(name);
    return NULL;
}


//---------------------------------------------------------------------------
// Syscall_Set1
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_Set1(const UCHAR *name, P_Syscall_Handler1 handler_func)
{
    SYSCALL_ENTRY *entry = Syscall_GetByName(name);
    if (! entry)
        return FALSE;
    entry->handler1_func = handler_func;
    return TRUE;
}

//---------------------------------------------------------------------------
// Syscall_Set3
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_Set3(const UCHAR *name, P_Syscall_Handler3_Support_Procmon_Stack handler_func)
{
    SYSCALL_ENTRY *entry = Syscall_GetByName(name);
    if (!entry)
        return FALSE;
    entry->handler3_func_support_procmon = handler_func;
    return TRUE;
}


//---------------------------------------------------------------------------
// Syscall_ErrorForAsciiName
//---------------------------------------------------------------------------


_FX void Syscall_ErrorForAsciiName(const UCHAR *name_a)
{
    WCHAR name_w[66];
    ULONG i;

    for (i = 0; (i < 64) && name_a[i]; ++i)
        name_w[i] = name_a[i];
    name_w[i] = '\0';

    Log_Msg1(MSG_1113, name_w);
}


//---------------------------------------------------------------------------
// Syscall_Invoke
//---------------------------------------------------------------------------
extern unsigned int g_TrapFrameOffset;

NTSTATUS Sbie_InvokeSyscall_asm(void* func, ULONG count, void* args);

_FX NTSTATUS Syscall_Invoke(SYSCALL_ENTRY *entry, ULONG_PTR *stack)
{
    NTSTATUS status;

    //
    // Note: when directly calling win32k functions with "Core Isolation" (HVCI) enabled
    //  the nt!guard_dispatch_icall will cause a bugcheck!
    //  Hence we use a call proxy Sbie_InvokeSyscall_asm instead of a direct call
    //  alternatively we could disable "Control Flow Guard" for this file
    //

    __try {

        //DbgPrint("[syscall] request param count = %d\n", entry->param_count);

        status = Sbie_InvokeSyscall_asm(entry->ntos_func, entry->param_count, stack);

    } __except (EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
    }

    return status;
}


//---------------------------------------------------------------------------
// Syscall_Api_Invoke
//---------------------------------------------------------------------------


_FX NTSTATUS Syscall_Api_Invoke(PROCESS *proc, ULONG64 *parms)
{
    ULONG_PTR *user_args;
    SYSCALL_ENTRY *entry;
    ULONG syscall_index;
    NTSTATUS status;
#ifdef _WIN64
    volatile ULONG_PTR ret = 0;
    volatile ULONG_PTR UserStack = 0;

    volatile PETHREAD pThread = PsGetCurrentThread();
    volatile PKTRAP_FRAME pTrapFrame = NULL;
#endif
    //int exception;

    //
    // caller must be sandboxed and service index must be known
    //

    if (! proc)
        return STATUS_NOT_IMPLEMENTED;

    syscall_index = (ULONG)parms[1];

#ifdef HOOK_WIN32K
    if ((syscall_index & 0x1000) != 0) { // win32k syscall
        return Syscall_Api_Invoke32(proc, parms);
    }
#endif

    syscall_index = (syscall_index & 0xFFF);

    //DbgPrint("[syscall] request for service %d / %08X\n", syscall_index, syscall_index);

    //
    // use direct syscalls to access 64 bit memory from a wow process 
    // instead of using heaven's gate / wow64ext
    //

    if (syscall_index == 0xFFF && parms[3] != 0) {
        __try {

            entry = Syscall_GetByName((UCHAR*)parms[3]);

            if(parms[4]) // return found index to the caller to be re used later
                *(USHORT*)parms[4] = entry->syscall_index; 

        } __except (EXCEPTION_EXECUTE_HANDLER) {
            entry = NULL;
        }
    }
    else 
        
    //

    if (Syscall_Table && (syscall_index <= Syscall_MaxIndex))
        entry = Syscall_Table[syscall_index];
    else
        entry = NULL;

    if (! entry)
        return STATUS_INVALID_SYSTEM_SERVICE;

    // DbgPrint("[syscall] request p=%06d t=%06d - BEGIN %s\n", PsGetCurrentProcessId(), PsGetCurrentThreadId(), entry->name);

#ifdef XP_SUPPORT
    //
    // make sure the thread has sufficient access rights to itself
    // then impersonate the full access token for the thread or process
    //

    if (!Thread_AdjustGrantedAccess()) {

        Process_SetTerminated(proc, 5);
    }
    else
#endif
    if(!proc->is_locked_down || entry->approved)
        Thread_SetThreadToken(proc);        // may set proc->terminated

    if (proc->terminated) {

        Process_TerminateProcess(proc);
        return STATUS_PROCESS_IS_TERMINATING;
    }

    //
    // if we have a handler for this service, invoke it
    //

    user_args = (ULONG_PTR *)parms[2];

    __try {

        BOOLEAN traced = FALSE;
        const ULONG args_len = entry->param_count * sizeof(ULONG_PTR);
#ifdef _WIN64
        ProbeForRead(user_args, args_len, sizeof(ULONG_PTR));

        // default - support procmon stack if handler3_func_support_procmon is null.
        if (!entry->handler3_func_support_procmon
            || entry->handler3_func_support_procmon(proc, entry, user_args)
            )
        {
            if (g_TrapFrameOffset) {

                pTrapFrame = (PKTRAP_FRAME) *(ULONG_PTR*)((UCHAR*)pThread + g_TrapFrameOffset);
                if (pTrapFrame) {
#ifdef _M_ARM64
                    //ret = pTrapFrame->Pc;
                    //UserStack = pTrapFrame->Sp;
                    //pTrapFrame->Sp = pTrapFrame->Fp;
                    //pTrapFrame->Pc = pTrapFrame->X27;
#else
                    ret = pTrapFrame->Rip;
                    UserStack = pTrapFrame->Rsp;
                    pTrapFrame->Rsp = pTrapFrame->Rbp; //*pRbp;
                    pTrapFrame->Rip = pTrapFrame->Rbx; //*pRbx;
#endif
                }
            }
            else
            {
                pTrapFrame = NULL;
            }
        }
        else
        {
            pTrapFrame = NULL;
        }

#else ! _WIN64
        ProbeForRead(user_args, args_len, sizeof(UCHAR));
#endif _WIN64

        
        //if (proc->ipc_trace & (TRACE_ALLOW | TRACE_DENY))
        //{
        //    if (strcmp(entry->name, "AlpcSendWaitReceivePort") == 0)
        //    {
        //        HANDLE  hConnection;
        //        hConnection = (HANDLE*)user_args[0];
        //        DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "SBIE [syscall] p=%06d t=%06d - %s, handle = %X >>>>>>\n",
        //            PsGetCurrentProcessId(), PsGetCurrentThreadId(),
        //            entry->name,
        //            hConnection);
        //    }
        //}


        if (entry->handler1_func) {

            status = entry->handler1_func(proc, entry, user_args);

        } else {

            status = Syscall_Invoke(entry, user_args);
        }

        // Debug tip. Display all Alpc/Rpc here.

        if (proc->ipc_trace & (TRACE_ALLOW | TRACE_DENY))
        {
            HANDLE  hConnection = NULL;
            UNICODE_STRING* puStr = NULL;

            if ((strcmp(entry->name, "ConnectPort") == 0) ||
                (strcmp(entry->name, "AlpcConnectPort") == 0) )
            {
                hConnection = *(HANDLE*)user_args[0];
                puStr = (UNICODE_STRING*)user_args[1];

                //DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "SBIE [syscall] p=%06d t=%06d - %s, '%.*S', status = 0x%X, handle = %X\n",
                //    PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                //    entry->name,
                //    (puStr->Length / 2), puStr->Buffer,
                //    status, hConnection);
                //if (puStr && puStr->Buffer && wcsstr(puStr->Buffer, L"\\RPC Control\\LRPC-"))
                //{
                    //int i = 0;          // place breakpoint here if you want to debug a particular port
                //}
            }
            else if ( (strcmp(entry->name, "AlpcCreatePort") == 0) ||
                (strcmp(entry->name, "AlpcConnectPortEx") == 0) )
            {
                hConnection = *(HANDLE*)user_args[0];
                POBJECT_ATTRIBUTES  pObjectAttributes = (POBJECT_ATTRIBUTES)user_args[1];
                if (pObjectAttributes)
                    puStr = (UNICODE_STRING*)pObjectAttributes->ObjectName;

                //DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "SBIE [syscall] p=%06d t=%06d - %s, '%.*S', status = 0x%X, handle = %X\n",
                //    PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                //    entry->name,
                //    (puStr->Length / 2), puStr->Buffer,
                //    status, hConnection);
            }
            else if ((strcmp(entry->name, "ReplyWaitReceivePort") == 0) ||
                (strcmp(entry->name, "ReceiveMessagePort") == 0) ||
                (strcmp(entry->name, "AlpcSendReply") == 0) ||
                (strcmp(entry->name, "AlpcAcceptConnectPort") == 0) ||
                (strcmp(entry->name, "AlpcCreatePortSection") == 0) ||
                // these 2 APIs will generate a lot of output if we don't check status
                ((status != STATUS_SUCCESS) && ((strcmp(entry->name, "AlpcSendWaitReceivePort") == 0) || (strcmp(entry->name, "RequestWaitReplyPort") == 0))) )
            {
                hConnection = (HANDLE*)user_args[0];

                //DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "SBIE [syscall] p=%06d t=%06d - %s, status = 0x%X, handle = %X\n",
                //    PsGetCurrentProcessId(), PsGetCurrentThreadId(),
                //    entry->name,
                //    status, hConnection);
            }

            if (hConnection)
            {
                WCHAR trace_str[128];
                RtlStringCbPrintfW(trace_str, sizeof(trace_str), L"%.*S, status = 0x%X, handle = %X; ", //59 chars + entry->name
                    max(strlen(entry->name), 64), entry->name,
                    status, hConnection);
                const WCHAR* strings[4] = { trace_str, trace_str + (entry->name_len + 2), puStr ? puStr->Buffer : NULL, NULL };
                ULONG lengths[4] = {entry->name_len, wcslen(trace_str) - (entry->name_len + 4), puStr ? puStr->Length / 2 : 0, 0 };
                Session_MonitorPutEx(MONITOR_SYSCALL | (entry->approved ? MONITOR_OPEN : MONITOR_TRACE), 
                    strings, lengths, PsGetCurrentProcessId(), PsGetCurrentThreadId());

                traced = TRUE;
            }
        }

        if (!traced && ((proc->call_trace & TRACE_ALLOW) || ((status != STATUS_SUCCESS) && (proc->call_trace & TRACE_DENY))))
        {
            // Suppress Sbie's own calls to DeviceIoControlFile
            if ((strcmp(entry->name, "DeviceIoControlFile") != 0) || user_args[5] != API_SBIEDRV_CTLCODE)
            {
                WCHAR trace_str[128];
                RtlStringCbPrintfW(trace_str, sizeof(trace_str), L"%.*S, status = 0x%X", //59 chars + entry->name
                    max(strlen(entry->name), 64), entry->name,
                    status);
                const WCHAR* strings[3] = { trace_str, trace_str + (entry->name_len + 2), NULL };
                ULONG lengths[3] = { entry->name_len, wcslen(trace_str) - (entry->name_len + 2), 0 };
                Session_MonitorPutEx(MONITOR_SYSCALL | (entry->approved ? MONITOR_OPEN : MONITOR_TRACE),
                    strings, lengths, PsGetCurrentProcessId(), PsGetCurrentThreadId());
            }
        }

#ifdef _WIN64
        if (g_TrapFrameOffset) {
            if (pTrapFrame) {
#ifdef _M_ARM64
                //pTrapFrame->Pc = ret;
                //pTrapFrame->Sp = UserStack;
#else
                pTrapFrame->Rip = ret;
                pTrapFrame->Rsp = UserStack;
#endif
            }
        }
#endif

    } __except (EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
    }

    /*exception = 0;
    if (status != 0 && _wcsicmp(proc->image_name, L"chrome.exe") == 0) {
        exception = 1;
        //if (strstr(entry->name, "Pipe") != 0) exception = 1;
        //if (strstr(entry->name, "FsControl") != 0) exception = 1;
    }
    if (exception) {
        DbgPrint("[syscall] request p=%06d t=%06d - END   (%0X)  %s\n", PsGetCurrentProcessId(), PsGetCurrentThreadId(), status, entry->name);
    }*/

    if (proc->terminated) {

        Process_TerminateProcess(proc);
        return STATUS_PROCESS_IS_TERMINATING;
    }

    //
    // clear any thread impersonation set during the syscall, to restore
    // use of the highly restricted primary token in this thread
    //
    // note that in one special case we leave the impersonation token as
    // was set by Thread_SetInformationThread_ChangeNotifyToken, see there
    //

    if (status == STATUS_THREAD_NOT_IN_PROCESS
            && entry == Syscall_SetInformationThread
            && user_args[0] == (ULONG_PTR)NtCurrentThread()) {

        status = STATUS_SUCCESS;

    } else {

        Thread_ClearThreadToken();
    }

    /*if (! NT_SUCCESS(status)) {
        DbgPrint("Process %06d Syscall %04X Status %08X\n", proc->pid, syscall_index, status);
    }*/

    return status;
}


//---------------------------------------------------------------------------
// Syscall_Api_Query
//---------------------------------------------------------------------------


_FX NTSTATUS Syscall_Api_Query(PROCESS *proc, ULONG64 *parms)
{
    ULONG buf_len;
    ULONG *user_buf;
    ULONG *ptr;
    SYSCALL_ENTRY *entry;

#ifdef HOOK_WIN32K
    if (parms[2] == 1) { // 1 - win32k
        return Syscall_Api_Query32(proc, parms);
    }
    else if (parms[2] != 0) { // 0 - ntoskrnl
        return STATUS_INVALID_PARAMETER;
    }
#endif

    BOOLEAN add_names = parms[3] != 0;

    //
    // caller must be our service process
    //

    if (proc || (PsGetCurrentProcessId() != Api_ServiceProcessId))
        return STATUS_ACCESS_DENIED;

    //
    // allocate user mode space for syscall table
    //

    buf_len = sizeof(ULONG)         // size of buffer
            + sizeof(ULONG)         // offset to extra data (for SbieSvc)
            + (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT) // saved code from ntdll
            + List_Count(&Syscall_List) * ((sizeof(ULONG) * 2) + (add_names ? 64 : 0))
            + sizeof(ULONG) * 2     // final terminator entry
            ;

    user_buf = (ULONG *)parms[1];

    ProbeForWrite(user_buf, buf_len, sizeof(ULONG));

    //
    // populate the buffer with syscall data.  first we store
    // the size of the buffer, and then we leave room for ULONG used
    // by SbieSvc, and for the code for four ZwXxx stub functions
    //

    ptr = user_buf;
    *ptr = buf_len;
    ++ptr;

    *ptr = 0;           // placeholder for offset to extra offset
    ++ptr;

    memcpy(ptr, Syscall_NtdllSavedCode, (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT));
    ptr += (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT) / sizeof(ULONG);

    //
    // store service index number and (only on 32-bit Windows) also
    // the parameter count for each syscall into one ULONG.
    // store corresponding offset within ntdll into the other ULONG
    //

    entry = List_Head(&Syscall_List);
    while (entry) {

        ULONG syscall_index = (ULONG)entry->syscall_index;
#ifndef _WIN64
        ULONG param_count = (ULONG)entry->param_count;
        syscall_index |= (param_count * 4) << 24;
#endif ! _WIN64

        *ptr = syscall_index;
        ++ptr;
        *ptr = entry->ntdll_offset;
        ++ptr;
        if (add_names) {
            memcpy(ptr, entry->name, entry->name_len);
            ((char*)ptr)[entry->name_len] = 0;
            ptr += 16; // 16 * sizeog(ULONG) = 64
        }

        entry = List_Next(entry);
    }

    //
    // store a final zero terminator entry and return successfully
    //

    *ptr = 0;
    ++ptr;
    *ptr = 0;

    return STATUS_SUCCESS;
}


//---------------------------------------------------------------------------
// Syscall_Update_Lockdown
//---------------------------------------------------------------------------


_FX void Syscall_Update_Lockdown()
{
    SYSCALL_ENTRY *entry;

#ifdef HOOK_WIN32K
    Syscall_Update_Lockdown32();
#endif

    LIST approved_syscalls;
    Syscall_LoadHookMap(L"ApproveWinNtSysCall", &approved_syscalls);

    entry = List_Head(&Syscall_List);
    while (entry) {

        entry->approved = (Syscall_HookMapMatch(entry->name, entry->name_len, &approved_syscalls) != 0);

        entry = List_Next(entry);
    }

    Syscall_FreeHookMap(&approved_syscalls);
}


//---------------------------------------------------------------------------
// Syscall_QuerySystemInfo_SupportProcmonStack
//---------------------------------------------------------------------------


_FX BOOLEAN Syscall_QuerySystemInfo_SupportProcmonStack(
    PROCESS *proc, SYSCALL_ENTRY *syscall_entry, ULONG_PTR *user_args)
{
    BOOLEAN bRet = TRUE;

    // In Win10, case 0xb9 triggers WbCreateWarbirdProcess/WbDispatchOperation/WbSetTrapFrame
    // and PspSetContextThreadInternal (Warbird operation?) to deliver a apc call in the current
    // thread in user mode. Warbird needs the real thread context.
    // https://github.com/xpn/warbird_exploit
    // this exploit only works on x86 windows but can still crash a x64 one

    // It seems only NtQuerySystemInfomation is doing this.
    // Call Syscall_Set3 in Syscall_Init if we see a different syscall does this in the future.

    // It is safe to access the parameter. ProbeForRead has been done by the caller already.
    if (syscall_entry->param_count > 0)
    {
        if (user_args[0] == 0xb9)
        {
            bRet = FALSE;
        }
    }

    return bRet;
}


//---------------------------------------------------------------------------
// 32-bit and 64-bit code
//---------------------------------------------------------------------------


#ifdef _WIN64
#include "syscall_64.c"
#else ! _WIN64
#include "syscall_32.c"
#endif _WIN64

#include "syscall_open.c"
