docs/templates/pages/sega/software/security/alphadvd.html

225 lines
8.4 KiB
HTML

{% extends "sega.html" %} {% block title %}AlphADVD{% endblock %} {% block body %}
<h1>AlphaDVD (aka &alpha;DVD)</h1>
<p>
&alpha;DVD is the custom copy-protection SEGA employ for update DVDs. It is handled by
<code>mxAuthDisc.exe</code> on Ring systems. Is is present on DVR-* discs, typically the first in a multi-DVD
install process.
</p>
<p>
In order to understand &alpha;DVD, it's important to first have a basic understanding of how data is stored on a
DVD. Rather than like random access storage, where the data stream can be moddeled as a large addressable series of
bytes, DVDs are more akin to HDDs in their division into sectors. Unlike HDDs, however, there is no prescribed order
for the sectors! Each sector of data on disc is prefixed by a header identifying that sector, and notably including
its sector number. When a DVD reader is asked to read a specific sector, it spins the disc until it reads the
appropriate header, then returns the data following that header. There is importantly nothing here that would stop a
disc from containing multiple sectors with the same sector number in their prefix!
</p>
<p>
DVD readers will return the first sector that matches the requested sector number, so if we know where the different
duplicates are on disc we can seek to a known sector a short distance before the instance of the duplicate we wish
to aquire, then ask the DVD reader to read the duplicated sector. Depending on where we first seek, we will receive
different data back.
</p>
<p>
&alpha;DVD utilises this, with 6 duplicated sectors, each with three distinct copies. When authenticating a disc,
only one of these 6 duplicates will be checked, however which is checked is random, so in practice all 6 should be
present lest the disc sporadically fails the authentication. This is similar to a copy-protection scheme called
TAGES, however more advanced. All three instances <b>must</b> be present, so it is impossible to create a single
flat image that passes authentication!
</p>
<p>
As well as this more hardware based authentication, there is a level of encryption applied to the disc headers too.
This is however much easeier to work with. Each disc has a header in sector 16, sector 1, or sector 17 (checked in
that order). There is no indication which sector contains the header, so in turn each sector is read and decryption
is attempted. We can then validate the header magic number.
</p>
<h2>&alpha;DVD Encryption</h2>
<p>
&alpha;DVD encryption is a basic XOR cipher, where the text is XORed with a key, repeating the key as needed. The
key is always 32768 (8000<sub>h</sub>) bytes, and is unmodified during this process.
</p>
<p>
Keys are derrived based on a key expansion algorithm that takes as input an unsigned short (16 bit) seed. This
algorithm is fundamentally an LSFR-based PRNG, used to generate a stream of bytes, which becomes the key. The
<code>& 0xffffffff</code> is not strictly necessary but is convenient in python in order to avoid
<code>seed</code> blowing up in size.
</p>
<pre>
{%highlight "python"%}
def amAuthDiskInitKey(seed):
key = bytearray(0x8000)
seed <<= 1
for i in range(len(key)):
x = seed
for _ in range(8):
x |= ((((seed >> 4) ^ x) >> 11) & 1)
seed <<= 1
x <<= 1
key[i] = (x >> 1) & 0xff
seed = x & 0xffffffff
return key
{% endhighlight %}</pre
>
<h2>&alpha;DVD Headers</h2>
<p>
Now that we know how to decrypt data on &alpha;DVDs we can search for the header. The header will always be
encrypted with a fixed key with seed <code>5369</code>. The header is a sequence of 53 bytes, located at offset 318
if it is in sector 16, and offset 508 if it is in sector 1 or 17.
</p>
<table>
<thead>
<tr>
<td colspan="17">Header Offset</td>
</tr>
<tr>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tbody>
<tr>
<td><b>0</b></td>
<td colspan="4">Magic = F1FFFF1F<sub>h</sub></td>
<td colspan="2"></td>
<td colspan="2">Auth sector 1</td>
<td colspan="2">Auth sector 2</td>
<td colspan="2">Auth sector 3</td>
<td colspan="6"></td>
</tr>
<tr>
<td><b>1</b></td>
<td colspan="2">Data Offset</td>
<td colspan="4"></td>
<td colspan="2">??</td>
<td colspan="20">DVD Name</td>
</tr>
<tr>
<td><b>2</b></td>
<td colspan="2">Key seed</td>
<td colspan="2">Dummy number</td>
<td colspan="3"></td>
</tr>
</tbody>
</table>
<p>
To validate the decryption of a header, both the magic number and the DVD name are checked. The DVD name must start
with <code>SEGA_DVD</code>.
</p>
<p>
The key seed present is the header is used to generate a new key that will be used to decrypt the authentication
sectors.
</p>
<h2>The Authentication Sectors</h2>
<p>
The three sector addresses in the header are now used to perform a series of seeks and reads. We seek the drive by
requesting a read of 16 sectors, but disregarding the returned data. The first step is to choose the authentication
sector we wish to read. The six duplicates are present using the following offsets:
</p>
<ul>
<li>140<sub>h</sub></li>
<li>150<sub>h</sub></li>
<li>160<sub>h</sub></li>
<li>170<sub>h</sub></li>
<li>180<sub>h</sub></li>
<li>190<sub>h</sub></li>
</ul>
<p>We will refer to our chosen offset, from this list, as <code>n</code>.</p>
<ul>
<li>Seek to <code>[Auth sector 1] - 16</code></li>
<li>Read(16) <code>([Auth sector 1] + (n - 1)) & 0xFFFFFFF0</code></li>
<li>Seek to <code>[Auth sector 1] - 16</code></li>
<li>Seek to <code>[Auth sector 1] + [Auth sector 2] - 8</code></li>
<li>Read(16) <code>([Auth sector 1] + (n - 1)) & 0xFFFFFFF0</code></li>
<li>Seek to <code>[Auth sector 1] + [Auth sector 3] + 8</code></li>
<li>Read(16) <code>([Auth sector 1] + (n - 1)) & 0xFFFFFFF0</code></li>
</ul>
<p>
Each of the three reads are decrypted using the key we generated earlier, and are authentication block 1, 2, and 3
respectively. The actual data is at offset 31228 in these 16-sector blocks, and follows the following structure:
</p>
<table>
<thead>
<tr>
<td colspan="17">Header Offset</td>
</tr>
<tr>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tbody>
<tr>
<td><b>0</b></td>
<td colspan="4">Magic = F1FFFF1F<sub>h</sub></td>
<td colspan="4">Num magic</td>
<td colspan="4"><code>n</code></td>
<td colspan="4"></td>
</tr>
</tbody>
</table>
<p>
Num magic will be F1FFFF1F<sub>h</sub> in the first sector, F2FFFF2F<sub>h</sub> in the second, and F3FFFF3F<sub
>h</sub
>
in the third.
</p>
<p>
There is, however, one extra curveball. One of these three sectors is a dummy sector that contains nonsensiacal data
(in practice this appears to just be nulls). This is the sector indicated by the lower byte of the
<code>Dummy number</code> field in the alpha header. It is essential that this header is <b>not</b> valid.
</p>
<p>
Assuming we pass these checks, &alpha;DVD authentication succeeded. The disc will now be be read as usual, applying
the data offset from the alpha header before any operations. Coindidentally if an ISO image has been made of an
alphaDVD (which will be unable to pass authentication anyway), all sectors preceeding this offset can be stripped,
and the ISO now matches that of a non-alpha disc.
</p>
{% endblock %}