Encryption details

This commit is contained in:
Bottersnike 2022-01-22 14:15:53 +00:00 committed by Nathan Taylor
parent 281a1f5f37
commit 0daa10b01d
4 changed files with 135 additions and 12 deletions

View File

@ -89,8 +89,13 @@
<h2 id="common"><code>game.sv4_common</code></h2>
<h3>Request:</h3>
<pre>{% highlight "cxml" %}<call ...>
<game method="sv4_common">
...placeholder
<game method="sv4_common" ver="0">
<locid __type="str" />
<cstcode __type="str" />
<cpycode __type="str" />
<hadid __type="str" />
<licid __type="str" />
<actid __type="str" />
</game>
</call>{% endhighlight %}</pre>
<h3>Response:</h3>
@ -117,14 +122,27 @@
<h2 id="hiscore"><code>game.sv4_hiscore</code></h2>
<h3>Request:</h3>
<pre>{% highlight "cxml" %}<call ...>
<game method="sv4_hiscore">
...placeholder
<game method="sv4_hiscore" ver="0">
<locid __type="str" />
</game>
</call>{% endhighlight %}</pre>
<h3>Response:</h3>
<pre>{% highlight "cxml" %}<response>
<game status="??status">
...placeholder
<sc>
<d[]>
<id __type="s32" />
<ty __type="u32" />
<a_sq __type="str" />
<a_nm __type="str" />
<a_sc __type="u32" />
<l_sq __type="str" />
<l_nm __type="str" />
<l_sc __type="u32" />
<avg_sc __type="u32" />
<cr __type="s32" />
</d>
</sc>
</game>
</response>{% endhighlight %}</pre>

View File

@ -11,10 +11,44 @@
<h3>Response:</h3>
<pre>{% highlight "cxml" %}<response>
<package status="??status">
<item[] url="" />
<item[] url="" name="" desc="" size="" pkgtype="" sumtype="" sum="" from="" till="" />
</package>
</response>{% endhighlight %}</pre>
<p>A list of all packages available for download.</p>
<table>
<tr>
<td><code>url</code></td>
<td> </td>
</tr>
<tr>
<td><code>desc</code></td>
<td> </td>
</tr>
<tr>
<td><code>size</code></td>
<td>Size of the resource at <code>url</code> in bytes</td>
</tr>
<tr>
<td><code>pkgtype</code></td>
<td> </td>
</tr>
<tr>
<td><code>sumtype</code></td>
<td>Only allowable value is <code>md5</code>.</td>
</tr>
<tr>
<td><code>sum</code></td>
<td>The <code>sumtype</code> digest of the file at <code>url</code>.</td>
</tr>
<tr>
<td><code>from</code></td>
<td> </td>
</tr>
<tr>
<td><code>till</code></td>
<td> </td>
</tr>
</table>
<h2 id="intend"><code>package.intend</code></h2>
<h3>Request:</h3>

View File

@ -178,6 +178,7 @@
<li><code>lobby2</code></li>
<li><code>netlog</code></li>
<li><code>globby</code></li>
<li><code>poseevent</code></li>
</ul>
<p>I'll try and figure these out in due course, promise!</p>
{% endblock %}

View File

@ -28,13 +28,83 @@
<p>Encryption is not performed using a single static key. Instead, each request and response has its own key that is
generated.</p>
<p>These keys are generated baesd on the <code>X-Eamuse-Info</code> header.</p>
<p>This header loosely follows the format <code>1-[0-9a-f]{8}-[0-9a-f]{4}</code>. This corresponds to
<code>[version]-[serial]-[salt]</code>. <b>TODO: Confirm this</b>
<p>Keys follow thge format <code>1-[0-9a-f]{8}-[0-9a-f]{4}</code>. This corresponds to
<code>[version]-[seconds]-[salt]</code>. The salt is generated by a simple PRNG.
</p>
<p>Our per-packet key is then generated using <code>md5(serial | salt | KEY)</code>. Identifying <code>KEY</code> is
left as an exercise for the reader, however should not be especially challenging. <span style="color: #fff">Check
the page source if you're stuck.</span></p>
<!-- libavs-win32-ea3.dll:0x10054160 -->
<details>
<summary>The PRNG</summary>
<pre>{% 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 %}</pre>
<p>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.</p>
<p>One interesting observation is that the "least random" bit, that is typically discarded by the
<code>0x7ff...</code> mask is not actually being discarded here, as the upper two bytes of
<code>PRNG_STATE</code>, not the lower two, are used unmasked.
</p>
<p>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
<code>PRNG_STATE = (PRNG_STATE * 0xc2a29a69) + 0xd3dc167e</code>. Make sure to understand cryptography before
trying to roll your own!
</p>
</details>
<p>Our per-packet key is then generated using <code>md5(seconds | salt | ENC_KEY)</code>. Identifying
<code>ENC_KEY</code> is left as an exercise for the reader, however should not be especially challenging.
</p>
<details>
<summary>Source code details</summary>
<p>The interesting stuff can be found at <code>libavs-win32-ea3.dll:0x1002a800</code>. Rather than screenshots, I've
gone and tidied up the code somewhat to make it easier to follow. <code>eamuse_info</code> is pre-populated with
<code>"X-Eamuse-Info: 1-"</code> by the calling function (<code>0x1000eeed</code>) after which the pointer is
incremented to leave it right after that <code>-</code>, ready for us to <code>vsnprintf</code> into it.
</p>
<pre>{% 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 %}</pre>
<p></p>
</details>
<h2 id="lz77">LZ77</h2>
<p>Packets are compressed using lzss. The compressed data structure is a repeating cycle of an 8 bit flags byte,