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:
- It was hidden as an obfuscated executable
- It deobfuscated itself, installed itself and installed itself in module memory (using the OS_Module SWI)
- It then read a sector from the floppy disk drive directly (using ADFS_DiscOp)
- 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.
- 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:
- Identify the Key module and encoded files
- Deobfuscate the module and amend it to not reset the computer
- Run the module
- Load the encoded files into memory
- Kill the module
- Save the now decoded file from memory
- 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:
Looking into the application directory (using a shift double-click) shows its contents:
Loading the !Run
file into the builtin !Edit text editor shows us what it’s doing:
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:
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:
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:
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:
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.