This is the ninth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

We already hijacked new in C#. Last time we did this for 32-bit applications, today we will modify the code to support x64. Let’s go.

Object allocation

Let’s start with object allocation. We had the helper method (which was not used anyway) for getting pointer to the object. Just for keeping everything clean, we can fix it:

private unsafe IntPtr* GetReferenceAsPointer(object obj)
	// The order of memory is:
	// [raw bytes] <- reference <- __makeref(reference) <- &__makeref(reference)
	var managedPointerToRawMemory = obj; // This is reference
	TypedReference managedPointerToPointerToRawMemory = __makeref(managedPointerToRawMemory); // This is pointer to reference, we need to cast it to correct type, but we cannot do it directly and we need to get address of it
	var pointerToPointerToPointerToRawMemory = (IntPtr*)&managedPointerToPointerToRawMemory; // We get an address
	var pointerToPointerToRawMemory = (IntPtr*)*pointerToPointerToPointerToRawMemory; // And we dereference it, this is __makeref(reference)
	var pointerToRawMemory = (IntPtr*)*pointerToPointerToRawMemory; //This is reference
	return pointerToRawMemory;

No big changes, just replaced int with IntPtr.

Now, let's modify RawAllocate:

private static bool Is64 = IntPtr.Size == sizeof(long);
private static IntPtr[] Memory = new IntPtr[102400]; // Array big enough to be stored in Generation 2

// Method needs to be static in order to maintain calling convention
public static unsafe IntPtr RawAllocate(IntPtr methodTable)
	int objectSize = Marshal.ReadInt32(methodTable, 4) / sizeof(int); // Calculate the object size by extracting it from method table and dividing by int size. We assume that the size starts 4 bytes after the beginning of method table (works on .NET 4 and .NET 3.5)
	_currentOffset++; // Skip sizeof(int) bytes for syncblock
	Memory[_currentOffset] = methodTable; // Write address to method table

	TypedReference newObjectReference = __makeref(Dummy); // Get handle for newly created object
	TypedReference memoryReference = __makeref(Memory); // Get handle for memory
	var spawnedObjectAddress = *(IntPtr*)*(IntPtr*)&memoryReference + (_currentOffset + 2) * sizeof(IntPtr); // Calculate the address of spawned object. We need to add 2 since we need to skip methodtable of array and methodtable of elements type

	*(IntPtr*)*(IntPtr*)&newObjectReference = spawnedObjectAddress; // Modify handle for new object using address of existing memory

	_currentOffset += objectSize; // Move within the memory

	return *(IntPtr*)*(IntPtr*)&newObjectReference;

Once again, we remove unnecessary casting to int and use IntPtr all the way.

Hijacking new

Now we need to modify method getting address of .NET allocation method:

private static long GetAllocMethodAddress()
	// Get handle to method creating object
	var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(CreateObject), BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle;

	// JIT methods

	// Get address of jitted method
	IntPtr methodAddress = Marshal.ReadIntPtr(methodHandle.Value, 8);

	// Call to internal function differs between builds
	int offset = Is64 ? 0x2D : 0x22;
	int offset = Is64 ? 0x1A : 0xE;
	// Read jump offset
	int jumpOffset = 0;
	for(int i = 1; i < 5; ++i)
		jumpOffset = jumpOffset + (Marshal.ReadByte(methodAddress, offset + i) << (i-1)*8);

	// Calculate absolute address
	long absoluteAddress = (long)methodAddress + offset + jumpOffset + 1 + 4; // 1 byte for jmp instruction, 4 bytes for relative address

	return absoluteAddress;

We replace integers with long integers, add two more offsets (for x64), and change casting to long instead of int. We can use the same code for x86 now as we can always cast to the wider types.

Last part, hijacking the operator:

public static void HijackNew()
	var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(RawAllocate)).MethodHandle;

	var myAllocAddress = Marshal.ReadIntPtr(methodHandle.Value, 8);
	var defaultAllocAddress = GetAllocMethodAddress();

	int offset = (int)((long)myAllocAddress - defaultAllocAddress - 4 - 1); // 4 bytes for relative address and one byte for opcode
	byte[] instruction = {
		0xE9, // Long jump instruction
		(byte)(offset & 0xFF),
		(byte)((offset >> 8) & 0xFF),
		(byte)((offset >> 16) & 0xFF),
		(byte)((offset >> 24) & 0xFF)

	Marshal.Copy(instruction, 0, (IntPtr)defaultAllocAddress, instruction.Length);

Different cast for offset calculation, nothing else. We rely on the method placement when subtracting addresses, if the methods would be in different order, we would need to do the absolute jump (e.g., via push + ret trick).

You should also notice call to UnlockPage which we do in the following way:

public enum Protection
	PAGE_GUARD = 0x100,
	PAGE_NOCACHE = 0x200,

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

// Unlocks page for executing
private static void UnlockPage(IntPtr address)
	uint old;
	VirtualProtect(address, 6, (uint)Protection.PAGE_EXECUTE_READWRITE, out old);


Just for the sake of completeness, here is the debugging session for doing the same manually:

Getting .NET allocation method:

0:003> !name2ee * Program
Module:      00007ffbaf491000
Assembly:    mscorlib.dll
Module:      00007ffb51944110
Assembly:    GenericUnsafeAlloc.exe
0:003> !dumpmodule -mt 00007ffb51944110
Name:       C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe
Attributes: PEFile SupportsUpdateableMethods
Assembly:   000001fbfa6f1a60
LoaderHeap:              0000000000000000
TypeDefToMethodTableMap: 00007ffb51940070
TypeRefToMethodTableMap: 00007ffb519400a0
MethodDefToDescMap:      00007ffb519401b8
FieldDefToDescMap:       00007ffb51940228
MemberRefToDescMap:      0000000000000000
FileReferencesMap:       00007ffb519402e8
AssemblyReferencesMap:   00007ffb519402f0
MetaData start address:  000001fbfa562384 (3872 bytes)

Types defined in this module

              MT          TypeDef Name
00007ffb51945bf0 0x02000003 GenericUnsafeAlloc.GenericMemoryAllocator
00007ffb51945a20 0x02000004 GenericUnsafeAlloc.Program
00007ffb51945ce8 0x02000005 GenericUnsafeAlloc.TestClass

Types referenced in this module

              MT            TypeRef Name
00007ffbafb270c0 0x02000014 System.Object
00007ffbafb28d30 0x02000016 System.RuntimeTypeHandle
00007ffbafb46980 0x02000018 System.IntPtr
00007ffbafb28778 0x02000019 System.Type
00007ffbafb464b0 0x0200001a System.Runtime.InteropServices.Marshal
00007ffbafb610a8 0x02000020 System.Console
00007ffbafb2a490 0x02000021 System.GC
00007ffbafb29510 0x02000022 System.Int32
0:003> !dumpmt -md 00007ffb51945a20 
EEClass:         00007ffb51942748
Module:          00007ffb51944110
Name:            GenericUnsafeAlloc.Program
mdToken:         0000000002000004
File:            C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffbaf93b1f0 00007ffbaf497538 PreJIT System.Object.ToString()
00007ffbaf93fd90 00007ffbaf497540 PreJIT System.Object.Equals(System.Object)
00007ffbaf961dc0 00007ffbaf497568 PreJIT System.Object.GetHashCode()
00007ffbaf93ce50 00007ffbaf497580 PreJIT System.Object.Finalize()
00007ffb51a50080 00007ffb51945a18   NONE GenericUnsafeAlloc.Program..ctor()
00007ffb51a50480 00007ffb51945a08    JIT GenericUnsafeAlloc.Program.Main()
0:003> !U 00007ffb51a50480 
Normal JIT generated code
Begin 00007ffb51a50480, size 265
*** WARNING: Unable to verify checksum for C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 162:
>>> 00007ffb`51a50480 55              push    rbp
00007ffb`51a50481 57              push    rdi
00007ffb`51a50482 4881ece8000000  sub     rsp,0E8h
00007ffb`51a50489 488dac24f0000000 lea     rbp,[rsp+0F0h]
00007ffb`51a50491 488dbd38ffffff  lea     rdi,[rbp-0C8h]
00007ffb`51a50498 b930000000      mov     ecx,30h
00007ffb`51a5049d 33c0            xor     eax,eax
00007ffb`51a5049f f3ab            rep stos dword ptr [rdi]
00007ffb`51a504a1 833de840efff00  cmp     dword ptr [00007ffb`51944590],0
00007ffb`51a504a8 7405            je      00007ffb`51a504af
00007ffb`51a504aa e8e1e7a95f      call    clr!JIT_DbgIsJustMyCode (00007ffb`b14eec90)
00007ffb`51a504af 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 164:
00007ffb`51a504b0 48b9f05b9451fb7f0000 mov rcx,7FFB51945BF0h (MT: GenericUnsafeAlloc.GenericMemoryAllocator)
00007ffb`51a504ba e85120605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a504bf 488945c0        mov     qword ptr [rbp-40h],rax
00007ffb`51a504c3 488b4dc0        mov     rcx,qword ptr [rbp-40h]
00007ffb`51a504c7 e854fcffff      call    00007ffb`51a50120 (GenericUnsafeAlloc.GenericMemoryAllocator..ctor(), mdToken: 0000000006000009)
00007ffb`51a504cc 488b4dc0        mov     rcx,qword ptr [rbp-40h]
00007ffb`51a504d0 48894de0        mov     qword ptr [rbp-20h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 165:
00007ffb`51a504d4 488b4de0        mov     rcx,qword ptr [rbp-20h]
00007ffb`51a504d8 48ba205e9451fb7f0000 mov rdx,7FFB51945E20h (MD: GenericUnsafeAlloc.GenericMemoryAllocator.Allocate[[GenericUnsafeAlloc.TestClass, GenericUnsafeAlloc]]())
00007ffb`51a504e2 3909            cmp     dword ptr [rcx],ecx
00007ffb`51a504e4 e867fcffff      call    00007ffb`51a50150 (GenericUnsafeAlloc.GenericMemoryAllocator.Allocate[[System.__Canon, mscorlib]](), mdToken: 0000000006000004)
00007ffb`51a504e9 488945b8        mov     qword ptr [rbp-48h],rax
00007ffb`51a504ed 488b4db8        mov     rcx,qword ptr [rbp-48h]
00007ffb`51a504f1 48894dd8        mov     qword ptr [rbp-28h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 168:
00007ffb`51a504f5 48b9c070b2affb7f0000 mov rcx,offset mscorlib_ni+0x6970c0 (00007ffb`afb270c0) (MT: System.Object)
00007ffb`51a504ff e80c20605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a50504 488945b0        mov     qword ptr [rbp-50h],rax
00007ffb`51a50508 488b4db0        mov     rcx,qword ptr [rbp-50h]
00007ffb`51a5050c e80fd0ee5d      call    mscorlib_ni+0x4ad520 (00007ffb`af93d520) (System.Object..ctor(), mdToken: 0000000006000221)
00007ffb`51a50511 488b45b0        mov     rax,qword ptr [rbp-50h]
00007ffb`51a50515 488945d0        mov     qword ptr [rbp-30h],rax

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 172:
00007ffb`51a50519 e8326d795e      call    mscorlib_ni+0xd57250 (00007ffb`b01e7250) (System.Console.ReadLine(), mdToken: 0000000006000b56)
00007ffb`51a5051e 488945a8        mov     qword ptr [rbp-58h],rax
00007ffb`51a50522 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 173:
00007ffb`51a50523 48b9c070b2affb7f0000 mov rcx,offset mscorlib_ni+0x6970c0 (00007ffb`afb270c0) (MT: System.Object)
00007ffb`51a5052d e8de1f605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a50532 488945a0        mov     qword ptr [rbp-60h],rax
00007ffb`51a50536 488b4da0        mov     rcx,qword ptr [rbp-60h]
00007ffb`51a5053a e8e1cfee5d      call    mscorlib_ni+0x4ad520 (00007ffb`af93d520) (System.Object..ctor(), mdToken: 0000000006000221)
00007ffb`51a5053f 488b4da0        mov     rcx,qword ptr [rbp-60h]
00007ffb`51a50543 48894dc8        mov     qword ptr [rbp-38h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 176:
00007ffb`51a50547 48b9d8350090fb010000 mov rcx,1FB900035D8h
00007ffb`51a50551 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a50554 48894d98        mov     qword ptr [rbp-68h],rcx
00007ffb`51a50558 488b4de0        mov     rcx,qword ptr [rbp-20h]
00007ffb`51a5055c e8afd9a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a50561 8945f4          mov     dword ptr [rbp-0Ch],eax
00007ffb`51a50564 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a5056e e89d1f605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a50573 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a50577 488b4d98        mov     rcx,qword ptr [rbp-68h]
00007ffb`51a5057b 48898d70ffffff  mov     qword ptr [rbp-90h],rcx
00007ffb`51a50582 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a50586 8b55f4          mov     edx,dword ptr [rbp-0Ch]
00007ffb`51a50589 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a5058c 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a50590 48898d68ffffff  mov     qword ptr [rbp-98h],rcx
00007ffb`51a50597 488b8d70ffffff  mov     rcx,qword ptr [rbp-90h]
00007ffb`51a5059e 488b9568ffffff  mov     rdx,qword ptr [rbp-98h]
00007ffb`51a505a5 e8268ffa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a505aa 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 177:
00007ffb`51a505ab 48b9e0350090fb010000 mov rcx,1FB900035E0h
00007ffb`51a505b5 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a505b8 48894d88        mov     qword ptr [rbp-78h],rcx
00007ffb`51a505bc 488b4dd8        mov     rcx,qword ptr [rbp-28h]
00007ffb`51a505c0 e84bd9a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a505c5 8945f0          mov     dword ptr [rbp-10h],eax
00007ffb`51a505c8 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a505d2 e8391f605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a505d7 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a505db 488b4d88        mov     rcx,qword ptr [rbp-78h]
00007ffb`51a505df 48898d60ffffff  mov     qword ptr [rbp-0A0h],rcx
00007ffb`51a505e6 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a505ea 8b55f0          mov     edx,dword ptr [rbp-10h]
00007ffb`51a505ed 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a505f0 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a505f4 48898d58ffffff  mov     qword ptr [rbp-0A8h],rcx
00007ffb`51a505fb 488b8d60ffffff  mov     rcx,qword ptr [rbp-0A0h]
00007ffb`51a50602 488b9558ffffff  mov     rdx,qword ptr [rbp-0A8h]
00007ffb`51a50609 e8c28efa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a5060e 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 178:
00007ffb`51a5060f 48b9e8350090fb010000 mov rcx,1FB900035E8h
00007ffb`51a50619 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a5061c 48894d80        mov     qword ptr [rbp-80h],rcx
00007ffb`51a50620 488b4dd0        mov     rcx,qword ptr [rbp-30h]
00007ffb`51a50624 e8e7d8a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a50629 8945ec          mov     dword ptr [rbp-14h],eax
00007ffb`51a5062c 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a50636 e8d51e605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a5063b 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a5063f 488b4d80        mov     rcx,qword ptr [rbp-80h]
00007ffb`51a50643 48898d50ffffff  mov     qword ptr [rbp-0B0h],rcx
00007ffb`51a5064a 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a5064e 8b55ec          mov     edx,dword ptr [rbp-14h]
00007ffb`51a50651 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a50654 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a50658 48898d48ffffff  mov     qword ptr [rbp-0B8h],rcx
00007ffb`51a5065f 488b8d50ffffff  mov     rcx,qword ptr [rbp-0B0h]
00007ffb`51a50666 488b9548ffffff  mov     rdx,qword ptr [rbp-0B8h]
00007ffb`51a5066d e85e8efa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a50672 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 179:
00007ffb`51a50673 48b9f0350090fb010000 mov rcx,1FB900035F0h
00007ffb`51a5067d 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a50680 48898d78ffffff  mov     qword ptr [rbp-88h],rcx
00007ffb`51a50687 488b4dc8        mov     rcx,qword ptr [rbp-38h]
00007ffb`51a5068b e880d8a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a50690 8945e8          mov     dword ptr [rbp-18h],eax
00007ffb`51a50693 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a5069d e86e1e605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a506a2 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a506a6 488b8d78ffffff  mov     rcx,qword ptr [rbp-88h]
00007ffb`51a506ad 48898d40ffffff  mov     qword ptr [rbp-0C0h],rcx
00007ffb`51a506b4 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a506b8 8b55e8          mov     edx,dword ptr [rbp-18h]
00007ffb`51a506bb 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a506be 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a506c2 48898d38ffffff  mov     qword ptr [rbp-0C8h],rcx
00007ffb`51a506c9 488b8d40ffffff  mov     rcx,qword ptr [rbp-0C0h]
00007ffb`51a506d0 488b9538ffffff  mov     rdx,qword ptr [rbp-0C8h]
00007ffb`51a506d7 e8f48dfa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a506dc 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 180:
00007ffb`51a506dd 90              nop
00007ffb`51a506de 488d65f8        lea     rsp,[rbp-8]
00007ffb`51a506e2 5f              pop     rdi
00007ffb`51a506e3 5d              pop     rbp
00007ffb`51a506e4 c3              ret
0:003> !U 00007ffb`b1052510
Unmanaged code
00007ffb`b1052510 8b5104          mov     edx,dword ptr [rcx+4]
00007ffb`b1052513 654c8b1c2598140000 mov   r11,qword ptr gs:[1498h]
00007ffb`b105251c 4d8b5370        mov     r10,qword ptr [r11+70h]
00007ffb`b1052520 498b4368        mov     rax,qword ptr [r11+68h]
00007ffb`b1052524 4803d0          add     rdx,rax
00007ffb`b1052527 493bd2          cmp     rdx,r10
00007ffb`b105252a 7708            ja      clr!JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset+0x21 (00007ffb`b1052534)
00007ffb`b105252c 49895368        mov     qword ptr [r11+68h],rdx
00007ffb`b1052530 488908          mov     qword ptr [rax],rcx
00007ffb`b1052533 c3              ret

As we can see, now we are calling different method clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)

Let's grab our allocator:

0:003> !dumpmt -md 00007ffb51945bf0 
EEClass:         00007ffb51a91020
Module:          00007ffb51944110
Name:            GenericUnsafeAlloc.GenericMemoryAllocator
mdToken:         0000000002000003
File:            C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 14
Number of IFaces in IFaceMap: 0
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffbaf93b1f0 00007ffbaf497538 PreJIT System.Object.ToString()
00007ffbaf93fd90 00007ffbaf497540 PreJIT System.Object.Equals(System.Object)
00007ffbaf961dc0 00007ffbaf497568 PreJIT System.Object.GetHashCode()
00007ffbaf93ce50 00007ffbaf497580 PreJIT System.Object.Finalize()
00007ffb51a50700 00007ffb51945bd8    JIT GenericUnsafeAlloc.GenericMemoryAllocator..cctor()
00007ffb51a507d0 00007ffb51945bd0    JIT GenericUnsafeAlloc.GenericMemoryAllocator..ctor()
00007ffb51a500d0 00007ffb51945b70   NONE GenericUnsafeAlloc.GenericMemoryAllocator.Allocate()
00007ffb51a500a0 00007ffb51945b08   NONE GenericUnsafeAlloc.GenericMemoryAllocator.VirtualProtect(IntPtr, UInt32, UInt32, UInt32 ByRef)
00007ffb51a500b0 00007ffb51945b50   NONE GenericUnsafeAlloc.GenericMemoryAllocator.UnlockPage(IntPtr)
00007ffb51a500c0 00007ffb51945b60   NONE GenericUnsafeAlloc.GenericMemoryAllocator.GetReferenceAsPointer(System.Object)
00007ffb51a50970 00007ffb51945b90    JIT GenericUnsafeAlloc.GenericMemoryAllocator.RawAllocate(IntPtr)
00007ffb51a500f0 00007ffb51945ba0   NONE GenericUnsafeAlloc.GenericMemoryAllocator.CreateObject()
00007ffb51a50100 00007ffb51945bb0   NONE GenericUnsafeAlloc.GenericMemoryAllocator.GetAllocMethodAddress()
00007ffb51a50110 00007ffb51945bc0   NONE GenericUnsafeAlloc.GenericMemoryAllocator.HijackNew()

And now modify the allocation:

0:003> a 0x00007ffb`b1052510
00007ffb`b1052510 jmp 0x51a50970 
jmp 0x51a50970 

Let's verify:

0:003> !U 00007ffb`b1052510
Unmanaged code
00007ffb`b1052510 e95be49fa0      jmp     00007ffb`51a50970
00007ffb`b1052515 8b1c2598140000  mov     ebx,dword ptr [1498h]
00007ffb`b105251c 4d8b5370        mov     r10,qword ptr [r11+70h]
00007ffb`b1052520 498b4368        mov     rax,qword ptr [r11+68h]
00007ffb`b1052524 4803d0          add     rdx,rax
00007ffb`b1052527 493bd2          cmp     rdx,r10
00007ffb`b105252a 7708            ja      clr!JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset+0x21 (00007ffb`b1052534)
00007ffb`b105252c 49895368        mov     qword ptr [r11+68h],rdx
00007ffb`b1052530 488908          mov     qword ptr [rax],rcx
00007ffb`b1052533 c3              ret

Jump is in place, we are good.

This code works for x86 and x64, both Debug and Release modes. Tested with Windows 10 x64 1703 and .NET Framework 4.5.2.