2024-01-25 — updated 2024-02-11

The Radius Rocket expansion card for classic Macintosh allows you to run a second instance of the Macintosh OS on a single machine for multi-processing applications via the RadiusShare software. They present as a Quadra 950, with all the normal ROM functionality including onboard devices and the Macintosh Toolbox when using the latest software. But how? Where does that ROM come from, and what is it?

I sat out to find out using my Rocket Stage II with ROM v1.1.

This article presumes a somewhat working knowledge of classic Macintosh internals.

Table of Contents

Extracting the ROM

The Rocket contains an onboard ROM, a 27C256 in DIP28 package. Conveniently, this means it can be removed and dumped with a minipro or similar ROM programmer.

But it also means it only contains 32KiB of data. Even the original Macintosh 128K had a 64KiB ROM, so we know there’s no way this ROM contains Macintosh support.

Dumping it we see a fairly normal, boring DeclROM, advertising a Board entry for the Radius NuBus Mac Accelerator and a CPU entry for a 68040. If you’re familiar with DeclROMs, this seems like exactly what you’d expect, as it declares the handling for the Board itself and the coprocessor it attaches to the system.

There is one element of interest; the Driver entries for the CPU don’t really make sense – they point outside of the offsets of the ROM. There’s no other ROMs on the board so I’m not sure what this means, but it wasn’t necessary to answer our question so I’ve left it an exercise for the reader.

Since the onboard ROM doesn’t have the answer we’re looking for, let’s set it aside for now and look further.

Digging through the RocketShare software

I pulled up a copy of the last version of RocketShare, 1.3.1, the software that allows a Rocket to boot a second, independent copy of the Macintosh OS (System 7, in this case).

It came on 4 disks, and even without installing them, we can see some interesting contents:

That’s a lot of stuff, but Rocket Cargo, which I’ve highlighted in bold, stands out as paricularly interesting because an earlier Quadra-era machine would typically use a 1MiB ROM. Could it contain the ROM data?

This would imply the ROM data is actually loaded into RAM on the Rocket when its host logic board boots. And that offers very interesting properties!

Opening the Cargo

Tools like ResEdit can allow us to take a peak inside the extension, but I really wanted to work on a modern machine to do more detailed exploration. Fortunately, the fantastic resource_dasm can be used to pull out and examine Mac resources. After getting the RocketShare extension onto my modern machine I used the simple high-level tool:

$ resource_dasm Rocket\ Cargo

My reward was a set of extracted resources in Rocket Cargo.out:

  284 Rocket Cargo_BNDL_4000.txt*
   56 Rocket Cargo_CRGO_0_Owner resource.bin*
   55 Rocket Cargo_FREF_4000.txt*
 4234 Rocket Cargo_ICN#_4000.bmp
 1824 Rocket Cargo_ICN#_4000.icns*
    9 Rocket Cargo_MACI_4000.bin*
65536 Rocket Cargo_MAC_4000.bin*
65536 Rocket Cargo_MAC_4001.bin*
65536 Rocket Cargo_MAC_4002.bin*
65536 Rocket Cargo_MAC_4003.bin*
65536 Rocket Cargo_MAC_4004.bin*
65536 Rocket Cargo_MAC_4005.bin*
65536 Rocket Cargo_MAC_4006.bin*
65536 Rocket Cargo_MAC_4007.bin*
65536 Rocket Cargo_MAC_4008.bin*
65536 Rocket Cargo_MAC_4009.bin*
65536 Rocket Cargo_MAC_4010.bin*
65536 Rocket Cargo_MAC_4011.bin*
65536 Rocket Cargo_MAC_4012.bin*
65536 Rocket Cargo_MAC_4013.bin*
65536 Rocket Cargo_MAC_4014.bin*
65536 Rocket Cargo_MAC_4015.bin*
 4234 Rocket Cargo_icl4_4000.bmp
 4234 Rocket Cargo_icl8_4000_Rocket Cargo.bmp
  283 Rocket Cargo_vers_1.txt*
  226 Rocket Cargo_vers_2.txt*

Ah this gets interesting, summing MAC resources 4000-4015 yields exactly 1MiB. Is this our ROM?

I wrote a quick one-liner to glue these binaries together into a resulting file via naive concatentation. As we’ll repeat through the exploration, systems were slow back then so it seems unlikely there’s any data interleaving going on. While interleaved RAM was a thing, it’s pretty unlikely this is using any tricks to load this directly in as interleaved data. So let’s start by assuming the simple case.

Let’s take a look at the first few bytes:

A3B80259 9E7A7A50 A3063480 81FA3480...

That’s not promising, for a Macintosh system ROM we should expect to see something like this:

3DC27823 0000002A 067C4EFA 00804EFA...

Roughly, a checksum, followed by the reset vector and a few other offsets to low-level ROM entrypoints. The astute of you may have already noticed a clue, however. We’ll come back to that later.

Further, looking at the end of the ROM we expect to see a DeclROM entry. This header describes the onboard peripherals like video chips and ethernet, here we see:

...08F738CE 0108FCE8 F07B2450 09A720E9 51BD7A75

Versus an expected:

...00FF30C6 0000CF4E F8482C58 01015A93 2BC7000F

Unfortunately, no good either. A valid DeclROM should contain the magic value 0x5A932BC7. So it’s not just a plain ROM you’d see on a normal board. But it does follow entropy patterns making us feel comfortable about our concatenation, for now. So what is it?

A hunch

Could it just be encrypted? Computing power was low in the era, so it’s always a good guess to start with a simple XOR cipher, not something more extensive like public-key cryptography. This is quite a primative technique, and it’s difficult to call this encryption at all. One of it’s most significant drawbacks is it can be easy to discover the “key” even without frequency analysis: large runs of repeated 0x00 bytes will become the cipher key itself in the encrypted data. This is simply a property of the XOR operation, and we’ll come back to this later.

In other words, if you can identify data you expect to be 0x00 in the original data and determine that same data is now 0xBA in the encrypted data, you’ve found the key! Simply run XOR over the encrypted data with that key and the resulting data should be the decrypted information.

So, reaching back a bit in our encrypted file we see some runs of 0x7A just after the DeclROM header. This section often has empty data so it might be a good guess that there’s zeros here. This forms a very basic known (or in this case, assumed) plaintext attack. Let’s give it a try.

Looking at the resulting data, first the system ROM header:

D9C27823 E400002A D97C4EFA FB804EFA...

And the DeclROM header:

...728D42B4 7B728692 8A015E2A 73DD5A93 2BC7000F

This is looking better! We see a more rational reset vector of 0x2A and the DeclROM magic of 0x5A932BC7! But…it’s still not right, much of the data doesn’t make sense and while we see bits of strings like copyright messages, it’s clear there’s still scrambled data.

So, we’ve validated that we’re likely looking at an XOR cipher, but not a simple one. It seems likely the cipher key has a twist, that is, the XOR value is modified on some regular pattern. This ensures that a simple XOR with a single value to reverse the data is insufficient. But how can we extract the original cipher key twisting algorithm to decode the data?

Known plaintext

It’s time to change tactics. We know the machine boots as a Quadra 950, so let’s pull it’s ROM from one of our period machines and take a look. Maybe they’re similar enough that we can really use a robust known plaintext in the working ROM to determine the cipher key twist.

Between 0xFF87C and 0xFFFED we see a long run of zeros, 0x771 worth, if you do the math. Let’s see what our Rocket Cargo data looks like in that area:

817AD17A 81D13308 33330833 33080808 08080108 08080801 08010833 33085E33 7A7A7A7A
7A7A7A7A 7A7A7A7A 7A7AD17A 567AD108 08080808 33080808 33080108 08085E01 08080808...

Interesting, that sure looks like a twisting cipher key! Could this be it?

Let’s assume it is for a second…what’s the algorithm? While there are runs of consistent characters, and some level of repeat, there’s odd varitions across the pattern. If we want to reverse the entire ROM, we’ll need to understand how this pattern is generated and apply it in reverse. Even though we may have a hint of the pattern, it may never

Dead ends

I spent an hour or so beating my head against that one. I wouldn’t be surprised if there’s some clear math in it, but I’ve never been good at that stuff and it didn’t present itself to me.

Someone can be really clever and evil with this sort of thing, starting with a seed and twisting the new cipher key value based on previously decoded data, even using things like CRC to require the entire data is checksummed as decrypted.

But, again, these were simple and fairly slow machines, having to do a large decryption pass on 1MiB of data is expensive and seemed unlikely. A simple XOR is cheap and can be done quickly. Something more complex requiring previous state or offset calculations is much more difficult.

So, maybe we should keep it simple, stupid. Let’s follow Occam’s razor.

No really, known plaintext!

We already know the ROM is quite similar to the Quadra 950 ROM based on functionality, and we see higher entropy in similar areas. That is, the encypted ROM has more entropy overall, but there is a correspondense between high entropy sections in the Quadra 950 ROM and our encrypted Rocket Cargo data.

The interesting thing about an XOR cipher is from a general standpoint, taking the plaintext and encrypted data and XORing them together will produce the cipher key used for each byte. We used that principle very simply before, as 0x00 serves as a simplified case where simply looking at the encrypted data will reveal the key (as 0x00 ^ X returns X).

Let’s try plaintext again, but this time running the 950 ROM and Rocket Cargo data against each other, producing a 1MiB file of data. What do we see?

9E7A7A7A 9E7A7A7A A57A7A7A 817A7A7A 817A817A 817A47A6...

That’s promising! The resulting data yields a very similar pattern to that which we saw before. There’s still, however, sections with higher entropy than the rest of the file. So either there’s a difference between the Quadra 950 ROM and Rocket Cargo, which we assumed from the start, or we haven’t cracked the key. Let’s assume, for a moment, that the former is the case since it matches our expectation.

Cracking the key

The data appears to repeat, with the 0x9E7A7A7A section presenting itself over a fairly large interval. Is it possible the pattern itself repeats, then? Again, compute is expensive, and an algorithm with significant entropy may not fit into this use case. What if instead of cracking the algorithm, we took a higher-level view? Can we just apply a hardcoded repeating cipher key table instead of determining the algorithm?

It turns out the hunch is correct, within the lower entropy sections, the pattern appears to repeat every 0x7BA bytes. Incredible! We extracted 0x771 bytes of key data earlier, were we really this close? Let’s write a quick script to take that table and decode the ROM. Results:

3DC27823 0000002A 067C4EFA 00804EFA...

Promising, that looks just like a Quadra 950 ROM! And when we parse it using system ROM and DeclROM utilities…perfect! It seems we’ve cracked this nut, and decrypted the ROM. All values are present and decodable.


For v1.1 Rocket ROMs, the Rocket loads its Mac ROM into RAM from the disk provided by the host logic board, decrypting the data by taking each byte, matching it’s offset modulo 0x7BA to a table present in the Rocket’s ROM (you can find out exactly where in Postfix 3), and applying XOR to reveal the plaintext.

It would be interesting to explore exactly how this happens mechanically, maybe we’ll take a look in the future!


Postfix 1: What’s in it?

Turns out for the most part the ROM which Rocket Share loads is a bog standard Quadra 950 ROM, with most (or potentially all) of the changes relating to removing unsupported hardware. A Rocket isn’t a Quadra 950, but it is similar. Most significantly it’s missing all, or almost all, of the onboard peripherals for I/O, etc. Presumbly removing these was required to avoid confusion in the OS, or a rudimentary copy protection scheme to prevent Rocket ROMs from being used to create unauthorized clones as they would be missing key data to perform as standalone machines.

Curiously, it even uses the Quadra 950’s ROM checksum, despite it being invalidated by these changes. I suspect this might be for compatibility with software that checks the ROM checksum data to do machine identification (a bad idea, but probably happened). While a ROM normally verifies itself on boot, Radius has either patched this check to always pass or skipped it entirely.

Postfix 2: Dumping the ROM from runtime

Of course, the Rocket does run a true copy of the Macintosh OS, meaning there’s a good chance we can run the normal ROM dumping tools built by the community over the years. That’s less fun than the route we took, but can be interesting. Because the ROM must be mapped to memory space to allow reading and execution, and classic Mac OS has no concept of memory protection, simply reading the data directly from memory is possible. Since this still required a physical Mac it was probably considered less valuable content than software that shipped on a disk provided by Radius, which warranted the higher level of protection.

And indeed, no extra protections are implemented for the Rocket; dumping the ROM from within the runtime works correctly. It, however, yields a slightly modified ROM from the one we extracted directly from the Rocket Cargo extension. It seems a few relatively small runtime patches are applied, the vast majority of which seem to replace the memory offsets for low-level functions. I am guessing most low-level initialization is done by Radius code which is loaded into memory space by another part of card setup by the main logic board, and these new offsets point to those implementations.

Postfix 3: Finding the real cipher key

It still bothered me, though, how did the card decrypt the ROM? Does the software contain the key? That seems to run afoul of the same risks above, that a single copy of the RadiusShare floppy disks could be used to decrypt the ROM. It seems far more likely the Radius card itself contains the decryption data, thus ensuring the key data was stored in a hardware component and not an easily distributed floppy disk (and at the time, a ROM writer wasn’t a common $60 hobbiest purchase).

So let’s take a look at the ROM again, did we miss something?

It turns out we did, what looked like random data in the Rocket’s 27C276, which I mentally wrote off as typical DeclROM data, is the exact cipher key table we used!

Specifically, under the CPU entry, at sResource 254, is 0x7BA bytes of cipher table, starting exactly as our own:

9E7A7A7A 9E7A7A7A A57A7A7A 817A7A7A...

So, even if there is a clever algorithm behind the table, it’s not generated at runtime. Instead it’s read from the card and applied to each byte of ROM data.

Thus, our solution exactly matches the original Radius design.

But this does yield some questions. We’ve been working with the v1.1 ROM, it appears earlier v1.0 ROMs don’t contain this table. Is there a backup strategy RocketShare employs? Or is this table perhaps derived dynamically from data present on v1.0 cards? Another interesting exploration for the future.

Postfix 4: The decrypted ROMs

ROM data is still copyright. I’ve pulled these ROMs from my personal collection for personal use, and as such, am not providing the decrypted copies here. If you have a desire to tinker with your Rocket, you should be able to replicate this process locally.