📜 ⬆️ ⬇️

Getting a pointer to a .Net object


Immediately answer the question "why?". It’s just interesting to get a pointer to an object and then think about what is special with it :) After all, if you get a pointer, then you can do anything. For example, it becomes possible to study the behavior of SyncBlockIndex, or to take the MethodTable and examine where its contents are and change its contents. You can make your own data types without resorting to Reflection. In general, you can do a lot of weird things that relate more to sports programming and to self-development. However, let's get started.



Method number 1

After the research, it was found that it is possible to put two reference types inside the structure and, at the same time, to specify their exact alignment relative to the beginning of the structure, putting them on each other. So, what do we have in this case?

To demonstrate the above, the structure `SafePtr` was implemented, which has just two internal types:` ReferenceType` (1) and `IntPtrWrapper` (2) and two properties for their installation and retrieval - (5) and (6), which read and retrieve field values ​​aligned at a single offset of 0 (3) and (4):
[StructLayout(LayoutKind.Explicit)] public struct SafePtr { // (1) public class ReferenceType { public object Reference; } // (2) public class IntPtrWrapper { public IntPtr IntPtr; } // (3) [FieldOffset(0)] private ReferenceType Obj; // (4) [FieldOffset(0)] private IntPtrWrapper Pointer; public static SafePtr Create(object obj) { return new SafePtr { Obj = new ReferenceType { Reference = obj } }; } public static SafePtr Create(IntPtr rIntPtr) { return new SafePtr { Pointer = new IntPtrWrapper { IntPtr = rIntPtr } }; } // (5) public IntPtr IntPtr { get { return Pointer.IntPtr; } set { Pointer.IntPtr = value; } } // (6) public Object Object { get { return Obj.Reference; } set { Obj.Reference = value; } } public void SetPointer(SafePtr another) { Pointer.IntPtr = another.Pointer.IntPtr; } } 

And finally, use:
  var safeptr = SafePtr.Create(new object()); Console.WriteLine("Address of object is: {0}", safeptr.IntPtr.ToInt32()); 

That's all :) Also you can use as a clean pointer:
  var safeptr = SafePtr.Create(new object()); unsafe { ((int *)safeptr.IntPtr)* = 0; } Console.WriteLine("Address of object is null: {0}", safeptr.Object == null); 

Method number 2

There is another, more convenient way. In order to cast a strictly typed .Net pointer to a regular pointer, you need to find a place where type information is lost. Where it makes no difference what is stored. And there is such a place: the thread stack. .Net uses a normal thread stack to call methods (in general, why should it use something else?), Which means that if a pointer to a .Net object is placed there, the size of which (for example) on a 32-bit machine is 4 bytes, you can simply read 4 bytes from there and that's it. The main thing that JITter does not check the types when compiling.

C # does not allow us to write to the stack data of one type and get another. That is, in other words, the design:
  int pointer = new Object(); 

is prohibited. However, it is not prohibited by MSIL. On it and write the conversion methods (EntityPtr.il):
 // Metadata version: v2.0.50215 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 2:0:0:0 } .assembly EntityPtr { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .hash algorithm 0x00008004 .ver 1:0:0:0 } .module sample.exe // MVID: {A224F460-A049-4A03-9E71-80A36DBBBCD3} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // =============== CLASS MEMBERS DECLARATION =================== .class public auto ansi abstract sealed beforefieldinit System.Runtime.CLR.EntityPtr extends [mscorlib]System.Object { .method public hidebysig static native int ToPointer<TType> ( !!TType input ) cil managed { // Method begins at RVA 0x2254 // Code size 7 (0x7) .maxstack 2 .locals init ( [0] native int CSS ) ldarg.0 conv.i4 ldc.i4 4 sub conv.i ret } // end of method Converter::ToPointer .method public hidebysig static native int ToPointerWithOffset<TType> ( !!TType input ) cil managed { // Method begins at RVA 0x2254 // Code size 7 (0x7) .maxstack 2 .locals init ( [0] native int CSS ) ldarg.0 ret } // end of method Converter::ToPointer // Methods .method public hidebysig static !!TType ToInstance<TType> ( native int input ) cil managed { // Method begins at RVA 0x2254 // Code size 7 (0x7) .maxstack 2 .locals init ( [0] native int CSS ) ldarg.0 conv.i4 ldc.i4 4 add conv.i ret } // end of method Converter::ToInstance .method public hidebysig static !!TType ToInstanceWithOffset<TType> ( native int input ) cil managed { // Method begins at RVA 0x2254 // Code size 7 (0x7) .maxstack 2 .locals init ( [0] native int CSS ) ldarg.0 ret } // end of method Converter::ToInstance } // end of class sandbox.Converter 

This code essentially contains the class `System.Runtime.CLR.EntityPtr` with four methods:
')

Source: https://habr.com/ru/post/219619/


All Articles