233 lines
8.7 KiB
HTML
233 lines
8.7 KiB
HTML
|
{% extends "sega.html" %} {% block title %}AlphADVD{% endblock %} {% block body %}
|
||
|
<h1>AlphaDVD (aka αDVD)</h1>
|
||
|
<p>
|
||
|
α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 α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>
|
||
|
α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>αDVD Encryption</h2>
|
||
|
<p>
|
||
|
α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. I'm not
|
||
|
totally sure what expansion algorithm this is, or if it's something totally custom, but for now here's a snippet of
|
||
|
python code that implements the expansion:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
{%highlight "python"%}
|
||
|
def amAuthDiskInitKey(seed):
|
||
|
key = bytearray(0x8000)
|
||
|
|
||
|
for i in range(0x8000):
|
||
|
uVar1 = (seed * 2 >> 4 ^ seed * 2) >> 10 & 2 | seed << 2
|
||
|
|
||
|
uVar2 = uVar1 * 2
|
||
|
uVar3 = ((seed << 2) >> 4 ^ uVar1) >> 10 & 2 | uVar2
|
||
|
uVar1 = uVar3 * 2
|
||
|
uVar3 = (uVar2 >> 4 ^ uVar3) >> 10 & 2 | uVar1
|
||
|
|
||
|
uVar2 = uVar3 * 2
|
||
|
uVar3 = (uVar1 >> 4 ^ uVar3) >> 10 & 2 | uVar2
|
||
|
uVar1 = uVar3 * 2
|
||
|
uVar3 = (uVar2 >> 4 ^ uVar3) >> 10 & 2 | uVar1
|
||
|
|
||
|
uVar2 = uVar3 * 2
|
||
|
uVar3 = (uVar1 >> 4 ^ uVar3) >> 10 & 2 | uVar2
|
||
|
uVar1 = uVar3 * 2
|
||
|
uVar2 = (uVar2 >> 4 ^ uVar3) >> 10 & 2 | uVar1
|
||
|
|
||
|
seed = uVar2 | (uVar1 >> 4 ^ uVar2) >> 11 & 1
|
||
|
key[i] = seed & 0xff
|
||
|
|
||
|
return key
|
||
|
{% endhighlight %}</pre
|
||
|
>
|
||
|
|
||
|
<h2>αDVD Headers</h2>
|
||
|
<p>
|
||
|
Now that we know how to decrypt data on α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, α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 %}
|