Skip to content

Making mods #2: Disassembly & Hooking

MCMrARM edited this page Sep 18, 2018 · 10 revisions

Let's get started

Make sure you've set up the Mod Loader SDK

You'll want a disassembler. IDA Freeware is a really good candidate for what we're doing. The other disassemblers that you can use include Binary Ninja, Hopper, and objdump. In this tutorial, I'll be using IDA.

Load the bedrock_server in IDA and get yourself a coffee in the meanwhile (as it takes around ~10 minutes or so to load the file).

IDA has two (or three) important views you'll want to use: IDA View (the place with text) and Exports (or Functions in the window on the left, which will not contain static members, but only functions). As mostly everything is exported in the server, you can search in the Exports window just fine. You can also bring up the Names subview (View->Open subviews->Names), which is like Exports, but should show a few more symbols (you shouldn't need to care about it) and you can also show the super-useful Strings subview (View->Open subviews->Strings) which basically indexes the binary and lets you search for fragments of text in the binary. This is way faster than using the built-in search functions in the Search menu.

Make sure to be ready to learn some amd64 assembly in the process ;) Note that HexRays also has a "decompiler" product (I'd say the output is mediocre though), which is rather expensive, but it could assist you in reverse engineering the binary, especially if you're new to assembly code. If you manage to get a copy of it, there's a useful open-source extension for it called HexRays CodeXplorer which should greatly assist you in reverse engineering structures. Note that I (MrARM) do not use the decompiler and instead read the disassembly, however most of the other people in the community do use the decompiler.

Our first mod

Introduction

First, make sure to read this page: Hooking API

The goal of this tutorial will be writing a simple mod that makes arrows explode.

First, we'll have to hunt for the functions we'll want to use. The first one is a difficult one - one called after an arrow hits a block.

ProTip: You can press the Name column in IDA to sort by name - this is pretty useful.

Let's look for ::onHit functions. This limits us to only a few functions. You probably may at first consider Throwable::onHit(HitResult const&) to be the one. However, if you look at the inheritance tree of Arrow (look for typeinfo for'Arrow) you will find the following: Inheritance tree of Arrow (IDA screenshot)

You should be looking at the comment at the top of the screenshot - you can notice that Arrow extends AbstractArrow which extends Actor (which is how Entity is called in the Bedrock codebase - make sure to remember this!). Throwable is not mentioned in the tree, therefore we can more or less safely exclude it from our list of function of interest. Then there's the ProjectileComponent::onHit(HitResult const&). It sounds interesting, but there's no way to quickly check it in IDA (as it's created as a component from their JSON system), so let's simply try to hook it.

Note: You're probably wondering how to find the functions quickly. Well, there's no simple way. My first try was to filter the IDA Exports tab with Arrow::, but this didn't result in any related matches.

Hooking

So how would we go about hooking ProjectileComponent::onHit(HitResult const&)? First we'll need to obtain the mangled name using IDA. Let's double click the function in IDA. IDA will open a graph view (you can switch to a full-text mode using space). No matter which mode you use you will see something looking like this: Screenshot of ProjectileComponent::onHit

The string on the second and third line starting with _ZN is the mangled symbol name - in this case it's _ZN19ProjectileComponent5onHitERK9HitResult. This is the name you'll be using with the hooking framework. On the first line you can also see IDA trying to guess the return type and arguments and whether the function is static or not. According to my experience however, this often fails badly, and don't ever assume that the information in the comment before the line with the mangled name is 100% correct. In this case, for example, the function return type is void, not __int64.

#include <modloader/log.h>
#include <modloader/statichook.h>
using namespace modloader;

#define TAG "ExplodingArrow"

class ProjectileComponent {};
class HitResult;
TInstanceHook(void, _ZN19ProjectileComponent5onHitERK9HitResult, ProjectileComponent, HitResult const& hitResult) {
  Log::verbose(TAG, "ProjectileComponent::onHit");
  original(this, hitResult);
}

Now let's compile the mod using the method of your choice (see the first part of the tutorial) and start the server using start_modloader.sh (see the homepage on how to set the modloader up). Join the game, pick up a bow and shoot an arrow. You should see Trace [ExplodingArrow] ProjectileComponent::onHit being printed to the console.

Explosion

So, first of all - we'll want to find the function responsible for explosions. Let's look for '::explode' in IDA:

Search results for ::explode

We'll be using Level::explode(). But the real question is - where do we get the Level* pointer from? As well as the block source and block position? Let's go step by step here:

Step 1: Actor*

Let's start by obtaining the Actor* pointer. First, let's look at the ProjectileComponent::ProjectileComponent(Actor &) constructor, which sounds like it'll store the pointer to Actor somewhere.

First, you'll need to know what __fastcall means. The Wikipedia article at https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI says the following: "The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, R9 (R10 is used as a static chain pointer in case of nested functions), while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for certain floating point arguments. As in the Microsoft x64 calling convention, additional arguments are passed on the stack.". The first argument we have here is the ProjectileComponent *__hidden this pointer - passed as rdi. Basically, C++ passes the value of this as an argument. Then the normal arguments follow - and we have only one, and it is Actor* - which will therefore use the rsi register. Well, so let's look at the code of the function in IDA:

TL;Dr of the above screenshot is that the rsi (Actor&) arguments gets stored after some register shuffle to [rsi+10h]. If you've been paying careful attention to what has been happening you must be asking, wait, what the hell? Wasn't rsi the Actor& argument in the first place? Indeed it was, but the rsi register also got shuffled, look at the following highlights:

In the end it turns out that rsi at this point was what rdi was in the first place (which was the this argument) and that the Actor & gets stored at this+0x10 (h stands for hex in the disassembly).

Let's expand the ProjectileComponent definition to the following:

class Actor;
class ProjectileComponent {
public:
  char filler[0x10];
  Actor& actor;
};

Step 2: Level* and BlockSource*

This step will be easy, because we have very nice functions that return us the actor's Level* and BlockSource*! They're correspondingly Level* Actor::getLevel(); and BlockSource* getRegion() const;. Let's expand the Actor definition to the following:

class Actor {
public:
  BlockSource* getRegion() const;
  Level* getLevel();
};
Clone this wiki locally