/*
 * Copyright 2004-2020 Sandboxie Holdings, LLC 
 * Copyright 2020-2022 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/>.
 */

 //---------------------------------------------------------------------------
 // Sandboxie Low Level code injection mechanism moved here from SbieSvc
 //---------------------------------------------------------------------------

#include "dll.h"

#include <windows.h>
#include <stdio.h>

#include "common/my_version.h"
#include "core/low/lowdata.h"
#include "common/win32_ntddk.h"
#include "core/drv/api_defs.h"
#include "common/dllimport.h"
#include "common/arm64_asm.h"



//---------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
// Structures and Types
//---------------------------------------------------------------------------


typedef struct _MY_TARGETS {
	unsigned long long entry;
	unsigned long long data;
} MY_TARGETS;

#ifdef _M_ARM64

WINBASEAPI BOOL WINAPI IsWow64Process2(
	HANDLE hProcess,
	USHORT* pProcessMachine,
	USHORT* pNativeMachine
	);

WINBASEAPI BOOL WINAPI GetProcessInformation(
    HANDLE hProcess,
    PROCESS_INFORMATION_CLASS ProcessInformationClass,
    LPVOID ProcessInformation,
    DWORD ProcessInformationSize
    );

typedef PVOID (*P_VirtualAlloc2)(
    HANDLE Process,
    PVOID BaseAddress,
    SIZE_T Size,
    ULONG AllocationType,
    ULONG PageProtection,
    MEM_EXTENDED_PARAMETER* ExtendedParameters,
    ULONG ParameterCount
	);
	
#endif


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

SBIEDLL_EXPORT  HANDLE SbieDll_InjectLow_SendHandle(HANDLE hProcess);

void *SbieDll_InjectLow_CopyCode(
	HANDLE hProcess, SIZE_T lowLevel_size, UCHAR *code, ULONG code_len
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
);
BOOLEAN SbieDll_InjectLow_BuildTramp(
	BOOLEAN long_diff, UCHAR *code, ULONG_PTR addr
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
);
void *SbieDll_InjectLow_CopySyscalls(HANDLE hProcess, BOOLEAN is_wow64
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
);
void* InjectLow_AllocMemory(HANDLE hProcess, SIZE_T size, BOOLEAN executable
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
);
BOOLEAN SbieDll_InjectLow_CopyData(
	HANDLE hProcess, void *remote_addr, void *local_data);
#ifdef _WIN64
BOOLEAN SbieDll_Has32BitJumpHorizon(void * target, void * detour);
void * SbieDll_InjectLow_getPage(HANDLE hProcess, void *remote_addr);
#endif
BOOLEAN SbieDll_InjectLow_WriteJump(
	HANDLE hProcess, void *remote_addr, BOOLEAN long_diff
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
);

#if defined(_M_ARM64) || defined(_M_ARM64EC)
ULONG Hook_GetSysCallFunc(ULONG* aCode, void** pHandleStubHijack);
#endif

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


void *m_sbielow_ptr = NULL;
//adding two offsets variables to replace the "head" and "tail" dependency
ULONG m_sbielow_start_offset = 0;
ULONG m_sbielow_data_offset = 0;

ULONG m_sbielow_len = 0;

ULONG *m_syscall_data = NULL;

ULONG_PTR m_LdrInitializeThunk = 0;

#ifdef _M_ARM64

ULONG *m_syscall_ec_data = NULL;

ULONG_PTR m_LdrInitializeThunkEC = 0;

P_VirtualAlloc2 __sys_VirtualAlloc2 = NULL;

#endif

#include "core/low/lowlevel_code.c"

//---------------------------------------------------------------------------
// InjectLow_InitHelper
//---------------------------------------------------------------------------


_FX ULONG SbieDll_InjectLow_InitHelper()
{
    //
    // lock the SbieLow resource (embedded within the SbieSvc executable,
    // see lowlevel.rc) and find the offset to executable code, and length
    //
    IMAGE_DOS_HEADER *dos_hdr = 0;
    IMAGE_NT_HEADERS *nt_hdrs = 0;
    IMAGE_SECTION_HEADER *section = 0;
    IMAGE_DATA_DIRECTORY *data_dirs = 0;
    ULONG_PTR imageBase = 0;
    MY_TARGETS *targets = 0;

	ULONG errlvl = 0x11;

    HRSRC hrsrc = FindResource(Dll_Instance, L"LOWLEVEL", RT_RCDATA);
    if (! hrsrc)
        return errlvl;

    ULONG binsize = SizeofResource(Dll_Instance, hrsrc);
    if (! binsize)
		return errlvl;

    HGLOBAL hglob = LoadResource(Dll_Instance, hrsrc);
    if (! hglob)
		return errlvl;

    UCHAR *bindata = (UCHAR *)LockResource(hglob);
    if (! bindata)
		return errlvl;

	errlvl = 0x22;
    dos_hdr = (IMAGE_DOS_HEADER *)bindata;

    if (dos_hdr->e_magic == 'MZ' || dos_hdr->e_magic == 'ZM') {
        nt_hdrs = (IMAGE_NT_HEADERS *)((UCHAR *)dos_hdr + dos_hdr->e_lfanew);

        if (nt_hdrs->Signature == IMAGE_NT_SIGNATURE) {   // 'PE\0\0'
#ifndef _WIN64
            if (nt_hdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
                IMAGE_NT_HEADERS32 *nt_hdrs_32 = (IMAGE_NT_HEADERS32 *)nt_hdrs;
                IMAGE_OPTIONAL_HEADER32 *opt_hdr_32 = &nt_hdrs_32->OptionalHeader;
                data_dirs = &opt_hdr_32->DataDirectory[0];
                imageBase = opt_hdr_32->ImageBase;
            }
#else
            if (nt_hdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
                IMAGE_NT_HEADERS64 *nt_hdrs_64 = (IMAGE_NT_HEADERS64 *)nt_hdrs;
                IMAGE_OPTIONAL_HEADER64 *opt_hdr_64 = &nt_hdrs_64->OptionalHeader;
                data_dirs = &opt_hdr_64->DataDirectory[0];
                imageBase = (ULONG_PTR)opt_hdr_64->ImageBase;
            }
#endif
            else {

				return errlvl;
            }
        }
        else {

			return errlvl;
        }
    }

	ULONG zzzzz;

#ifdef _M_ARM64
	zzzzz = 4;
#else
    if (imageBase != 0) {
		return errlvl;
    }

	zzzzz = 1;
#endif

    section = IMAGE_FIRST_SECTION(nt_hdrs);
    if (nt_hdrs->FileHeader.NumberOfSections < 2) return errlvl;
    if (strncmp((char *)section[0].Name, SBIELOW_INJECTION_SECTION, strlen(SBIELOW_INJECTION_SECTION)) ||
        strncmp((char *)section[zzzzz].Name, SBIELOW_SYMBOL_SECTION, strlen(SBIELOW_SYMBOL_SECTION))) {
		return errlvl;
    }


    targets = (MY_TARGETS *)& bindata[section[zzzzz].PointerToRawData];
    m_sbielow_start_offset = (ULONG)(targets->entry - imageBase - section[0].VirtualAddress);
    m_sbielow_data_offset = (ULONG)(targets->data - imageBase - section[0].VirtualAddress);

    m_sbielow_ptr = bindata + section[0].PointerToRawData; //Old version: head;
    m_sbielow_len = section[0].SizeOfRawData; //Old version: (ULONG)(ULONG_PTR)(tail - head);

    if ((!m_sbielow_start_offset) || (!m_sbielow_data_offset))
		return errlvl;

    //
    // record information about ntdll and the virtual memory system
    //

	errlvl = 0x33;

    m_LdrInitializeThunk = (ULONG_PTR) GetProcAddress(Dll_Ntdll, "LdrInitializeThunk");

    if (! m_LdrInitializeThunk)
		return errlvl;

#ifdef _M_ARM64

	//
	// for x64 on arm64 we need the EC version of LdrInitializeThunk as well as VirtualAlloc2, 
	// if those are missing we will only fail SbieDll_InjectLow for x64 processes on arm64
	//

	WCHAR path[MAX_PATH];
	if (! GetSystemDirectory(path, MAX_PATH))
        wcscpy(path, L"C:\\Windows\\System32");
	wcscat(path, L"\\ntdll.dll");

	m_LdrInitializeThunkEC = (ULONG_PTR)Dll_Ntdll + FindDllExportFromFile(path, "#LdrInitializeThunk");

	__sys_VirtualAlloc2 = (P_VirtualAlloc2)GetProcAddress(Dll_KernelBase, "VirtualAlloc2");

#elif _WIN64
    if (Dll_Windows >= 10) {
        unsigned char * code;
        code = (unsigned char *)m_LdrInitializeThunk;
        if (*(ULONG *)&code[0] == 0x24048b48 && code[0xa] == 0x48) {
            m_LdrInitializeThunk += 0xa;
        }
    }
#endif

    return 0;
}

#if defined(_M_ARM64) || defined(_M_ARM64EC)

//---------------------------------------------------------------------------
// SbieDll_FindFirstSysCallFunc
//---------------------------------------------------------------------------


_FX ULONG* SbieDll_FindFirstSysCallFunc(ULONG* aCode, void** pHandleStubHijack)
{
	//
	// Scan the ntdll.dll/win32u.dll from its base up to 16mb max 
	// in search of the first ec syscall wrapper function
	//

	__try {
		for (ULONG pos = 0; pos < 16 * 1024 * 1024; aCode++, pos += 4) {
			if (Hook_GetSysCallFunc(aCode, pHandleStubHijack) != -1)
				return aCode;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {}

	return NULL;
}

//---------------------------------------------------------------------------
// SbieDll_GetEcExitThunkPtr
//---------------------------------------------------------------------------


_FX void* SbieDll_GetEcExitThunkPtr(void* HandleStubHijack)
{
    UCHAR* ptr = (UCHAR*)HandleStubHijack;

	//
	// Analyze the HandleStubHijack function to find the address of
	// __os_arm64x_dispatch_call_no_redirect which points to
	// xtajit64.dll!#BinaryTranslatorEcExitThunkCallX64 in EC processes
	//

    for (ULONG i = 0; i < 0x40; i += 4, ptr += 4) {

        ADRP adrp;
        adrp.OP = *(ULONG*)ptr;

        if (IS_ADRP(adrp) && adrp.Rd == 16) { // adrp x16, 0x180379000

            LDR ldr;
            ldr.OP = *(ULONG*)(ptr + 4);

            if (IS_LDR(ldr) && ldr.Rn == 16 && ldr.Rt == 16) { // ldr x16, [x16, #0xae0]

                LONG delta = (adrp.immHi << 2 | adrp.immLo) << 12;
                delta += (ldr.imm12 << ldr.size);

                // Note: ADRP clears the lower 12 bits of the PC
                return (void*)(((ULONG_PTR)ptr & ~0xFFF) + delta);
            }
        }
    }

    return NULL;
}

#endif

//---------------------------------------------------------------------------
// SbieDll_GetSysCallOffset
//---------------------------------------------------------------------------


_FX ULONG SbieDll_GetSysCallOffset(const ULONG *SyscallPtr, ULONG syscall_index)
{
    ULONG SyscallNum;

    while (SyscallPtr[0] || SyscallPtr[1]) {
            
        SyscallNum = SyscallPtr[0];
            
        SyscallNum &= 0xFFFF; // clear the not needed param count
            
        if (SyscallNum == syscall_index)
            return SyscallPtr[1];

        SyscallPtr += 2;
    }

    return 0;
}


//---------------------------------------------------------------------------
// InjectLow_InitSyscalls
//---------------------------------------------------------------------------


_FX ULONG SbieDll_InjectLow_InitSyscalls(BOOLEAN drv_init)
{
	const WCHAR *_SbieDll = L"\\" SBIEDLL L".dll";
	WCHAR sbie_home[512];
	ULONG status;
	ULONG len;
	SBIELOW_EXTRA_DATA *extra;
	WCHAR *ptr;
	ULONG *syscall_data;
#ifdef _M_ARM64
	ULONG *syscall_ec_data;
#endif

	//
	// Get the SbieDll Location
	//

	if (drv_init)
	{
		status = SbieApi_GetHomePath(NULL, 0, sbie_home, 512);
		if (status != 0)
			return status;
	}
	else
	{
		GetModuleFileName(Dll_Instance, sbie_home, 512);
		ptr = wcsrchr(sbie_home, L'\\');
		if (ptr)
			*ptr = L'\0';
	}

#define ULONG_DIFF(b,a) ((ULONG)((ULONG_PTR)(b) - (ULONG_PTR)(a)))

	//
	// get the list of syscall from the driver
	//

	if (!m_syscall_data) {
		syscall_data = (ULONG *)HeapAlloc(GetProcessHeap(), 0, 8192);
		if (!syscall_data)
			return STATUS_INSUFFICIENT_RESOURCES;
		*syscall_data = 0;
	}
	else
		syscall_data = m_syscall_data;

	if (drv_init)
	{
		//
		// Get a full sys call list from the driver
		//

		status = SbieApi_Call(API_QUERY_SYSCALLS, 1, (ULONG_PTR)syscall_data);
		if (status != 0)
			return status;

		len = *syscall_data;
		if ((!len) || (len & 3) || (len > 4096))
			return STATUS_INVALID_IMAGE_FORMAT;
	}
	else
	{
		//
		// Create a minimalistic driverless data
		//

		*syscall_data = sizeof(ULONG) + sizeof(ULONG); // + 248; // total_size 4, extra_data_offset 4, work area sizeof(INJECT_DATA)

		const char* NtdllExports[] = NATIVE_FUNCTION_NAMES;
		for (ULONG i = 0; i < NATIVE_FUNCTION_COUNT; ++i) {
			void* func_ptr = GetProcAddress(Dll_Ntdll, NtdllExports[i]);
			memcpy((void*)((ULONG_PTR)syscall_data + (sizeof(ULONG) + sizeof(ULONG)) + (NATIVE_FUNCTION_SIZE * i)), func_ptr, NATIVE_FUNCTION_SIZE);
		}

		len = *syscall_data;
	}

	/*
	struct SYS_CALL_DATA {
		ULONG size_of_buffer;
		ULONG offset_to_extra_data;

		struct _CODE {
			UCHAR bytes[NATIVE_FUNCTION_SIZE]
		}			saved_code[NATIVE_FUNCTION_COUNT];

		struct SYSCALL_INFO {
			ULONG number;
			ULONG offset;
		}			sys_call_list[n + 1];

		// the above is provided by the driver, the below we have to construct

		ULONG64 EcExitThunkPtr;

		struct SBIELOW_EXTRA_DATA {
			// ...
		}			extra_data; // 52 bytes

		char LdrLoadDll_str[16]			= "LdrLoadDll";
		char LdrGetProcAddr_str[28]		= "LdrGetProcedureAddress";
		char NtRaiseHardError_str[20]	= "NtRaiseHardError";
		char RtlFindActCtx_str[44]		= "RtlFindActivationContextSectionString";

		wchar_t KernelDll_str[13]		= L"kernel32.dll";

		wchar_t NativeSbieDll_str[]		= L"...\\SbieDll.dll";
		wchar_t Arm64ecSbieDll_str[]	= L"...\\64\\SbieDll.dll";
		wchar_t Wow64SbieDll_str[]		= L"...\\32\\SbieDll.dll";

		struct INJECT_DATA {
			// ...
		}			InjectData;
	}
	*/

	//
	// the second ULONG in syscall_data points to extra data appended
	// by us here on top of what the driver returned
	//

	extra = (SBIELOW_EXTRA_DATA *)((ULONG_PTR)syscall_data + len);

	syscall_data[1] = len;

	//
	// write an ASCII string for LdrLoadDll (see core/low/inject.c)
	//

	ptr = (WCHAR *)((ULONG_PTR)extra + sizeof(SBIELOW_EXTRA_DATA));

	strcpy((char *)ptr, "LdrLoadDll");

	extra->LdrLoadDll_offset = ULONG_DIFF(ptr, extra);
	ptr += 16 / sizeof(WCHAR);

	//
	// write an ASCII string for LdrGetProcedureAddress
	//

	strcpy((char *)ptr, "LdrGetProcedureAddress");

	extra->LdrGetProcAddr_offset = ULONG_DIFF(ptr, extra);
	ptr += 28 / sizeof(WCHAR);

	//
	// write an ASCII string for NtRaiseHardError
	//

	strcpy((char *)ptr, "NtRaiseHardError");

	extra->NtRaiseHardError_offset = ULONG_DIFF(ptr, extra);
	ptr += 20 / sizeof(WCHAR);

	//
	// write an ASCII string for RtlFindActivationContextSectionString
	//

	strcpy((char *)ptr, "RtlFindActivationContextSectionString");

	extra->RtlFindActCtx_offset = ULONG_DIFF(ptr, extra);
	ptr += 44 / sizeof(WCHAR);

#ifdef _M_ARM64
	//
	// write an ASCII string for LdrQueryImageFileExecutionOptionsEx
	//

	strcpy((char *)ptr, "LdrQueryImageFileExecutionOptionsEx");

	extra->RtlImageOptionsEx_offset = ULONG_DIFF(ptr, extra);
	ptr += 40 / sizeof(WCHAR);
#endif

	//
	// ntdll loads kernel32 without a path, we will do the same
	// in our hook for RtlFindActivationContextSectionString,
	// see entry.asm
	//

	wcscpy(ptr, L"kernel32.dll");

	len = wcslen(ptr);
	extra->KernelDll_offset = ULONG_DIFF(ptr, extra);
	extra->KernelDll_length = len * sizeof(WCHAR);
	ptr += len + 1;

	//
	// append paths for native and wow64 SbieDll to the syscall buffer
	//

	wcscpy(ptr, sbie_home);
	wcscat(ptr, _SbieDll);

	len = (ULONG)wcslen(ptr);
	extra->NativeSbieDll_offset = ULONG_DIFF(ptr, extra);
	extra->NativeSbieDll_length = len * sizeof(WCHAR);
	ptr += len + 1;

#ifdef _M_ARM64
	wcscpy(ptr, sbie_home);
	wcscat(ptr, L"\\64");
	wcscat(ptr, _SbieDll);

	len = (ULONG)wcslen(ptr);
	extra->Arm64ecSbieDll_offset = ULONG_DIFF(ptr, extra);
	extra->Arm64ecSbieDll_length = len * sizeof(WCHAR);
	ptr += len + 1;
#endif

#ifdef _WIN64
	wcscpy(ptr, sbie_home);
	wcscat(ptr, L"\\32");
	wcscat(ptr, _SbieDll);

	len = (ULONG)wcslen(ptr);
	extra->Wow64SbieDll_offset = ULONG_DIFF(ptr, extra);
	extra->Wow64SbieDll_length = len * sizeof(WCHAR);
	ptr += len + 1;
#endif _WIN64


	// 
	// Note: the work area was now moved after the extra area 
	// hence the syscall_data is no longer overwritten
	//

	extra->InjectData_offset = ULONG_DIFF(ptr, extra);

#ifdef _WIN64

	//
	// Copy the required non shell code into INJECT_DATA.DetourCode_*
	//

	memcpy((UCHAR*)ptr + FIELD_OFFSET(INJECT_DATA, DetourCode_x86), SbieDll_ShellCode_x86, sizeof(SbieDll_ShellCode_x86));
#endif

	//
	// adjust size of syscall buffer to include path strings
	//

	*syscall_data = ULONG_DIFF(ptr, syscall_data) + sizeof(INJECT_DATA);

	m_syscall_data = syscall_data;


#ifdef _M_ARM64

	//
	// create m_syscall_ec_data for arm64 ec
	//

	if (!m_syscall_ec_data) {
		syscall_ec_data = (ULONG *)HeapAlloc(GetProcessHeap(), 0, 8192);
		if (!syscall_ec_data)
			return STATUS_INSUFFICIENT_RESOURCES;
		*syscall_ec_data = 0;
	}
	else
		syscall_ec_data = m_syscall_ec_data;

	if (drv_init)
	{
		//
		// Search the ntdll.dll for the unexported syscall wrapper functions
		//

		void* HandleStubHijack = NULL;
		ULONG* aCode = SbieDll_FindFirstSysCallFunc((ULONG*)Dll_Ntdll, &HandleStubHijack);

		if (aCode == NULL) {
			SbieApi_Log(2303, L"syscall, wrappers not found");
			return STATUS_SUCCESS; // we will fail process creation for ec processes later
		}

		void* EcExitThunkPtr = SbieDll_GetEcExitThunkPtr(HandleStubHijack);

		//
		// create the syscall EC data from the ntdll.sll's syscall wrapper functions
		//

		const ULONG* SyscallPtr = (ULONG*)((ULONG64)syscall_data + sizeof(ULONG) + sizeof(ULONG) + (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT));
		ULONG* SyscallPtrEC = (ULONG*)((ULONG64)syscall_ec_data + sizeof(ULONG) + sizeof(ULONG) + (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT));

		for (ULONG pos = 0; pos < 128; aCode++, pos += 4) {

			ULONG SyscallNum = Hook_GetSysCallFunc(aCode, NULL);
			if (SyscallNum == -1)
				continue;

			if (SbieDll_GetSysCallOffset(SyscallPtr, SyscallNum)) {

				SyscallPtrEC[0] = SyscallNum;
				SyscallPtrEC[1] = ULONG_DIFF(aCode, Dll_Ntdll);
				SyscallPtrEC += 2;
			}

			//if (aCode[13] != 0 || aCode[14] != 0 || aCode[15] != 0 || aCode[16] != 0)
			//    break;
			//aCode += (13 + 3 + 3 + 1);
			aCode += 16;
			pos = 0; // reset
		}

		// end marker
		SyscallPtrEC[0] = 0;
		SyscallPtrEC[1] = 0;
		SyscallPtrEC += 2;

		// put the EcExitThunkPtr at (extra_data_offset - 8)
		*(ULONG64*)SyscallPtrEC = (ULONG64)EcExitThunkPtr;
		SyscallPtrEC += sizeof(ULONG64) / sizeof(ULONG);

		*syscall_ec_data = ULONG_DIFF(SyscallPtrEC, syscall_ec_data);
	}
	else
	{
		*syscall_ec_data = sizeof(ULONG) + sizeof(ULONG); // +248; // total_size 4, extra_data_offset 4, work area sizeof(INJECT_DATA)
	}

	//
	// copy the required functions duplicates to the ec data
	//

	memcpy(syscall_ec_data + 2, syscall_data + 2, NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT);

	//
	// copy the extra data section
	//

	len = *syscall_ec_data;
	ULONG extra_len = syscall_data[0] - syscall_data[1];

	memcpy((UCHAR*)syscall_ec_data + len, extra, extra_len);


	//
	// adjust length and extra offset
	//

	syscall_ec_data[1] = len;
	syscall_ec_data[0] = len + extra_len;

	m_syscall_ec_data = syscall_ec_data;
#endif

#undef ULONG_DIFF

	return STATUS_SUCCESS;
}


//---------------------------------------------------------------------------
// InjectLow
//---------------------------------------------------------------------------


_FX ULONG SbieDll_InjectLow(HANDLE hProcess, ULONG init_flags, BOOLEAN dup_drv_handle)
{
	//SVC_PROCESS_MSG *msg = (SVC_PROCESS_MSG *)_msg;
	ULONG errlvl = 0;

	SBIELOW_DATA lowdata;
	memset(&lowdata, 0, sizeof(lowdata));

	lowdata.flags.init_flags = init_flags;

#ifdef _M_ARM64
	
	USHORT ProcessMachine = 0xFFFF;
	if (Dll_OsBuild >= 22000) { // Win 11
		PROCESS_MACHINE_INFORMATION info;
		if(GetProcessInformation(hProcess, (PROCESS_INFORMATION_CLASS)ProcessMachineTypeInfo, &info, sizeof(info)))
			ProcessMachine = info.ProcessMachine;
	} 
	else {  // Win 10
		IsWow64Process2(hProcess, &ProcessMachine, NULL);
	}

	if (ProcessMachine != 0xFFFF) {
        
		//
        // Currently 32-bit ARM processes are not supported,
        // there doesn't seam to be a significant amount of these out there anyways
        //

        if (ProcessMachine == IMAGE_FILE_MACHINE_ARMNT) {
            lowdata.flags.is_wow64 = 1;
			SetLastError(ERROR_NOT_SUPPORTED);
			errlvl = -1;
			goto finish;
        }
        else if (ProcessMachine == IMAGE_FILE_MACHINE_AMD64) {
            lowdata.flags.is_arm64ec = 1;
            lowdata.flags.is_xtajit = 1;
        }
        else if (ProcessMachine == IMAGE_FILE_MACHINE_I386) {
            lowdata.flags.is_wow64 = 1;
            lowdata.flags.is_xtajit = 1;
        }
	}
	else {

		SetLastError(ERROR_INVALID_FUNCTION);
		errlvl = 0xCC;
		goto finish;
	}

#endif

	//
	// verify all aspects of initialization were successful
	//
	if ((!m_sbielow_ptr) || (!m_syscall_data)
#ifdef _M_ARM64
			|| (lowdata.flags.is_arm64ec && ((!m_LdrInitializeThunkEC) || (!__sys_VirtualAlloc2) || (!m_syscall_ec_data)))
#endif
		) {

		SetLastError(ERROR_NOT_READY);
		errlvl = 0xFF;
		goto finish;
	}

	//
	// prepare the lowdata parameters area to copy into target process
	//

	lowdata.ntdll_base = (ULONG64)(ULONG_PTR)Dll_Ntdll;

#ifdef _WIN64
	static const WCHAR *_Ntdll32 = L"\\syswow64\\ntdll.dll";    // 19 chars
#ifdef _M_ARM64
	static const WCHAR *_NChpe32 = L"\\SyChpe32\\ntdll.dll";    // 19 chars

	//
	// Sandboxie requires CHPE to be disabled for x86 applications
	// for simplicity and compatibility with forced processes we disable CHPE globally using the Wow64\x86\xtajit key
	// alternatively we can hook LdrQueryImageFileExecutionOptionsEx from LowLevel.dll
	// and return 0 for the L"LoadCHPEBinaries" option
	// 
	// HKLM\SOFTWARE\Microsoft\Wow64\x86\xtajit -> LoadCHPEBinaries = 0
	// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\{image_name} -> LoadCHPEBinaries = 0
	// HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers -> {full_image_path} = "~ ARM64CHPEDISABLED"
	//  
#endif

	if (lowdata.flags.is_wow64) {
		lowdata.ntdll_wow64_base = FindDllBase64(hProcess, _Ntdll32);
#ifdef _M_ARM64
		if (!lowdata.ntdll_wow64_base) {
			//lowdata.ntdll_wow64_base = FindDllBase64(hProcess, _NChpe32);
			//if (lowdata.ntdll_wow64_base) 
			//	lowdata.flags.is_chpe32 = 1;
			//else {
				SetLastError(ERROR_NOT_SUPPORTED);
				errlvl = -2;
				goto finish;
			//}
		}
#endif
	}
#endif

	lowdata.RealNtDeviceIoControlFile = (ULONG64)GetProcAddress((HMODULE)lowdata.ntdll_base, "NtDeviceIoControlFile");
	lowdata.NativeNtRaiseHardError = (ULONG64)GetProcAddress((HMODULE)lowdata.ntdll_base, "NtRaiseHardError");

	//
	// on 64-bit Windows 8, there might be a difference of more than
	// 2GB bytes between ntdll and the injected SbieLow, which requires
	// use of longer jump sequences than the 5-byte 0xE9 relative jump
	//
	if (Dll_Windows >= 10) {
		lowdata.flags.is_win10 = 1;
	}

#ifdef _M_ARM64
	//
	// Windows on Arm64 offers only 16 bytes per syscall for our detour,
	// not enough to store the syscall number (4), load (4) a 64 bit address (8) and jump to it (4).
	// 
	// Hence we allocate the memory containing SystemServiceAsm in the lower 4 GB's
	// this way we save 4 bytes on the address and can fit in the 16 bytes available
	// 
	// An alternative aproche is to use the jump table as 16 bytes are just enough for 
	// a 64 bit detour, and in the jump table we have as much space per syscall as we need.
	//
#endif

	SIZE_T lowLevel_size;
#ifdef _WIN64 
	BOOLEAN use_jump_Table = FALSE;
	if(use_jump_Table)
		lowLevel_size = m_sbielow_len + sizeof(SBIELOW_J_TABLE) + 0x400;
	else
#endif
		lowLevel_size = m_sbielow_len;
		
	void *remote_addr = SbieDll_InjectLow_CopyCode(hProcess, lowLevel_size, lowdata.LdrInitializeThunk_tramp, sizeof(lowdata.LdrInitializeThunk_tramp)
#ifdef _M_ARM64
		, (BOOLEAN)lowdata.flags.is_arm64ec
#endif
	);
	if (!remote_addr) {
		errlvl = 0x33;
		goto finish;
	}

#ifndef _M_ARM64
#ifdef _WIN64
	lowdata.flags.long_diff = 1;
	if (SbieDll_Has32BitJumpHorizon((void *)m_LdrInitializeThunk, remote_addr)) {
		lowdata.flags.long_diff = 0;
	}
#else
	lowdata.flags.long_diff = 0;
#endif
#endif

	if (dup_drv_handle)
	{
		//
		// duplicate the SbieDrv API file device handle into target process
		//

		lowdata.api_device_handle = (ULONG64)(ULONG_PTR)
			SbieDll_InjectLow_SendHandle(hProcess);
		if (!lowdata.api_device_handle) {

			errlvl = 0x22;
			goto finish;
		}

		lowdata.api_sbiedrv_ctlcode = API_SBIEDRV_CTLCODE;

		lowdata.api_invoke_syscall = API_INVOKE_SYSCALL;
	}

	//
	// the driver sent us a copy of some functions from ntdll in the
	// syscall_data buffer, copy that code to SbieLow through the
	// lowdata area (see also core/drv/syscall.c and core/low/lowdata.h)
	//

	memcpy(lowdata.NtDelayExecution_code, &m_syscall_data[2], (NATIVE_FUNCTION_SIZE * NATIVE_FUNCTION_COUNT));
	// lowdata.NtDelayExecution_code
	// lowdata.NtDeviceIoControlFile_code
	// lowdata.NtFlushInstructionCache_code
	// lowdata.NtProtectVirtualMemory_code

	//
	// allocate space for and write the lowlevel (SbieLow) code
	// and copy code at LdrInitializeThunk from target process
	//

#ifdef _WIN64
	if(use_jump_Table)
		lowdata.Sbie64bitJumpTable = (SBIELOW_J_TABLE *)((ULONG_PTR)remote_addr + m_sbielow_len + 0x400); //(0x400 - (m_sbielow_len & 0x3ff))+ m_sbielow_len;
#endif

	//
	// remove hard coded data block offset 
	ULONG_PTR tramp_remote_addr =   // calculate address in remote process
		(ULONG_PTR)remote_addr
		+ m_sbielow_data_offset     // offset of args area
		+ FIELD_OFFSET(SBIELOW_DATA, LdrInitializeThunk_tramp);

	if (!SbieDll_InjectLow_BuildTramp(lowdata.flags.long_diff == 1,
		lowdata.LdrInitializeThunk_tramp, tramp_remote_addr
#ifdef _M_ARM64
			, (BOOLEAN)lowdata.flags.is_arm64ec
#endif
		)) {

		//UCHAR *code = lowdata.LdrInitializeThunk_tramp;
		//SbieApi_LogEx(msg->session_id, 2335,
		//	L"%S [%02X %02X %02X %02X %02X %02X"
		//	L" %02X %02X %02X %02X %02X %02X]",
		//	msg->process_name,
		//	code[0], code[1], code[2], code[3], code[4], code[5],
		//	code[6], code[7], code[8], code[9], code[10], code[11]);

		SetLastError(ERROR_UNKNOWN_PRODUCT);

		errlvl = 0x44;
		goto finish;
	}

	//
	// copy the syscall data buffer (m_syscall_data) to target process
	//

	void* remote_syscall_data = SbieDll_InjectLow_CopySyscalls(hProcess, (BOOLEAN)lowdata.flags.is_wow64
#ifdef _M_ARM64
		, (BOOLEAN)lowdata.flags.is_arm64ec
#endif
	);
	if (!remote_syscall_data) {

		errlvl = 0x55;
		goto finish;
	}

	lowdata.syscall_data = (ULONG64)(ULONG_PTR)remote_syscall_data;

	//
	// write lowdata parameter area, including the converted trampoline
	// code, into target process, and make it execute-read
	//
	if (!SbieDll_InjectLow_CopyData(hProcess, remote_addr, &lowdata)) {

		errlvl = 0x66;
		goto finish;
	}

	//
	// overwrite the top of LdrInitializeThunk to jump to injected code
	// note that we have to skip the 8-byte signature (.HEAD.00)
	//
	// Removed hard coded dependency on (.HEAD.00). No longer need to add 8 to
	// the remote_addr
	if (!SbieDll_InjectLow_WriteJump(hProcess, (UCHAR *)remote_addr + m_sbielow_start_offset, lowdata.flags.long_diff == 1
#ifdef _M_ARM64
		, (BOOLEAN)lowdata.flags.is_arm64ec
#endif
	)) {
		errlvl = 0x77;
		goto finish;
	}

	//
	// finish
	//

finish:

	return errlvl;
}


//---------------------------------------------------------------------------
// InjectLow_AllocMemory
//---------------------------------------------------------------------------


_FX void* InjectLow_AllocMemory(HANDLE hProcess, SIZE_T size, BOOLEAN executable
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
) {
	SIZE_T region_size = size;
	void *remote_addr = NULL;

#ifdef _M_ARM64
	if (use_arm64ec && executable) 
	{
		MEM_EXTENDED_PARAMETER Parameter = { 0 };
		Parameter.Type = MemExtendedParameterAttributeFlags;
		Parameter.ULong64 = MEM_EXTENDED_PARAMETER_EC_CODE;

		for (UINT_PTR base_addr = 0x10000; !remote_addr; base_addr <<= 1) {

			remote_addr = __sys_VirtualAlloc2(hProcess, (void*)base_addr, region_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE, &Parameter, 1);
		}
	} 
	else 
#endif

	//
	// allocate virtual memory somewhere in the process.  to force an
	// address in the low 24-bits of the address space, we have to use
	// NtAllocateVirtalMemory and specify ZeroBits = 8 (32 - 8 = 24)
	//

	//for (int i = 8; !remote_addr && i > 2; i--) {
	for (int i = 8; !remote_addr && i >= 0; i--) {
		NTSTATUS status = NtAllocateVirtualMemory(hProcess, &remote_addr, i, &region_size, MEM_COMMIT | MEM_RESERVE, executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
		if (!NT_SUCCESS(status)) {
			SetLastError(RtlNtStatusToDosError(status));
			remote_addr = NULL;
			region_size = size;
		}
	}

	return remote_addr;
}


//---------------------------------------------------------------------------
// InjectLow_SendHandle
//---------------------------------------------------------------------------


_FX HANDLE SbieDll_InjectLow_SendHandle(HANDLE hProcess)
{
	NTSTATUS status;
	HANDLE HandleLocal, HandleRemote;
	UNICODE_STRING uni;
	OBJECT_ATTRIBUTES objattrs;
	IO_STATUS_BLOCK MyIoStatusBlock;

	//
	// open the Sandboxie driver API file handle
	//

	RtlInitUnicodeString(&uni, API_DEVICE_NAME);
	InitializeObjectAttributes(
		&objattrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL);

	status = NtOpenFile(
		&HandleLocal, FILE_GENERIC_READ, &objattrs, &MyIoStatusBlock,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0);

	if (NT_SUCCESS(status)) {

		//
		// duplicate opened handle into new process
		//

		BOOL ok = DuplicateHandle(NtCurrentProcess(), HandleLocal,
			hProcess, &HandleRemote, 0, FALSE,
			DUPLICATE_SAME_ACCESS);

		CloseHandle(HandleLocal);

		if (ok) {

			return HandleRemote;
		}
	}

	return NULL;
}


//---------------------------------------------------------------------------
// InjectLow_CopyCode
//---------------------------------------------------------------------------


_FX void *SbieDll_InjectLow_CopyCode(HANDLE hProcess, SIZE_T lowLevel_size, UCHAR *code, ULONG code_len
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
) {
	void* remote_addr;
	void* pLdrInitializeThunk = (void*)m_LdrInitializeThunk;

#ifdef _M_ARM64
	if (use_arm64ec) 
		pLdrInitializeThunk = (void*)m_LdrInitializeThunkEC;
#endif

	remote_addr = InjectLow_AllocMemory(hProcess, lowLevel_size, TRUE
#ifdef _M_ARM64
		, use_arm64ec
#endif
	);

	if (remote_addr) {

		//
		// copy SbieLow into the allocated region in the new process
		//

		SIZE_T len1 = m_sbielow_len;
		SIZE_T len2 = 0;
		BOOL vm_ok = WriteProcessMemory(
			hProcess, remote_addr, m_sbielow_ptr,
			len1, &len2);

		if (vm_ok && len1 == len2) {

			//
			// copy code at LdrInitializeThunk from new process
			//

			len1 = code_len;
			len2 = 0;
			/*
			sprintf(buffer,"CopyCode: copy ldr size %d\n",code_len);
			OutputDebugStringA(buffer);
			*/
			vm_ok = ReadProcessMemory(
				hProcess, pLdrInitializeThunk, code,
				len1, &len2);

			if (vm_ok && len1 == len2) {

				return remote_addr;
			}
		}
	}

	return NULL;
}


//---------------------------------------------------------------------------
// InjectLow_BuildTramp
//---------------------------------------------------------------------------


_FX BOOLEAN SbieDll_InjectLow_BuildTramp(
	BOOLEAN long_diff, UCHAR *code, ULONG_PTR addr
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
) {

#ifdef _M_ARM64

	//
	// code contains already a copy of LdrInitializeThunk (48 bytes)
	// here we need to add the jump to the original at the right location
	//

	ULONG code_len = 16; // the length of the jump to detour code

	ULONG* aCode = (ULONG*)(code + code_len);
	*aCode++ = 0x58000048;	// ldr x8, 8
	*aCode++ = 0xD61F0100;	// br x8
	*(DWORD64*)aCode = (use_arm64ec ? m_LdrInitializeThunkEC : m_LdrInitializeThunk) + code_len;

#else

#define IS_1BYTE(a)     (                 code[offset + 0] == (a))
#define IS_2BYTE(a,b)   (IS_1BYTE(a)   && code[offset + 1] == (b))
#define IS_3BYTE(a,b,c) (IS_2BYTE(a,b) && code[offset + 2] == (c))

	//
	// skip past several bytes in the code copied from the top of the
	// LdrInitializeThunk function, where we will inject a jmp sequence.
	//
	// a simple E9 relative JMP five byte instruction in most cases,
	// a slightly longer seven byte version in case there is a long
	// distance between ntdll and SbieLow, i.e. on 64-bit Windows 8
	//

	ULONG code_len = (long_diff ? 7 : 5);

	ULONG offset = 0;
	while (offset < code_len) {

		ULONG inst_len = 0;

		if (0)
			;

		// push ebp
		else if (IS_1BYTE(0x55))
			inst_len = 1;

		// mov ebp, esp
		else if (IS_2BYTE(0x8B, 0xEC))
			inst_len = 2;

		// mov edi, edi
		else if (IS_2BYTE(0x8B, 0xFF))
			inst_len = 2;

		// push ebx
		else if (IS_2BYTE(0xFF, 0xF3))
			inst_len = 2;

		// push rbx (Windows 8.1)
		else if (IS_2BYTE(0x40, 0x53))
			inst_len = 2;

		// mov dword ptr [esp+imm8],eax
		else if (IS_3BYTE(0x89, 0x44, 0x24))
			inst_len = 4;

		// lea eax, esp+imm8
		else if (IS_3BYTE(0x8D, 0x44, 0x24))
			inst_len = 4;

		// sub rsp, imm8
		else if (IS_3BYTE(0x48, 0x83, 0xEC))
			inst_len = 4;

		// mov rbx, rcx
		else if (IS_3BYTE(0x48, 0x8B, 0xD9))
			inst_len = 3;

		/*
		else if (IS_3BYTE(0x48, 0x8B, 0x04))
			inst_len = 4;
		*/
		//
		// abort if we don't recognize the instruction
		//

		if (!inst_len) {

			return FALSE;
		}

		offset += inst_len;
	}

#undef IS_3BYTE
#undef IS_2BYTE
#undef IS_1BYTE

	//
	// append a jump instruction at the bottom of our trampoline for
	// LdrInitializeThunk, which jumps back to the real LdrInitializeThunk
	//
	// note that on Windows 8 the difference between the address of
	// LdrInitializeThunk in the 64-bit ntdll and where SbieLow was copied
	// may be greater than 32-bit, so we use JMP QWORD rather than the
	// 5-byte 0xE9 relative JMP
	//
#ifdef _WIN64
	if (!long_diff) {

		if (Dll_Windows >= 10) {
			code[offset] = 0x48;
			code[offset + 1] = 0xE9;					// jmp
			*(ULONG *)&code[offset + 2] = (ULONG)
				(m_LdrInitializeThunk + offset - (addr + offset + 6));

		}
		else {
			code[offset] = 0xe9;
			*(ULONG *)&code[offset + 1] = (ULONG)
				(m_LdrInitializeThunk + offset - (addr + offset + 5));
		}
	}
	else {

		*(USHORT *)&code[offset] = 0x25FF;              // jmp qword ptr
		*(ULONG *)&code[offset + 2] = 0;
		*(ULONG64 *)&code[offset + 6] = m_LdrInitializeThunk + offset;
	}
#else
	code[offset] = 0xE9;								// jmp 

	*(ULONG *)&code[offset + 1] = (ULONG)
		(m_LdrInitializeThunk + offset - (addr + offset + 5));
#endif

#endif

	return TRUE;
}


//---------------------------------------------------------------------------
// InjectLow_CopySyscalls
//---------------------------------------------------------------------------


_FX void *SbieDll_InjectLow_CopySyscalls(HANDLE hProcess, BOOLEAN is_wow64
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
) {
	void *remote_addr = NULL;
	ULONG* data;
#ifdef _M_ARM64
	if(use_arm64ec)
		data = m_syscall_ec_data;
	else
#endif
		data = m_syscall_data;
	SIZE_T region_size = *data;

	remote_addr = InjectLow_AllocMemory(hProcess, region_size
		, is_wow64 // we copy the detour code into this area, hence executable = TRUE
#ifdef _M_ARM64
		//|| use_arm64ec
		, FALSE
#endif
	);

	if (remote_addr) {

		//
		// copy the syscall data buffer into the new process
		//

		SIZE_T len1 = *data;
		SIZE_T len2 = 0;
		BOOL vm_ok = WriteProcessMemory(
			hProcess, remote_addr, data, len1, &len2);
		if (vm_ok && len1 == len2) {

			return remote_addr;
		}
	}

	return NULL;
}


//---------------------------------------------------------------------------
// InjectLow_CopyData
//---------------------------------------------------------------------------


_FX BOOLEAN SbieDll_InjectLow_CopyData(
	HANDLE hProcess, void *remote_addr, void *local_data)
{
	//
	// copy SBIELOW_DATA data into the area reserved within SbieLow
	// (i.e. at offset SBIELOW_DATA_OFFSET) in the new process
	//

	void *data_addr = (void *)((ULONG_PTR)remote_addr + m_sbielow_data_offset);
	SIZE_T len1 = sizeof(SBIELOW_DATA);
	SIZE_T len2 = 0;
	BOOL vm_ok = WriteProcessMemory(
		hProcess, data_addr, local_data, len1, &len2);
	if (vm_ok && len1 == len2) {

		ULONG protect;
		vm_ok = VirtualProtectEx(hProcess, remote_addr, m_sbielow_len,
			// we want to be able to pass data from the low level dll we do this here
			// we set PAGE_EXECUTE_READ in SbieDll.dll Dll_Ordinal1
			PAGE_EXECUTE_READWRITE, &protect);
			//PAGE_EXECUTE_READ, &protect);
		if (vm_ok) {
			return TRUE;
		}
	}

	return FALSE;
}

#ifndef _M_ARM64
#ifdef _WIN64

_FX BOOLEAN SbieDll_Has32BitJumpHorizon(void * target, void * detour)
{
	ULONG_PTR diff;
	long long delta;
	diff = (ULONG_PTR)((ULONG_PTR)target - (ULONG_PTR)detour);
	delta = diff;
	delta < 0 ? delta *= -1 : delta;
	//is DetourFunc in 32bit jump range
	if (delta < 0x80000000) {
		return TRUE;
	}
	return FALSE;
}

_FX void * SbieDll_InjectLow_getPage(HANDLE hProcess, void *remote_addr)
{
	SIZE_T mySize;
	ULONG_PTR tempAddr;
	void * myTable = 0;
	UCHAR *func;
	ULONG myProtect;
	short myBuffer[1024];
	SIZE_T readSize;
	BOOL myVM;
	HANDLE myKernel32;
	HANDLE myNtDll;
	//HANDLE myTestDll;
	func = (UCHAR *)((ULONG_PTR)m_LdrInitializeThunk);
	myKernel32 = GetModuleHandleA("kernel32.dll");
	myNtDll = GetModuleHandleA("ntdll.dll");
	//  myTestDll = 0; 

	/*
	if(myTestDll) {
		//for testing remove this code!
		sprintf(buffer,"Dll Collision Test: address %p\n",myTestDll);
		OutputDebugStringA(buffer);
		myTable = VirtualAllocEx(hProcess,myTestDll,0x100, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
	}
	else {
	*/
	tempAddr = ((ULONG_PTR)myNtDll < (ULONG_PTR)myKernel32 ? (ULONG_PTR)myNtDll : (ULONG_PTR)myKernel32) - 0x10000;
	myTable = VirtualAllocEx(hProcess, (void *)tempAddr, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	//  }
	/*
	else {
		//use hack if all else fails
		//OutputDebugStringA("Unable to allocate page!\n");
		max_attempts = 0;
	}
	*/

	if (myTable) {
		mySize = 0;
		if (SbieDll_Has32BitJumpHorizon(myTable, func)) {
			WriteProcessMemory(hProcess, myTable, &remote_addr, 8, &mySize);
			/*
			sprintf(buffer,"myPage = %p, kernel32 = %p, ntdll = %p\n",myTable,myKernel32,myNtDll);
			OutputDebugStringA(buffer);
			*/
			if (mySize == 8) {
				return myTable;
			}
		}
	}
	/*
	sprintf(buffer,"Failed to find table for target address %p, func = %p\n",myTable,func);
	OutputDebugStringA(buffer);
	*/

	readSize = 1;
	tempAddr = (ULONG_PTR)func - 8;
	ReadProcessMemory(hProcess, (void *)((ULONG_PTR)tempAddr), &myBuffer, 8, &readSize);

	// if hot patch area
	if (*((ULONG_PTR *)&myBuffer) == 0x9090909090909090 || *((ULONG_PTR *)&myBuffer) == 0xcccccccccccccccc) {
		//OutputDebugStringA("Using hotpatch area\n");
		myTable = (void *)tempAddr;
	}
	else { //not hot patch area: This is a hack
		//patch area in .rdata section of ntdll
		ReadProcessMemory(hProcess, (void *)((ULONG_PTR)tempAddr + 0x100000), myBuffer, sizeof(myBuffer), &readSize);
		if (readSize != sizeof(myBuffer)) {
			//OutputDebugStringA("Error reading Memory\n");
			return NULL;
		}
		for (int i = 0; i < sizeof(myBuffer) && !myTable; i++) {
			if (*((ULONG_PTR*)&myBuffer[i]) == 0x9090909090909090 ||
				*((ULONG_PTR*)&myBuffer[i]) == 0xcccccccccccccccc) {
				myTable = (void *)((ULONG_PTR)tempAddr + i);
				/*
				sprintf(buffer,"HACK: table found at %p, index %x\n",myTable, i);
				OutputDebugStringA(buffer);
				*/
			}
		}
		if (!myTable) {
			//OutputDebugStringA("Table not found\n");
			return NULL;
		}

	} //end else not hotpatch area

	myVM = VirtualProtectEx(hProcess, myTable, sizeof(void *), PAGE_READWRITE, &myProtect);
	if (myVM) {
		SIZE_T len2 = 0;
		myVM = WriteProcessMemory(hProcess, myTable, &remote_addr, 8, &len2);
		if (myVM && 8 == len2) {
			myVM = VirtualProtectEx(hProcess, myTable, 8, myProtect, &myProtect);
			if (myVM) {
				return myTable;
			}
		}
	}

	return NULL;
}
#endif  //#ifdef _WIN64
#endif //#ifndef _M_ARM64

//---------------------------------------------------------------------------
// InjectLow_WriteJump
//---------------------------------------------------------------------------
_FX BOOLEAN SbieDll_InjectLow_WriteJump(HANDLE hProcess, void *remote_addr, BOOLEAN long_diff
#ifdef _M_ARM64
	, BOOLEAN use_arm64ec
#endif
) {
	//
	// prepare a short prolog code that jumps to the injected SbieLow
	//
	UCHAR jump_code[20];
	void * detour = (void *)remote_addr;
	UCHAR *func = (UCHAR *)((ULONG_PTR)m_LdrInitializeThunk);
	SIZE_T len1;
	BOOL myVM;
	ULONG myProtect;

#ifdef _M_ARM64

	if(use_arm64ec)
		func = (UCHAR *)((ULONG_PTR)m_LdrInitializeThunkEC);

	ULONG* aCode = (ULONG*)jump_code;
	//*aCode++ = 0xD43E0000;	// brk #0xF000
	*aCode++ = 0x58000048;	// ldr x8, 8
	*aCode++ = 0xD61F0100;	// br x8
	*(DWORD64*)aCode = (DWORD64)detour; aCode += 2;

	len1 = (UCHAR*)aCode - jump_code;

#elif _WIN64
	if (!long_diff) {
		if (Dll_Windows >= 10) {
			len1 = 6;
			jump_code[0] = 0x48; //jump to entry code in entry.asm
			jump_code[1] = 0xE9; //jump to entry code in entry.asm
			*(ULONG *)(jump_code + 2) = (ULONG)((ULONG_PTR)detour - (m_LdrInitializeThunk + 6));
			//remote_addr = (void *)m_LdrInitializeThunk;
		}
		else {
			len1 = 5;
			jump_code[0] = 0xe9; //jump to entry code in entry.asm
			*(ULONG *)(jump_code + 1) = (ULONG)((ULONG_PTR)detour - (m_LdrInitializeThunk + 5));
		}
	}

	else {
		void * myTable = 0;
		if (!((ULONG_PTR)remote_addr & 0xffffffff00000000)) {
			len1 = 7;
			jump_code[0] = 0xB8;
			*(ULONG *)(jump_code + 1) = (ULONG)(ULONG_PTR)remote_addr;
			*(USHORT *)(jump_code + 5) = 0xE0FF;        // jmp rax
		}
		else {
			ULONG_PTR target;
			ULONG_PTR diff;

			len1 = 6;
			target = (ULONG_PTR)&func[6];
			myTable = SbieDll_InjectLow_getPage(hProcess, remote_addr);
			if (!myTable) {
				//OutputDebugStringA("Error: Table not set!\n");
				return FALSE;
			}

			diff = (ULONG_PTR) &((ULONG_PTR *)myTable)[0];
			diff = diff - target;

			*(USHORT *)&jump_code[0] = 0x25ff;
			*(ULONG *)&jump_code[2] = (ULONG)diff;
		}
	}
#else
	len1 = 5;
	jump_code[0] = 0xE9; //jump to entry code in entry.asm
	*(ULONG *)(jump_code + 1) = (ULONG)((ULONG_PTR)detour - (m_LdrInitializeThunk + 5));
	//remote_addr = (void *)m_LdrInitializeThunk;
#endif

	 //
	 // modify the bytes at LdrInitializeThunk with the prolog code
	 //

	myVM = VirtualProtectEx(hProcess, func, len1, PAGE_READWRITE, &myProtect);
	if (myVM) {
		SIZE_T len2 = 0;
		myVM = WriteProcessMemory(hProcess, func, jump_code, len1, &len2);
		/*
		sprintf(buffer,"WriteJump: len2 = %d\n",len2);
		OutputDebugStringA(buffer);
		*/
		if (myVM && len1 == len2) {
			myVM = VirtualProtectEx(hProcess, func, len1, myProtect, &myProtect);
			if (myVM) {
				return TRUE;
			}
		}
	}
	return FALSE;
}
