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
   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

FOR IC=0 TO 2 STEP 2
 REM Assemble at &12000
 P%=&12000
 [OPTIC
 ; base address
 .base
  dcd &0
 .doit
  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
 .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}
 ]
NEXT

REM load encoded file
addr=&13000
*LOAD 3DV_Handle 13000

REM decode 30 counts of the encoding cycle
FOR i=1 TO 30
 !base=addr
 A%=0:CALL doit
 addr=addr+&34
 PRINT ~addr
NEXT

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.

Part 2 is here.