<转>[C#][Tutorial] How to become an EndScene() hooker

NOTICE: I am a C# noob and this tutorial is for other C# noobs. I have no doubt that I have broken countless C# coding conventions and good practices and it is best to assume the way I did everything is one of the least efficient possible. Credits go to many people, some of which I will mention towards the end of the tutorial.

Things you will need:

    • Microsoft Visual Studio 2010 - You can easily write this with any other version of Visual Studio that supports C# and .Net 2.0 and higher but for the sake of this tutorial I will be using VS2010
    • SlimDX - This is the managed wrapper around DirectX which we will be using to handle DirectX as well as find the function address of EndScene. SlimDX can be downloaded here: SlimDX Homepage
    • EasyHook - This is a great library for injecting DLLs and hooking functions in C#. With this you can hook managed as well as unmanaged functions (more about that later). Download here: easyhook-continuing-detours - Project Hosting on Google Code
    • A quick explanation of the object model
      I'm not going to explain the object model in detail, I'm just going to explain where the info we want is and what we're going to do with it. Whenever an instance of an object is created in memory, memory is allocated for that object and all of its properties (variables). However, it would be insanely messy and inefficient to allocate memory for every function of that object and copy those functions there. Instead, a VMT is allocated, which points to all to the methods which are already in the memory of the DLL (in the case of Direct3D) and any and all instances of the object will point to the same function.

      This makes it very simple for us once we have injected a DLL: Create a Direct3D object and find EndScene in its VMT. Hook our object's EndScene and we will be hooking the same EndScene thatWoW uses. Simple... right?

      The Work Space
      This is where the inefficiency comes in. My workspace, especially the layout of the files is pretty awful, but deal with is.

      First off, you want to create a project and a solution. File->New->Project and call it whatever you want, something along of "host" or "injector" so that you know which is which and select Console application for its type. Then, in the solution browser, right click on the solution and select Add->New Project. Name this one something along the lines of "DLL" or "InjectedDll" or whatever and select "Class Library" for its type.

      Next, go to the Properties of the DLL and under Build change the build path to ../[Host name]/bin/Debug/ or click browse and point it to the bin/debug folder of the first project (the console application). This way, whenever you build solution both the DLL and the injector are built into the same folder and you don't have to do any of the copying from one folder to the other nonsense.

      References
      Now due to the way that interfaces are set up to communicate between the injected DLL and the host, the DLL must reference the executable in order to be able to create an instance of the interface class.

      First, in the host process (the console application), add a reference to EasyHook.dll and copy all of the EasyHook binaries to your /bin/Debug folder (I'll let you figure out which ones on your own, but for now just copy all of them). Also I would copy the SlimDX.dll from the SlimDX binaries folder just to save yourself some trouble. That's it. That's all you gotta do for the host. Now, the using code:
    • using System;
      using System.Collections.Generic;
      using System.Diagnostics;
      using System.Runtime.Remoting;
      using EasyHook;

      System.Diagnostics is needed for finding the PID of WoW and System.Runtime.Remoting is required in order to allow the DLL to communicate with the host process.

      Now do the same with the inject DLL project and add the EasyHook.dll as a reference. Then, add SlimDX (from the .Net tab) and from the Projects tab add a reference to the host project from the DLL.

    • using System;
      using System.Runtime.InteropServices;
      using System.Windows.Forms;
      using EasyHook;
      using SlimDX;
      using SlimDX.Direct3D9;

      Same thing as before but now you add SlimDX so that you can create a device and use the WoW D3D device. (Windows.Forms is for a MessageBox in EndScene to know whether its working)

      Setting up the communication interface
      Like I said, I am a noob. Seriously, a total noob, so all I know so far is how to do one way communication but for the purposes of this tutorial that's all you need. What happens is you define your communication interface in the host process and then the DLL can call functions from inside the memory space of the host.

      Here's the interface I'll use:

    •     public class WoWInterface : MarshalByRefObject
          {
              public void IsInstalled(Int32 InClientPID)
              {
                  return;
              }
      
              public void WriteConsole(String Write)
              {
                  Console.WriteLine(Write);
              }
      
          }

      Very simple, no? All you're doing is create a class that displays something on the console. Put this in the main namespace of your project.

      The actual injection!
      Once again, this part is very simple thanks to EasyHook. The first thing you want to do is declare a static string called "ChannelName" which will be the random name of our IPC communication channel. Then, in your main function you'll want to put something like this:

    •             int wowPid;
                  Config.Register("WowInjector App", "InjectedDll.dll", "DllInjector.exe");
      
                  Process[] procs = Process.GetProcessesByName("WoW");
      
                  wowPid = procs[0].Id;
      
                  RemoteHooking.IpcCreateServer<WoWInterface>(ref ChannelName, WellKnownObjectMode.SingleCall);
      
                  RemoteHooking.Inject(wowPid, InjectionOptions.Default, "InjectedDll.dll", "InjectedDll.dll", ChannelName);

      The first thing you'll want to do is add your executable and dll to the Global Assembly Cache. According to the internet god, "The Global Assembly Cache or GAC is a machine-wide .NET assemblies cache for Microsoft's CLR platform. The approach of having a specially controlled central repository addresses the shared library concept and helps to avoid pitfalls of other solutions that lead to drawbacks like DLL hell." (Wikipedia). Basically, it's supposed to make management of DLLs a bit easier (ala the theory of .NET: Let the coder have as little control as possible). The first argument is a quick note about what it is, doesn't matter what you put here. The rest of the function is a list of all the assemblies you want to add. Essentially, just add your executable and dll (EasyHook and all those are automatically added. Don't worry).

      The next 2 lines create an array of processes with the name "WoW.exe" (not case sensitive) and choses the first one and get its Process ID. Because it's an array you can always make some kind of dialog which asks for the instance to inject into on your own.

      Now on to the more complex line, creating the IPC server. The server basically serves the interface which other functions call. The object in the < > is the one that will be open for the clients to use while the "ref ChannelName" is essentially a pointer to the string where the name of our IPC server will be stored. This is totally irrelevant for this tutorial but "WellKnownObjectMode.SingleCall" means that a new instance of the WoWInterface object will be created for each client while "WellKnownObjectMode.Singleton" would set it so that a single instance is used for all clients.

      The injection line is also very simple. The first argument is the PID of the process we want to inject, the second is the options (Look it up for yourself), the third argument is the DLL to inject in the case of 32 bit while the 4th is the DLL for 64 bit which in our case would be the same for both. If you make your own bot I would recommend differentiating between the 2 DLLs because the SlimDX reference could come in both 32bit and 64bit varieties. And last but not least we the arguments we will pass to the DLL which in our case would be only the ChannelName for the IPC connection.

      The DLL!
      The first thing you want to do with the DLL is to make sure that the namespace for the DLL is the same as the namespace in your host project. Don't ask me why, it's just a quirk with EasyHook that I have run into. Make your life simple and just do it.

      The next step is specifying the entry point of your DLL which EasyHook wants. In this case it is always jsut " : EasyHook.IEntryPoint"

    •  public class Main : EasyHook.IEntryPoint
          {
              WoWInterface Interface;
              LocalHook EndSceneHooker;

      This declares our Main class and sets it as the entry point EasyHook is looking for. THIS IS ABSOLUTELY NECESSARY! Otherwise your DLL will never get injected.

      The variables we declare are an Instance of the IPC interface and the LocalHook object we will use to hook EndScene.


      The Main function

    •         public Main(RemoteHooking.IContext InContext, String InChannelName)
              {
                  Interface = RemoteHooking.IpcConnectClient<WoWInterface>(InChannelName);
                  Interface.WriteConsole("Dll successfully injected.");
              }

      This functions sole purpose is to create the communications interface and confirm that the DLL is injected.

      Once again, the IPC call has the object we will be using for the interface in < > and the only argument is the ChannelName (which was passed to our DLL by EasyHook). Then we call a function from that interface which we declared earlier called WriteConsole which is just Console.WriteLine.

      Code:
    • public void Run(RemoteHooking.IContext InContext, String InChannelName)
              {
                  Device dev;
                  dev = new Device(new Direct3D(), 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.HardwareVertexProcessing, new PresentParameters() { BackBufferWidth = 1, BackBufferHeight = 1 });
      
                  IntPtr addy = dev.ComPointer;
      
                  addy = (IntPtr)Marshal.ReadInt32(addy);
      
                  addy = (IntPtr)((int)addy + 0xA8);
                  addy = (IntPtr)Marshal.ReadInt32(addy);
      
                  EndSceneHooker = LocalHook.Create((IntPtr)addy, new DEndScene(EndSceneHook), this);
                  EndSceneHooker.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
      
                  while (true)
                  {
                  }
              }

      This is the Run function which is the lifeblood of your DLL (at least until you inject). I BELIEVE (I do not know for sure) that if you return from this function your DLL might be unloaded by EasyHook so that's why every example I've seen had some sort of unending while loop.

      Then we create Direct3D device. This is a copy pasta from SlimDX except I changed the form pointer to IntPtr.Zero and the buffer to 1x1. Then by setting addy to the ComPointer (the real D3D device object) we get the pointer we need to get our VMT.

      We then read the Int32 at that address (and cast it to IntPtr) and this new address is the VMT. Then, since EndScene() is the 42 function we add 0xA8 (A8h = 168 = 42 * 4) to that to get the address of EndScene().

      Sidenote: You can always use Black/White magic or whatever their names are (White rain?) to read the values, just use "addy = (IntPtr) WhiteMagic.Read<Int32>(addy);" since it's basically the same thing.

      THAT IS ALL THIS IS!

      Boom we have the address of EndScene (protip: this is the address of EndScene() in any version ofWoW and in almost any game that uses DirectX9).

      These last two lines hook the function to our own. 

      Code:
    •   EndSceneHooker = LocalHook.Create(addy, new DEndScene(EndSceneHook), this);
                  EndSceneHooker.ThreadACL.SetExclusiveACL(new Int32[] { 0 });

      The first argument of LocalHook.Create() is the address of the function, the second is an unmanaged pointer (I'll explain it in a bit) and the last is a pointer to the Main object that makes up most of the DLL. (Look at EasyHook examples, I won't explain how all of that works.)

      OK I honestly have no idea what ThreadACL.SetExclusiveACL does or why you need it. That line is a copy pasta from the EasyHook examples so go read about it yourself. From what I can tell, I don't need to worry about it.

      The last code is here:

      Code:
    •         [UnmanagedFunctionPointer(CallingConvention.StdCall,
              CharSet = CharSet.Unicode,
              SetLastError = true)]
              delegate int DEndScene(
                  IntPtr Direct3dDevice);
      
              public int EndSceneHook(IntPtr Direct3dDevice)
              {
                  using (Device d3d = Device.FromPointer(Direct3dDevice))
                  {
                      MessageBox.Show("LOL");
                      return d3d.EndScene().Code;
                  }
              }

      The Unmanaged pointer declaration just makes it possible for us to pass the EndSceneHook function to the LocalHook.Create. In the function itself, we do using directive to set our device object (d3d) to the one passed to the function. Thus, our d3d object is actually the Direct3D object that WoW uses. Thus we can manipulate the graphics and do whatever the **** we want.

      The return line just calls the original EndScene and returns the error code (or success code or w.e)


      And... that's it. This code will continuously spam that MessageBox and WoW will never render because of the MessageBoxes blocking the thread.

      Sorry for my crappy explanations but now that you have the code in front of you it should be pretty easy to figure it out and this tutorial is more for WoW hacking noobs, not for people who don't even know the most basic features or syntax of the language. (I left out Strong name signing for a reason). I also made this pretty fragmented so that you had to know at least something to be able to use this tutorial and not be able to just copy paste.

      Also, I have no idea how to release the Device created in that code but since its a managed DLL its already heavy on the memory.

      If I missed something, message me or something and I will correct it. I made sure to create a project from scratch and copy pasted all this code and it worked perfectly (though since this is on a computer it might not work as exactly on yours. If it doesn't, please provide more information than just "it doesn't work"). Tested on a 64bit Windows 7.

      Big note: Any time you get an exception that says something along the lines of your binaries not being in the GAC it is probably because you are not running the executable with administrator privileges. Save yourself the trouble and just run Visual Studio as Administrator

      Now, WTB TUTORIAL ON HOW TO CALL WOW FUNCTIONS AND GET RETURN VALUES!

      Credits go to:
      http://www.rohitab.com/discuss/index...howtopic=34411
      http://spazzarama.wordpress.com/2010...t3d-api-hooks/
      and the EasyHook and SlimDX examples. COPY PASTE FTW!

posted @ 2012-10-26 19:34  瓜蛋  阅读(1618)  评论(1编辑  收藏  举报