CYBERSECURITY, IDENTITÄTSMANAGEMENT UND MULTI-FAKTOR-AUTHENTIFIZIERUNG

PwnAdventure3 – Hooking shared library

Lesezeit: 6 Minuten

With our blog post about binary patching, we saw how to edit the client binary to modify a function in our advantage. The change was minor, i.e. a single line in assembly. If we want to modify the function to add more complex logic and thus more assembly code, we will need to use code cave in order to avoid overwriting essential instructions for the game to execute properly.

The code cave technique consist of finding an area in the binary that is not used and add our assembly instruction in it. Then, in the function we want to modify, we overwrite one instruction to jump in our code cave. Our code cave should save the registers and flags and restored them at the end of it, as well as re-aligning the stack. Finally, we should also add in our code cave the overwritten instruction (used for the the jump) and jump back in the initial function.

This procedure is often used by malware developer to hide malicious code into benign applications. The problem with this solution is that it is easy the break the application, and also, it requires you to write your changes in assembly.

An easier solution would be to hook the library used by the client in order to „hijack“ the execution flow and run custom code. With this solution, you write a new library in C/C++ and use LD_PRELOAD (in Linux) to load your new library before all others. This post will give you an example of how to use LD_PRELOAD in order to modify the logic of the game and be able to teleport wherever you want at any time and change your movement speed and jump height on the fly.

Like with our packet injection proxy, we want to decide when to activate the hack. Therefore, we will hook the chat function so that whenever we send a specific string in the chat, it activates the appropriate „hack“. Chat is a great target since we can trigger it whenever we want, and send custom strings, which allow us a activate a wide range hack.

In this example, I will use Hopper to identify the chat function.

The function to hook is Player::Chat(). So in our new library, we will write the following code (hook.cc):

// #define _GNU_SOURCE

#include <dlfcn.h>
#include <stdio.h>
#include <iostream>

class Player {
 
    public:
        void Chat(const char *text);

};


typedef void (*orig_chat_f_type)(Player *, const char *);


void Player::Chat(const char *text)
{
    std::string str(text);
    std::cout << "Chat: " << str << "\n";

    // Call orignal Player::Chat() function
    orig_chat_f_type orig;
    orig = (orig_chat_f_type) dlsym(RTLD_NEXT, "_ZN6Player4ChatEPKc");
    orig(this, text);
}

In this example, we want to make sure that we hooked the right function. Therefore, we simply outputted the content of the chat for debug purposes. Since Player::chat is a member function of a class, we first need to define the class itself (i.e. Player) and declare the new function chat within. Once the function declared, we simply defined it. Thanks to the debug information from the libGameLogic.so, we know that the function name as well at the arguments and return types (i.e. it takes as argument one pointer to a Player object and one pointer to a char array, and return void).

We want the function chat to still operate as initially, so we want to re-route the execution flow back to the original Chat function. For this, we use dlsym to find the original Chat function address in the shared libraries and call it from the new Chat function. For this, we first need to define the variable that will receive the pointer to the original chat function. Then we use the mangled name to find the function with dlsym. Finally, we forward the parameters when calling the function.

Once the new C++ code written, we can compile our new shared library:

g++ -shared -fPIC -o hook.so hook.cc

Once done, we need to run the client with LD_PRELOAD so that we can load our new library before libGameLogic.so.

LD_PRELOAD=`pwd`/hook.so ./PwnAdventure3-Linux-Shipping

The binary PwnAdventure3-Linux-Shipping is the actual executable for the game. The binary is located here:

PwnAdventure3_data/PwnAdventure3/PwnAdventure3/Binaries/Linux/PwnAdventure3-Linux-Shipping

Now, we just need to join the game with any character and type anything in the chat dialog box (press enter) and you should see the text in the console where you executed the client.

Teleport

Now the we have successfully hijacked the chat function, we want to hack the game. For instance, teleporting wherever we want. We could develop a new function that will take care of changing our X, Y, Z position and update the server with our new location, but let’s check first if there is no function already available for that.

After spending some time looking for specific functions that contains words related to teleporting, I ended up with a very interesting function: Player::PerformTeleport. We can see in that function at an offset of 0x15843A a call to Actor::SetPosition(Vector3 const&). This function take as argument a Vector3 variable (an array of 3 floats) that contains the coordinate to set the new position of an Actor (including our player).

Let’s try to call that function with arbitrary coordinate. Vector3 is a variable that contains 3 floats, for the 3 coordinate X, Y and Z:

struct Vector3 { float x; float y; float z; };

Let’s take a position close to Michael Angelo for instance:

Vector3 pos;
pos.x = 260255.0;
pos.y = -249336.0;
pos.z = 1476.0;

We will then use again dlsym to find the address of the function original function in the library:

typedef void (*orig_pos_f_type)(Player *, const Vector3 *);
orig_pos_f_type orig;
orig = (orig_pos_f_type) dlsym(RTLD_NEXT, "_ZN5Actor11SetPositionERK7Vector3");

Same as we’ve done before, we want to execute the function Actor::SetPosition() whenever we send a specific string in the chat, e.g. „Michael“:

void Player::Chat(const char *text)
{
    std::string str(text);

    if (str.compare("Michael") == 0)
    {
        orig(this, &p);
    }

    // Call orignal Player::Chat() function
    orig_chat_f_type orig;
    orig = (orig_chat_f_type) dlsym(RTLD_NEXT, "_ZN6Player4ChatEPKc");
    orig(this, text);
}

Once, again, you compile then you run the client with DL_PRELOAD and when in the game, type „Michael“ and you should be teleported to Michael Angelo.

Move faster, Jump higher

For this final exercise, we will change the capacity of our character to move faster on the fly. Instead of always editing the binary and restart the game, we will hook the library so that whenever we send a particular string in the chat, it will modify our movement speed.

In our previous blog post, we edited the sprint multiplier, however, here we want to change the default walking speed. For the, I simply search for „getspeed“ in Hopper.

The function Player::GetWalkingSpeed() belongs to the class Player and returns a local variable of the instantiated object that calls the function. It’s a typical accessor method.

Unfortunately, I cannot simply get the value of a variable based on its name. The only thing I know is that the value for the walking speed is located at an offset of 736 (integer) bytes. Between that variable and the beginning of the objects are other variables and pointers, which would take too much time to entirely reverse. But there is no need to, the offset is sufficient. In our hook, we will simply edit the value located at this specific offset. For this, I will use an array as a „padding“:

class Player {

    public:
        char padding[736];
        float speed;
        void Chat(const char *text);

};


void Player::Chat(const char *text)
{
    
    std::string str(text);

    if (str.compare("speed up") == 0)
    {
        this->jumpspeed = this->jumpspeed * 1.5;
    }

    // Call orignal Player::Chat() function
    orig_chat_f_type orig;
    orig = (orig_chat_f_type) dlsym(RTLD_NEXT, "_ZN6Player4ChatEPKc");
    orig(this, text);
}

We can see in Hopper that the jump speed and the jump hold time is located at an offset of 740 (int) and 744 (int) bytes, thus right after the walking speed. We then simply need to add the two additional variables:

class Player {

    public:
        char padding[736];
        float speed;
        float jumpspeed;
        float jumphold;
        void Chat(const char *text);

};

And later access and modify them with:

this->jumpspeed 
this->jumphold

You can find the final hook.cc code in our GitHub page.


Blog series: IntroductionReverse Engineering Network ProtocolPwn Adventure 3 Network ProtocolBuilding a Wireshark ParserAsynchronous Proxy in PythonIntercepting Packets – Reverse Engineering BinaryPatching BinaryHooking shared library

Von |2017-07-25T09:00:45+00:0025. Juli, 2017 um 09:00 Uhr|KEYIDENTITY|Noch keine Kommentare

Über den Autor:

Manuela Kohlhas