Function Hooking - Ezekial711/MonsterHunterWorldModding GitHub Wiki

Function Hooking

This guide will demonstra how to hook/detour functions to allow for more dynamic control-flow modification.

Note: This guide is for internal only. It is possible to hook externally but it is a much more advanced topic.

Table of contents

Requirements

To follow this guide you should know/understand

  • how to write a plugin
  • decent C/C++ knowledge

Tools Used:

  • Ghidra / Cheat Engine
  • x64dbg
  • Visual Studio 2019

Setting a goal

The goal for this plugin will be to make all monsters completely inanimate, i.e. they will only use the IDLE action. We will also not do any AI modifications.

I recommend installing Ghidra to follow this guide. Ghidra is a static analysis tool with a disassembler and a decompiler built in. You can optionally also follow along with IDA Pro, tho if you have IDA already I doubt you'd need a guide for function hooking.

I will be using Ghidra for this guide as it's what I'm used to.

The Function to Hook

First of all, the function we're gonna be hooking is at 0x141ce0a50, inofficially dubbed "LaunchAction". With that out of the way, to hook a function properly we need to figure out what its parameters and return types are. Ghidra takes care of both of those automatically however they can be figured out manually as well using just a disassembler, like Cheat Engine for example.

Loading the Game into Ghidra

First we need to load the game's exe into ghidra. However we don't want to just import the raw exe as it is packed. We need the unpacked exe which we can only get by doing a memory-dump of the running game.

Creating a Memory-Dump

To create a reliable memory dump we need x64dbg. Go ahead and install that.

Now launch the game and x64dbg. Once launched, attach to the game using x64dbg:

img0

In the newly opened process list select MonsterHunterWorld.exe. Now go to Plugins > Scylla:

img1

Now click on IAT Autosearch and then click Dump. Once done you can close x64dbg.

Importing to Ghidra

  1. Create a new ghidra project
  2. Click File > Import File...

img2

  1. Select the executable you just dumped using x64dbg.
  2. Leave everything at default and hit Ok
  3. Double click the exe in ghidra

Auto-Analysis

Upon opening the CodeBrowser ghidra will prompt you to let it analyze the exe. You can either do that or not. Doing so will yield much better results in both decompilation and disassembly. However based on how many options you select it can take a long time. Expect it to take at least 2 hours.

If you plan on working with ghidra again in the future I highly recommend you let it do the analysis.

If you want a recommendation on which analyzers to select, here is what I usually use:

img3

This will take a while and you cannot interrupt it. The only way of "pausing" it without losing progress is to put your PC into sleep mode. You can't save without it cancelling.

Getting the Signature

Regardless of if you have decided to let it analyze the exe or not, now we are going to get the signature of the function we are trying to hook.

First, hit G in the CodeBrowser and enter the address of the function.

img4

Now hit OK. This will take you to the function we're looking for. If you can't see any disassembly, hit D, this should force ghidra to disassemble this function.

Upon disassembling, the decompiler (on the right side of the window) should display some sort of result. The first decompilation always looks very rough and not very readable. The function signature might look something like this:

undefined FUN_141ce0a50(undefined* param_1, undefined4 param_2)

Ghidra uses these undefined types when it can't guess the actual type of something, and only knows their size in bytes. The actual signature of the function is

bool uEnemy::LaunchAction(uint32_t action_id);

Which, when we make it a free-standing function, looks like this:

bool LaunchAction(uEnemy* this, uint32_t action_id);

When I take a look at it in ghidra, it looks like this:

img5

This is because I have already mapped out a lot of stuff in the exe and added custom types to make reverse engineering easier.

But what we need to know is, that the first parameter is some kind of pointer type, and the second is a 32-bit integer.

Manually deducing the signature

Ghidra does a very nice job of telling you what the parameters or return types of a function are, but it is of course possible (and sometimes necessary) to do it manually.

Doing so requires some knowledge about the x64 calling convention.

This site states the order and the registers in which parameters are passed to a function:

Parameter Type first second third fourth fifth+
Integers RCX RDX R8 R9 Stack
Floating point values XMM0 XMM1 XMM2 XMM3 Stack
Aggregates (8-64 bits) RCX RDX R8 R9 Stack
Pointers RCX RDX R8 R9 Stack

These are the parameter registers, while the return value is always passed in RAX or XMM0.

There are two distict ways of figuring out a functions parameters:

  • Where the function is called
  • At the beginning of the function

And as such, there are also two ways of figuring out the return value of a function:

  • After the function is called
  • At the end of the function

If you see any of the parameter registers being used without first having a value put into them, then it is safe to say that those are parameters.

Example (from the LaunchAction function):

mov dword ptr[rsp+0x10],edx ; rdx/edx used uninitialized
push rbx
sub rsp,0x20
mov rbx,rcx ; rcx used uninitialized

We can see that both rcx and rdx are used without first being initialized with some value. This already tells us that the function most likely has two (or possibly more) parameters.

Additionally, the fact that rdx is used as edx tells us that the second parameter is a 32-bit type, probably an integer.

For the return value, if we head to some of the rets of the function, we can see that the game uses al exclusively:

mov al,0x1
pop rbx
ret

This means that the return value is a one-byte type, which ends up being a bool most of the time. Occasionally it's actually an 8-bit integer or a char, but that's pretty rare, at least in this game.

If we check some calls to the function, we might see patterns like this:

mov rcx,qword ptr[rbx+0x30]
mov edx,0x93
call LaunchAction

We already see that 2 values are being put into rcx and edx respectively, followed by a call. This almost always indicates parameters being passed.

MinHook

MinHook is a hooking library that will make our lives a whole lot easier. With this library we don't have to go into the nitty gritty of overwriting jmps and creating gateways for our hooks. We can simply declare a function like normal and hook:

// Declare a function pointer which will be used to call the original function
bool(*LaunchActionOriginal)(void*, uint32_t) = nullptr;

// Declare the hook function
bool LaunchActionHook(void* monster, uint32_t action_id)
{
  // do whatever
  return LaunchActionOriginal(monster, action_id); // Run original function
}

BOOL APIENTRY DllMain(HMODULE hDll, DWORD dwReason, LPVOID lpReserved)
{
  if (dwReason == DLL_PROCESS_ATTACH)
  {
    // Initialize MinHook
    MH_Initialize();

    // Create the hook
    MH_CreateHook((void*)0x141ce0a50, LaunchActionHook, reinterpret_cast<void**>(&LaunchActionOriginal));

    // Enable the hook
    MH_EnableHook((void*)0x141ce0a50);
  }

  return true;
}

Abstraction

This might get a little annoying if you have to hook a bunch of functions. Which is why we have abstraction. Stracker happens to have made a little header file to simplify things. You can find the file here. Go ahead and include this file in your dllmain.cpp (or wherever you'd like to place your hook).

With this file you can write the above code like so:

CreateHook((void*)0x141ce0a50, LaunchActionHook, bool, void* monster, uint32_t action_id)
{
  // do whatever
  return original(monster, action_id);
}

BOOL APIENTRY DllMain(HMODULE hDll, DWORD dwReason, LPVOID lpReserved)
{
  if (dwReason == DLL_PROCESS_ATTACH)
  {
    MH_Initialize();

    QueueHook(LaunchActionHook);
    MH_ApplyQueued();
  }

  return true;
}

And... well, this is basically already all of the plugin code we need. Now in our hook, all we have to do is always pass action id 1 to the original function, which is always the IDLE action. (With a few exceptions).

We can do this like so:

CreateHook((void*)0x141ce0a50, LaunchActionHook, bool, void* monster, uint32_t action_id)
{
  return original(monster, 1);
}

Now we just pass 1 to the original LaunchAction function, which will make all monsters do their action id 1 exclusively. You'll have a bunch of monsters standing around doing nothing in the map.

And that's it! All done. Go ahead and compile your plugin and give it a test.

⚠️ **GitHub.com Fallback** ⚠️