[WoW][C#]RemoteThreadHijack (alternative to BlackMagic's InjectAndExecute) menu

User Tag List

Results 1 to 13 of 13
  1. #1
    vulcanaoc's Avatar Member
    Reputation
    31
    Join Date
    Jul 2008
    Posts
    125
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    [WoW][C#]RemoteThreadHijack (alternative to BlackMagic's InjectAndExecute)

    The full source code (which uses remote thread hijacking to call one of WoW's functions) can be found near the bottom of this post.

    What?
    Remote thread hijacking is what I am dubbing the process of redirecting a thread's execution in a remote process in order to execute some (temporary) code.

    In regards to WoW, this can be used as a functional equivalent to BlackMagic's InjectAndExecute. Whereas InjectAndExecute relies upon CreateRemoteThread, remote thread hijacking relies upon Get/SetThreadContext.

    Why?
    The main motive to using this technique is to naturally bring about thread ..nirvana.. when executing WoW's code from out-of-process. Instantly, this removes the need for any TLS-handling code. (CreateRemoteThread requires TLS-handling)

    Code & Analysis
    (Warning: in this demo, I use some poor coding practices.)

    We need to do a few things first, namely: open WoW and its main thread. I will not talk in detail about this here, but it is in the source.

    Moving on, we need a generic way to call code on WoW's main thread using remote thread hijacking. In my code, this method is embedded in a class that knows about the Process and MainThread on which code must run. Hence, the method signature:
    Code:
    public uint RemoteThreadHijackToExecuteAsm(string fnInAsm)
    It takes one parameter, the assembly string to inject and execute, and returns a uint, which will be either the return value of the executed assembly string OR 0xDEADBEEF if it failed.

    The next step is to gather information about the thread and suspend it after we allocate memory for our code.
    Code:
    uint lpAsmStub, returnValue = 0, initialReturnValue = 0xDEADBEEF;
    CONTEXT ctx;
    lpAsmStub = Win32Wrapper.AllocateMemory(_client.ProcessHandle);
    
    if (SThread.SuspendThread(_client.MainThreadHandle) != uint.MaxValue)
    {
        ctx = SThread.GetThreadContext(_client.MainThreadHandle, CONTEXT_FLAGS.CONTEXT_CONTROL);
    You must suspend the thread before calling GetThreadContext, as the context will change when the thread is running.

    Now that the thread is suspended, we can inject our code snippet before executing it.
    Code:
    //This is for holding a return values
                        _client.Fasm.AddLine("lpReturnValue dd 0x{0:X}", initialReturnValue);
    
                        //This is what gets executed
                        _client.Fasm.AddLine("push 0x{0:X}", ctx.Eip);
                        _client.Fasm.AddLine("pushfd");
                        _client.Fasm.AddLine("pushad");
                        _client.Fasm.AddLine(fnInAsm);
                        _client.Fasm.AddLine("mov [lpReturnValue], eax");
                        _client.Fasm.AddLine("popad");
                        _client.Fasm.AddLine("popfd");
                        _client.Fasm.AddLine("retn");
    
                        _client.Fasm.Inject(lpAsmStub);
                        _client.Fasm.Clear();
    As you can see, we need to keep the thread in the same state that it was in when it suspended, which is why we are calling pushad/popad and pushfd/popfd (which essentially save the registers and flags.)

    Now we begin to set up the thread for execution!

    Code:
    ctx.ContextFlags = CONTEXT_FLAGS.CONTEXT_CONTROL;
                        ctx.Eip = lpAsmStub + 4; //skip over lpReturnValue data
    
                        if (SThread.SetThreadContext(_client.MainThreadHandle, ctx))
                        {
                            if (SThread.ResumeThread(_client.MainThreadHandle) != uint.MaxValue)
                            {
    And we wait for it to return before cleaning up the memory we allocated for our code.

    Code:
    for (int i = 0; i < 2000; i++) //wait for up to 2 seconds for the function to return
                                {
                                    if ((returnValue = _client.ReadUInt(lpAsmStub)) != initialReturnValue)
                                        break;
                                    System.Threading.Thread.Sleep(1);
                                }
                            }
                        }
                    }
                }
    
                Win32Wrapper.FreeMemory(_client.ProcessHandle, lpAsmStub);
    
                return returnValue;
            }
    Download Full Source

    Credits
    Shynd, for writing BlackMagic, ManagedFasm, and much of the code referenced by this demo. Oh, and for the general idea too!

    -------------------------------------
    Hope someone finds this useful
    Last edited by vulcanaoc; 08-15-2009 at 11:48 PM.

    [WoW][C#]RemoteThreadHijack (alternative to BlackMagic's InjectAndExecute)
  2. #2
    Cypher's Avatar Kynox's Sister's Pimp
    Reputation
    1358
    Join Date
    Apr 2006
    Posts
    5,368
    Thanks G/R
    0/6
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I can see a few problems.

    Beside the obvious portability issues of course....

    First off
    Code:
                                for (int i = 0; i < 2000; i++) //wait for up to 2 seconds for the function to return
                                {
                                    if ((returnValue = _client.ReadUInt(lpAsmStub)) != initialReturnValue)
                                        break;
                                    System.Threading.Thread.Sleep(1);
                                }
    WHY GOD WHY!

    Please, for the love of god take a look at WaitForSingleObject. You can use that to wait until a thread finishes executing.

    Second, you're not resuming the thread if for example GetThreadContext fails, or SetThreadContext fails.

    Third, you should be comparing the return value of SuspendThread to '-1', not uint.MaxValue. Whilst they are the same thing at the binary level they're not the same thing at the source level and using -1 makes your intentions clear when someone is checking your usage of the function against the documentation. It's just good practice.

    In the coming days I'll be posting something similar to this, except slightly more advanced. If you're interested in some overall ways you could improve the concept then you should keep a look out.
    (Mine is based off the implementation of RtlRemoteThread, an undocumented Windows API exported by NTDLL)


  3. #3
    Viano's Avatar Active Member
    Reputation
    37
    Join Date
    May 2008
    Posts
    172
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So I could use this method for executing DoString and GetLocalizedText without WoW crashing whole the time and without injecting DLLs?

    Well the following code is working, my toon is dancing (DoEmote("dance") and GetLocalizedText seem to work too. Please evaluate my code and tell me what is missing, should be better (remember I am still learning things).

    Code:
    public String GetLocalizedText(string variable)
            {
                uint lpAsmStub, returnValue = 0, initialReturnValue = 0xDEADBEEF;
                Magic.CONTEXT ctx;
    
                lpAsmStub = Win32Wrapper.AllocateMemory(magic.ProcessHandle);
                // my dirty addition
                ProcessThread wowMainThread = SThread.GetMainThread(magic.ProcessId);
                IntPtr hThread = SThread.OpenThread(wowMainThread.Id);
                // --
                if (SThread.SuspendThread(hThread) != uint.MaxValue)
                {
                    // modified SThread
                    ctx = SThread.GetThreadContext(hThread, CONTEXT_FLAGS.CONTEXT_CONTROL);
                    // --
                    if (ctx.Eip > 0)
                    {
                        try
                        {
                            //This is for holding a return values
                            magic.Asm.AddLine("lpReturnValue dd 0x{0:X}", initialReturnValue);
    
                            //This is what gets executed
                            magic.Asm.AddLine("push 0x{0:X}", ctx.Eip);
                            magic.Asm.AddLine("pushfd");
                            magic.Asm.AddLine("pushad");
                            // my asm code
                            // magic.Asm.AddLine(fnInAsm);
                            uint codecave = magic.AllocateMemory();
                            ulong playerGuid = magic.ReadUInt64(
                            manager + (uint)Globals.Constants.Offsets.LOCAL_GUID);
                            uint playerPointer = Globals.ObjectManager.Instance.GetObjectByGuid(playerGuid);
                            magic.WriteASCIIString(codecave + 0x100, variable);
    
                            magic.Asm.AddLine("mov ecx, {0}", playerPointer);
                            magic.Asm.AddLine("push {0}", -1);
                            magic.Asm.AddLine("push {0}", codecave + 0x100);
                            magic.Asm.AddLine("call {0}", (uint)Globals.Constants.LuaFunctions.GET_LOCALIZED_TEXT);
    
                            // ---
                            magic.Asm.AddLine("mov [lpReturnValue], eax");
                            magic.Asm.AddLine("popad");
                            magic.Asm.AddLine("popfd");
                            magic.Asm.AddLine("retn");
    
                            magic.Asm.Inject(lpAsmStub);
                            magic.Asm.Clear();
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
    
                            Win32Wrapper.FreeMemory(magic.ProcessHandle, lpAsmStub);
                            SThread.ResumeThread(hThread);
                            return "";
                        }
    
                        ctx.ContextFlags = CONTEXT_FLAGS.CONTEXT_CONTROL;
                        ctx.Eip = lpAsmStub + 4; //skip over lpReturnValue data
    
                        if (SThread.SetThreadContext(hThread, ctx))
                        {
                            if (SThread.ResumeThread(hThread) != uint.MaxValue)
                            {
                                // WHY GOD WHY ... I KNOW ...
                                for (int i = 0; i < 2000; i++) //wait for up to 2 seconds for the function to return
                                {
                                    if ((returnValue = magic.ReadUInt(lpAsmStub)) != initialReturnValue)
                                        break;
                                    Thread.Sleep(1);
                                }
                            }
                        }
                    }
                }
    
                Win32Wrapper.FreeMemory(magic.ProcessHandle, lpAsmStub);
    
                String sResult = magic.ReadASCIIString(returnValue, 256);
                return sResult;
            }
    
            public uint DoString(string command)
            {
                uint lpAsmStub, returnValue = 0, initialReturnValue = 0xDEADBEEF;
                Magic.CONTEXT ctx;
    
                lpAsmStub = Win32Wrapper.AllocateMemory(magic.ProcessHandle);
                // my dirty addition
                ProcessThread wowMainThread = SThread.GetMainThread(magic.ProcessId);
                IntPtr hThread = SThread.OpenThread(wowMainThread.Id);
                // --
                if (SThread.SuspendThread(hThread) != uint.MaxValue)
                {
                    // modified SThread
                    ctx = SThread.GetThreadContext(hThread, CONTEXT_FLAGS.CONTEXT_CONTROL);
                    // --
                    if (ctx.Eip > 0)
                    {
                        try
                        {
                            //This is for holding a return values
                            magic.Asm.AddLine("lpReturnValue dd 0x{0:X}", initialReturnValue);
    
                            //This is what gets executed
                            magic.Asm.AddLine("push 0x{0:X}", ctx.Eip);
                            magic.Asm.AddLine("pushfd");
                            magic.Asm.AddLine("pushad");
                            // my asm code
                            // magic.Asm.AddLine(fnInAsm);
                            uint space = magic.AllocateMemory(command.Length + 1);
                            magic.WriteASCIIString(space, command);
    
                            magic.Asm.AddLine("mov ecx, 0");
                            magic.Asm.AddLine("mov eax, " + space);
                            magic.Asm.AddLine("push ecx");
                            magic.Asm.AddLine("push eax");
                            magic.Asm.AddLine("push eax");
                            magic.Asm.AddLine("mov eax, " + (uint)Globals.Constants.LuaFunctions.DO_STRING);
                            magic.Asm.AddLine("call eax");
                            magic.Asm.AddLine("add esp, 0xC");
    
                            // ---
                            magic.Asm.AddLine("mov [lpReturnValue], eax");
                            magic.Asm.AddLine("popad");
                            magic.Asm.AddLine("popfd");
                            magic.Asm.AddLine("retn");
    
                            magic.Asm.Inject(lpAsmStub);
                            magic.Asm.Clear();
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
    
                            Win32Wrapper.FreeMemory(magic.ProcessHandle, lpAsmStub);
                            SThread.ResumeThread(hThread);
                            return 0;
                        }
    
                        ctx.ContextFlags = CONTEXT_FLAGS.CONTEXT_CONTROL;
                        ctx.Eip = lpAsmStub + 4; //skip over lpReturnValue data
    
                        if (SThread.SetThreadContext(hThread, ctx))
                        {
                            if (SThread.ResumeThread(hThread) != uint.MaxValue)
                            {
                                // WHY GOD WHY ... I KNOW ...
                                for (int i = 0; i < 2000; i++) //wait for up to 2 seconds for the function to return
                                {
                                    if ((returnValue = magic.ReadUInt(lpAsmStub)) != initialReturnValue)
                                        break;
                                    Thread.Sleep(1);
                                }
                            }
                        }
                    }
                }
    
                Win32Wrapper.FreeMemory(magic.ProcessHandle, lpAsmStub);
    
                return returnValue;
            }
    I love you vulcanaoc. Cannot +REP you anymore

    EDIT:
    Strange thing is DoString works if I test it at the beginning of my application. But it ALWAYS crashes at "ResumeThread(IntPtr hThread)" when executing somewhere in the middle. Any ideas why? It's like calling something totally different.

    What can I possibly be doing "wrong" during execution of my program that it causes DoString() from above to crash WoW and why does that work at the beginning of it?
    Is it possible to check whether some other part of my code is doing something with WoW's main thread so that I should wait? Something like GetThreadStatus?
    Last edited by Viano; 08-16-2009 at 11:56 AM.
    Viano

  4. #4
    Kryso's Avatar Active Member
    Reputation
    40
    Join Date
    Jul 2009
    Posts
    97
    Thanks G/R
    0/3
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm actually working with same method. However I'm trying to make it more dotnetty Gonna publish it in like week or so, still have some things to figure out.

  5. #5
    amadmonk's Avatar Active Member
    Reputation
    124
    Join Date
    Apr 2008
    Posts
    772
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Um, this won't save you from the need to inject DLL's any more than CreateRemoteThread does. You still need to have code to execute in the remote process, and there are only two real choices for that; inject a dll and execute that, or create a code cave and execute that.
    Don't believe everything you think.

  6. #6
    Viano's Avatar Active Member
    Reputation
    37
    Join Date
    May 2008
    Posts
    172
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by amadmonk View Post
    Um, this won't save you from the need to inject DLL's any more than CreateRemoteThread does. You still need to have code to execute in the remote process, and there are only two real choices for that; inject a dll and execute that, or create a code cave and execute that.
    Mhm, so I guess I have trouble working with threads somewhere in my application. As stated before DoString() is working 1000 times if I execute it at the beginning of my application and fails in the middle of it. But what can possibly go wrong?

    Sigh ... if only someone could take a look at my code and tell me what I am missing ...
    Viano

  7. #7
    amadmonk's Avatar Active Member
    Reputation
    124
    Join Date
    Apr 2008
    Posts
    772
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sounds like either a timing issue or a memory leak. Hope for a memory leak; you can actually FIX those...
    Don't believe everything you think.

  8. #8
    Viano's Avatar Active Member
    Reputation
    37
    Join Date
    May 2008
    Posts
    172
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by amadmonk View Post
    Sounds like either a timing issue or a memory leak. Hope for a memory leak; you can actually FIX those...
    What do you mean by a memory leak? How do you identify those?
    Viano

  9. #9
    amadmonk's Avatar Active Member
    Reputation
    124
    Join Date
    Apr 2008
    Posts
    772
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, I'm just going off the basis of the behavior changing over time, particularly becoming more flaky after more calls -- to me this sounds like a memory leak (for instance, perhaps you're forgetting to deallocate a code cave or something). Eventually you'd start doing bad things (exhausting your heap or fragmenting your VA space or whatever).

    I may be way off base -- can't be sure just from a description of the issue. But double check to make sure that you free every piece of memory that you allocate (even in exception cases, etc.) I'm sure Cypher would have more to say on the topic
    Don't believe everything you think.

  10. #10
    Viano's Avatar Active Member
    Reputation
    37
    Join Date
    May 2008
    Posts
    172
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by amadmonk View Post
    Well, I'm just going off the basis of the behavior changing over time, particularly becoming more flaky after more calls -- to me this sounds like a memory leak (for instance, perhaps you're forgetting to deallocate a code cave or something). Eventually you'd start doing bad things (exhausting your heap or fragmenting your VA space or whatever).

    I may be way off base -- can't be sure just from a description of the issue. But double check to make sure that you free every piece of memory that you allocate (even in exception cases, etc.) I'm sure Cypher would have more to say on the topic
    It was my GetUnitName function. Now I am using VMT to get it and everything seems fine. Bot is running over an 2 hours now Thank you amadmonk fo your tips and the patience.

    @Cypher: can you please show me how to use WaitForSingleObject in this particular code example? My thread seems to run for ages and is not returning.
    Last edited by Viano; 08-17-2009 at 05:09 PM.
    Viano

  11. #11
    violentmagician's Avatar Member
    Reputation
    1
    Join Date
    Dec 2008
    Posts
    8
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    umm mate i tired this code... it seems that


    your code deallocates just the asm stub
    the codecave is not deallocated causing a memory leak.


    Originally Posted by Viano View Post
    Code:
    lpAsmStub = Win32Wrapper.AllocateMemory(magic.ProcessHandle);
    ...
    uint codecave = magic.AllocateMemory();
    ...
    Win32Wrapper.FreeMemory(magic.ProcessHandle, lpAsmStub);
    as such there is a memory leak.
    just deallocate codecave with
    Code:
     magic.FreeMemory(codecave);
    the code will then not crash after multiple runs.
    Last edited by violentmagician; 09-10-2009 at 05:24 PM. Reason: spelling

  12. #12
    vulcanaoc's Avatar Member
    Reputation
    31
    Join Date
    Jul 2008
    Posts
    125
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by violentmagician View Post
    umm mate i tired this code... it seems that


    your code deallocates just the asm stub
    the codecave is not deallocated causing a memory leak.



    as such there is a memory leak.
    just deallocate codecave with
    Code:
     magic.FreeMemory(codecave);
    the code will then not crash after multiple runs.
    You're talking to Viano, right?

  13. #13
    Viano's Avatar Active Member
    Reputation
    37
    Join Date
    May 2008
    Posts
    172
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by violentmagician View Post
    ...
    just deallocate codecave with
    Code:
     magic.FreeMemory(codecave);
    ...
    Yep that was a memory leak. But still, after freeing memory my WoW crashes.

    Code:
    public String HijackGetLocalizedText(string variable)
            {
                uint lpAsmStub, returnValue = 0, initialReturnValue = 0xDEADBEEF,
                // --- alloc memory    
                    codecave, playerPointer;
                codecave = _magic.AllocateMemory();
                playerPointer = ObjectManager.Instance.Player.Pointer;
                String sResult = "null";
                // --- and vars
    
                Magic.CONTEXT ctx;
    
                lpAsmStub = Win32Wrapper.AllocateMemory(_magic.ProcessHandle);
    
                // --- getting main handle
                ProcessThread wowMainThread = SThread.GetMainThread(_magic.ProcessId);
                IntPtr hThread = SThread.OpenThread(wowMainThread.Id);
                // ---
    
                if (SThread.SuspendThread(hThread) != uint.MaxValue)
                {
                    ctx = SThread.GetThreadContext(hThread, CONTEXT_FLAGS.CONTEXT_CONTROL);
                    if (ctx.Eip > 0)
                    {
                        try
                        {
                            //This is for holding a return values
                            _magic.Asm.AddLine("lpReturnValue dd 0x{0:X}", initialReturnValue);
    
                            //This is what gets executed
                            _magic.Asm.AddLine("push 0x{0:X}", ctx.Eip);
                            _magic.Asm.AddLine("pushfd");
                            _magic.Asm.AddLine("pushad");
    
                            // --- asm code for GetLocalizedText
                            
                            _magic.WriteASCIIString(codecave + 0x100, variable);
    
                            _magic.Asm.AddLine("mov ecx, {0}", playerPointer);
                            _magic.Asm.AddLine("push {0}", -1);
                            _magic.Asm.AddLine("push {0}", codecave + 0x100);
                            _magic.Asm.AddLine("call {0}", (uint)Constants.LuaFunctions.GET_LOCALIZED_TEXT);
    
                            // --- asm code
    
                            _magic.Asm.AddLine("mov [lpReturnValue], eax");
                            _magic.Asm.AddLine("popad");
                            _magic.Asm.AddLine("popfd");
                            _magic.Asm.AddLine("retn");
    
                            _magic.Asm.Inject(lpAsmStub);
                            _magic.Asm.Clear();
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
    
                            Win32Wrapper.FreeMemory(_magic.ProcessHandle, lpAsmStub);
                            // --- free memory
                            _magic.FreeMemory(codecave);
                            SThread.ResumeThread(hThread);
                            return sResult;
                        }
    
                        ctx.ContextFlags = CONTEXT_FLAGS.CONTEXT_CONTROL;
                        ctx.Eip = lpAsmStub + 4; //skip over lpReturnValue data
    
                        if (SThread.SetThreadContext(hThread, ctx))
                        {
                            if (SThread.ResumeThread(hThread) != uint.MaxValue)
                            {
                                for (int i = 0; i < 2000; i++) //wait for up to 2 seconds for the function to return
                                {
                                    if ((returnValue = _magic.ReadUInt(lpAsmStub)) != initialReturnValue)
                                        break;
                                    System.Threading.Thread.Sleep(1);
                                }
                            }
                        }
                    }
                }
    
                Win32Wrapper.FreeMemory(_magic.ProcessHandle, lpAsmStub);
                // --- free memory
                _magic.FreeMemory(codecave);
    
                sResult = _magic.ReadASCIIString(returnValue, 256);
                return sResult;
            }
    EDIT:
    Strange thing is DoString works o0.
    Last edited by Viano; 09-11-2009 at 04:14 PM.
    Viano

Similar Threads

  1. [Guide] Making a WoW Hack with iHook or BlackMagic
    By wiirgi in forum WoW Memory Editing
    Replies: 5
    Last Post: 08-15-2011, 11:17 AM
  2. WoW 3.2 & BlackMagic. Some questions.
    By GordonGekko in forum WoW Memory Editing
    Replies: 4
    Last Post: 08-18-2009, 08:53 AM
  3. Alternative WoW Server RU
    By Barrt73Rus in forum WoW Emulator Server Listings
    Replies: 0
    Last Post: 07-26-2009, 07:36 AM
All times are GMT -5. The time now is 10:34 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