Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Generic Allocator
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