Tencent – Arena of Valor – Reverse engineering and protocol documentation

Hello everyone, today I’m going to document a little bit my found on Arena of Valor after a couple of week of exploration, analysis and debugging.

Security measures which I had to face this time was something totally different from the one previously met on the new Boom Beach compiler, I can list some of them but I can guess there are probably way more also to prevent the tamper of the binary, which I didn’t touched to understand and reverse the protocol.

  1. Anti debugging. There are a lot of way to prevent a debugger to get attached to the main process. Tencent is probably (not sure) attaching a “self-made” debugger (which is coded in the game library) and its sending “messages” to it during runtime (which crash GDB even if you attach it before the Tencent debugger get attached). Im not sure it’s a debugger (I really think so) but in any case, something is attaching/forking the game process which can’t be attached from another debugger/process.
  2. Encryption + Compression. Protocol is encrypted using (a custom?) AES implementation which sees common “features” from the different AES mode. The CBC mode is used for sure on encryption and decryption but also, there is a TAG verification which is used on different AES modes.
    Initial IVS for decryption/enryption: 000102030405060708090a0b0c0d0e0f.
    Verification TAG: tsf4gwhich is verified both on serverside and clientside.

    After encryption, I had to face also a custom LZ4 compression/decompression implementation. (I’m assuming it’s a custom one since python, c# and java libraries are giving me different results).

  3. Hosts file. Here comes a totally crazy thing which I think it’s managed inside that ‘Gameprotector’ binary (This gameprotector is created, executed and deleted during runtime), but once again I could be totally wrong. The certain fact is that Tencent is somehow checking and editing hosts file before connecting to game server. As a proof of this if you add any game server domain in your hosts file and start playing – right before the game is going to connect to it, you will somehow see this line gone from your hosts file. This, at me, look like a BIG security flaw but I could once again be wrong and they are using some trick to achieve this result.
  4. A couple of line more to close protection side:
    Tencent is logging everything. So sad to see this and I’m still questioning my self about why do they need to know my phone number (i.e) or my MAC address or my position.

The protocol structure:

Protocol structure looks really crazy. There are multiple involved rest server and TCP / UDP sockets. I didn’t went so far so I cant say much about the UDP protocol which I’m sure it’s running during battles, but I can spend a lot of words on the TCP, which is used to build the “game home” in example or to retrieve player informations.

Once the game has been started, there are multiple sockets connecting to different endpoints; One is involved with updates and another one for configs and “dir server”. This data exchange is used to let the lobby server knows if we have any account and so, it can return us a server list (in my example, i get gs5 – which should be server 5 on europe).
NOTE: Pointing gs5 server to a dead ip, will let the original client jump automatically to gs6 using the same data.

The TCP protocol structure begin with a GET requests at
ng-eu-sdkapi.gameitop.com which give us a token and a couple of extra things. This token is then used to login to our gs5.

Once our token is stored, a connection to gs5 will be initialized with the following protocol strcture:

self.struct = {
    "headers": {
        "magic": magic,
        "head_version": head_version,
        "body_version": body_version,
        "command": command,
        "is_response": is_response,
        "sequence": sequence,
        "head_len": head_len,
        "body_len": body_len
    }
}

These headers changes a bit during lifecycle, the very first request sent from the client will have these headers + an extra header called SYN.
SYN includes account ID and which encryption/compression/route (etc etc) is our client going to use. In response, we will receive the headers + an extra header called ACK which will give us the encryption key to use.

SYN head structure:

msg_struct[os.path.basename(__file__)[:-3]] = {
    "key_method": decoder.read_ubyte()["r"],
    "enc_method": decoder.read_ubyte()["r"],
    "service_id": decoder.read_uint32_be()["r"],
    "client_type": decoder.read_uint32_be()["r"],
    "account_type": decoder.read_ushort_be()["r"],
    "account_format": decoder.read_ubyte()["r"],
    "account_id": decoder.read_string(skiplenlen=1)["r"],
    "has_relay_info": decoder.read_ubyte()["r"],
    "has_auth_info": decoder.read_ubyte()["r"],
    "support_compress_list": decoder.read_ubyte()["r"],
    "route_flag": decoder.read_ubyte()["r"],
    "route_info_type": decoder.read_uint32_be()["r"],
    "route_info_zone": decoder.read_uint32_be()["r"],
    "reserved": decoder.read_uint32_be()["r"]
}

Once encryption key is set, the auth request with our token is sent to gs5 which will welcome us with an “auth ok” message. The encryption is a bit more complicated then what I wrote on the beginning of this post, in fact there is also the last byte of the encrypted payload which is verified serverside and which came as well from different encryption logic.

Here is a short recap of the “lifecycle”:

  • Client request the token through GET request.
  • Server provide token and additional informations.
  • Client connect to game server sending common headers + SYN head.
  • Server response with common headers + ACK head
  • Client send auth request with token and account id.
  • Server response with “auth ok”.Everything after this, will have 16403 as command and the body will have his own new structure with his own headers + body. In addition, a byte more in the common headers is added indicating if the body is compressed or not.Headers for client requests:
    – sequence
    – command id
    – magic (incremented after Google Play update)
    – response_sequence
    – request_sequence

    Headers for server responses:
    – command id
    – magic (incremented after Google Play update)
    – response_sequence
    – request_sequence

  • Server send us bingo message (with an unknown key, probably due to my lack on game knowledge) + command 1014.
  • Client send command 1041 with device informations
  • Server response with another big big big welcome shipping 72+ responses used to build the home screen.
  • Client send command 1019 with token + account id
  • Everything else is gold 🙂

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.