[RELEASE] Modification of GreyMagic to support detouring of fastcalls menu

User Tag List

Results 1 to 2 of 2
  1. #1
    Valediction's Avatar Active Member
    Reputation
    37
    Join Date
    Jul 2012
    Posts
    48
    Thanks G/R
    8/4
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    [RELEASE] Modification of GreyMagic to support detouring of fastcalls

    Hi all,

    I was attempting to hook some interesting function which, in 1.12, happens to be implemented with the __fastcall calling convention. After a few hours of searching and frustration, I recalled that I had already done the work a few months ago, extending Apoc's GreyMagic (LINK) to support detouring of fastcall functions.

    The code isn't particularly clean, implementing a new DetourManager just for fastcall hooks and not using any sort of inheritance to avoid having to touch original anywhere not strictly necessary. To be honest I don't even remember completely how I did it, feel free to point out any potential bugs/leaks or anything else, for me it's working properly with a few functions from 1.12.

    To use, you need to define more stuff than for a regular hook. In particular:
    • 1. A delegate for the original function you are detouring. Make this delegate match the signature of the original function.
    • 2. A delegate for the fastcall stub (more on this later) which should take the signature of the original function, with the addition of an extra argument at the beginning of the argument list, an IntPtr.
    • 3. A delegate for the detour (which will point to the managed function serving as the detour function), with the same signature as item #1.


    The fastcall stub is a function defined in some non-managed DLL that allows you to call an arbitrary fastcall function just by calling into the stub with the stdcall calling convention providing as an extra (first) argument the address of the fastcall function.

    In particular the one I'm using was picked from the 1.12 thread (LINK)(by MartyT) which goes like this:

    Code:
        void __declspec(dllexport) __declspec(naked) InvokeFastcall()
        {
            __asm
            {
                pop edx
                XCHG DWORD PTR SS:[ESP+8],EDX
                pop eax
                pop ecx
                jmp eax
            }
        }
    Just load the DLL and get the address for this function with the usual Windows API functions. Then, create the stub delegate instance like this:
    Code:
    dSomeFunctionStub = (D_SomeFunction_Stub) 
                    Marshal.GetDelegateForFunctionPointer(Addr_InvokeFastcall, typeof(D_SomeFunction_Stub));
    I think you can also use CreateFunction from GreyMagic which sort of wraps the above call if I remember right.

    Overall, the process of installing these kind of hooks is more involved than regular (stcall/cdecl/thiscall) hooks, but since I've only used it so far in a couple of places I haven't bothered to provide a facade to make it easier.

    A more comprehensive example, hooking the SpellStartFunction in 1.12 (I include the relevant pieces only without context):

    Code:
       // The managed detour function itself
    
       private int SpellStartHandlerDetour(int arg1, int pkt_code, int arg3, IntPtr pDataStore)
       {
           // * Note how the arguments are passed to the original function *
           object[] args = { ADDR_SPELLSTARTHANDLER, arg1, pkt_code, arg3, pDataStore};
    
           // Do stuff...
    
           return (int)gr.in_magic.FastcallDetours[SPELLSTARTHANDLER_DETOUR_ID].CallOriginal
               (args);
    
       }
    
       private const string SPELLSTARTHANDLER_DETOUR_ID = "SpellStartHandlerDetour";
       private static IntPtr ADDR_SPELLSTARTHANDLER = (IntPtr)0x6e7640;
     
       // Getting the stub function
    
       [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
       static extern int LoadLibrary(
           [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
    
       [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
       static extern IntPtr GetProcAddress(int hModule,
           [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
    
       int hModule = LoadLibrary(Cwd + @"\FastcallDll.dll");
       Addr_InvokeFastcall = GetProcAddress(hModule, "InvokeFastcall");
    
       // Signatures
    
       [UnmanagedFunctionPointer(CallingConvention.StdCall)]
       delegate int D_SpellStartHandler(int arg1, int pkt_code, int arg3, 
                                        IntPtr pDataStore);
    
       [UnmanagedFunctionPointer(CallingConvention.StdCall)]
       delegate int D_SpellStartHandler_Stub(IntPtr lpfnAddr, int arg1, 
                             int pkt_code, int arg3, IntPtr pDataStore);
    
       // Initializing instances
    
       dSpellStartHandlerOriginal = gr.in_magic.CreateFunction<D_SpellStartHandler>
           (ADDR_SPELLSTARTHANDLER);
       dSpellStartHandlerFastcallStub = (D_SpellStartHandler_Stub) 
           Marshal.GetDelegateForFunctionPointer(Addr_InvokeFastcall, 
                                    typeof(D_SpellStartHandler_Stub));
       dSpellStartHandlerDetour = this.SpellStartHandlerDetour;
    
       // Applying
    
       gr.in_magic.FastcallDetours.CreateAndApply(dSpellStartHandlerOriginal,
           dSpellStartHandlerFastcallStub, dSpellStartHandlerDetour, 
           SPELLSTARTHANDLER_DETOUR_ID);
    It should be enough to replace DetourManager.cs, which I attach, along with the file InProcessMemoryReader.cs, to which you should add:
    Code:
    private FastcallDetourManager _fastcallDetourManager;
    public virtual FastcallDetourManager FastcallDetours { get { return _fastcallDetourManager ?? (_fastcallDetourManager = new FastcallDetourManager(this)); } }
    Attached Files Attached Files

    [RELEASE] Modification of GreyMagic to support detouring of fastcalls
  2. #2
    tutrakan's Avatar Contributor
    Reputation
    134
    Join Date
    Feb 2013
    Posts
    175
    Thanks G/R
    124/52
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I changed the fastcall detour class in order to spare the c++ injection and the extra stub delegate.

    Code:
        /// <summary>
        /// Contains methods, and information for a detour, or hook.
        /// </summary>
        public class FCDetour : IMemoryOperation
        {
            private readonly IntPtr _hook;
    
            /// <summary>
            /// This var is not used within the detour itself. It is only here
            /// to keep a reference, to avoid the GC from collecting the delegate instance!
            /// </summary>
            private Delegate _hookDelegate;
    
            private MemoryBase _memory;
    
            private readonly List<byte> _new;
            private readonly List<byte> _orginal;
            private readonly IntPtr _target;
            private Delegate _targetDelegate;
    
            private int paramCount;
            private List<byte> first;
            private IntPtr firstPtr;
            private List<byte> last;
            private IntPtr lastPtr;
            private Delegate lastDelegate;
    
            internal FCDetour(Delegate target, Delegate hook, string name, MemoryBase memory)
            {
                _memory = memory;
                Name = name;
                _targetDelegate = target;
                _target = Marshal.GetFunctionPointerForDelegate(target);
                _hookDelegate = hook;
                _hook = Marshal.GetFunctionPointerForDelegate(hook);
    
                //Store the orginal bytes
                _orginal = new List<byte>();
                _orginal.AddRange(memory.ReadBytes(_target, 6));
    
                //here the mess starts ...
                //-----------------------
                paramCount = target.Method.GetParameters().Length;
    
                //preparing the stack from fastcall to stdcall
                first = new List<byte>();
    
                first.Add(0x58);                    // pop eax - store the ret addr
    
                if (paramCount > 1)
                    first.Add(0x52);                // push edx
    
                if (paramCount > 0)
                    first.Add(0x51);                // push ecx  
    
                first.Add(0x50);                    // push eax - retrieve ret addr
    
                //jump to the hook
                first.Add(0x68);                    // push ...
                first.AddRange(BitConverter.GetBytes(_hook.ToInt32()));
                first.Add(0xC3);                    // ret - jump to the detour handler
    
                firstPtr = Marshal.AllocHGlobal(first.Count);
                memory.WriteBytes(firstPtr, first.ToArray());
    
                //Setup the detour bytes
                _new = new List<byte> { 0x68 };     //push ...
                byte[] tmp = BitConverter.GetBytes(firstPtr.ToInt32());
                _new.AddRange(tmp);
                _new.Add(0xC3);                     //ret - jump to the first
    
                //preparing ecx, edx and the stack from stdcall to fastcall
                last = new List<byte>();
                last.Add(0x58);                     // pop eax - store the ret addr
    
                if (paramCount > 0)
                    last.Add(0x59);                 // pop ecx
    
                if (paramCount > 1)
                    last.Add(0x5A);                 // pop edx 
    
                last.Add(0x50);                     // push eax - retrieve ret addr
    
                //jump to the original function
                last.Add(0x68);                     // push ...
                last.AddRange(BitConverter.GetBytes(_target.ToInt32()));
                last.Add(0xC3);                     // ret
    
                lastPtr = Marshal.AllocHGlobal(last.Count);
                memory.WriteBytes(lastPtr, last.ToArray());
    
                //create the func called after the hook
                lastDelegate = Marshal.GetDelegateForFunctionPointer(lastPtr, _targetDelegate.GetType());
            }
    
            #region IMemoryOperation Members
    
            /// <summary>
            /// Returns true if this Detour is currently applied.
            /// </summary>
            public bool IsApplied { get; private set; }
    
            /// <summary>
            /// Returns the name for this Detour.
            /// </summary>
            public string Name { get; private set; }
    
            /// <summary>
            /// Applies this Detour to memory. (Writes new bytes to memory)
            /// </summary>
            /// <returns></returns>
            public bool Apply()
            {
                if (_memory.WriteBytes(_target, _new.ToArray()) == _new.Count)
                {
                    IsApplied = true;
                    return true;
                }
                return false;
            }
    
            /// <summary>
            /// Removes this Detour from memory. (Reverts the bytes back to their originals.)
            /// </summary>
            /// <returns></returns>
            public bool Remove()
            {
                if (_memory.WriteBytes(_target, _orginal.ToArray()) == _orginal.Count)
                {
                    IsApplied = false;
                    return true;
                }
                return false;
            }
    
            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            /// <filterpriority>2</filterpriority>
            public void Dispose()
            {
                if (IsApplied)
                {
                    Remove();
                }
                Marshal.FreeHGlobal(firstPtr);
                Marshal.FreeHGlobal(lastPtr);
                GC.SuppressFinalize(this);
            }
    
            #endregion
    
            /// <summary>
            /// Calls the original function, and returns a return value.
            /// </summary>
            /// <param name="args">The arguments to pass. If it is a 'void' argument list,
            /// you MUST pass 'null'.</param>
            /// <returns>An object containing the original functions return value.</returns>
            public object CallOriginal(params object[] args)
            {
                Remove();
                object ret = lastDelegate.DynamicInvoke(args);
                Apply();
                return ret;
            }
    
            /// <summary>
            /// Allows an <see cref="T:System.Object"/> to attempt to free resources and perform other cleanup operations before the <see cref="T:System.Object"/> is reclaimed by garbage collection.
            /// </summary>
            ~FCDetour()
            {
                Dispose();
            }
        }
    And while we're still on the fastcall topic, so here is a trick originating from Namreeb and dealing with calling fastcall functions from .net assembly:
    Code:
    internal static class Fastcall
        {
            public static List<IntPtr> FastCallWrappers;
    
            //Is important to pass at least two params and know that the first two parameters are ALWAYS 32bit
            public static T StdcallToFastcall<T>(IntPtr functionPtr) where T : class
            {
                var wrapper = new List<byte>();
    
                wrapper.Add(0x58);          // pop eax  - store the return address
                wrapper.Add(0x59);          // pop ecx  - move the 1st argument to ecx
                wrapper.Add(0x5A);          // pop edx  - move the 2nd argument to edx
                wrapper.Add(0x50);          // push eax - restore the return address
    
                wrapper.Add(0x68);                                                  // push ...
                wrapper.AddRange(BitConverter.GetBytes(functionPtr.ToInt32()));     // the function address to call
                wrapper.Add(0xC3);                                                  // ret - and jump to          
    
                var wrapperPtr = Marshal.AllocHGlobal(wrapper.Count);
                Marshal.Copy(wrapper.ToArray(), 0, wrapperPtr, wrapper.Count);
    
                if (FastCallWrappers == null)
                    FastCallWrappers = new List<IntPtr>();
                FastCallWrappers.Add(wrapperPtr);
    
                return Marshal.GetDelegateForFunctionPointer<T>(wrapperPtr);
            }
    
            public static void RemoveFastcalls()
            {
                if (FastCallWrappers == null)
                    return;
                foreach (var p in FastCallWrappers)
                    Marshal.FreeHGlobal(p);
            }
        }
    Last edited by tutrakan; 12-23-2017 at 11:54 PM.

Similar Threads

  1. [Release] Realmlist Changer v1.3 -- Supports all patches!
    By Ryoushi. in forum World of Warcraft Bots and Programs
    Replies: 19
    Last Post: 11-28-2008, 11:08 AM
  2. [Release]WoW Laucher 2.0 (supporting new patches)
    By darkgabou15 in forum WoW EMU Programs
    Replies: 4
    Last Post: 11-14-2008, 08:24 AM
  3. Replies: 8
    Last Post: 08-25-2008, 06:48 AM
  4. Replies: 6
    Last Post: 02-12-2008, 11:48 PM
All times are GMT -5. The time now is 03:53 PM. Powered by vBulletin® Version 4.2.3
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2025 DragonByte Technologies Ltd.
Google Authenticator verification provided by Two-Factor Authentication (Free) - vBulletin Mods & Addons Copyright © 2025 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search