Managed FASM, CLRHosting durch C# und natives Detouring

Der Titel sagt eigentlich schon alles aus: Es wird um drei verschiedene Themen gehen, die trotzdem irgendwie zusammenhängen. Es wird die Managed FASM Implementation vorgestellt und gleich darauf in einem Programm verwendet. Dieses Programm wird von C# aus einen CLR-Host in einer nativen Exe starten. Zum Abschluss wird noch eine Libary zum detouring von nativen Funktionen von C# aus erklärt und analysiert.

Viellicht werden sich noch Leser an Teil 8 der MW2 Bot Serie erinnern. Hier wurde, mangels normaler Calling Conventions, eine Funktion in inline ASM in C++ erklärt und anschließend in C# über die Opcodes und ein Delegate aufgerufen. Dass das Welten einfacher geht war mir bis zur Downtime von B2H (und somit auch dieses Blogs) nicht klar. Doch durch Zufall stieß ich auf die Managed FASM Libary, welche es uns erlaubt Mnemonics zu nutzen und anschließend den Code über CreateRemoteThread zu Injecten. Alternativ dazu kann man sich die ASM-Befehle direkt als Byte-Code kompilieren zu lassen und anschließend ein Delegate drauf zu setzen. Die Libary an sich ist zwar ganz cool, aber was hat das mit CLR Hosting zu tun?

Zur Zeit wird für den MW2 Bot in C# eine unmanaged DLL als CLR-Host verwendet. Diese injectete C++ DLL macht den Detour der EndScene und ruft danach jedes mal die managed DLL-Funktion über den CLR-Host auf. Für nähere Infos verlinke ich gerne auf den passenden Post ;-) Doch wenn wir nun Code über C# injecten können, kann doch einfach ein CLR-Host ohne Bootstrap DLL erzeugt werden! Die Idee ist nicht von mir, aber trotzdem will ich schnell den doch interessanten Source anschauen. Die Orginalcredits gehen natürlich an JuJuBoSc aus OwnedCore.

Die Idee dahinter ist simpel: Man holt sich aus der mscoree.dll die wohl bekannte Funktion CorBindToRuntimeEx um eine Instanz des CLR-Hosts zu starten. Aus dieser Instanz wird aus der VTable die ExecuteInDefaultAppDomain-Funktion aufgerufen um den Code auszuführen. Im Code und in der Praxis sieht das ganze natürlich etwas komplexer aus, aber es ist nur die Umsetzung dieser einen Idee!

UIntPtr CorBindToRuntimeExPtr = Magic.Imports.GetProcAddress(Magic.Imports.GetModuleHandle("mscoree.dll"), "CorBindToRuntimeEx");

Über die nativen WinAPI-Funktionen GetModuleHandle und GetProcAddress holen wir die Adresse der CorBindToRuntimeEx Funktion.

uint CLSID_CLRRuntimeHostPtr = BM.AllocateMemory(CLSID_CLRRuntimeHost.Length * 4);
uint IID_ICLRRuntimeHostPtr = BM.AllocateMemory(IID_ICLRRuntimeHost.Length);
uint ClrHostPtr = BM.AllocateMemory(0x4);
uint dwRetPtr = BM.AllocateMemory(0x4);
uint codeCave_Code = BM.AllocateMemory(0x256);
uint AssemblyPathPtr = BM.AllocateMemory(AssemblyPath.Length + 1);
uint TypeNamePtr = BM.AllocateMemory(TypeName.Length + 1);
uint MethodNamePtr = BM.AllocateMemory(MethodName.Length + 1);
uint ArgsPtr = BM.AllocateMemory(Args.Length + 1);
uint BuildFlavorPtr = BM.AllocateMemory(0x10);
 
BM.WriteUnicodeString(BuildFlavorPtr, "wks");
BM.WriteUnicodeString(AssemblyPathPtr, AssemblyPath);
BM.WriteUnicodeString(TypeNamePtr, TypeName);
BM.WriteUnicodeString(MethodNamePtr, MethodName);
BM.WriteUnicodeString(ArgsPtr, Args);
BM.WriteBytes(CLSID_CLRRuntimeHostPtr, CLSID_CLRRuntimeHost);
BM.WriteBytes(IID_ICLRRuntimeHostPtr, IID_ICLRRuntimeHost);

In diesem Zeilen wird jeweils über VirtualAllocExSpeicher für die UnicodeStrings der Funktionsparamter reserviert. Danach wird dieser reservierte Speicher mit den benötigten Parametern gefüllt.

Fasm.ManagedFasm fasm = new Fasm.ManagedFasm(BM.ProcessHandle);
 
fasm.AddLine("push " + ClrHostPtr);
fasm.AddLine("push " + IID_ICLRRuntimeHostPtr);
fasm.AddLine("push " + CLSID_CLRRuntimeHostPtr);
fasm.AddLine("push 0");
fasm.AddLine("push " + BuildFlavorPtr);
fasm.AddLine("push 0");
fasm.AddLine("call " + CorBindToRuntimeExPtr);
fasm.AddLine("mov eax, [" + ClrHostPtr + "]"); // Save pointer in EAX
fasm.AddLine("mov ecx, [eax]"); // Save instance in ECX

Hier wird die Managed FASM Libary verwendet um zuerst 6 Parameter auf den Stack zu pushen und dann die Funktion CorBindToRuntimeEx aufzurufen. Die Instanz wird in ECX gespeichert, der Zeiger in EAX.

fasm.AddLine("mov edx, [ecx+0xC]"); // Get Start()-Pointer from VTable
fasm.AddLine("push eax"); // Push this-Pointer
fasm.AddLine("call edx"); // Call CLIHost->Start()

Nun wird in EDX die VTable Adresse von CLIHost->Start() gespeichert. Nun wird der This-Pointer gepushed und die Start()-Funktion gecalled.

fasm.AddLine("push " + dwRetPtr);
fasm.AddLine("push " + ArgsPtr);
fasm.AddLine("push " + MethodNamePtr);
fasm.AddLine("push " + TypeNamePtr);
fasm.AddLine("push " + AssemblyPathPtr);
fasm.AddLine("mov eax, [" + ClrHostPtr + "]");
fasm.AddLine("mov ecx, [eax]"); // Pointer to Instance
fasm.AddLine("push eax"); // Push This
fasm.AddLine("mov eax, [ecx+0x2C]"); // Get VTable Entry
fasm.AddLine("call eax"); // Call VTable Entry
fasm.AddLine("retn");
 
fasm.InjectAndExecute(codeCave_Code);

Der Rest ist eigentlich selbsterklärend. Es werden die Parameter der ExecuteInDefaultAppDomain-Funktion auf den Stack gepushed, die VTable-Adresse dieser Funktion ausgelesen und sie anschließend gecalled. Im Abschluss wird das ganze von der FASM-Lib über CreateRemoteThread injected und ausgeführt. Fertig ist der CLR-Host in einem nativen Programm!

Als kleiner Exkurs: Diese Technik kann man (theoretisch) auch dazu verwenden, .NET Malware direkt in andere Programme zu injecten, da hier kein Prozess erzeugt wird sollte das ziemlich unbemerkt bleiben…

Doch kommen wir zum dritten Thema, dem nativen Detouring! Bei einem Detour werden die ersten 6 Bytes einer Funktion überschrieben:

[0] call OpCode
[1-4] Sprungadresse
[5] RET Opcode

Sobald die orginale Funktion wieder gecalled werden sollte, werden die 6, zuvor gesicherten, Bytes einfach wiederhergestellt. Und genau das macht der folgende Code der “WhiteMagic” Libary auch. Der Clou dabei ist, dass wir den Sprung zwischen nativen Code und managed Code einfach durch Delegates machen können! Konkret heißt das: Wir können eine native Funktion detouren, in eine managed Funktion springen und danach wieder die native Funktion aufrufen!

Um dieses Hooking verwenden zu können brauchen wir, wie in C, ein Prototyp dieser Funktion. Nur so weiß der Compiler wie viele Parameter er vom Stack zu holen braucht. wie diese angeordnet sind und was zurückgeliefert wird. Die folgende Funktion ist der EndScene-Methoden Header vom wohl bekannten MW2 Bot.

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void EndSceneDelegate();

Simpler könnte dieser Header nicht sein, es wird kein Parameter entgegen genommen und auch nichts zurückgeliefert.

private static readonly EndSceneDelegate EndSceneHandler = EndScene;
private static void EndScene()
{
     // Do HÄÄÄX
     m.Detours["EndScene"].CallOriginal(null);
}

Es wird nun eine statische Instanz dieses Delegates erzeugt, die Zuweißung erfolgt dabei einfach über ein Gleichheitszeichen. Diese Zuweißung schließt die Lücke zwischen nativem und managed Code! Nun folgt der Methoden Body dieser Funktion, dieser ruft einfach nur die originale Funktion auf.

IntPtr endSceneAddr = (IntPtr)0x586E00;
m.Detours.CreateAndApply(m.RegisterDelegate(endSceneAddr), EndSceneHandler, "EndScene");

Hier wird der Detour gesetzt! RegisterDelegate liefert einfach ein Delegate zurück, welches wiederum durch Marshal.GetDelegateForFunctionPointer geliefert wurde. Dann folgt unser erstellter Delegate-Type und als letzter Parameter folgt ein String, über den wir diesen Detour ansprechen können. Damit ist die Anleitung zur Verwendung der Libary abgeschlossen, aber um alles 100% zu verstehen müssen wir einen Blick hinter die Kulissen werfen ;-)

public class Detour : IMemoryOperation
    {
        private readonly IntPtr _hook;
        private readonly Delegate _hookDelegate;
        private readonly List<byte> _new;
        private readonly List<byte> _orginal;
        private readonly IntPtr _target;
        private readonly Delegate _targetDelegate;
        private readonly Win32 _win32; // Eine Klasse zur Sicherung des Handles etc.

Hier sind alle Member der Detour-Klasse aufgelistet. Natürlich wird ein Pointer der zu hookenden und der zu ersetzenden Funktion gespeichert, die beiden Delegates und schließlich zwei Listen mit den Bytes, die gespeichert und jeweils überschrieben werden. Das ganze sollte im Konstruktor etwas klarer werden, also weiter gehts!

        internal Detour(Delegate target, Delegate hook, string name, Win32 win32)
        {
            _win32 = win32; // S.o. hier wird u.a. das Prozesshandle mit übergeben
            Name = name;
            _targetDelegate = target;
            _target = Marshal.GetFunctionPointerForDelegate(target);
            _hookDelegate = hook;
            _hook = Marshal.GetFunctionPointerForDelegate(hook);
 
            //Store the orginal bytes
            _orginal = new List<byte>();
            _orginal.AddRange(win32.ReadBytes(_target, 6));
 
            //Setup the detour bytes
            _new = new List<byte>() {0x68};
            byte[] tmp = BitConverter.GetBytes(_hook.ToInt32());
            _new.AddRange(tmp);
            _new.Add(0xC3);
        }

Die ersten beiden interessanten Zeilen sind die Marshal.GetFunctionPointerForDelegate. Dadurch bekommen wir für unsere beiden Delegates die Speicheradressen. Aus dieser ersten Speicheradresse werden die ersten 6 Bytes ausgelesen und in der Liste gesichert. In die zweiten Liste wird der Call-Opcode geschrieben, danach folgt die Adresse unserer neuen Sprungadresse und schließlich wird das ganze mit 0x3C (RET) abgeschlossen. Das wars zur Initialisierung, jetzt folgt der Teil in dem das Ganze angewendet wird.

public bool Apply()
        {
            if (_win32.WriteBytes(_target, _new.ToArray()) == _new.Count)
            {
                IsApplied = true;
                return true;
            }
            return false;
        }

Hier wird der Detour angewendet. Es wird einfach WriteProcessMemory (oder je nachdem ob man Injected ist Marshal.Copy) verwendet, um die ersten 6 Bytes der Funktion zu überschreiben. Wenn alle Bytes geschrieben wurden ist der Detour angewand. Das selbe Funktion bei der Remove(), nur anders herrum. Die CallOrginal Funktion sollte also nun schon fast selbsterklärend sein:

        public object CallOriginal(params object[] args)
        {
            Remove();
            object ret = _targetDelegate.DynamicInvoke(args);
            Apply();
            return ret;
        }

Die Delegate-Klasse liefert uns die schöne Methode DynamicInvoke. Sie erwartet ein Array von Objekten, die als Parameter fungieren. Also wird erst der Detour entfernt (Remove()), danach wird die originale Funktion aufgerufen und der Detour wiederhergestellt (Apply()). Der Rückgabewert wird einfach als Objekt zurückgegeben.

Diese Detourtechnik lässt sich auf so ziemlich alles anwenden, damit ist es z.B. möglich über C# Funktionen der WinSocks2 Lib zu hooken und umzuleiten, vielleicht wird da mal ein Spaßprojekt daraus ;-)

Fragen und Anregungen sind natürlich, wie immer, in den Kommentaren erwünscht ;-)

Greez Easy

6 people like this post.
    • Armin
    • 4. Dez. 2011 9:50pm

    Bin eben durch Zufall auf den Blog gekommen. Gefaellt mir ziemlich gut.

  1. Noch keine TrackBacks.