⬆️ ⬇️

Undocumented features of undocumented features: Transfer ref to another stream

I never had a question: “ how can I save / transfer to another stream a link to a field? ”? The logical assumption would be “pass the ref to the method and save. Stop, oh shi ~. ” Yes, refs are not saved (and yet, you cannot use closures on them, so creating a function inside such a method and creating a stream from it will not work either). But you can turn the ref into a TypedReference using the undocumented keyword __makeref . Alas, TypedReference cannot be directly stored in the field and does not inherit from System.Object , so caste is also impossible using conventional methods (and indeed there are a whole bunch of restrictions imposed on their use). It would seem a dead end. But that's not all - there is also a RuntimeArgumentHandle , which has TypedReference properties, with one exception - after a tricky cast in System.Object , you can still use it as long as the stack frame in which it was created is alive. About this post.





TypedReference



TypedReference itself is the most amazing thing - you can wrap ref in it and transfer another method (that is, from a method, one of whose parameters is ref 'om). However, the methods of this structure do not allow us to set the value - NotSupportedException expects us to call the appropriate methods. But it does not matter - there is the keyword __refvalue , which allows not only to get the value, but also to set it. But it looks rather strange:



void Out(ref int someInt) { Input(__makeref(someInt)); } void Input(TypedReference @ref) { int val = __refvalue(@ref, int);//  __refvalue(@ref, int) = 0;//   someInt } 


')

Given that the type is set by handles, it will not work, for example, int in string - the test of belonging to a type is still carried out.

At the same time, TypedReference cannot be used in closures either - so in order to create something with a closure on TypedReference, it also will not work.



RuntimeArgumentHandle



It is nothing but params , only in profile. In fact, it represents a kind of TypedReference 's list (access to which is made by constructing ArgIterator ' a), but it also creates ... I don’t even know how to describe it:



 void Out(int something) { Input(__arglist(something)); } void Input(__arglist) { new ArgIterator(__arglist); } 




At the same time, the __arglist keyword cannot be used in delegates when they are advertised. But RuntimeArgumentHandle can be (but only as parameters, TypedReference and RuntimeArgumentHandle cannot be returned from methods). __arglist () also cannot be used as an argument to call a delegate, but __arglist is possible. The meaning of this somewhat vague formulation is better supported by an example:



 delegate void ArgWarrior(RuntimeArgumentHandle argh); void Out(int something) { (new ArgWarrior(u => { } ))(__arglist(someting));//  Input(new ArgWarrior(u => { } ), __arglist(someting); } void Input(ArgWarrior argh, __arglist) { argh(__arglist);//   } 




So I got to the key point of this Marlezonsky ballet: delegates.



Manipulations of _methodPtrAux as a way of exquisite bullying of delegates



_methodPtrAux is the fourth field in any delegated type that will play a key role here. What is the point? The bottom line is that _methodPtrAux stores a pointer to an already jit method. By writing arbitrary unmanaged code by that pointer, this unmanaged code can be executed in this way. But this is not the point. The delegate remains usable even after replacing the _methodPtrAux value, and when you call it, control will go exactly where the value of this field indicates. Thus, having two delegates with different input parameters, I can replace the pointer from delegate a with the pointer from delegate b . Even if they have a different set of arguments, everything will work. The key point will be the fact that even if the types of the corresponding arguments are different, clr will not forget the alarm - the int will be empty in string, all desires will be fulfilled, no one will go offended , or ... RuntimeArgumentHandle will be converted to System.Object :



  delegate void Encast(RuntimeArgumentHandle @ref); delegate void Uncast(object @object); static void UseWith(Encast en, __arglist) { en(__arglist); } static object m_storedRef; static void Engage(ref object @object) { Encast en = new Encast(@ref => { }); Uncast un = new Uncast(o => { m_storedRef = o;//     <b>en</b>. }); typeof(Encast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].SetValue(en, typeof(Uncast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].GetValue(un));//   en UseWith(en, __arglist(__makeref(@object))); // } 




As you can see from the lambda, I immediately save the resulting value in a static field. Yes, there is one incomprehensible limitation - if you store o in a non-static field, then you can drop clr (read-write protected memory). Even if the target field is not a field, but an object field that is stored in a static field (for example, Dictionary) everything should go smoothly. The lambda argument in the debugger looks somewhat strange: you can only see "{object}" (without quotes) when viewing it and nothing more. Attempting to retrieve a type or result in a String does not bode well (you can drop clr)
Hidden text
But if you turn this trick with TypedReference , you can see something else interesting (I leave it to readers for self-study. You can try to play int , which is also curious).


The inverse transform is similar. The same stack frame is saved using monitors:



  static object m_locker = new object(); //... Monitor.Enter(m_locker); Monitor.Exit(m_locker); 




m_locker is already pre-locked from another thread, so execution stops, so RuntimeArgumentHandle remains on the stack without being destroyed.

The full program code looks like this:



 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ThreadJiggler { class Program { delegate void Encast(RuntimeArgumentHandle @ref); delegate void Uncast(object @object); static object m_storedRef; static object m_locker = new object(); static bool m_useFlag; static void Main(string[] args) { object @v = "means \"vendetta\""; Victim1(ref @v); Console.WriteLine(@v); } static void UseWith(Encast en, __arglist) { en(__arglist); } static Thread m_someThread; static void Victim1(ref object @object) { Thread t = new Thread(() => { Monitor.Enter(m_locker); { for (; !m_useFlag; ) { Thread.Sleep(10); } Encast en = new Encast(@ref => { TypedReference tr = new ArgIterator(@ref).GetNextArg(); __refvalue( tr, object) = 0; }); Uncast un = new Uncast(o => { m_storedRef = o; }); typeof(Uncast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].SetValue(un, typeof(Encast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].GetValue(en)); un(m_storedRef); } Monitor.Exit(m_locker); }); t.IsBackground = false; t.Start(); { Encast en = new Encast(@ref => { }); Uncast un = new Uncast(o => { m_storedRef = o; m_useFlag = true; Monitor.Enter(m_locker); Monitor.Exit(m_locker); }); typeof(Encast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].SetValue(en, typeof(Uncast).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[3].GetValue(un)); UseWith(en, __arglist(__makeref(@object))); } } } } 




At the end of Main, you can see that the @v value has changed to 0.

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



All Articles