Security docs

This commit is contained in:
Bottersnike 2022-11-27 22:29:36 +00:00
parent 3f234d05de
commit 9eb4887288
8 changed files with 545 additions and 9 deletions

View File

@ -49,14 +49,15 @@ SEGA_CONTENTS = {
"software": ("Software", {
"pcp": ("PCP", {"libpcp.html": "libpcp"}),
"drivers": ("Device drivers", None),
"security": ("Security", {
"game.html": "Game encryption",
}),
"security.html": "Security",
"groovemaster.html": "GrooveMaster.ini",
}),
# "network": ("Networking", {
# "allnet.html": "ALL.Net"
# }),
"misc": ("Misc", {
"partition.html": "SEGA Partition Structure"
}),
}
CONTENTS = {
"": EAMUSE_CONTENTS,

BIN
images/ftk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1 @@
adsaf21519189aq1g56161asdff19as1f9:PF:CA[][_159191wef

View File

@ -0,0 +1,2 @@
アュ
ソ<>㌣餒#考

View File

@ -294,6 +294,18 @@ mark {
}
}
.ata-bad {
color: #f5417d;
}
.ata-good {
color: #4df541;
}
.ata-ignore {
color: #f5ad41;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;

View File

@ -0,0 +1,526 @@
{% extends "sega.html" %}
{% block title %}{% endblock %}
{% block body %}
<h1>System Security</h1>
<p>The Ring* series have a number of security measures in place, some easy to bypass, others requiring more work. They
are listed here in, roughly, the order in which each layer is applied.</p>
<h2 id="ata">Drive ATA Password</h2>
<p>The SSD contained within the system has an ATA password set. The system BIOS contains a password derivation function
that derives the specific password for that drive based on its serial number.</p>
<p>This can be bypassed either by extracting the password used, or by first powering on the Ring* system with the drive
connected, then hotplugging the SATA data cable on the drive while keeping the drive powered.</p>
<details>
<summary>Why does this work?</summary>
<p>The following is the sequence of possible security modes for an ATA drive:</p>
<svg viewbox="-0.5 -0.5 800 400" style="width: 800px">
<defs>
<marker id="arrowhead-good" markerWidth="10" markerHeight="8" refX="0" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="currentColor" class="ata-good" />
</marker>
</defs>
<defs>
<marker id="arrowhead-ignore" markerWidth="10" markerHeight="8" refX="0" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="currentColor" class="ata-ignore" />
</marker>
</defs>
<defs>
<marker id="arrowhead-bad" markerWidth="10" markerHeight="8" refX="0" refY="4" orient="auto">
<polygon points="0 0, 10 4, 0 8" fill="currentColor" class="ata-bad" />
</marker>
</defs>
<g class="ata-ignore">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC0
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="43" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Power: off
</text>
<text x="75" y="57" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: No
</text>
</g>
<g class="ata-ignore">
<text x="200" y="30" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Power on
</text>
<line x1="150" y1="40" x2="240" y2="40" stroke="currentColor" marker-end="url(#arrowhead-ignore)"></line>
<text x="450" y="20" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Freeze
</text>
<line x1="400" y1="30" x2="490" y2="30" stroke="currentColor" marker-end="url(#arrowhead-ignore)"></line>
<line x1="500" y1="50" x2="410" y2="50" stroke="currentColor" marker-end="url(#arrowhead-ignore)"></line>
<text x="450" y="60" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
HW Reset
</text>
<line x1="325" y1="80" x2="325" y2="150" stroke="currentColor"></line>
</g>
<g transform="translate(250 0)" class="ata-ignore">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC1
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="43" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: No
</text>
<text x="75" y="57" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Frozen: No
</text>
</g>
<g transform="translate(500 0)" class="ata-ignore">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC2
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="43" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: No
</text>
<text x="75" y="57" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Frozen: Yes
</text>
</g>
<g transform="translate(250 150)" class="ata-good">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC5
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="36" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: Yes
</text>
<text x="75" y="50" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Unlocked: Yes
</text>
<text x="75" y="64" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Frozen: No
</text>
</g>
<g transform="translate(250 300)" class="ata-bad">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC4
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="36" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: Yes
</text>
<text x="75" y="50" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Unlocked: No
</text>
<text x="75" y="64" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Frozen: No
</text>
</g>
<g transform="translate(500 225)" class="ata-good">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC6
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="36" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: Yes
</text>
<text x="75" y="50" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Unlocked: Yes
</text>
<text x="75" y="64" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Frozen: Yes
</text>
</g>
<g class="ata-bad">
<text x="200" y="330" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Power on
</text>
<line x1="150" y1="340" x2="240" y2="340" stroke="currentColor" marker-end="url(#arrowhead-bad)"></line>
<text x="470" y="340" fill="currentColor" dominant-baseline="middle" text-anchor="start"
font-family="monospace">
HW reset
</text>
<line x1="575" y1="305" x2="410" y2="340" stroke="currentColor" marker-end="url(#arrowhead-bad)"></line>
</g>
<g class="ata-good">
<text x="318" y="265" fill="currentColor" dominant-baseline="middle" text-anchor="end"
font-family="monospace">
Unlock
</text>
<line x1="325" y1="300" x2="325" y2="240" stroke="currentColor" marker-end="url(#arrowhead-good)"></line>
<line x1="400" y1="195" x2="490" y2="265" stroke="currentColor" marker-end="url(#arrowhead-good)"></line>
<text x="435" y="210" fill="currentColor" dominant-baseline="middle" text-anchor="start"
font-family="monospace">
Freeze
</text>
</g>
<g transform="translate(0 300)" class="ata-bad">
<rect fill="transparent" x="0" y="0" width="150" height="20" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="10" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
SEC3
</text>
<rect fill="transparent" x="0" y="20" width="150" height="60" stroke-width="1px" stroke="currentColor">
</rect>
<text x="75" y="43" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Power: off
</text>
<text x="75" y="57" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace">
Security: Yes
</text>
</g>
</svg>
<p>When the drive has a password set, it initially starts in SEC3. The RingEdge then transitions the drive to SEC5
then SEC6. Importantly, however, as long as power is never lost, the drive will remain in SEC6 mode, even if we
connect it to a different system.</p>
<p>This allows us full read-write access to the drive without ever knowing the password!</p>
</details>
<h2 id="windows">Windows Password</h2>
<p>It seems silly to mention, but it's worth noting. <code>AppUser</code> will automatically log in, but if you need to
log back in, or wish to login as <code>SystemUser</code>, you'll need the passwords.</p>
<p><code>AppUser</code>'s password is <code>segahard</code>.</p>
<p><code>SystemUser</code>'s password is <code>&lt;6/=U=#tpe!$*3!5</code>. <b>NOTE:</b> if a debugger is attached to
<code>mxprestartup</code>, <code>Miflac=Ifme9Jfp0</code> will be attempted as the password for
<code>SystemUser</code> instead. This is not the correct password for a production unit.
</p>
<details>
<summary>Finding SystemUser's password</summary>
<p>When AppUser logs in, mxprestartup is executed. This binary constructs SystemUser's password then elevates
permissions. When no debugger is present, the following steps are performed:</p>
<ul>
<li>Add <code>153815264b5839090b0d1c1a423c02241633130673071a1e38443912410b47380f213c1d</code> and
<code>2e0247311e162b666c6640393737724157001f56045b4b4f24333457335a26381f4c3349</code> together (these are
strings contained within the binary).
</li>
<ul>
<li>Result: <code>C:\Windows\System32\wbem\wmitemp.mof</code></li>
</ul>
<li>Read <code>C:\Windows\System32\wbem\wmitemp.mof</code>.
</li>
<ul>
<li>Result: <code>270a2a053b29042b261d1b22070d140c</code></li>
</ul>
<li>Add <code>152c05381a141f494a48060223260d29</code> to this value.
</li>
<ul>
<li>Result: <code>&lt;6/=U=#tpe!$*3!5</code></li>
</ul>
</ul>
<p>If a debugger is present, a similar process is performed:</p>
<ul>
<li>Add <code>d0b1c034a32243340505a343659517c121f0319583d205c593a690719604846000e062a6</code> and
<code>63f10541f0c201c0703022f332a13094b542f130c25491a1c280a65440831296e2563590</code> together, with each
byte
modulo 255 (not 256!).
</li>
<ul>
<li>Result: <code>34a3c57594e444f47535c53797374756d63323c546279667562737c5d68796f6e2379737</code></li>
</ul>
<li>Flip the nibbles of each byte.</li>
<ul>
<li>Result: <code>C:\WINDOWS\system32\drivers\mxio.sys</code></li>
</ul>
<li>Read <code>C:\WINDOWS\system32\drivers\mxio.sys</code>.</li>
<ul>
<li>Result: <code>9160e5c22392918371e43573f2b095b1</code></li>
</ul>
<li>Add <code>43368004f2a34211f4f12120b1b57151</code> to this value, with each byte modulo 255.
</li>
<ul>
<li>Result: <code>d49666c61636d39466d65693a4660703</code></li>
</ul>
<li>Flip the nibbles of each byte.</li>
<ul>
<li>Result: <code>Miflac=Ifme9Jfp0</code></li>
</ul>
</ul>
<p>Why is the process for a debugged process more elaborate? I'm not sure.</p>
</details>
<h2 id="sdrive">System binaries encryption</h2>
<p>The system binaries, normally located on <code>S:</code>, are a TrueCrypt partition. The partition file can be found
at <code>C:\System\Execute\System</code>. It has password <code>segahardpassword</code>, and used the <a
href="https://www.sans.org/blog/alternate-data-streams-overview/" rel="noopener noreferrer">alternate data
stream</a> loated at
<code>C:\System\Execute\DLL:SystemKeyFile</code> as a keyfile.
</p>
<p><code>C:\System\Execute\DLL:UpdateKeyFile</code> is also present here, which is used for encryption of update
data (but is not utilised in the process of mounting <code>S:</code>).</p>
<details>
<summary>What's an Alternate Data Stream?</summary>
<p>An alternate data stream, or ADS for short, is a feature of the NTFS filesystem where additional data can be
stored alongside a file or directory. On modern Windows systems, they can be shown using <code>dir /R</code>. If
you are performing analysis on the system using digital forensics software, which you probably should be, all
major packages will show these streams clearly.</p>
<figure>
<img class="graphic" src="{{ROOT}}/images/ftk.png">
<figcaption>The alternate data streams, in FTK Imager</figcaption>
</figure>
<p>The <a href="https://www.sans.org/blog/alternate-data-streams-overview/" rel="noopener noreferrer">SANS Institude
website</a> is a great resource for more information and links.</p>
</details>
<details>
<summary>Finding this information</summary>
<p>The decryption of the system partition is the responsibility of <code>mxstartup</code>. This file contains pairs
of hex strings which sum to produce the paths to the alternate data streams, and the volume password.</p>
</details>
<h3 id="keyfiles">Keyfile downloads</h3>
<ul>
<li><a href="{{ROOT}}/static/keys/SystemKeyFile">SystemKeyFile</a></li>
<li><a href="{{ROOT}}/static/keys/UpdateKeyFile">UpdateKeyFile</a></li>
</ul>
<h2 id="keychip">Keychip</h2>
<p>This is the first point during the boot process where a physical keychip is required in order to continue the system
boot process.</p>
<p>With the exception of <a href="{{ROOT}}/sega/software/mx/mxkeychip.html"><code>mxkeychip</code></a>, processes do not
directly communicate with the keychip. Instead, they communicate with <a
href="{{ROOT}}/sega/software/mx/mxkeychip.html"><code>mxkeychip</code> via a PCP</a>. <a
href="{{ROOT}}/sega/software/mx/mxkeychip.html"><code>mxkeychip</code></a> itself is then responsible for
communicating with the keychip. These communications are AES encrypted with keys agreed during the initial
handshake.</p>
<p>The keychip is responsible for a number of functions:</p>
<ul>
<li>Each keychip is assigned a region, and this region must match the region stored in the Ring*'s EEPROM.</li>
<li>It contains configuration tables for the network setup.</li>
<li>It is able to perform basic encryption and decryption, which is used to derive the game encryption key.</li>
<li>It includes a large challenge-response table used to authenticate with the specific game.</li>
<li>It contains Aime billing information regarding how many credits are allowed on this machine before
re-authenticating with network services.</li>
<li>It contains a small amount of writable storage used to store trace logs, such as when the system booted or
authenticated with network services.</li>
</ul>
<p>This security layer can be bypassed by replacing the <code>mxkeychip.exe</code> binary with a custom binary,
eliminating the need to emulate the physical parallel device, and its encryption.</p>
<h2 id="game-data">Game data encryption</h2>
<p>Once the system has verified it is allowed to continue booting, it proceeds to decrypt the game partition. This is
done by performing a <code>keychip.decrypt</code> request.</p>
<p>The specific key used varies by game. The easiest way to retrieve this key is to, well, ask the keychip for it. We
can first start a listener on port <code>40106</code> to retrieve the value that the boot process is passing.
Following that, we can start the real mxkeychip, and request the same decryption. It will then provide us with the
key to be used for the game encryption!</p>
<p>TODO: Some proper digging into the mx binaries to determine exactly where it pulls the encrypted string in the
request</p>
<p>The value we have now should be 16 bytes, and is the contents for a keyfile.</p>
<p>Like the <code>S:</code> drive, game data is a TrueCrypt partition. Check the <a
href="{{ROOT}}/sega/misc/partition.html">SEGA partition description</a> to determine which partition contains
the game data. If in doubt, just try they all until one works :).</p>
<h2 id="game-handshake">Game-keychip handshake</h2>
<p>There is one final security step present, however I believe this to only be present in some games.</p>
<p>When the game boots, it makes requests to <code>keychip.ssd.proof</code> and <code>keychip.ds.compute</code>. These
are challenge-response queries, which the keychip will internally look up in a large table of possible values.</p>
<p>While we could dump the EEPROM chip located on the keychip, there is no need for this. As the game also needs a copy
of the responses to validate against, we can just grab that file, as we have already decrypted the game partition by
this point. The file will likely be named <code>[game ID]_Table.dat</code>.</p>
<p>I'll flush this out later, but for now, here's the structure of that file:</p>
<pre>{% highlight "c" %}
struct {
struct {
char challenge[7];
char responses[4][20];
} ds[10000];
struct {
char challenge[16];
char response[16];
} ssd[10000];
}
{% endhighlight %}</pre>
<p>The responses are scrambled as described below:</p>
<h4>DS Scramble:</h4>
<table class="code">
<thead>
<tr>
<td>Output index</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>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
<td>16</td>
<td>17</td>
<td>18</td>
<td>19</td>
</tr>
</thead>
<tbody>
<tr>
<td>Input index</td>
<td>15</td>
<td>5</td>
<td>13</td>
<td>14</td>
<td>10</td>
<td>1</td>
<td>2</td>
<td>11</td>
<td>16</td>
<td>7</td>
<td>4</td>
<td>18</td>
<td>12</td>
<td>6</td>
<td>3</td>
<td>0</td>
<td>17</td>
<td>8</td>
<td>19</td>
<td>9</td>
</tr>
</tbody>
</table>
<h4>SSD Scramble:</h4>
<table class="code">
<thead>
<tr>
<td>Output index</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>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
</tr>
</thead>
<tbody>
<tr>
<td>Input index</td>
<td>6</td>
<td>8</td>
<td>4</td>
<td>12</td>
<td>7</td>
<td>13</td>
<td>1</td>
<td>10</td>
<td>2</td>
<td>3</td>
<td>11</td>
<td>14</td>
<td>15</td>
<td>0</td>
<td>5</td>
<td>9</td>
</tr>
</tbody>
</table>
<p><b>NOTE: The following information may only be true for MaiMai FiNALE! I have not yet verified this on other
games!</b>
<br>
In dev mode, the game will only ever request a single string as the challenge.
</p>
<p>This string is <code>2CFECBC71CF1E4</code>, and its corresponding four response pages are (not scrambled):</p>
<ol>
<li><code>ca6ed736401682dd4411a27d2440ac4b478bad8b</code></li>
<li><code>4ac606302ce5ef51abb3df2dc46c863b3c06aa2c</code></li>
<li><code>2aaf35b2aba4c6840bdb7bd40ecbce2cca934795</code></li>
<li><code>2f6d713dabde3c43df818491ab9467ba8ba0fed4</code></li>
</ol>
<p>
<b>NOTE: The following information is super un-verified!</b><br>
Occasionally the game will make a query to the DS table that is not present in the table. In this instance, the
keychip responds with the entry at index n, where n is a counter that increments every time a
<code>keychip.ds.compute</code> query is performed, modulo 100.
</p>
<p>It should be noted that if the keychip binary imemdiatly terminates the connection, rather than sending a
known-incorrect response (such as if the challenge matches none in the known tables), the game will re-attempt the
query, and this query will, with a high likelyhood, be different.</p>
<p>It should additionally be noted that this whole process can be skipped by returning <code>code=54</code> rather than
a valid response.</p>
{% endblock %}

View File

@ -1,6 +0,0 @@
{% extends "sega.html" %}
{% block title %}{% endblock %}
{% block body %}
<h1>Game Encryption</h1>
<p>Games are encrypted with CrackProof encryption.</p>
{% endblock %}