-
-
Save afish/33046b6c90833c922d6d to your computer and use it in GitHub Desktop.
Generic Allocator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Runtime.Serialization; | |
// WinDBG instructions: | |
// !name2ee * Program Extract program module | |
// !dumpmodule -mt <address> Extract method table for classes | |
// !dumpmt -md <address> Extract description of method | |
// !U <address> Decompile jitted code | |
// !a <address> Modify code | |
namespace GenericUnsafeAlloc | |
{ | |
class GenericMemoryAllocator | |
{ | |
// Fields needs to be static in order to be accessible from RawAllocate | |
private static int[] Memory = new int[102400]; // Array big enough to be stored in Generation 2 | |
private static int _currentOffset; | |
private static object Dummy = new object(); | |
private static readonly object DummyBackup = Dummy; // In order to avoid memory leak | |
#region Unused | |
private unsafe int* 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 = (int*)&managedPointerToPointerToRawMemory; // We get an address | |
var pointerToPointerToRawMemory = (int*)*pointerToPointerToPointerToRawMemory; // And we dereference it, this is __makeref(reference) | |
var pointerToRawMemory = (int*)*pointerToPointerToRawMemory; //This is reference | |
return pointerToRawMemory; | |
} | |
#endregion | |
#region Allocation | |
public T Allocate<T>() | |
{ | |
var methodTable = typeof(T).TypeHandle.Value; // Get handle to method table | |
RawAllocate(methodTable); // Allocate object and set field, also JIT method | |
return (T)Dummy; | |
} | |
// 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] = (int)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 = *(int*)*(int*)&memoryReference + (_currentOffset + 2) * sizeof(int); // Calculate the address of spawned object. We need to add 2 since we need to skip methodtable of array and array size | |
*(int*)*(int*)&newObjectReference = spawnedObjectAddress; // Modify handle for new object using address of existing memory | |
_currentOffset += objectSize; // Move within the memory | |
return (IntPtr) (int*)* (int*)*(int*)&newObjectReference; | |
} | |
#endregion | |
#region Hijack | |
[MethodImpl(MethodImplOptions.NoOptimization)] | |
private void CreateObject() | |
{ | |
new object(); | |
} | |
private static int GetAllocMethodAddress() | |
{ | |
// Get handle to method creating object | |
var methodHandle = typeof(GenericMemoryAllocator).GetMethod("CreateObject", BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle; | |
// JIT methods | |
RuntimeHelpers.PrepareMethod(methodHandle); | |
// Get address of jitted method | |
IntPtr methodAddress = Marshal.ReadIntPtr(methodHandle.Value, 8); | |
// Call to internal function differs between builds | |
#if DEBUG | |
int offset = 0x22; | |
#else | |
int offset = 0xE; | |
#endif | |
// Read jump offset | |
int address = 0; | |
for(int i = 1; i < 5; ++i) | |
{ | |
address = address + (Marshal.ReadByte(methodAddress, offset + i) << (i-1)*8); | |
} | |
// Calculate absolute address | |
address = address + (int)methodAddress + offset + 1 + 4; | |
return address; | |
} | |
public static void HijackNew() | |
{ | |
var methodHandle = typeof(GenericMemoryAllocator).GetMethod("RawAllocate").MethodHandle; | |
RuntimeHelpers.PrepareMethod(methodHandle); | |
var myAllocAddress = Marshal.ReadInt32(methodHandle.Value, 8); | |
var defaultAllocAddress = GetAllocMethodAddress(); | |
int offset = 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); | |
} | |
#endregion | |
} | |
class Program | |
{ | |
static void Main() | |
{ | |
// Create allocator and allocate object | |
var allocator = new GenericMemoryAllocator(); | |
var customlyAlocated = allocator.Allocate<TestClass>(); | |
// Allocate ordinary object | |
var ordinary = new object(); | |
// Hijack method and allocate object | |
GenericMemoryAllocator.HijackNew(); | |
var hijacked = new object(); | |
// Observe that hijacked objects are in generation 2 | |
Console.WriteLine("Allocator: {0}", GC.GetGeneration(allocator)); | |
Console.WriteLine("Object customly allocated by hand: {0}", GC.GetGeneration(customlyAlocated)); | |
Console.WriteLine("Object created normally: {0}", GC.GetGeneration(ordinary)); | |
Console.WriteLine("Object with hijacked newobj: {0}", GC.GetGeneration(hijacked)); | |
} | |
} | |
class TestClass | |
{ | |
public int a, b, c, d; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment