Reverse Engineering Stranded

It’s time to start reversing another text adventure. For this one I wanted to go back to one I remember playing when I was a sprog: Stranded, published on the BBC Micro by Superior Software and by English Software on other platforms.

It isn’t the best adventure, but I remember it, mainly because it was one of the few BBC text adventures with some graphics.

I’m starting this from the beginning to demonstrate exactly how I go about reversing adventures.


The first step is to look at the files and gain an idea of how the data is structured. This involved a short play through to remind myself of the game and check for the complexity of the parser and size of descriptions etc.

Looking at the files, the disk image contains two real files:

  • Strand, the loader which loads in at &2500 and draws the intro image before *RUNing Strand1
  • STR1_2, which contains everything for the adventure

The adventure is unusual because it runs in screen MODE 1, this is a rarely used screen mode, because it provides a high (for the 1980s) resolution of 320 x 256; which, although is not the highest the BBC can handle, is higher than the C64 (320 x 200) or the Spectrum (256 x 192). There are two downsides to MODE 1: it only allows 4 colours and it uses a massive 20 KB of memory just for the screen.

This means that once the screen has been taken into account, there is only 12 KB of memory left on the Beeb. That’s 12 KB for the whole game, including graphics, code, descriptions and all the gubbins that the OS needs!

The file is close to that size: being &2c28 bytes in size: which is a tad over 11 KB. It loads into &1100, which is about as low as you can go with DFS. This causes some over-writing of screen memory. It then executes code from &3d00 (which is in screen memory) to move everything from &1100 to &400.

File analysis

I normally start this by looking from the hex dump of the file and looking for text, verbs etc.

At &380 are the OSCLI commands for saving the game. The save is useful because it tells us where the game flags and inventory will be in memory:


So the memory between &B2F and &B38 (or &72F to &738 in the file). This is rather small for an adventure, so if probably just the object locations and therefore the game may not have any flags. Using a debugger on a BBC emulator shows that this memory space does indeed store the object locations, with the default locations stored in &738

I can see verbs and nouns starting at &500:

Hex dump showing verbs and nouns
Hexdump of STR1_2

The list is unusual as it looks like whole commands are defined, with both words lock to four characters. We can see:


This may make it easier later to work out actions.

These are immediately followed, at &660, by the list of objects with NOUN names:


Once again, these are four characters in length.

After this, at &688 are the long forms of the object names, each separated by a &FF byte. Followed at &6E0 by the names of the directions (probably used for displaying).

The system messages and intro text can be found at &1580.

System Messages

We’re missing one important thing: the description locations, which means they’re compressed or obfuscated. Normally I’d expect to see a dictionary of words, but can’t. There is one clue though, at &760, for &40 bytes there is the string:

" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz12345'!()."

This looks like a lookup character table, it’s &40 bytes long, so this means that you only need 6 bits for each character.

At this point we can search through the text for known text patterns. We know that the description of the starting room is:

"You are standing on a barren plateau. Exits lead SOUTH EAST WEST"

If we assume that the string at &760 is a lookup table, we can manually plot out the start of the string to:

Y = &19
o = &29
u = &2f

A hex search for these bytes finds them at &f68, with a &FF separater; looks like this has legs! We can look for words, if we process the whole file through the lookup table, using a simple bit of python:

from future import print_function
dictionary=" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz12345'!().\n"


for x in data:
  if ord(x) == 0xff:
  char=ord(x) & 0x3f
  print(dictionary[char], end='')
  if (ord(x) & 0x40):
    print(" ", end='')

I’ve tuned this to start at &900, which is the start of the descriptions:

C:\Users\dave\Desktop\Stranded> | more
A B C D E barren plateau.
A B D E rocky mountain path.
A B D F top G E steep H.
I space shuttle J visible below.
A B D E ledge halfway down F
H face.

And here we can sell the room descriptions and messages. Note the A B C D E in the first description, this matches with the words in the room description. So we can assume the strings at 0xf68 are a dictionary of words for compression.

Looking at &900 in a hex editor, we can see those A B C D E all have bit 7 set:

C1 C2 C3 C4 C5 1C

So we can make an assumption of how the strings are created:

for byte in data:
  if byte == 0xff:
    # print the sentence
  else if byte & 0x80:
    # it's a dictionary word
    sentence+=dict[byte & 0x3f]
  else if byte & 0x40:
    # add a space to the end
    sentence+=" "
    # read from the table
    sentence+=table[byte & 0x3f]

A quick bit of python allows the extraction of all these strings, which show them to be not just the room descriptions, but also the messages.

1: You are standing on a barren plateau.
2: You are on a rocky mountain path.
3: You are on the top of a steep cliff.
A space shuttle is visible below.
4: You are on a ledge halfway down the
cliff face.
5: You are at the base of the cliff.
6: You hit the ground with a thud.

There are also some terrible spelling errors, including “vegitation” and “does’nt”.

An assumption must be made: as the descriptions are just messages, then somewhere there needs to be a matching of rooms to description. This will probably have the exits as well. The game supports six directions: N, S, E, W, U, D.

This means we’re looking for some data that will say room 1 has a description string of 1 and exits that match the following (I’m making an assumption that rooms start from 1):

  • SOUTH to room 3
  • EAST to room 2
  • WEST to room 42 (or a room with description 42)

And there’s a possibility at the start of the file:

01 00 00 03 03 02 02 03 04 3D FF 02 01 00 03 05
01 06 06 FF 03 02 00 03 01 01 C1 07 04 81 07 07

This springs out as:

01 description
00 picture
03 Flags, 0x2 = CLS
03 02 EAST to room 02
02 03 SOUTH to room 03
04 3D WEST to room 61
02 description
01 picture
00 03 unknown
05 01 UP to room 01
06 06 DOWN to room 06

This mostly works, but there’re some strange extra bytes on some entries, e.g. room 3 has:

03 02 00 03 01 01 C1 07 04 81 07 07

That C1 07 04 81 07 07 doesn’t match to anything and there are similar in other rooms. Messing with the value in an emulator doesn’t do anything exciting, so this may have to be something we do when we bring out the disassembler.

I’d recently moved to Ghidra as my primary disassembler, and as I was trying to get used to it, I used this to try and work out how stuff worked. So first off I loaded the STR1_2 binary into Ghidra and set it load where it was relocated (&0400) then I set a variable Start to the execution address at &3100 and then let Ghidra analyse from there.

At that point it took many hours of stepping through the code and trying to understand what the code meant.

The easiest way of doing this was to search for the OS calls, as they are easy to find. I spent a while entering the labels and then following references. The majority of OS calls are OSWRCH (OS Write Character) and OSASCI (OS ASCII, which is basically an OSWRCH that performs a linefeed if it sees a CR. There are a lot of them and most will be around messages.

It was easier to look for some of the other calls, especially OSWORD, this performs multiple tasks from a defined block, one of these tasks is to read keyboard data into a buffer. A reference search of OSWORD leads to two sources, where we have:

        1442 a2 50           LDX        #0x50
        1444 a0 00           LDY        #0x0
        1446 a9 00           LDA        #0x0
        1448 20 f1 ff        JSR        OSWORD


        1cf0 a9 09           LDA        #0x9
        1cf2 a2 51           LDX        #0x51
        1cf4 a0 00           LDY        #0x0
        1cf6 20 f1 ff        JSR        OSWORD

So OSWORD takes the type of operation in A (in this case 0x00 and 0x09) and a block defined at the address pointed to by the X and Y registers (in this case 0x5000 and 0x5100). OSWORD 0x09 is read pixel value, so is likely used in the floodfill. OSWORD 0x00 is read line – this is useful.

Then this continues in this route, but finding calls and then working out what the call does and working out the code. My disassembled version can be found on github at

Tracing this through allowed me to work out the data stored for the rooms. Each room consists of the following:

uchar description,
uchar image,
uchar unknown=0,
uchar flags=0 // 2 == room

Then there are a selection of destinations:

[verb] [room/message]

These can be preceded by a byte with bit 7 set which indicates an condition in bits 6 and 5:

  • bits 6 and 5 set: object not in room
  • bit 6 set: object in room
  • bit 5 set: object in inventory
  • bits 6 and 5 clear: object not in inventory

bits 0 – 4 contain an index into an object.

This is followed by the two bytes of [verb] [room/message]

To break down an example, at &0414 we have:

03 02 00 03 01 01 c1 07 04 81 07 07 ff

Which breaks into:

Message 03 "You are on the top of a steep cliff. A space shuttle is visible below."
Image 02
Padding 00
Flags 03
As C1 has bit 7 and 6 set, but bit 5 clear which is in room
OBJECT 01 (Parachute) inroom
GOTO 04 "You are on a ledge halfway down the cliff face."
As 81 has bit 7 set, but bit 5 and 6 clear then not inventory
OBJECT 01 (Parachute) not in inv
GOTO 07 "You are lost in the forest."

The final bit is the graphics. Playing through we can see that the graphics are drawn lines which are then filled. On the BBC drawing lines can be done through PLOT codes. The plot codes are sent through VDU codes, which are values less than &20 sent to OSWRCH.

Some mucking around lead to this code. I’ve renamed the branches to be sensible:

        1c30 48              PHA
        1c31 29 07           AND        #0x7
        1c33 aa              TAX
        1c34 68              PLA
        1c35 e0 04           CPX        #0x4
        1c37 b0 13           BCS        imageXGreater4
        1c39 29 20           AND        #0x20
        1c3b c9 00           CMP        #0x0
        1c3d f0 03           BEQ        graphicsCommand
        1c3f 20 4b 1d        JSR        SetGraphicsBGColour
        1c42 b1 70           LDA        (LookupVector),Y 
        1c44 e0 00           CPX        #0x0
        1c46 f0 0c           BEQ        graphicsDrawCommand
        1c48 e0 02           CPX        #0x2
        1c4a f0 0e           BEQ        graphicsMoveCommand
        1c4c e0 05           CPX        #0x5
        1c4e f0 23           BEQ        graphicsFloodFillCommand
        1c50 e0 07           CPX        #0x7
        1c52 f0 0c           BEQ        graphicsTextCommand

So, basically there’s each image has colours set. The background colour is set at the start, then there’s a selection of commands defined by the value of a byte, which works out as:

00 draw a line from current cursor to value
02 move to point
05 floodfill
07 plot a graphics character (defined separately)

The top two bits has the colour (0 – 3) for the line. As an experiment I wrote a simple BBC basic file to plot the graphics (minus the special graphics characters).

After working all this out I wrote a basic Python script to decode it to a JSON file. If theory this looks like the whole database, although I haven’t tried to “run” it in anger. This script along with my annotated disassembly can be found on my github.

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…