Cracking the Key part 2

I left you last time with a decoded file protection module, now let’s dive in and find out what this insidious module actually does.

Note when I say word here I mean 32-bit words.

Module Header

The module starts off with a module header – up to 13 words which each point to either code or a specific data structure to show what facilities the module provides. Of these only the first 7 are required, the rest are optional. This module only uses the first 11.

Module header

Code offsets

Start offset is a pointer to code if the module is Run (e.g. if it provides a language interpreter). As this offset is set to 0 then the module cannot be run.

Intialisation offset is a pointer to code the is executed when the module is loaded or reloaded. The module has a pointer to the code at address &70. We will look at this later.

Finalisation offset is a point to code that is executed when the module is killed, before it is removed from memory. The module has a point to address &3E4 and we will look at this later.

The Service call handler is to manage operating system calls, this is set to 0, so is not used.

Strings and command tables

The Title and Help string offsets are points to word aligned NUL terminated strings that define title and help strings. In this case, the title, i.e. the name of the module, is:


And the help string (i.e. what is shown with a *help sol command) is:

Please go away!

The help and command table defines any commands that can be issued on the operating system command line (known as CLI). These have a defined structure which I have expanded below:

Code offset&1974
Minimum parameters&00
Parameters which can be translated strings&06 (i.e. 0110)
Maximum number of parameters&FF
Flags&00 (normal command)
Syntax string offset&4B0 "Caution!! DO NOT USE THIS COMMAND"
Help string offset&4B0 "Caution!! DO NOT USE THIS COMMAND"
Command" " (a hard space – &A0 in the Risc OS character set)
Code offset&570
Minimum parameters&00
Parameters which can be translated strings&06 (i.e. 0110)
Maximum number of parameters&01
Flags&00 (normal command)
Syntax string offset&4D4 "Go Away you 'Orrible little worm"
Help string offset&4D4 "Go Away you 'Orrible little worm"
Command" " (two hard spaces – &A0 in the Risc OS character set)
Code offset&CFC
Minimum parameters&00
Parameters which can be translated strings&06 (i.e. 0110)
Maximum number of parameters&02
Flags&00 (normal command)
Syntax string offset&4D4 "Go Away you 'Orrible little worm"
Help string offset&4D4 "Go Away you 'Orrible little worm"

SWI tables

Risc OS used the ARM facility to add routines that could be called through a SoftWare Interrupt to allow calls that could allow the modules to pretty much expand the operating system.

This module provides a SWI facility with the parameters:

SWI Base (32-bit)&B8500
SWI Handler code&2D8
SWI decoding table&40
SWI decoding code&00

The module has a predefined table of the following SWI calls. The SWI decoding table will convert a list of zero terminated names to a SWI number, starting from the SWI base, e.g. the first entry in the table is &B8500, the second is &B8501 etc.

The first entry in the table is the SWI prefix; so the module provides the following SWI call:

Reed_Disk &B8500

This can be called as Reed_Disk, XReed_Disk or &B8500. The XReed_Disk is a special variant which can return errors.

This is the all the defined header, so let’s look a bit closer at the code.

Intialisation Code

Initialisation code

Rather than bore you trying to explain the ARM code, I’ll split it into sections.

The first part it attempts to work out whether the application whether the game is being run from a floppy disk and configures Disc1$Dir, Disc2$Dir and Disc3$Dir to point to the right place.

It will then perform a raw read of 1024 bytes from the floppy disk in drive zero at address &BCD00.

Finally it will attach itself to a number operating system vectors – these are a chain of addresses that are call when the operating system performs certain actions. Most of these are attached simply to prevent easy manipulation of the module or running files out of band whilst it is running.

It does make a mistake here, instead of attaching itself to ByteV (the OS_Byte vector) it sets the wrong register and overwrites the claim for ArgsV. (It should be MOV R0, #6 below.)

Claiming the wrong vector

It also claims CLIV, the command line vector where it will perform the reset of doom if commands that match certain patterns are issues:

  • M (presumably short of modules)
  • A (don’t know)
  • LOAD (load memory from a file)
  • SAVE (save memory to a file)
  • CAT (list a directory)
  • . (alias for cat)
  • DRIVE (change to another disk drive)

Finally it sets up a delayed call to the routine at &1974 to reset the computer after 1 second and make it harder to deactivate the module.

Almost every execution branch of the module has multiple errors and status checks, if these fail they go to a routine at &1974. This routine basically blanks out a load of memory, forces a full memory wipe on hard reset and then crashes the computer by forcing the program counter to an address of &00. Whilst this is annoying on an emulator it was really, really aggravating on a real computer and one of the most hated aspects, as it didn’t safely suspend the hard disk or anything. Imagine your anti virus software doing a hardware reset if it found something it didn’t like!

Where it does all its work is attaching itself to FileV which is call with the system call OS_File, which is used to perform an action on a complete file. Any action that causes a write does a system reset, but anything that loads a whole file goes into a special routine which performs a byte by byte exclusive or with the disc sectors that had been read during intialisation.

Software interrupts

The software interrupt code is really simple – it doesn’t check which interrupt is call – it just sets R0 (the return value) to &2BC and returns.

I suspect that different versions of the module have different facilities and the SWI isn’t used.


As we identified earlier, there are three defined commands.

“Dtya” has a code offset of &1974; above we mention that this is the manky reset code – and is obviously a trap for someone being too curious.

” ” (&A0 – a hard space) the first this does is disable the delayed call defined in the initialisation code, so it’s another trap to prevent attempts to run the game out of sequence. It will then load the files Code.CMP_Handle, Code.SFX_Handle and Code.Map1 so that they can be decoded by the claimed OS_File vector


But where are these commands called? How about we take another look at the !Run file, but this time look at a hexdump. And here we are, two instances of the ” ” command.

Calls to the ” ” command in !Run

More next time when hopefully we shall totally remove the copy protection.

Cracking the Key part 1

A discussion on Twitter lead to a bit of a reminiscence about breaking game copy protection on the Archimedes, in the good old days of yore. There was one copy protection system I used to be able to break with in about 30 minutes (once I had my techniques down). This is a simple write up of me doing that.

The system was known as the Key module – a quite dodgy-written module by Gordon Key used to protect games from the 4th Dimension. I’d love to be able to provide semi-cracked disks, but for some strange reasons the current copyright holders for 4th Dimension games don’t want to allow distribution.

I’m going to work from a copy of the game Starfighter 3000. To try and reproduce what I did in the early 90s I’m going to ignore modern tools and only work from an emulator (both of my Archimedes are currently in bits).

Back in the 90s I used to have access to a copy of the Acorn Programmer Reference Manuals and a mate who know ARM assembly better than I. I’m going to emulate this by allowing myself access to Risc OS manuals and books that I can find on the Internet.

The Key Module

At the heart of the 4thDimension copy protection was an obscured module that we called the Key module. On Risc OS, a module was loaded into a reserved section of memory and could be called using a number of techniques, such as through SWI (Software Interrupt) calls, command line commands or by intercepting several operating system vectors.

The way the Key module worked was:

  1. It was hidden as an obfuscated executable
  2. It deobfuscated itself, installed itself and installed itself in module memory (using the OS_Module SWI)
  3. It then read a sector from the floppy disk drive directly (using ADFS_DiscOp)
  4. It put in a load of restrictions to make it hard to easily kill off (by putting routines on the OSCLI vector). These would reboot the computer requiring a hard reset and clearing RAM.
  5. It also put code on the disk read vector which would used the read sector to decode any read block

So the whole cracking process was:

  1. Identify the Key module and encoded files
  2. Deobfuscate the module and amend it to not reset the computer
  3. Run the module
  4. Load the encoded files into memory
  5. Kill the module
  6. Save the now decoded file from memory
  7. Clean supporting files to remove any trace of the Key module

Finding the module

Risc OS stored applications in a special format that allowed storing of multiple files. An application was a directory starting with a pling (!). Just viewing this application in the filer would run an obey (Risc OS’s equivalent of a shell script file) called !Boot. Double clicking on the application would run a file called !Run.

Below we have the root of the directory of disk one of Starfighter 3000, the application !Star3000 is the game itself:

Root Directory of the disk

Looking into the application directory (using a shift double-click) shows its contents:

Contents of application directory

Loading the !Run file into the builtin !Edit text editor shows us what it’s doing:

Contents of !Run file

This set up some variables (Set), the GIU graphics (IconSprites) and adjects memory (WimpSlot) before it runs two executables (Run).

This provides a big clue that the Key module may be hiding in the Code directory (Risc OS uses a period as a directory separator). Normally the protection was applied as part of production so would generally only effect some files.

Having a look in the Code directory shows:

Contents of Code directory

But, it is running the Utility file 3DV_Handle. A Utility file contains position independent code that is designed to not overwrite the application memory (think of it as being like a command line command). This has the advantage as it can be loaded and executed from anywhere in memory.

Loading this in the Zap editor (which has a handy disassemble feature) shows that the code is interesting:

Disassembled 3DV_Handle code

Note that the data from address &34 onwards (the & in Risc OS is used to show a hexadecimal number). This looks encoded. The ARM code also shows that it is self modifying code, interpreting this in pseudocode:

# Set R4 to size of data
R4 = &2120
# Set R0 to a base source address
R0 = &00000008
# Set R1 to a base destination address
R1 = R0 + &2C # = &00000034
while R4 > 0:
   R2 = MEMORY(R0)
   R0 ++
   R3 = MEMORY(R1)
   R3 += R2
   MEMORY(R1) = R3

As can be seen, this does a very basic deobfuscation by adding two bytes together and writing it back. Annoyingly because it uses itself as the deobfuscation key we can’t use the builtin OS debugger, as the OS debugger actually set break points by altering the data in memory.

But, we have another tactic available – Risc OS has a BASIC interpreter with an ARM assembler, as ARM code is (mostly) relative we can replicate the code into another program and use that to deobfuscate the code.

Doing this shows use it has several depths of obfuscation, where the similar code is used, but the obfuscation action varies, it can be either an ADD, an EOR or a SUB. Fortunately the code is the same but for the obfuscation action, so we can write a generic route which will read the deobfuscated data and vary depending on that.

This code looks something like:

REM Two pass assembly

 REM Assemble at &12000
 ; base address
  dcd &0
  stmfd r13!,{r0-r6,r14}
 ; r6 is a flag for the last decoding loop which does an
 ; extra eor r3,r3,#&ff
  mov r6,r0
 ; set up the source, destination and size
 ; size is set up from within the data
  ldr r0, base
  ldr r4, [r0,#4]
 ; pull the top nybble of the 3rd octet in the operation instruction
 ; This will allow to work it out
  ldrb r5, [r0, #&26]
  and r5, r5, #&f0
  add r0,r0,#8
  add r1,r0,#&2c
  teq r6, #1
  addeq r1,r1,#4
 ; decryption loop
  ldrb r2,[0],#1
  ldrb r3,[r1,#0]
 ; auto work out the decryption operation
  teq r5,#&80
  addeq r3,r3,r2
  teq r5,#&20
  eoreq r3,r3,r2  
  teq r6,#1
  eoreq r3,r3,#&ff
  teq r5,#&40
  subeq r3,r3,r2
 ; store it back
  strb r3,[r1],#1
  subs r4,r4,#1
  bne loop
  ldmfd r13!,{r0-r6,pc}

REM load encoded file
*LOAD 3DV_Handle 13000

REM decode 30 counts of the encoding cycle
FOR i=1 TO 30
 A%=0:CALL doit
 PRINT ~addr

REM There's one last decoding where and extra EOR R2,R2,£#&ff is done
!base=addr:A%=1:CALL doit

Now we have a decoding program, it can be run. After execution, a quick call to the Risc OS built in disassembler, *memoryi shows us the OS_Module call:

Disassembly after decoding

The call is:

SWI XOS_Module, &0B, &12674, &1A44

Looking this up in the reference this call is to insert a module from the contents of memory at &12674 of size &1A44.

We can just *save that memory and view it at our pleasure in !Zap:

Decoded module header

We have a valid module header and some defined strings – I will look at the module to demonstrate how it works in a later post.

Infusing Rum the Quick Way

After a discussion about using ultrasonic cleaners to infuse flavour into alcohol I ended up buying a cheap one for Amazon to mess around with it. The cleaner cost around £20, so if it didn’t work I wouldn’t have lost too much money.

For my first experiment I did an orange infused rum. I requested that the wife get the cheapest clear spirit she could find in Aldi (as she was heading there anyway). So armed with just shy of a litre of “Hopkins” rum and some orange peel I made orange infused rum.

It was amazing. I followed this up with banana rum (too bananaery) and Marmite rum (that was strange).

To demonstrate the technique, here’s me making some ginger infused rum.


In the below photo is the basic equipment:

  • A cheap ultrasonic cleaner.
  • About a quadruple measure of rum free poured into an old, clean honey jar.
  • Some homemade preserved ginger in ginger syrup. This was made when I had an excess of ginger, it is quite firey and sweet with a nice kick of flavour.
Equipment used

Step one

Take a few chunks of ginger and dump them in the rum, this was guestimated by eye. Try not to eat too many of the ginger chunks from the original jar.

Ginger and Rum

Step two

Place the jar in the ultrasonic cleaner and pour water around the jar until it reaches the max level. Set the timer for 380 seconds (this model only allows the timer to be 180, 280, 380 or 480). Press start.

I leave the lid of the jar open, this probably makes no real difference.

Ready to infuse

Step three

The observant amongst you may have noticed the half lemon behind the ultrasonic cleaner. I did too and thought “sod it”: lemon and ginger go well together. So I peeled it, squeezed it into the jar and then through everything in with the rum.

Twist of Lemon?

Step four

Set it off for another 380 seconds; when finished drain the liquids off into another jar. I ended up eating the, now rum infused ginger bits. I guess I won’t be driving for at least four hours.

Separated product

The resulting rum is quite lemony with some of the fireyness of the ginger.

I found from experimentation that if you leave the infusion to age for at least a week then the flavours intensify. So shake, and let it finish mingling.

Making Banana Mead


In a slight diversion from my normal tech entries, it’s time to talk about something I like doing: creating alcohol. I’ve dabbled in the old home brew before; starting with homebrew beer kits and then going into some bespoke drinks.

I’ve never actually made beer from malt and hops, mainly because its a lot of work and requires a significant amount of equipment. Instead I start messing about with quick brews that can be fermented in five litre demi-johns, meaning I haven’t lost too much if it tastes awful.

(I also don’t drink enough at home to go through the average 40 litre fermenter before it goes off.)

My previous experiments have been quite sane: trying to make cheap cider from bread yeast and apple juice (it’s surprisingly good and I don’t even like cider) to making some more exotic alcohols, such as nettle beer, elderflower wine and a fruity melomel (fruit flavoured mead).

My strangest brew was started after a discussion in the beer garden of the pub; where one of my mates mentioned that he would love to try sprout beer. As this was winter in the UK and the humble brussels is ubiquitous, this was a challenge I could do.

So long story short: boiling sprouts with sugar, mashing them, filtering them, making the house smell of rancid brassica, leaving the demi-john of proto beer for over a year because I couldn’t face bottling it, eventually my mate tried it. He lived, so I count that as a success.

Anyway, one of my work colleagues likes bananas; so I though it would be the time to do him the honours of a special alcoholic beverage.

After consideration of what could pair with banana and ruling out stuff that I didn’t have the equipment for, I decided on a simple banana melomel. The recipe for this is simple: honey, bananas and yeast.

As there is sugar from the honey and the bananas, it should be strongly alcoholic (in double figures).

Getting Started

So, I went shopping to a German supermarket brand which is common in the UK:

Bananas and Honey!

That’s around two kilos of bananas and about the same of honey.

I’ve used cheap honey, as, after all, it’s not for me to drink. The total cost came to about £7.00.

Step one was to soak the honey in warm water (hot tap, so about 40 Celcius) to soften it.

With this happening I sterilised my demijohn and other equipment (rubber bung, funnel and a large jug).

Then I sliced up every one of those bananas into coin slices. I left the skin on with about half of them, in case that helped.

The bits of banana were through into a rough bag made from a muslin, tied up with a scrap of wool I stole from t’other half. Just to horrify the recipient; this was a muslin that had previously been used as a generic clean up cloth when my sprogs were babies (it had been laundered a few times though).

Here you can see that I needed to really clean my hob:

Really dirty hob and 2 Kg of bananas.

This was placed in a pot, covered with water and simmered for around 30 minutes until the bananas started to break down.

At this point the honey was added to the demi-john:

Lots and lots of bee poo

Once the bananas had been cooked, the water was added to the demijohn and then I start the painful job of squishing the all last banana juice out of the muslin bag. Because I’d forgotten to sterilise my colander , I started doing this with a potato masher.

Then I continued by hand. If you’ve never squished a bag of 50 degree mushed bananas you’ve never lived!

Eventually all the banana juice was added to the demi-john, shaken and left to cool to 20 degrees. Yeast was added and a bubbler was fitted to the top. Here it looks rather unappetising.

This is really bananas and honey – I know it doesn’t look like it!

The original gravity taken was an unbelievable 1120, which holds out for it to become quite strong.

First Fermentation

I actually had to replace the bubbler after the first night as it had fermented enough that foam had replace all the water:

As we can see after a couple of days its started to separate and there’s quite a bit of foam there.

Foamy banana gunk

First deracking

After a week most of the fermentation had stopped, so I deracked it into a clean, sterile demi-john.

To try and enhance the flavour I added an extra bunch of bananas that I’d peeled and chopped up. I added more yeast as the first lot had burnt themselves out.

More bananas for that real bananaery flavour

At this point it was about 6% alcohol and surprisingly smooth. The taste was mainly honey with a hint of something, but not strongly banana. After a few sips I started getting the headache that indicates the wrong sort of alcohol, so it needs a lot more fermentation.

It is still rather brown in colour.

Second deracking

Two weeks later, I was seeing little fermentation activity, so I deracked into a clean, sterile demi-john to mainly get rid of the solids.

Cleaning it up

The colour is much more yellow now and the test is becoming less honey like, but there’s still not much banana.

It has a strong cidery smell. An estimate of the alcohol content is around 13%, which I regard as a success!

Sampler near the end of fermentation


One week later, with no further sign of fermentation I bottled it into 10 330ml and 1 500ml bottle.

Final pre-bottling sample is about 14%. It’s still smooth to the taste, but there’s less of a honey flavour and a subtle banana taste.

Ideally it should be aged for between one to two years before it will be at its best.

Final bottled version

Reversing the “nokelock” BLE padlock

I’ve been reversing a couple of cheap Chinesium “smart” padlocks that unlock via Bluetooth instead of with a key. My intent is for a talk and a couple of posts on how “smartness” doesn’t stop these things being breakable via simple techniques. For example, the one on the right I can open with just a screwdriver and a pair of pliers due to bad physical design.

But anyway, I’ve been looking at the BLE communication, as are other people, so I thought I’d get my notes up now so people can crib from them if they want. I know how the protocol works, but I’m missing a few bits and can’t actually get it to unlock over BLE, but I’m close.

The device offers the standard BLE services, but two others:

  • 0000fee7-0000-1000-8000-00805f9b34fb – which handles all communication with the lock
  • f000ffc0-0451-4000-b000-000000000000 – which appears to be the TI Over the Aid Download (OAD) service

The 0xffc0 service leaves lots of potential for manipulating the firmware, but I haven’t looked at this yet. It does imply that its built on a TI CC2540 or clone.

The 0xfee7 service, which I’ll call the lock service from now on, appears to handle all communication between the app and the lock. This includes battery life and unlocking it.

The lock service has two characteristics:

  • 0x36f5 – which has write permissions
  • 0x36f6 – which has notify permissions

This implies a two way communication with data sent to 0x36f5 and received via notifications on 0x36f6. This also makes it harder to intercept using, say Frida as tying to the notify method is difficult in the way the Frida maps Java to JavaScript.

The lock requires that an apk is downloaded from the companies site and installed, which is not the safest way of distributing an app, especially as most of the site is in Chinese.

The app has been obfuscated, although I’ve found a few older versions on the web that have lesser levels of obfuscation.

A simple Frida script to hook onto writeCharacteristic shows a random set of data, the below is an exchange of connecting to the lock and unlocking it:

Write: 000036f5-0000-1000-8000-00805f9b34fb
Data: 9e 85 ce c7 9e 63 18 f4 cd a3 25 ec 3f ae fe 98 .....c....%.?...
Write: 000036f5-0000-1000-8000-00805f9b34fb
Data: eb e0 f7 56 e7 7b d8 8f e9 64 94 67 1e 1e 40 82 ...V.{...d.g..@.
Write: 000036f5-0000-1000-8000-00805f9b34fb
Data: 25 85 d3 31 c9 d0 19 74 a4 90 e6 6c 2e ee c6 99 %..1...t...l....

The length and randomness of the packet (16 octets) implies that it is encrypted with something like AES (as it is the right block size).

Some digging into the source shows us what looks like AES encryption being performed in the method com.fitsleep.sunshinelibrary.utils.g.a(‘[B’,'[B’). This takes two byte arrays as parameters, which I suspected was the key and data to be encrypted. So I stuck a hook on this and retried the connection:

Data1: 06 01 01 01 55 63 48 7d 0f 59 7c 3c 2c 6f 4d 2c ....UcH}.Y|<,oM,
Data2: 35 3a 4b 0f 23 21 57 4a 58 4a 21 24 35 33 3c 56 5:K.#!WJXJ!$53<V

Write: 000036f5-0000-1000-8000-00805f9b34fb
Data: a2 d2 b0 c5 2b 3f 06 f0 fe 2c 65 87 88 51 dc ec ....+?...,e..Q..

Data1: 02 01 01 01 f1 07 c3 10 5f 2a 35 6a 5f 3b 23 4f ........_*5j_;#O
Data2: 35 3a 4b 0f 23 21 57 4a 58 4a 21 24 35 33 3c 56 5:K.#!WJXJ!$53<V

Write: 000036f5-0000-1000-8000-00805f9b34fb
Data: 19 38 f6 ab 47 94 5c e6 5f 2c d4 44 b4 63 df 37 .8..G.\._,.D.c.7

Data1: 05 01 06 30 30 30 30 30 30 f1 07 c3 10 63 23 1d ...000000....c#.
Data2: 35 3a 4b 0f 23 21 57 4a 58 4a 21 24 35 33 3c 56 5:K.#!WJXJ!$53<V

Write: 000036f5-0000-1000-8000-00805f9b34fb
Data: 23 80 70 53 24 5e 59 f9 88 54 83 0e 16 c3 6e b3 #.pS$^Y..T....n.

We can see here that the octets are encoded and then passed directly to a writeCharacteristic call. If you look, data2 never changes, implies that it is the encryption key. Let’s try this out with a bit of python:

>>> from Crypto.Cipher import AES
>>> import binascii
>>> key=binascii.unhexlify("353a4b0f2321574a584a212435333c56")
>>> data=binascii.unhexlify("02010101f107c3105f2a356a5f3b234f")
>>> binascii.hexlify(aes.encrypt(data))

That’s a perfect match (look at the red entry above). So we know that they match perfectly, so this is just for some simple obfuscation of the payload and to prevent replay attacks. Both of my locks seemed to use the same key.

After some retries, it looks like this is the magic unlocking command:

05 01 06 30 30 30 30 30 30 f1 07 c3 10 63 23 1d ...000000....c#.

Some interpretation about the protocol:

  • 05 is lock/unlock
  • 01 is unlock
  • 06 is the length of the padlock password
  • 30 30 30 30 30 30 (“000000”) is the padlock password
  • f1 07 c3 10 62 23 1d I have no idea

The last three octets change with every request, but not in a way that suggests a time stamp. The first three alter, but less often. This is the bit I can’t work out.

I noticed that the app logs a lot of stuff logcat as Errors, so running logcat whilst the app is running can show some interesting information:

The first thing I noticed that it logs its calls to the backend API to the console, these map up with stuff I intercepted with burp:

7-11 18:15:59.518 32159 32159 E BaseObserver: onNext:{"result":[{"name":"bob","id":12923,"lockKey":"37,75,66,15,61,42,1,27,49,43,6,8,78,31,29,98","isAdmin":0,"firmwareVersion":"5.0","type":0,"barcode":"GBY040001453","deviceId":"","lockPwd":"000000","mac":"C8:DF:84:2B:9A:D9","account":"","gsmVersion":null},{"name":"orange","id":88116,"lockKey":"53,58,75,15,35,33,87,74,88,74,33,36,53,51,60,86","isAdmin":0,"firmwareVersion":"5.0","type":0,"barcode":"GBF040000969","deviceId":"","lockPwd":"000000","mac":"3C:A3:08:C1:AA:A3","account":"","gsmVersion":null}],"status":"2000"}
7-11 18:15:59.519 32159 32159 E HomeActivity: [{"name":"bob","id":12923,"lockKey":"37,75,66,15,61,42,1,27,49,43,6,8,78,31,29,98","isAdmin":0,"firmwareVersion":"5.0","type":0,"barcode":"GBY040001453","deviceId":"","lockPwd":"000000","mac":"C8:DF:84:2B:9A:D9","account":"","gsmVersion":null},{"name":"orange","id":88116,"lockKey":"53,58,75,15,35,33,87,74,88,74,33,36,53,51,60,86","isAdmin":0,"firmwareVersion":"5.0","type":0,"barcode":"GBF040000969","deviceId":"","lockPwd":"000000","mac":"3C:A3:08:C1:AA:A3","account":"","gsmVersion":null}]

Where “Bob” and “orange” are my two padlocks. We can see that encryption key in lockKey and the lock password in lockPwd.

The system log shows more than this though, if we venture down a bit we can see:

07-11 18:16:11.355 32159 32159 E a : 060101011A1B314F421966216B407C2E
07-11 18:16:11.446 32159 32170 E a : 返回:06020725986A6D010500000000000000
07-11 18:16:11.978 32159 32159 E a : 0201010125986A6D7873473C5C5D124C
07-11 18:16:12.374 32159 32170 E a : 返回:02020162986A6D010500000000000000
07-11 18:16:12.892 32159 32159 E a : 05010630303030303025986A6D476152
07-11 18:16:13.498 32159 32170 E a : 返回:05020100986A6D010500000000000000
07-11 18:16:15.191 32159 32170 E a : 返回:050D0100986A6D010500000000000000
07-11 18:16:21.955 32159 32159 E a : 05010630303030303025986A6D60360B
07-11 18:16:23.169 32159 32170 E a : 返回:05020100986A6D010500000000000000
07-11 18:16:25.167 32159 32169 E a : 返回:050D0100986A6D010500000000000000
07-11 18:16:27.447 32159 32159 E a : 05010630303030303025986A6D4F2241
07-11 18:16:29.155 32159 32170 E a : 返回:05020100986A6D010500000000000000
07-11 18:16:31.151 32159 32264 E a : 返回:050D0100986A6D010500000000000000

It’s those magic numbers again, but it looks like we’re getting the response two (the entries with Chinese characters). Mixed with some reversing of the app, here’s a slightly annotated version:

Out: 0201010125986A6D7873473C5C5D124C
In:  02020162986A6D010500000000000000
Out: 05010630303030303025986A6D476152 
In:  05020100986A6D010500000000000000
In:  050D0100986A6D010500000000000000
Out: 05010630303030303025986A6D60360B
In:  05020100986A6D010500000000000000
In:  050D0100986A6D010500000000000000
Out: 05010630303030303025986A6D4F2241

That’s about as far as I can get. I’m pretty much six octets off a full attack.

Reversing the Recreated ZX Spectrum Part 1

A couple of years back I kickstarted the “Recreated ZX Spectrum”, a creation by Elite who basically made a Spectrum case and turned it into a Bluetooth/USB keyboard with an Android/iOS app to interface and be a Spectrum emulator.

It was one of my first experiences that kickstarter projects where a bit of risk; let’s just say that I was not impressed with the organisation and professionalism of Elite. What irritated me most was that I saw it for sale in Maplins before mine was even dispatched.

To be honest though, it’s sat in a corner of my office doing nothing since it arrived.

The outside

Superficially it looks like a ZX Spectrum. It’s a tad wider and a lot lighter in feel but, except for the bottom and back it looks right. So points on look. Real Spectrum at the bottom, Recreated ZX Spectrum at the top:

On the bottom is a space to place two rechargable AA cells to allow it act wirelessly and seven screws. One of which was covered by a QC Pass sticker, which could easily be pealed off. There’s even a place where speaker holes are on a real Spectrum (though this has no speaker.

Interestingly enough there’s an FCC ID (XEN-ZXSPECTRUM) and a hint of the manufacturer (Accuratus) and a “made in China”. Now that’s useful. The US Federal Communications Commission require that electronic devices are check for RF emissions. What’s the most useful is that this can show us what stuff looks like without taking it apart.

And lo, a search for the FCC-ID reveals the goods!

What we have here are a load of form filling, but some internal photos which show us the mesh keyboard, which is pretty much an exact copy of the Spectrum’s; though annoyingly the connector isn’t compatible.

We can see the (tiny) main circuit board and the Bluetooth module separately, though we can only read the chip name on the Bluetooth module, which is an ISSC IS1852S, made by Microchip. I can’t find a datasheet for this, but I can find information for a IS1652N, so I’m wondering whether this my old eyes and will take a better photo once I take the cover off.

The IS1652N is a Bluetooth 3.0 controller designed for devices in the HID (Human Interface Devices) class. This would perfectly suit the Spectrum Next’s use as a keyboard. So I’m assuming that is mostly correct.

Unlocking It

Before I crack it open, in a perfect example of how badly managed and thought out the device is: it comes in a “locked state” which is designed to only be used by with the app and as a game keyboard. Which means that most of the keys don’t do anything.

I cannot understand what the decision behind this was, it makes zero sense.

Anyway, you can unlock it online at the Elite systems web site (for now at least).

You have to accept terms and conditions to go through this. Hmmm… I don’t think so. Looking at the source, the unlocked is written in JavaScript, so to avoid any copyright problems, I’ve rewritten it in python.

The unlocking process takes a string from the keyboard (which is provided by pressing Caps Shift) and mangles it before decrypting it with DES and a static password, following this process:

To demonstrate this, I’m going to use the code in the unlocking video.

And, as you can see, the code matches, and I unlocked my Recreated Spectrum this way to prove it. The algorithm is simple, the device sends four 32 bit integers in hex format. These are mangled into two 64 bit numbers, but on the way the endianness is changed.

These 64 bit numbers are taken in their hex form (including the 0x), padded with NULs and encrypted with DES in ECB mode with a static key of “H3U89XT1”; the two encoded numbers are xor’d together and returned as a decimal number.

Reversing the ZX Vega: The Emulator

If you’ve been reading this, I’ve been (over a couple of years) messing around with my ZX Vega to try and work out how it works. You can read part one, where I look at the firmware (now no longer available) and part two, where I look at the hardware.

For a change I’m going to look at the emulator to work out which flavour of Spectrum emulator it is. From part one, my original supposition is that the device is running the fuse emulator.

[Quick note: as this is referring to stuff on the Spectrum, I’m using $ as a signifier for a hexadecimal number as opposed to the C standard 0x or the Acorn & which I grew up with]


The thing about emulators is that they are written from the knowledge available about the device at the time. Some stuff, such as the contents of the system ROMs can be extracted from live devices. Other stuff has to be gleaned from the datasheets and/or behaviours seen on the real systems.

Why is this important? Well, the Spectrum is based on a NEC D780C-1, which is a version of the Zilog Z80, which was based on the Intel 8080. This means that we can analyse how the emulator handles the CPU in unusual circumstances.

Specifically, we’re looking at undocumented op-codes. Unlike sensible modern CPUs, the older 8-bit CPUs build commands up on certain patterns of bytes that were physically mapped onto the silicon.

This meant that by providing unused opcode it is possible to cause strange effects. By mapping these out, we can fingerprint the emulator in use.

It should be noted at this point that when fuse was first written the Z80 had not been fully mapped out. This has happened a number of times and the information is publicly available, meaning there are less unknowns with how the Z80 behaves. Support for some facilities has improved and later versions of fuse show a different signature.

There are a number of test suites for emulators out there, but I’m going to concentrate on one specific aspect: the MEMPTR register. Because I’ve made the assumption the Vega is running fuse and fuse didn’t support MEMPTR until 2017, which is a long time after the last Vega firmware release.


The Z80 used a group of 16 bit registers, some of which could be split into two 8 bit registers . For example the register BC coud be used as two 8 bit registers, B and C.

In total there are four of these registers that can be easily accessed: AF, BC, DE and HL. These registers are special because they are duplicated to allow quick swapping of data, the duplicates are known as AF’, BC’, DE’ and HL’.

There are some other, well known 16 bit registers that cannot be as easily read, these are the two index registers, IX and IY; the stack pointer, SP; the program counter, PC and the interrupt register, IR.

There is one final 16 bit register, WZ, which is often mistakenly thought to be duplicatible. This register is also know as MEMPTR. It is used as a temporary holder for instructions that directly access memory without having to use the stack. For example, the instruction LD A,($8000) will load the accumulator with the byte at the contents of the memory address $8000. When this instruction is found, the value of the memory address needs to be stored somewhere – it is placed in MEMPTR. This goes back to the Intel 8080.

It is meant to be totally unaccessible from machine code. So, this is all good; expect, it was noted that the BIT n, (HL) instruction produced weird effects in bits 3 and 5 of the flags register (the F part of the AF register, or F’ of the AF’ register). Specific it will copy bits 11 and 13 of MEMPTR to bits 3 and 5 of F.

This now gives us a technique we can use to read something from MEMPTR: we can write a specific value to it and read some of it back with BIT n, (HL). As reading the whole value of MEMPTR through two bits would take a long time; we could manage this by writing the four known values for bits 11 and 13 (i.e. set them to 00, 01, 10 and 11) and retrieving them.

Although we can’t directly read the flags register, we can push it to the stack and retrieve it into another register. Here’s this is Z80 code (the first Z80 I’ve written in nearly 30 years, I’ve always preferred 6502 or ARM):

01 FF 27    LD BC, $27FF   ; After this WZ will be $2800, bit 13 = 1; bit 11 = 1
0A          LD A, (BC)     ; WZ = $27FF + 1
CB 66       BIT 4, (HL)    ; F[3]=WZ[11]; F[5]=WZ[13]
F5          PUSH AF        ; push AF onto the stack
C1          POP BC         ; pop it into BC
79          LD A, C        ; Take the lower byte (i.e. F)
E6 20       AND $20        ; AND with $20 - i.e. mask out bit 5
47          LD B, A        ; Push into B (for return to BASIC)
79          LD A, C        ; Take F again
E6 08       AND $08        ; AND with $08 - i.e. mask out bit 3
4F          LD C, A        ; Push into C (for return to BASIC)
C9          RET            ; Return to Basic

So this will result in just bit 5 in B and just bit 3 in C. We’re using BC as the Spectrum USR command will return the value of BC to BASIC.

So we can now run this in Spectrum BASIC and recover some of WZ; using this noddy BASIC program:

10 FOR a=32768 to 32784
20 READ j
30 POKE a,j
40 NEXT a
50 LET r = USR 32768
60 PRINT r
70 DATA 1, 255, 39, 10, 203, 102, 245, 193, 121, 230, 32, 71, 121, 230, 8, 79, 201

If we run this we get back the response 8200, which is $2008 in hex. This means that bit 5 is set in B and bit 3 is set in C; which is exactly what we wanted. This is the same response in both fuse (non-MEMPTR supporting version) and ZXSpectrum4.NET (which supports MEMPTR).

Okay, lets check all four values:

10 FOR a=32768 TO 32784
20 READ j
30 POKE a,j
40 NEXT a
50 LET p=39: LET v=8200
60 GO SUB 200
70 LET p=31: LET v=8192
80 GO SUB 200
90 LET p=7: LET v=8
100 GO SUB 200
110 LET p=0: LET v=0
120 GO SUB 200
130 STOP
200 POKE 32770, p
210 LET r=USR 32768
220 IF r=v THEN PRINT (p+1), "Pass"
240 DATA 1, 255, 39, 10, 203, 102, 245, 193, 121, 230, 32, 71, 121, 230, 8, 79, 201

So, if we run the above on ZXSpectrum4.NET we see that they all pass:

But if we run it on our old version of fuse, only the first test passes, further debugging shows that fuse always sets the flags to 1 and 1:

Running on the Vega

To actually prove what the Vega runs, we need something a bit better than my proof of concept above.

Fortunately there are a number of emulator test suites out there that fully test undocumented and unusual opcodes.

So in theory if the results from one of these test suites matches up between fuse and the Vega we can be very certain what the Vega is running.

I chose the RAXOFT z80memptr test suite to try this out.

No surprise, it works correctly on ZXSpectrum4.NET:

When we try this on fuse, as this is the fuse version before MEMPTR support was added, it fails on every test, running on my Windows box:

So what happens on the Vega? I think you can already guess – it gets the exact same results as the old version of fuse. In this case I need to apologise for my bad photography:


Now this is by no means final that it doe or does not prove that the Vega is running fuse. But if it isn’t running fuse, it is quite indicative that the emulator it uses it very similar to that of fuse.

The only way to really confirm this would be to get further into the device as it is running…

Reversing the ZX Vega: Hardware

It’s been a while since I wrote part one. I promised I’d have a look at the hardware on the Vega.

Here is the front of the board:

And the rear:

As can be seen from the front photo, the components are quite simple, with the major ICs being:

  • CPU (in red), an NXP MCIMX233DAG4C ARM based processor
  • Storage (in yellow), a Spansion FL512SA1F01 512 Mb SPI Flash chip
  • Memory (in purple), an Alliance AS4C8M16D1-5TCN 128 Mb DRAM

Note memory sizes are in Mbits – i.e. 1024 * 1024 bits; to get these in bytes we need to divide by 8, so that’s 64 MB of storage and 16 MB of RAM.

The storage chip is NOR SPI flash. SPI flash is relatively simple to read and (mostly) follows a standard core of commands defined by JEDEC.

I’ve rigged this up through a custom cable, from a 16 pin Pomona clip to one of my Raspberry Pis (as it gives me a lot of control over what I can do).

I have a few python scripts written to read NOR flash over SPI. One of these is a raw command line interface to the JEDEC op codes.

The first step here is to identify I can actually talk to the chip, this can be performed by using the RDID (opcode 0x9f), RES (opcode 0xAB) and REMS (opcode 0x90) commands. These should return the same information about the chip identifying the manufacturer and the density of the device which should tell us the size.

This produces the below, which isn’t quite right, the manufacturer ID is AMD and the density would work out as 4294967296 bytes or 4096 MB, which is a bit silly. We know it should be about 64MB:

A quick test to see whether we can read data, using the READ (opcode 0x03) command. Ah; we’re reading data. That’s a good thing, maybe the identification results aren’t quite perfect.

Using flashrom fails to identify the chip, which means flashrom won’t even try to read it:

pi@raspberrypi:~/spireader $ flashrom -p linux_spi:dev=/dev/spidev0.0
flashrom v0.9.9-r1954 on Linux 4.9.24-v7+ (armv7l)
flashrom is free software, get the source code at

Calibrating delay loop... OK.
Found Generic flash chip "unknown SPI chip (RDID)" (0 kB, SPI) on linux_spi.
This flash part has status NOT WORKING for operations: PROBE READ ERASE WRITE
The test status of this chip may have been updated in the latest development
version of flashrom. If you are running the latest development version,
please email a report to if any of the above operations
work correctly for you with this flash chip. Please include the flashrom log
file for all operations you tested (see the man page for details), and mention
which mainboard or programmer you tested in the subject line.
Thanks for your help!
No operations were specified.

So, I’m going to use my own code, which allows you to read it from a web browser with a one click solution, as shown below:

Because the chip didn’t respond correctly to the RDID call I had to make a couple of amendments to not try and read too much data from the chip.

So, I know have the file read from the onboard storage and… It’s exactly the same as the firmware, meaning that the CPU decrypts it on the fly, which, I’d expected. Bugger.

Right, onwards, if you look closely at the photos of the rear of the board, there are a couple of test points that are visible: one in-circuit programming type header at the top (red) and two sets of three test points a bit further down (yellow, marked J4 and blue marked J3) .

Using a multimeter in continuity mode to attempt to buzz out where these go shows me that the connector marked J4 connects to ground and the PWM0 and PWM1 pins of the CPU, according to the datasheet these are commonly used for Debug UART. J3 has 3.3V and a connection to the DEBUG pin of the CPU, this is used for SJTAG.

The header at the top only appears to buzz to ground and VCC, this is probably due to components between the connectors and the pins they connect to. This is something to look into later.

I can’t test the SJTAG connection with the tools I have with me, but UART is a simple serial protocol that uses two wires for transit and receive and is commonly used for consoles on devices.

A quick trip to the garage, where I keep the soldering iron, allows me to tack some wires to the board; now I can rig up a logic analyser to allow me to monitor those pins whilst I boot my Vega.

And what do I get… not quite what I expected:

Looks like they updated the bootloader text screen. Unfortunately that’s it, there’s nothing else as I continue to use the Vega. This really isn’t going anywhere fast unless I can either get the SJTAG interface or the other header working.

Reversing Robico’s Island of Xaan


This is a basic description of a reverse engineer of Robico’s first text adventure: Island of Xaan.

Although it is one of their weakest, it shows a number of trademark features of Robico’s adventures: lots of detail descriptions, lots of humour and some strange logic. The parser, being just verb and noun, is quite weak compared to Robico’s later adventures.

The adventure was only available on the BBC Micro and Acorn Electron computers. The version I’m reverse engineering is the BBC Micro one, taken from the Stairway to Hell archive and is designed to run in &1900 DFS, using MODE 7.

This write up uses BBC Micro conventions (e.g. using & to signify a hexadecimal number) and assumes that some knowledge is known about the BBC micro.

All notes and scrap code is available from my github.

The disk

The disk contains six files, three of which are loaders:

I’ll get these out of the way, as they’re the simplest and have nothing really interesting from a reverse engineering perspective:

  • $.!BOOT – is loaded when the disc is run with shift-break and just runs $.LOAD
  • $.LOAD – is the general Stairway to Hell loader and just runs $.LOADER
  • $.LOADER – prints the introduction, then loads and runs $.XAAN1. It also shows the address for Robico software, which is an unimposing semi-detached house in Wales.

The other files are code for the adventure which are loaded in the following order, and locations:

  • $.XAAN1 which is loaded from &880 to &D00 with an exec address of &888
  • $.XAAN which is loaded from &1200 to &7BFC with an exec address of &225D
  • $.XAAN2 which is loaded from &2000 to &2400 with an exec address of &2000


XAAN1 contains the intial loader, a selection of library routines, such as the message printer, and several command routines, for short commands, probably placed here because memory was short.
Once loaded it performs the basic system calls, before handing over control to the XAAN file:

  1. OSBYTE 225        (disable function keys)
  2. OSBYTE 200 3      (disable escape and clear memory on break)
  3. OSBYTE 139 1      (*OPT 1 equivalent – report nothing during file ops)
  4. OSCLI “R. XAAN”   (Load XAAN and jump to its executable address)

Other obvious functions include (these are the names I’ve given the functions):

  • printmsg – to decompress and print a message. This uses OSWRCH to print to the screen.
  • findmsg – finds the message passed in X and passes it to printmsg
  • getinput – retrieves a input from the command line, using OSWORD 0. This also splits the input into a verb (stored at &41f) and a noun (stored at &43e)
  • searchlist – looks for the passed word in the passed list and returns the index in X. This is used to convert the verb and noun to a number.
  • handlecmd – looks up the entered verb and the looks for the appropriate address from a list of pointers.
  • the routines for the commands WAIT, SIT and QUIT


XAAN2 looks like it was originally the copy protection file – it contains a selection of calls to OSWORD to read sectors directly from the disk. This is unused on the Stairway to Hell disk.


XAAN contains nearly everything, the code, the game data and everything else, apart from a few routines in XAAN1.

XAAN has multiple chunks of data with roughly the following uses:

From To Addr Purpose
0000 10be 1200 Code
10bf 30ff 22bf Empty data
3100 3248 4300 Introduction text
3249 32ff 4449 Unknown
3300 5c56 4500 Messages
5c57 5c5b 6e57 Unknown
5c5c 6e5c Room exits
62f6 74f6 Object locations and flags
6354 7554 Room messages
6400 6437 7600 Verb action pointer (low)
6438 646f 7638 Verb action point (high)
6473 65a8 7673 List of verbs
65a9 66e1 77a9 List of nouns
66e4 69fb 78e4 List of tokens

As far as we’re concerned, the data is all listed towards the end of free memory (&7c00 in Mode 7). The bit that surprises me is the amount of spare memory – bearing in mind that the BBC Micro only had 32KB of memory, of which large swathes are unusable due to disk and screen memory, there’s still around 8 KB of free memory whilst XAAN is running!

I’ll go into detail of the different sections later, but for now, the code block about performs all the stuff to run the game and apply the verbs. Unfortunately Robico decided against using a form of bytecode (like other text adventure creators), so all the logic is done in raw 6502.

The machine code does do a lot of JMPing to JMP instructions suggesting to me that it was probably assembled in blocks to reduce memory usage whilst coding it.

Objects are handled in a slightly strange way – every noun is counted as an object, with a location byte.

Anyway, to the data formats…


All strings are compressed with routine referred to by the creator as MIDGE. THis is relatively easy to decompress and there have been public domain examples found in the past.

This uses a dictionary of common patterns with some handy special characters.

Decompressing it is a matter of reading the next byte and then expanding according to the following rules. The best way to show this is to show my notes for the first location in the game Rick Hanson (which uses MIDGE too):

45 = "you"
FC = "'"
ED = "r" = 0xed - 0xdb (size of database)
59 = "e "
48 = "in "
3a = "the "
00 = "entrance "
70 = "ha"
93 = "ll"
FB = " "
49 = "of "
5D = "a "
EE = "s"
81 = "ma"
93 = "ll"
F9 = ","
DF = "d"
73 = "es"
66 = "er"
6D = "te"
5C = "d "
43 = "rail"
4B = "way"
FB = " "

Internally, this is all handled by printmsg in the XAAN file.
To save space, messages in the code are constructed from several different messages, e.g. from the THROW command:

.canthrow   lda invsize
            bne stuffinv      ; if we have stuff in inv 
            ldx #&10          ; message 16 "You are not"
            jsr findmsg
            ldx #&1a          ; message 26 "holding"
            jsr findmsg
            ldx #&18          ; "anything"
            jmp prtmsg

This is used to compress the intro message (at offset &3100), the bank of messages (at offset &3300) and the room descriptions (more later).


Rooms are stored in two chunks:

  • exits (at offset &5c5c)
  • descriptions (at offset &6354)

Room numbers start at 52; this is because they are treated as nouns (see later).
For each room there is are a sequence of bytes. The bottom nybble of the first byte signifies the direction (up to 10, including IN and OUT); the top nybble indicates whether the exit is blocked (&40) or whether there are no exits (&80).

If there is an exit, the flag byte is followed by the destination.

Descriptions are stored as a list of tokens, like the messages.


Object and nouns (and rooms) all share a common number, which follows the ranges of:

  • 2 – 47 nouns
  • 52 – 223 rooms

For each noun/room there is a location byte (at offset &62f6) which has the location of the object. 0 is the inventory and 1 is being worn. This does mean that potentially items can be carried by other items!
Verbs/NounsThese are a simple list of strings separated by an “@” character. The number offset is used as an internal reference for the verb/noun number.

Running verb specific actions

There is a table of action entry points, at offset &6400. This is divided into two sections – low byte and high byte; this is to make it easy to do a vector jump:

            lda verbptrsl,x      ; lookup table to control each verb
            sta verbv
            lda verbptrsh,x
            sta verbvh
            jsr setnouns
            jmp (verbv)

Out of curiosity

I found one example of self modifying code in the handlecmd function, where it sets the colour of the output text. It does this by modifying this statement in printmsg:

 colourinst  = &0922
; &10921
            lda #&86          ; this instruction can be altered
            jmp oswrch        ; VDU 134 (Colour = cyan)

We see this location modified in both xaan1.asm (in handlecmd) and in xaan.asm, for example:

.l20ca      lda #&86
            sta l0922

Reversing the ZX Vega: Firmware


A while back I got a ZX Vega for the LOLs and trying to remember what the Spectrum was like. Of course, I didn’t need to remember: I have quite a few in watertight boxes in the garage and, of course, I was a BBC Micro fan as a lad.

So, after a quick play and realising that 80s resolution on a 42” TV does not look good and that Spectrum artefact clash has always been horrible (thanks to the way that the Speccy did screen memory), I wondered how this thing worked.

Obviously, it’s running a Spectrum emulator – my initial guess is that the creators of the Vega wouldn’t have written their own – not when there’s many open source and easily licensed emulators around.

As a quick finger-in-the-air estimation, I thought it would probably use some simple Linux like base OS, running a framebuffer menu over the top and probably spawning fuse. After all, that’s how I would do it.

The Firmware

When reverse engineering stuff like this you can attack at multiple levels: looking at the software, looking at the hardware or attempting to reverse the firmware.

I’m lazy, I started with the firmware (to be honest I looked at this even before I had the hardware). This is easily obtainable from the site highlighted above. This comes in at just shy of 29 MB:

The first step when reversing stuff is to use some binary matching tools, these look for signatures in the data and attempt to work out what stuff is in there. The one of these that gives the best results, usually is binwalk.

This time, though it wasn’t brilliantly helpful, below is an edited extract of what it returned:

71            0x47            Copyright string: "Copyright (C) 2015 Retro Computers Limited."
579           0x243           Copyright string: "Copyright (C) 2015 Retro Computers Limited."
665530        0xA27BA         StuffIt Deluxe Segment (data): fton Manor Assignment.tap.gz
2689030       0x290806        StuffIt Deluxe Segment (data): fton Manor Assignment
2689060       0x290824        StuffIt Deluxe Segment (data): fton Manor Assignment.tap
2963660       0x2D38CC        Zlib compressed data, best compression
4015378       0x3D4512        Zlib compressed data, best compression
4024320       0x3D6800        gzip compressed data, has original file name: "1994.tap", from Unix, last modified: 2015-09-07 19:57:07
4036608       0x3D9800        gzip compressed data, has original file name: "1999.tap", from Unix, last modified: 2015-09-07 19:57:07
4057088       0x3DE800        gzip compressed data, has original file name: "20-20 Vision.tap", from Unix, last modified: 2015-09-07 19:57:07

This is sort of helpful; but not really – a copyright string that I could’ve guessed and some references to compressed Spectrum games.

Let’s use a more common string to search through here, the standard strings utility, which will look for strings that look to be ASCII text, once again, here’s an edited output:

0.1.64 20150909
The Sinclair ZX Spectrum Vega.
Copyright (C) 2015 Retro Computers Limited.
Concept, CAD, hardware design, C, ARM and Z80
programming: Chris Smith.
The Sinclair ZX Spectrum Vega.
Copyright (C) 2015 Retro Computers Limited.
Concept, CAD, hardware design, C, ARM and Z80
programming: Chris Smith.

This is more useful: we have some comments, a version number ( and what look like chunk headers (VEGA, SMEM, STMP). We’ll come back to these later.

A bit further down the file we have this:

This is not a bootable disk.  Please insert a bootable floppy and
press any key to try again ...

Now that’s useful – that’s the header for a FAT16 volume made by mkdosfs. As this is a filing system we should be able to rip it out and mount it through loopback.

Looking at it through hexdump we can see the header at offset 0x88800:

This all follows what we’d expect to see with a FAT16 volume. At this point we can safely assume that there’s a FAT16 volume from 0x88800 onwards. Using a simple structure viewer to highlight the header gives us:

From here we can see that the volume consists of 55032 sectors of 512 bytes, which is 28,176,384 bytes, or 27,516 KB. Or, to put it simply, the remainder of the file:

To finally confirm this, let’s extract the end bit and mount it. First, splitting the file:

Now we can mount it:

Inside the Games Archive

Looks like we have a filesystem; there’s a load of text files on there, which seem to have a simple file format, for example looking at adventure.idx:

00000000  5a 5a 5a 5a 01 5a 5a 5a  5a 2e 74 61 70 2e 67 7a  |ZZZZ.ZZZZ.tap.gz|
00000010  0a 5a 65 6e 20 51 75 65  73 74 01 5a 65 6e 20 51  |.Zen Quest.Zen Q|
00000020  75 65 73 74 2e 74 61 70  2e 67 7a 0a 58 74 72 6f  |uest.tap.gz.Xtro|
00000030  74 68 20 2d 20 54 68 65  20 41 64 76 65 6e 74 75  |th - The Adventu|
00000040  72 65 01 58 74 72 6f 74  68 20 54 68 65 20 41 64  |re.Xtroth The Ad|
00000050  76 65 6e 74 75 72 65 20  2d 20 31 32 38 6b 2e 74  |venture - 128k.t|
00000060  61 70 2e 67 7a 0a 57 6f  6c 66 6d 61 6e 01 57 6f  |ap.gz.Wolfman.Wo|
00000070  6c 66 6d 61 6e 2e 74 61  70 2e 67 7a 0a 57 69 7a  |lfman.tap.gz.Wiz|
00000080  61 72 64 20 51 75 65 73  74 20 2d 20 54 68 65 20  |ard Quest - The |
00000090  47 75 69 64 65 01 57 69  7a 61 72 64 20 51 75 65  |Guide.Wizard Que|

In red I’ve highlighted the end of field markers and in blue the end of line.

The format is basically;

string     gamename
byte       FS (0x01)
string     filename
byte       RS (0x0a)

So a quick bit of Python to convert it to TSV format:

>>> with open('adventure.idx','rb') as f:
...     for line in f:
...             sp=line.rstrip().split(b'\x01')
...             print "%s\t%s" % (sp[0], sp[1])
ZZZZ   ZZZZ.tap.gz
Zen Quest     Zen Quest.tap.gz
Xtroth - The Adventure Xtroth The Adventure - 128k.tap.gz
Wolfman Wolfman.tap.gz
Wizard Quest - The Guide     Wizard Quest - Guide.tap.gz

Looking into the games directory and we see (no surprise) the games:

[dave@mictlan games]$ ls -l | head
total 26972
-rwxr-xr-x 1 root root  11485 Sep  7  2015 1994.tap.gz
-rwxr-xr-x 1 root root    170 Sep  7  2015 1994.zxk
-rwxr-xr-x 1 root root  19736 Sep  7  2015 1999.tap.gz
-rwxr-xr-x 1 root root    119 Sep  7  2015 1999.zxk
-rwxr-xr-x 1 root root  22115 Sep  7  2015 20-20 Vision.tap.gz
-rwxr-xr-x 1 root root    123 Sep  7  2015 20-20 Vision.zxk
-rwxr-xr-x 1 root root  13631 Sep  7  2015 2088.tap.gz
-rwxr-xr-x 1 root root    115 Sep  7  2015 2088.zxk
-rwxr-xr-x 1 root root   4022 Sep  7  2015 3 Dimensional Noughts & Crosses.z80.gz

Each game has a gzip version of the file in either z80 or tap file format and an extra file, that zxk file. The zxk file has a simple dictionary format:

[dave@mictlan games]$ cat ZZZZ.zxk

These seems to be a simple file format of Attribute:Value pairs, the obvious ones are:

T              Game Title
F              Filename
M            Memory (48 or 128)
C             Classification (only adventure seen)
K             Keyboard definitions
D             Keyboard descriptions

The last thing to note is that this volume is pretty much full to capacity:

So presumably the firmware would expand in size dependent on games.

The rest of it

Right, we’ve decoded the lion’s share of the firmware, what about the rest?

First off I’m going to pull it into a separate file, so I can leave the original in case of error:

The hexdump is not that exciting:

[dave@mictlan zxvega]$ hexdump -C firstpart.bin | head -40
00000000  56 45 47 41 40 01 00 00  09 09 df 07 01 00 00 00  |VEGA@...........|
00000010  00 76 b6 01 00 00 00 00  30 2e 31 2e 36 34 20 32  |.v......0.1.64 2|
00000020  30 31 35 30 39 30 39 0a  54 68 65 20 53 69 6e 63  |0150909.The Sinc|
00000030  6c 61 69 72 20 5a 58 20  53 70 65 63 74 72 75 6d  |lair ZX Spectrum|
00000040  20 56 65 67 61 2e 0a 43  6f 70 79 72 69 67 68 74  | Vega..Copyright|
00000050  20 28 43 29 20 32 30 31  35 20 52 65 74 72 6f 20  | (C) 2015 Retro |
00000060  43 6f 6d 70 75 74 65 72  73 20 4c 69 6d 69 74 65  |Computers Limite|
00000070  64 2e 0a 77 77 77 2e 72  65 74 72 6f 2d 63 6f 6d  |d..www.retro-com|
00000080  70 75 74 65 72 73 2e 63  6f 2e 75 6b 20 20 77 77  |  ww|
00000090  77 2e 7a 78 76 65 67 61  2e 63 6f 2e 75 6b 0a 43  ||
000000a0  6f 6e 63 65 70 74 2c 20  43 41 44 2c 20 68 61 72  |oncept, CAD, har|
000000b0  64 77 61 72 65 20 64 65  73 69 67 6e 2c 20 43 2c  |dware design, C,|
000000c0  20 41 52 4d 20 61 6e 64  20 5a 38 30 0a 70 72 6f  | ARM and|
000000d0  67 72 61 6d 6d 69 6e 67  3a 20 43 68 72 69 73 20  |gramming: Chris |
000000e0  53 6d 69 74 68 2e 20 77  77 77 2e 7a 78 64 65 73  |Smith. www.zxdes|
000000f0  69 67 6e 2e 69 6e 66 6f  0a 00 00 00 00 00 00 00  ||
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

There’s the string we saw earlier with the copyright string, version number and header, we can try to clean this up:

Start Length Type Value Use
00 4 u32 “VEGA” Magic
04 4 u32 0x00000140 Version number (1.64)
08 2 u32 0x07df0909 Date stamp (2015-09-09)
0C 4 u32 0x00000001 Unknown
10 4 u32 0x01b67600 Length of file without this header
14 4 u32 0x00000000 Unknown
18 16 uchar “0.1.64 20150909\n” Version String
28 419 uchar “The Sinclair ZX Spectrum Vega …” Copyright String

Then there is empty space up to the end of the header (0x200), after which we get:

00000200  53 4d 45 4d f6 00 00 00  00 02 00 00 00 00 00 00  |SMEM............|
00000210  04 00 00 00 01 00 02 00  40 01 00 00 09 09 df 07  |........@.......|
00000220  43 04 00 00 54 68 65 20  53 69 6e 63 6c 61 69 72  |C...The Sinclair|
00000230  20 5a 58 20 53 70 65 63  74 72 75 6d 20 56 65 67  | ZX Spectrum Veg|
00000240  61 2e 0a 43 6f 70 79 72  69 67 68 74 20 28 43 29  |a..Copyright (C)|
00000250  20 32 30 31 35 20 52 65  74 72 6f 20 43 6f 6d 70  | 2015 Retro Comp|
00000260  75 74 65 72 73 20 4c 69  6d 69 74 65 64 2e 0a 77  |uters Limited..w|
00000270  77 77 2e 72 65 74 72 6f  2d 63 6f 6d 70 75 74 65  |ww.retro-compute|
00000280  72 73 2e 63 6f 2e 75 6b  20 20 77 77 77 2e 7a 78  |  www.zx|
00000290  76 65 67 61 2e 63 6f 2e  75 6b 0a 43 6f 6e 63 65  ||
000002a0  70 74 2c 20 43 41 44 2c  20 68 61 72 64 77 61 72  |pt, CAD, hardwar|
000002b0  65 20 64 65 73 69 67 6e  2c 20 43 2c 20 41 52 4d  |e design, C, ARM|
000002c0  20 61 6e 64 20 5a 38 30  0a 70 72 6f 67 72 61 6d  | and Z80.program|
000002d0  6d 69 6e 67 3a 20 43 68  72 69 73 20 53 6d 69 74  |ming: Chris Smit|
000002e0  68 2e 20 77 77 77 2e 7a  78 64 65 73 69 67 6e 2e  |h. www.zxdesign.|
000002f0  69 6e 66 6f 0a 00 35 c5  b7 bf 56 4c 0c 6d ec a6  |info..5...VL.m..|
00000300  94 c5 b4 80 71 97 6c 03  b5 86 53 54 4d 50 01 01  |....q.l...STMP..|
00000310  00 00 40 88 00 00 09 00  00 00 00 00 00 00 01 00  |..@.............|
00000320  07 00 06 00 01 00 01 00  36 61 09 0a 80 2d 80 b2  |........6a...-..|
00000330  a2 e2 56 c2 01 00 99 09  00 00 99 09 00 00 99 09  |..V.............|
00000340  00 00 99 09 00 00 99 09  00 00 99 09 00 00 00 00  |................|
00000350  cd 62 12 00 0e c5 00 00  00 00 0a 00 00 00 34 88  |.b............4.|
00000360  00 00 01 00 00 00 32 6d  3f 60 db db 73 48 e2 7e  |......2m?`..sH.~|
00000370  73 e6 61 50 8a 56 79 56  d1 87 c3 22 0d 2c 84 1c  |s.aP.VyV...".,..|
00000380  fa 0c 59 f8 d3 31 e1 6b  76 6a 55 3c 76 97 7e 80  |..Y..1.kvjU<v.~.|
00000390  59 7e db 7d c5 99 1e bc  af 3c 89 3e 62 47 24 fd  |Y~.}.....<.>bG$.|
000003a0  d3 5e 08 35 a9 d8 ce 1b  03 07 ad 20 23 0f 19 b6  |.^.5....... #...|
000003b0  2f f3 11 6d f9 08 6a 56  89 1c 24 17 ea 2f 7f fd  |/..m..jV..$../..|
000003c0  c6 9a 7f e3 a2 a9 9f d8  69 8f b5 83 e5 9c 7f b4  |........i.......|
000003d0  91 3b 87 4a 6d ab 4d cb  c7 9f e5 45 87 44 9b 19  |.;.Jm.M....E.D..|
000003e0  e4 1a 15 64 b8 eb 05 62  7e c3 62 d9 31 54 7a 9d  |...d...b~.b.1Tz.|
000003f0  a4 b4 be 10 d4 60 d3 d7  35 6d fd 42 a8 f6 2b 1f  |.....`..5m.B..+.|
00000400  1f f6 9b 62 96 26 28 29  e6 e1 7e ed ec d5 45 c3  |...b.&()..~...E.|

This looks like a chunked file format; we can see two chunk headers:

  1. SMEM
  2. STMP

The STMP is a big clue of where the file format comes from and we can find a binary definition at rockbox of all places! There’s a copy of the tool, elf2sb that can be found on github. There’s also documentation at

This defines the STMP chunk, but not the SMEM, so we’ll take a guess ourselves:

Start Length Type Value Use
0x00 4 u32 “SMEM” Magic
0x04 4 u32 0x000000fb Length of SMEM chunk
0x08 4 u32 0x00000200 Unknown
0x0C 4 u32 0x00000000 Unknown
0x10 4 u32 0x00000004 Unknown
0x14 4 u32 0x00020001 Unknown
0x18 4 u32 0x00000140 Version String (1.64)
0x1C 4 u32 0x07df0909 Timestamp (2015-09-09)
0x20 4 u32 0x00000443 Unknown
0x24 210 uchar “The Sinclair ZX Spectrum Vega …” Copyright String

Using the rockbox link we can decode the STMP chunk, starting at 0x2f6; where the word block is used below, these refer to 16 bytes blocks.

Start Length Type Value Use
0x00 20 uchar 35c5b7bf564c0c6deca694c5b48
SHA1(STMP chunk)
Also IV for encryption
0x14 4 u32 “STMP” Magic
0x18 1 u8 0x01 Major version of format
0x19 1 u8 0x01 Minor version of format
0x1A 2 u16 0x0000 Flags
0x1C 4 u32 0x00008840 Image size in blocks

(0x8840 x 16 = 0x88400)

0x20 4 u32 0x00009000 Offset to first boot tag in blocks

(0x9000 x 16 = 0x90000)

0x24 4 u32 0x00000000 First bootable section
0x28 2 u16 0x0001 Number of encryption keys
0x2A 2 u16 0x0007 Start block for key dictionary

(7 x 16 = 0x70)

0x2C 2 u16 0x0006 Size of header in blocks

(6 x 16 = 0x60)

0x2E 2 u16 0x0001 Number of sections headers
0x30 2 u16 0x0001 Size of chunk headers in blocks
0x32 2 uchar 0x6136 Padding
0x34 4 u32 0x2d800a09 Second signature
0x38 8 u64 0x0001c256e2a2b280 Creation time in μs since 2000
0x40 4 u32 0x00000999 Product major version
0x44 4 u32 0x00000999 Product minor version
0x48 4 u32 0x00000999 Product sub version
0x4C 4 u32 0x00000999 Component major version
0x50 4 u32 0x00000999 Component minor version
0x54 4 u32 0x00000999 Component sub version
0x58 2 u16 0x0000 Drive tag
0x5A 6 uchar 0xcd6212000ec5 Padding

Even though there are some things that look wrong in the header we can check that it is correct by working out the SHA1 value of the header bytes from 0x14 to 0x5F to show that they match the hash in the header:

Which they do!

Then we have the chunk header:

00000350  cd 62 12 00 0e c5 00 00  00 00 0a 00 00 00 34 88  |.b............4.|
00000360  00 00 01 00 00 00 32 6d  3f 60 db db 73 48 e2 7e  |......2m?`..sH.~|
Start Length Type Value Use
0x00 4 u32 0x00000000 chunk name
0x04 4 u32 0x0000000a chunk offset in blocks

(0x0a * 16 = 0xa0)

0x08 4 u32 0x00008834 chunk size in blocks

(0x8834 * 16 = 0x88340)

0x0C 4 u32 0x00000001 Flags; bit 0 is bootable.

If you’ve kept track of the sizes you can see they’re all roughly correct indicating that we’re interpreting this correctly.

After this we have the DEK block – a block of Data Encryption Keys; each of the DEKs are encrypted with the KEK – the Key Encryption Key.

And that’s our problem – we don’t know the KEK. Bugger.

According to the Rockbox documentation it uses AES-128 in CBC mode to encrypt the DEK with a KEK passed on the command line.

If we follow on the with the Rockbox documentation the DEK block should start at block 7 (i.e. 0x70 from the header), which is straight after the chunk header. This consists of two blocks:

00000360  00 00 01 00 00 00 32 6d  3f 60 db db 73 48 e2 7e  |......2m?`..sH.~|
00000370  73 e6 61 50 8a 56 79 56  d1 87 c3 22 0d 2c 84 1c  |s.aP.VyV...".,..|
00000380  fa 0c 59 f8 d3 31 e1 6b  76 6a 55 3c 76 97 7e 80  |..Y..1.kvjU<v.~.|

Which translates into:

Start Length Type Value Use
0x00 16 uchar 326d3f60dbdb7348e27e73e661508a56 Encrypted CBC-MAC of the header
0x10 16 uchar 7956d187c3220d2c841cfa0c59f8d331 Encrypted DEK

We can confirm what we know using the sbtool program found on the eewiki github:

We’ve hit a wall with the firmware here, unless we can brute force the key (128 bits – that’s 3.4028236692093846346337460743177e+38 different combinations – that’s not happening any times soon) or we can look into using side channel mechanisms (maybe more on that later).

It’s time to look at the hardware.