mirror of
https://gitea.tendokyu.moe/eamuse/docs.git
synced 2024-11-27 16:10:51 +01:00
PCP and Alpha
This commit is contained in:
parent
b1544d136c
commit
c408eff8d0
BIN
images/pcp-railroad.png
Normal file
BIN
images/pcp-railroad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
232
templates/pages/sega/software/alphadvd.html
Normal file
232
templates/pages/sega/software/alphadvd.html
Normal file
@ -0,0 +1,232 @@
|
||||
{% 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 %}
|
@ -1,48 +1,71 @@
|
||||
{% extends "sega.html" %}
|
||||
{% block title %}PCP{% endblock %}
|
||||
{% block body %}
|
||||
{% extends "sega.html" %} {% block title %}PCP{% endblock %} {% block body %}
|
||||
<h1>PCP</h1>
|
||||
<h2>What is PCP?</h2>
|
||||
<p>PCP is the protocol used for inter-process communication between services running on Ring* systems. I have no idea
|
||||
what it stands for; head-canon it as Process Command Protocol or whatever you want really. The official
|
||||
implementation is <code>libpcp</code>, which is statically linked in to binaries that make use of the protocll (and
|
||||
is itself dependent on <code>amLib</code>).</p>
|
||||
<p>On paper, there are many things the format would at first appear to
|
||||
support, but is unable to due to the reference implementation in <code>libpcp</code>. Specification of this nature
|
||||
will be <span class="mark">marked</span>. Custom implementations should be liberal in what they receive, and
|
||||
conservative in what they transmit; marked specification are key areas of focus for this.</p>
|
||||
<p>
|
||||
PCP is the protocol used for inter-process communication between services running on Ring* systems. I have no idea
|
||||
what it stands for; head-canon it as Process Communication Protocol or whatever you want really. The official
|
||||
implementation is <code>libpcp</code>, which is statically linked in to binaries that make use of the protocol (and
|
||||
is itself dependent on <code>amLib</code>).
|
||||
</p>
|
||||
<p>
|
||||
On paper, there are many things the format would at first appear to support, but is unable to due to the reference
|
||||
implementation in <code>libpcp</code>. Specification of this nature will be <span class="mark">marked</span>. Custom
|
||||
implementations should be liberal in what they receive, and conservative in what they transmit; marked specification
|
||||
are key areas of focus for this.
|
||||
</p>
|
||||
|
||||
<h2>Protocol Specification</h2>
|
||||
<p>We consider two processes communicating: a server, and a consumer. A server need only be capable of processing at
|
||||
least one consumer concurrently, though implementations may desire the ability to do so.</p>
|
||||
<p>When a server is ready to process a command, it transmits a single <code>></code> byte to its connected consumer.
|
||||
<p>
|
||||
We consider two processes communicating: a server, and a consumer. A server need only be capable of processing at
|
||||
least one consumer concurrently, though implementations may desire the ability to process multiple consumers.
|
||||
</p>
|
||||
<p>
|
||||
When a server is ready to process a command, it transmits a single <code>></code> byte to its connected consumer.
|
||||
</p>
|
||||
<p>The consumer responds with a CRLF-terminated payload packet, containing the command.</p>
|
||||
<p>The server then responds syncronously with a CRLF-terminated payload packet. If a data transfer is being a performed,
|
||||
this packet will contain <code>port</code> and <code>size</code> as paramaters.</p>
|
||||
<p>In a server->consumer data transfer operation, the consumer connects to the provided port, and expects to receive
|
||||
<p>
|
||||
The server then responds syncronously with a CRLF-terminated payload packet. If a data transfer is being a
|
||||
performed, this packet will contain <code>port</code> and <code>size</code> as paramaters.
|
||||
</p>
|
||||
<p>
|
||||
In a server->consumer data transfer operation, the consumer connects to the provided port, and expects to receive
|
||||
<code>size</code> bytes of data. It then closes the connection to the data port and transmits a <code>$</code> byte
|
||||
to the server to ackgnowledge receipt. The server will only process this ackgnowledgement after it has succesfully
|
||||
transmitted its data to a consumer.
|
||||
</p>
|
||||
<p>The characteristics of a consumer->server transfer as as yet undocumented. I'll get round to it!</p>
|
||||
|
||||
<p>If the server is unable to process a request for any reason, it may respond with a single <code>?</code>. This may be
|
||||
due to a non existant command being requested, or it may be due to an invalid packet.</p>
|
||||
<p>
|
||||
If the server is unable to process a request for any reason, it may respond with a single <code>?</code>. This may
|
||||
be due to a non existant command being requested, or it may be due to an invalid packet.
|
||||
</p>
|
||||
|
||||
<h3>Payload format</h3>
|
||||
<p>Payloads are a non-zero number of <code>&</code> delimited <code>=</code> seperated key-value pairs. i.e.
|
||||
<code>key1=value2&key2=value2</code>. Both the key and the value can contain any alphanumeric character, and any of
|
||||
the symbols <code>._-:@%/\{}</code>. The value may alternatively be a single <code>?</code>. Leading and trailing
|
||||
ampersands are illegal, as is more than one ampersand consecuitevely. Empty strings for either the key or value is
|
||||
likewise illegal.
|
||||
|
||||
<p>
|
||||
Outside of special packets such as <code>></code> and <code>$</code>, all communication in both directions
|
||||
strictly follows the following structure:
|
||||
</p>
|
||||
<p>Spaces (ascii 20<sub>h</sub>) and tabs (ascii 09<sub>h</sub>) are allowable whitespace. They may be present anywhere
|
||||
|
||||
<img src="{{ROOT}}/images/pcp-railroad.png" class="graphic" />
|
||||
|
||||
<p>
|
||||
<i><code>Text</code></i> is defined as a series of one or more bytes matching <code>[a-zA-Z0-9._:@%/\{}-]</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Spaces (ascii 20<sub>h</sub>) and tabs (ascii 09<sub>h</sub>) are allowable whitespace. They may be present anywhere
|
||||
in the packet surrounding keys, values, any delimiter, or seperator, and will be ignored. They are not valid within
|
||||
a key or a value.</p>
|
||||
<p>Comments begin and end with the <code>#</code> symbol. They may appear at any point in a packet, and the packet
|
||||
should be processed as if the comment is not there. <b>The content of comments observe the same restrictions as keys
|
||||
and values. This notably includes no whitespace.</b></p>
|
||||
a key or a value.
|
||||
</p>
|
||||
<p>
|
||||
Comments begin and end with the <code>#</code> symbol. They may appear at any point in a packet, and the packet
|
||||
should be processed as if the comment is not there.
|
||||
<b
|
||||
>The content of comments must match <i><code>Text</code></i
|
||||
>. This notably includes no whitespace.</b
|
||||
>
|
||||
</p>
|
||||
|
||||
<h2>Format restrictions</h2>
|
||||
<ul>
|
||||
@ -65,24 +88,31 @@
|
||||
|
||||
<details>
|
||||
<summary>libpcp bugs</summary>
|
||||
<p>When parsing requests, libpcp null-terminates <code>?</code> values with an off-by-one error. This means if you
|
||||
<p>
|
||||
When parsing requests, libpcp null-terminates <code>?</code> values with an off-by-one error. This means if you
|
||||
transmit <code>test=12345</code> followed by <code>test=?</code>, it will be parsed as if you had transmitted
|
||||
<code>test=?2</code>. This could actually be an issue with how I am inspecting the internal state; I will
|
||||
update/remove this spoilier once I've had a chance to dig deeper.
|
||||
</p>
|
||||
<p>libpcp does not enforce ampersand placement, causing strange memory artifacts. Consumers <b>MUST</b> conform to
|
||||
the ampersand specification.</p>
|
||||
<p>libpcp allows empty keys and values. The case of an empty key causes the pair to be keyed with an empty string,
|
||||
<p>
|
||||
libpcp does not enforce ampersand placement, causing strange memory artifacts. Consumers <b>MUST</b> conform to
|
||||
the ampersand specification.
|
||||
</p>
|
||||
<p>
|
||||
libpcp allows empty keys and values. The case of an empty key causes the pair to be keyed with an empty string,
|
||||
however an empty value causes it to contain a random value, reading from memory where the previous packet was
|
||||
decoded. In fact, the presence of a <code>=</code> is not validated either, likewise causing it to read
|
||||
undefined regions of memory. Consumers <b>MUST</b> always provide both the key and the value.</p>
|
||||
undefined regions of memory. Consumers <b>MUST</b> always provide both the key and the value.
|
||||
</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Using libpcp</summary>
|
||||
<p>I have reproduced a locally functioning standalone distribution of libpcp, warts and all. Eventually I will
|
||||
<p>
|
||||
I have reproduced a locally functioning standalone distribution of libpcp, warts and all. Eventually I will
|
||||
produce some basic docs for making use of the exported functions, and will hopefully be able to provide a
|
||||
download for a precompiled library. I'm still unsure if the source code will ever be made available however
|
||||
because it's a <em>very</em> true to the original reproduction, potentially problematically so!</p>
|
||||
because it's a <em>very</em> true to the original reproduction, potentially problematically so!
|
||||
</p>
|
||||
<p>If rather than implementing your own pcp you wish to use <em>the</em> libpcp, watch this space.</p>
|
||||
</details>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user