February 1, 2021

    Attacking the Network Layer in Multiplayer Games

    Welcome to the first in our blog series on our team’s mobile game security research. In it, we’ll cover our researchers’ findings on popular games, and give you tips on how to protect your app against commonly executed attacks. All research was done ethically, and game publishers were notified of the findings and remediation recommendations.

    Earlier this month, we got our hands on a remake of Bomberman. Bomberman-Friends is a remake of Bomberman from the old days and has millions of downloads on both the AppStore and Google Play. However, we played the game by a different set of rules: the person that comes up with the best cheats wins the game.

    In this blog post, we will present our winning solution –teleportation – along with the cheat implementation. We will also present a take-home lesson for game devs looking to harden their games and protect themselves against these types of cheats.

    gif_network

    We kicked our research off with a brief reconnaissance on the app in order to find all function and variable names that were left unobfuscated by developers inside the app. We looked for any name that would reveal a part of the code in the binary where the player's position is updated. Radare2, a lightweight static analysis tool that can list all names (also known as symbols) from a binary, is the ideal tool to accomplish this task.

    Listing symbols with Radare2

    We also listed all strings in the binary by using the command iz. We were hoping to find artifacts from logging functions or any strings related to the player’s position. There were over 85,000 strings in the app and 60,000 symbols, from which many had their name stripped. We definitely didn’t want to spend a lot of time filtering out the results and diving deep into reverse engineering. After a quick look, we noticed that the app used send(int, void*, size_t, int), which is a socket API used to send data over the network. At the same time, there were many strings denoting the usage of the UDP protocol. Therefore, we decided to check how the game handles communication with the server at runtime.

    Intercepting the game’s network traffic with Frida

    Frida is a popular dynamic instrumentation tool, used to control the behavior of an app at runtime. It is commonly used by hackers to tamper with an app on jailbroken and even unjailbroken devices. Scripting in Frida is well-documented and requires minimal knowledge of JavaScript. Hence, it’s a widely adopted tool in the mobile security community.

    First, we created a Frida script to hook the send function that prints out the data that it sends to the server:

    // size_t send(int fd, void *buf, size_t len int flags)

    Interceptor.attach(
    Module.getExportByName(null,'send'),{
    onEnter(args) {
    var socketType = Socket.type(args[0].toInt31());
    if(socketType.indexOf('udp') == -1){
    return; // Not a UDP packet
    }
    var dataLen = args[2].toInt32();
    var data = ptr(args[1]).readByteArray(dataLen)
    console.log(data);
    }
    });

    At first sight, the printed bytes didn’t reveal too much about the data. Therefore, we moved our “player one” position to the left and then back to the right in order to see which bytes would change by the changed positions. The intercepted traffic is shown below.

    We noticed that outgoing messages have a very regular and repetitive structure and only certain bytes changed. This clearly indicated that the network data wasn’t encrypted. 

    Therefore, we assumed that if a message contains a position, the bytes describing the horizontal position should decrease when we move to the left and increase back to its original value when we move back to the right. The bytes marked in red behave exactly this way. We converted the bytes to their decimal representation in order to gain a better understanding of their meaning.

    At that time, we knew that our player (bottom right corner) was walking between horizontal positions of 1100 and 1000. Using this knowledge, we could easily reassemble the whole layout of the map, which is shown below along with position labels. 

    Then, we performed a few more test actions, for example placing a bomb with our avatar, and collected the corresponding network samples. Further analysis revealed a few additional properties of the protocol, which we put in the outline of the game protocol below.

    Creating a teleportation cheat using dynamic instrumentation

    We decided to create a cheat that could bomb our opponent remotely, since this would guarantee us to win. When we press the bomb button, our cheat intercepts the network message and modifies the bomb’s position. This way, we only need to modify a single message in order for our cheat to work as intended.

    First, we wrote a script to filter out “bomb placement” messages sent to the server.

    function deserializeMsg(msg_buffer, offset){
    var msg = null;
    // Parse the message type of size 1 byte from the buffer
    var msg_type = msg_buffer.add(MSG_DATA_TYPE_OFFSET).readU8()
    offset += MSG_DATA_TYPE_SIZE;

    if(msg_type == MSG_TYPE_MOVE){
    msg = deserializeMsgMove(msg_buffer, offset, msg);
    } else if(msg_type == MSG_TYPE_BOMB){
    msg = deserializeMsgBomb(msg_buffer, offset, msg)
    }
    return msg
    }

    Then, we added a function to determine the bomb's position and overwrite it with the position of our opponent.

    function deserializeMsgBomb(msg_buffer, offset, msgBomb){
    offset += MSG_DATA_UNKNOWN_VALUE_1_SIZE;
    offset += MSG_DATA_BOMB_RANGE_SIZE;
    offset += MSG_DATA_UNKNOWN_VALUE_2_SIZE;

    // swap32 swaps endianness of a 4-byte integer
    msgBomb.pos_x = swap32(msg_buffer.add(offset).readU32())
    if(cheat_enabled){
    msg_buffer.add(offset).writeU32(swap32(new_pos_X))
    }
    offset =+ MSG_POS_X_SIZE;

    msgBomb.pos_y = swap32(msg_buffer.add(offset).readU32())
    if(cheat_enabled){
    msg_buffer.add(offset).writeU32(swap32(new_pos_y))
    }
    }

    After injecting the Frida script and clicking on the bomb button, we noticed that the game desynchronized for a second. To our surprise, it teleported us to the new position instead of placing a bomb there. Happy with this result too, we decided not to dive into it any further and leverage this unexpected behavior to its maximum, as we already had everything scripted in Frida.

    Running this script three times sequentially with properly picked positions could instantly teleport our avatar to the opponent, place a bomb there and teleport back.

    Mobile security tips for game developers

    Looking back at how we managed to create the above cheat, we’ve tried to collect some actionable advice for game developers looking to better protect against this kind of cheating

    • User’s actions should not only be validated at the client side (e.g. on an iPhone) but also at the server side. In other words, whenever the values received by the server deviate from the expected data, the server should take appropriate measures to stop the attacker. Ideally, we would validate all users’ actions on the server but tracking every little movement of each player may impact the game’s synchronization time. Therefore, finding a well balanced sweet spot for the client and the server side validation seems to be the most efficient way of protection.

    • Initially, we scouted the function and variable names in the game by using Radare2. This could be made more difficult by being aware of symbol and metadata visibility. Even without any specialized tools, proper compiler and linker configuration can already prevent some semantic data from leaking. Our previous articles on this topic can provide further guidance, An Introduction to Objective-C Metadata & Symbols in Swift & Objective-C Apps (part1) and part 2.

    • Afterward, we took the path of least resistance, dynamic instrumentation on a jailbroken iOS device. A successful countermeasure can be implemented by simply using a one-liner freely available at OWASP’s Resiliency Against Reverse Engineering page, or by using an open-source framework like IOSSecuritySuite.
    • Using a secure protocol with UDP is another must for multiplayer games, in order to prevent network tampering. Encrypting the network traffic not only hides all semantics but also comes with built-in integrity validation. CryptoSwift is an open-source cryptographic framework with a wide range of such symmetric and asymmetric ciphers that could be used to prevent these types of attacks.
    • Less time critical operations, like signing up for and buying items, are often performed with HTTPS protocol. For such communication, we recommend implementing SSL pinning, as it protects against intercepting network requests with man-in-the-middle attacks.

    Guardsquare offers advanced tools for developers to implement all of these protections securely, and without interfering with the codebase.

    Stay tuned for our next blog in the series, where we’ll cover cheating in another popular mobile game.

    Build_the_Best_Mobile_Gaming_Experience

    Discover how Guardsquare provides industry-leading protection for mobile apps.

    Request Pricing

    Other posts you might be interested in