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.