{% extends "konami.html" %} {% block title %}Network format{% endblock %} {% block body %}

# Network format

eAmuse packets are sent and received over HTTP (no S), with requests being in the body of `POST` requests, and replies being in the, well, reply.

The packets are typically both encrypted and compressed. The compression format used is indicated by the `X-Compress` header, and valid values are

- `none`
- `lz77`

Source code details
`libavs-win32-ea3.dll:0x1000fa29`

Encryption is performed after compression, and uses RC4. RC4 is symmetric, so decryption is performed the same as encryption. That is, `packet = encrypt(compress(data))` and `data = decompress(decrypt(data))`.

## Encryption keys

Encryption is not performed using a single static key. Instead, each request and response has its own key that is generated.

These keys are generated baesd on the `X-Eamuse-Info` header.

Keys follow the format `1-[0-9a-f]{8}-[0-9a-f]{4}`. This corresponds to `[version]-[seconds]-[salt]`. The salt is generated by a simple PRNG.

The PRNG

    {% highlight "c" %}
    uint32_t PRNG_STATE = 0x41c64e6d;

    uint32_t prng() {
        int upper = (PRNG_STATE * 0x838c9cda) + 0x6072;
        PRNG_STATE = (PRNG_STATE * 0x41c64e6d + 0x3039) * 0x41c64e6d + 0x3039;
        return upper & 0x7fff0000 | PRNG_STATE >> 0xf & 0xffff;
    }
    {% endhighlight %}

This is a simple linear conguential pseudorandom number generator (what a mouthful) being stepped twice every call. The constants being used for the main LCPRNG are taken from glibc, along with the dropping of the "least random" bit. Where this implementation differs however is that the LCPRNG is stepped twice every call, and that a second LCPRNG is used for the upper 2 bytes.

One interesting observation is that the "least random" bit, that is typically discarded by the `0x7ff...` mask is not actually being discarded here, as the upper two bytes of `PRNG_STATE`, not the lower two, are used unmasked.

Another interseting observation is that due to the nature of LCGs, stepping it twice is no more secure than once. The implementation presented here is actually just a single step of `PRNG_STATE = (PRNG_STATE * 0xc2a29a69) + 0xd3dc167e`. Make sure to understand cryptography before trying to roll your own!

Our per-packet key is then generated using `md5(seconds | salt | ENC_KEY)`. `ENC_KEY` is currently `69d74627d985ee2187161570d08d93b12455035b6df0d8205df5` for all games.

Source code details

The interesting stuff can be found at `libavs-win32-ea3.dll:0x1002a800`. Rather than screenshots, I've gone and tidied up the code somewhat to make it easier to follow. `eamuse_info` is pre-populated with `"X-Eamuse-Info: 1-"` by the calling function (`0x1000eeed`) after which the pointer is incremented to leave it right after that `-`, ready for us to `vsnprintf` into it.

    {% highlight "c" %}
    static const char *ENC_KEY[26] = "Wait you didn't think I'd put this here, did you?";

    int xrpc_crypt(char *packet, char *xeamuse_info) {
        char md5_key[16];
        char key[32];

        // Copy the {8}-{4} hex char pairs into key as 6 bytes
        if (copy_from_hex(key, 4, xeamuse_info) == -1)
            return -1;
        if (xeamuse_info[8] != '-')
            return -1;
        if (copy_from_hex(key + 4, 2, xeamuse_info + 9) == -1)
            return -1;

        // Add our constant key after the two variable parts...
        strncpy(key + 6, ENC_KEY, 26);
        // ...MD5 it all...
        mdigest_create_local(0, key, 32, md5_key, 16);
        // ...and use that digest as the key for RC4
        arc4(packet, md5_key, 16);
        return 0;
    }

    int xrpc_key_and_crypt(char *packet, char *eamuse_info, size_t size) {
        uint64_t miliseconds = read_timer(0);
        uint32_t seconds = __aulldiv(miliseconds, 1000, 0);
        uint16_t salt = prng() & 0xffff;

        int bytes_formatted = vsnprintf(eamuse_info, size, "%08x-%04x", seconds, salt);
        if (bytes_formatted < size) {
            xrpc_crypt(packet, eamuse_info);
        }
        return bytes_formatted;
    }
    {% endhighlight %}

## LZ77

Packets are compressed using lzss. The compressed data structure is a repeating cycle of an 8 bit flags byte, followed by 8 values. Each value is either a single literal byte, if the corresponding bit in the preceeding flag is high, or is a two byte lookup into the window.

The lookup bytes are structured as `pppppppp ppppllll` where `p` is a 12 bit index in the window, and `l` is a 4 bit integer that determines how many times to repeat the value located at that index in the window.

The exact algorithm used for compression is not especially important, as long as it follows this format. One can feasibly perform no compression at all, and instead insert `0xFF` every 8 bytes (starting at index 0), to indicate that all values are literals. While obviously poor for compression, this is an easy way to test without first implementing a compressor.

{% endblock %}