I’ve shown off a load of inline hooks in previous posts but I don’t think I’ve ever done any mid-function hooking on this blog before. Shocking, because there’s a ton of cool stuff you can do with it and, as luck would have it, I recently came across a library that makes both inline and mid-function hooking easier and safer than anything I’ve been using in the past.

SafetyHook is, as you’ve might’ve guessed already, a hooking library. With the most notable safe part being how it suspends any running threads when you create or delete your hooks. I must admit it’s been great being able to inject and unload stuff without worrying about the game crashing anymore. Really makes me question how I was dealing with that before.

It does a bunch of other stuff too, but rather than reiterate the documentation, let’s jump straight into some usage.

Music select screen from beatmania IIDX INFINITAS Not the first, or last time we’ll be seeing this game.

Here’s the music select scene from beatmania IIDX INFINITAS.

On the left you can see the song genre, title, and the artist. One of my projects features a custom chart loader, which allows you to switch out and play user-made variations. Often these would add extra notes… a lot of extra notes, so it seemed like a good idea to quickly show the note count difference to the original chart, along with the new total on the select screen.

I opted to replace the genre text for this, which in this instance is “HANDZ UP”.

Cheat Engine memory viewer with an access breakpoint on the first byte of the genre text

I found the function responsible for drawing the text by simply scanning for the genre string, then setting a breakpoint on access. There was only one hit, and replacing the call with nop bytes made the text disappear, confirming it as the correct target.

An inline hook isn’t really suitable here. Not only is the bm2dx.exe+1D3DA0 function called in a hundred other places, we only want to affect the genre text on the music select screen and leave all other instances untouched. It’s time for a mid-function hook.

SafetyMidHook genre_hook;

void install()
{
   auto genre_hook_target = (bm2dx->scan("BA 07 00 00 00 44 88 74 24 20 E8 ? ? ? ? 33 D2 49 8B CD") + 10); // bm2dx.exe+C0997

   auto factory = SafetyHookFactory::init();
   auto builder = factory->acquire();

   genre_hook = builder.create_mid(genre_hook_target, [] (safetyhook::Context& ctx)
      { spdlog::info("Hello from genre text mid-function hook."); });
}

All other threads will be suspended after the call to acquire, so I’ve moved all the hook installing stuff into its own function.

You’ll notice that the lambda function doesn’t receive the arguments you’d usually get with an inline hook. While I’ve put this hook over a call instruction, it really could’ve been placed anywhere, so that makes sense. Instead, we get a Context object containing the values of each register before the call occurs. It’s not a const reference, so we’re free to change any of the values as we see fit.

So yeah, our hook there works but it doesn’t do anything useful yet. We’ll need to figure out where the genre text is first.

Cheat Engine memory viewer with an execute breakpoint on where the mid-function hook is placed

Switching back to the debugger, I’ve now set a breakpoint where the mid-function hook is currently placed.

You’ll notice that it’s been changed from a call into a jmp, but more importantly, it looks like the rsi register contains the address to the genre text… so it should be as easy as swapping out that address with one of our own, right?

Not quite. The text string is actually the 7th argument to the function, meaning it won’t be in a register, but on the stack instead. Since we know it’s already in rsi, we just need to look back a few instructions to see where on the stack it got pushed to.

bm2dx.exe+C0980 - 48 89 74 24 30        - mov [rsp+30],rsi

If we jump to rsp (0x7FF4B0) in the memory viewer, we can confirm that the genre string appears +0x30 (48) bytes in.

Cheat Engine memory viewer showing that the text string appears 48 bytes after the stack pointer "Show relative addresses" (Ctrl+Enter) is super useful here.

The buffer for genre text is usually only 64 bytes in length, but we can go beyond that now.

genre_hook = builder.create_mid(genre_hook_target, [] (safetyhook::Context& ctx)
{
    auto const replacement = "<color 70ff58ff>Check</color> "
                             "<color 58baffff>out</color> "
                             "<color ffb658ff>this</color> "
                             "<color ff5858ff>genre</color> "
                             "<color b658ffff>text!</color>";

    *reinterpret_cast<char const**>(ctx.rsp + 0x30) = replacement;
});
Music select screen from beatmania IIDX INFINITAS displaying custom colored genre text

So, that’s essentially how mid-function hooking can be done… but there’s a problem. The game doesn’t always use text for genres. There are a few characters in these strings that the in-game fonts can’t render properly. For such cases, a texture is used instead.

Luckily, the check for this occurs in the same function, so we just need to scroll up a bit and add another mid-function hook.

bm2dx.exe+C07C6 - 44 39 B0 08010000     - cmp [rax+00000108],r14d
bm2dx.exe+C07CD - 0F84 91000000         - je bm2dx.exe+C0864

rax here is pointing to the song data structure. There’s a flag at +0x108 which determines whether to use a custom texture for the genre text or not. In this example, ‘Ristaccia’ uses it because of the “Ü” character, which is internally replaced with a “?”.

To override this, we just need to force ZF after the cmp instruction to always be set to 1. Fortunately, Context contains rflags, and according to Wikipedia, the 6th bit is the zero flag, so our second hook will look something like this:

auto genre_texture_hook_target = bm2dx->scan("0F 84 91 00 00 00 44 8B 88 70 02 00 00"); // bm2dx.exe+C07CD

genre_texture_hook = builder.create_mid(genre_texture_hook_target, [] (safetyhook::Context& ctx)
    { ctx.rflags |= 0x40; });

So far, so good. But what if we wanted to go one step further and replace not only the genre, but the title and artist text too?

Those other two are handled in the same function we’ve placed our existing two hooks in. Just like the genre text, they have the same texture check that will need bypassing, meaning it’ll require six mid-function hooks in total.

Rather than scan for six addresses, we can cut that down to just two and use a disassembler for the rest. Here’s what we need:

We’ll disassemble the entire function, looking for any calls to the text rendering function. When we find one, we simply place a hook on it. Of course, we’ll also need to hook the cmp instructions for the texture checks too.

SafetyHook already includes a disassembler as a dependency, so just #include <bddisasm.h> and we’re good to go.

We’ll start with a for loop at bm2dx.exe+C0740, our “target” function. After every iteration of the loop, the instruction pointer will advance until we hit the C3 byte, which is the ret instruction, indicating the end of the function.

auto const text_render_function = bm2dx->scan("48 8B C4 44 89 48 20 44 89 40 18 89 50 10 48 89 48 08 55"); // bm2dx.exe+1D3DA0
auto const mselect_target_function = bm2dx->scan("4C 8B DC 55 41 57 49 8D AB F8 FE FF FF"); // bm2dx.exe+C0740

INSTRUX ix;

for (auto ip = mselect_target_function; *ip != 0xC3; ip += ix.Length)
{

After we decode the next instruction, we first check if it’s a cmp. There are a lot of these that aren’t relevant, so we’ll need to narrow things down a bit. In this case, only the texture checks had r14d as the second operand, so we can check for that.

    if (!ND_SUCCESS(NdDecode(&ix, reinterpret_cast<const std::uint8_t*>(ip), ND_CODE_64, ND_DATA_64)))
        break;

    if (ix.Instruction == ND_INS_CMP)
    {
        if (!ND_IS_OP_REG(&ix.Operands[1], ND_REG_GPR, ND_SIZE_32BIT, NDR_R14D))
            continue;

        // place the hook _after_ the cmp occurs
        hooks.emplace_back(builder.create_mid(ip + ix.Length, [] (safetyhook::Context& ctx)
            { ctx.rflags |= 0x40; }));
    }

Now for the calls. These are easier, as we only need to confirm that the call address matches the text rendering function.

        if (ix.Instruction == ND_INS_CALLNR)
        {
            // account for rip relative addressing
            auto const address = (ip + std::int32_t(ix.RelativeOffset) + ix.Length);

            if (address != text_render_function)
                continue;

            hooks.emplace_back(builder.create_mid(ip, [] (safetyhook::Context& ctx) -> void
                { *reinterpret_cast<char const**>(ctx.rsp + 0x30) = "Test text"; }));
        }
    }
}

Pretty easy, huh? While this does work, we don’t know whether we’re drawing genre, title, or artist text in our hook now. We’ll need to gather some more context so that we can differentiate the types of text in our hook function.

Referring back to the disassembly, let’s see where the strings being passed to the text rendering function are coming from.

IDA disassembler and pseudo-code views showing where the genre text string is loaded before the render call

As it turns out, the song data structure containing the strings, can always be found at r15+0x10 throughout the entire function, so as long as we have that address, and the address in rsp+0x30, we can figure out which one is currently being rendered.

struct music_t
{
    char const title[64];            // +0x000
    char const title_ticker[64];     // +0x040 (unused)
    char const genre[64];            // +0x080
    char const artist[64];           // +0x0C0
    std::int32_t use_texture_title;  // +0x100
    std::int32_t use_texture_artist; // +0x104
    std::int32_t use_texture_genre;  // +0x108
};
auto const text_address = *reinterpret_cast<std::uintptr_t*>(ctx.rsp + 0x30);
auto const song_address = *reinterpret_cast<std::uintptr_t*>(ctx.r15 + 0x10);

auto const is_title = (text_address - song_address == offsetof(music_t, title));
auto const is_genre = (text_address - song_address == offsetof(music_t, genre));
auto const is_artist = (text_address - song_address == offsetof(music_t, artist));
Music select screen from beatmania IIDX INFINITAS displaying custom colored title, genre and artist text

Before we finish off, here’s a quick tip that you might find useful when working with bddisasm. Since this was my first time using a disassembler library, I initially wasn’t sure what to check against when I was trying to filter out the irrelevant comparisons.

When in doubt, a combination of IDE breakpoints, inspecting structures, and the disasmtool from any of the available releases can help you figure it out. Let’s use one of the instructions from earlier as an example:

44 39 B0 08010000     - cmp [rax+00000108],r14d

I only checked that the second operand was r14d before, but we could be a bit more specific if we wanted to.

Copy the 7 opcode bytes to a separate file (let’s say input.bin), then run the following:

disasmtool.exe -exi -b64 -f input.bin
0000000000000000 4439b008010000                  CMP       dword ptr [rax+0x108], r14d
        DSIZE: 32, ASIZE: 64, VLEN: -
        ISA Set: I86, Ins cat: ARITH, CET tracked: no
        FLAGS access
                CF: m, PF: m, AF: m, ZF: m, SF: m, OF: m,
        Valid modes
                R0: yes, R1: yes, R2: yes, R3: yes
                Real: yes, V8086: yes, Prot: yes, Compat: yes, Long: yes
                SMM on: yes, SMM off: yes, SGX on: yes, SGX off: yes, TSX on: yes, TSX off: yes
                VMXRoot: yes, VMXNonRoot: yes, VMXRoot SEAM: yes, VMXNonRoot SEAM: yes, VMX off: yes
        Valid prefixes
                REP: no, REPcc: no, LOCK: no
                HLE: no, XACQUIRE only: no, XRELEASE only: no
                BND: no, BHINT: no, DNT: no
        Operand: 0, Acc:  R-,  Type:     Memory, Size:  4, RawSize:  4, Encoding: M,
                Segment: 3, Base: 0, Displacement: 0x0000000000000108,
        Operand: 1, Acc:  R-,  Type:   Register, Size:  4, RawSize:  4, Encoding: R, RegType:  General Purpose, RegSize:  4, RegId: 14, RegCount: 1
        Operand: 2, Acc:  -W,  Type:   Register, Size:  4, RawSize:  4, Encoding: S, RegType:            Flags, RegSize:  4, RegId: 0, RegCount: 1

Since we know the texture flag offsets were always 0x100, 0x104, and 0x108, we could add an additional check.

// Operand: 0, Acc:  R-,  Type:     Memory, Size:  4, RawSize:  4, Encoding: M,
// Segment: 3, Base: 0, Displacement: 0x0000000000000108,
if (ix.Operands[0].Type != ND_OP_MEM || ix.Operands[0].Size != ND_SIZE_32BIT)
    continue;
if (ix.Operands[0].Info.Memory.Disp < 0x100 || ix.Operands[0].Info.Memory.Disp > 0x108)
    continue;

That about covers everything I wanted to talk about in this post. I used to write these mid-function hooks manually by hand, so you can imagine how happy I was when I discovered SafetyHook. I’ve previously always used MinHook for inline hooking before, but it looks like that won’t be the case for much longer. Big thanks to cursey over on GitHub for the excellent work!

Published on Wednesday, 28 December 2022 at 05:32 PM.