-
Notifications
You must be signed in to change notification settings - Fork 13
Making mods #2: Disassembly & Hooking
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.
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:
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.
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:
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.
So, first of all - we'll want to find the function responsible for explosions. Let's look for '::explode' in IDA:
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:
We have the following function: ProjectileComponent::getEntity()
that returns us a Actor*
. The function is not called getActor()
because Mojang didn't fully manage to refactor Entity to Actor a few versions ago.
Let's expand the ProjectileComponent definition to the following:
class Actor;
class ProjectileComponent {
public:
Actor& getEntity();
};
Note: If you use any functions directly, like the above one, all arguments must exactly match the ones displayed in the IDA Exports section (not the guessed ones above the function mangled name). References are different from pointers, and it does matter whether the function is const or not
This step will also 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 BlockSource;
class Level;
class Actor {
public:
BlockSource* getRegion() const;
Level* getLevel();
};
There's a function called HitResult::getPos() const
and it returns a Vec3 const&
(You can figure that out by guessing or analyzing the function/constructor).
Let's define Vec3 and HitResult in our header:
class Vec3;
class HitResult {
public:
Vec3 const& getPos() const;
};
Let's define the explode function in the Level (append it after all the other definitions):
class Level {
public:
void explode(BlockSource&, Actor*, Vec3 const&, float, bool, bool, float, bool);
};
Note that we have no idea what these arguments mean, as we don't have the names. The easiest way is to pick random values and see how they affect the game. You can also analize what the function does/see x-refs to see what arguments does the game code use, but this may or may not be worth the effort depending on the function complexity.
For the purpose of the tutorial, I decided to go with picking random values. My first attempt was to use (5.f, true, false, 0.f, false) as the values, however that resulted in the explosion being only visual and destroyed no blocks. So, keep experimenting!
Okay, so now we know how to get the parameters needed to get this to work. Let's modify the hook to the following:
TInstanceHook(void, _ZN19ProjectileComponent5onHitERK9HitResult, ProjectileComponent, HitResult const& hitResult) {
Log::verbose(TAG, "ProjectileComponent::onHit");
original(this, hitResult);
Actor& entity = this->getEntity();
entity.getLevel()->explode(*entity.getRegion(), nullptr, hitResult.getPos(), 5.f, /* fire */ false, /* damages blocks */ true, 1.f, true);
}
Show the full code
#include <modloader/log.h>
#include <modloader/statichook.h>
using namespace modloader;
#define TAG "ExplodingArrow"
class Vec3;
class BlockSource;
class Level;
class HitResult {
public:
Vec3 const& getPos() const;
};
class Actor {
public:
BlockSource* getRegion() const;
Level* getLevel();
};
class ProjectileComponent {
public:
Actor& getEntity();
};
class Level {
public:
void explode(BlockSource&, Actor*, Vec3 const&, float, bool, bool, float, bool);
};
class HitResult;
TInstanceHook(void, _ZN19ProjectileComponent5onHitERK9HitResult, ProjectileComponent, HitResult const& hitResult) {
Log::verbose(TAG, "ProjectileComponent::onHit");
original(this, hitResult);
Actor& entity = this->getEntity();
entity.getLevel()->explode(*entity.getRegion(), nullptr, hitResult.getPos(), 5.f, /* fire */ false, /* damages blocks */ true, 1.f, true);
}
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 the so-called calling convention (the way arguments are passed to function, values returned, etc.). 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;
};