The first copy protection scheme I cracked on my Apple ][ was that of Zork. It wasn’t that difficult, the data prologue bits were changed from D5 AA AD to D5 AA BC. Copying the disk involved patching the standard copy program COPYA and then editing the disk so that it could read itself.
But today I’m interested in the IBM PC version of Zork. Like the Apple ][ version, the disk could not be copied by the standard tool (in this case diskcopy) but Copy II PC worked just fine. Interestingly, the disk allowed you to make one copy of the game for backup purposes.
The weak point of copy protected disks is that the standard BIOS must be able to read the first bit of the disk. This code is then executed to read the “uncopyable” part of the disk. Anyone can read the first sector, disassemble it, and work out how to read the rest of the disk. Granted, this process is sometimes extremely difficult. Not so in the case of Zork.
On the IBM PC, the BIOS reads track 0 sector 1 into segment 0, offset 7C00 and jumps to it.
0000:7C00 FA CLI Disable interrupts
The first thing after disabling the interrupts (which is pretty standard) is to change the pointer (at 0000:0078) to the disk controller parameter block to point at the first location after this actual code, 0000:7C79.
0000:7C01 2BC0 SUB AX,AX AX=0 0000:7C03 8ED8 MOV DS,AX DS=AX=0 0000:7C05 BB7800 MOV BX,0078 DISK_POINTER, points to parameter block 0000:7C08 B97900 MOV CX,0079 0000:7C0B BAC007 MOV DX,07C0 0000:7C0E 8B37 MOV SI,[BX] Save current pointer at 0000:0078 in SI 0000:7C10 8B7F02 MOV DI,[BX+02] Save current pointer at 0000:007A in DI 0000:7C13 890F MOV [BX],CX 0000:0078 = 0079 0000:7C15 895702 MOV [BX+02],DX 0000:007A = 07C0 0000:7C18 8CC8 MOV AX,CS 0000:7C1A 8ED8 MOV DS,AX Set DS = CS ; Set stack to 0000:7C00 Fairly standard 0000:7C1C BA0000 MOV DX,0000 0000:7C1F 8ED2 MOV SS,DX 0000:7C21 BB007C MOV BX,7C00 0000:7C24 8BE3 MOV SP,BX 0000:7C26 FB STI Enable interrupts 0000:7C27 B86000 MOV AX,0060 0000:7C2A 8ED8 MOV DS,AX Set DS and ES to 0x0060 0000:7C2C 8EC0 MOV ES,AX ; Reset disk drive (AH=0) 0000:7C2E 2BC0 SUB AX,AX AX=0 0000:7C30 2BD2 SUB DX,DX DX=0 0000:7C32 CD13 INT 13 0000:7C34 BA0300 MOV DX,0003 DX=3 0000:7C37 2BDB SUB BX,BX BX=0 0000:7C39 B501 MOV CH,01 CH=1 0000:7C3B 52 PUSH DX Save DX 0000:7C3C B101 MOV CL,01 CL=1 0000:7C3E 51 PUSH CX Save CX (= 0101) 0000:7C3F 2BD2 SUB DX,DX DX=0 0000:7C41 B80402 MOV AX,0204 AH=2, AL=4 0000:7C44 CD13 INT 13
INT 13 with AH=2 reads AL (=4) sectors from cylinder CH (=1), sector CL (=1) of head/drive DX (0/0) into ES:BX (0060:0000). This read uses the new parameter block further down, which differs from the standard parameter block in that it specifies 1024 byte sectors, four sectors per track.
0000:7C46 721C JB 7C64 If INT13 returns error, print "ILL" and halt 0000:7C48 59 POP CX Restore CX (I don't think INT13 corrupts CX, so I don't know why) 0000:7C49 FEC5 INC CH Next cylinder 0000:7C4B 81C30010 ADD BX,1000 Move data pointer 4 kb ahead 0000:7C4F 5A POP DX Track (cylinder) counter, started at 3... 0000:7C50 4A DEC DX ...2...1... 0000:7C51 75E8 JNZ 7C3B Loop back unless 0
So we have now read three tracks of four sectors of 1024 bytes each into 0060:0000, 0060:1000 and 0060:2000.
; Restore disk controller parameter block pointer 0000:7C53 2BC0 SUB AX,AX AX=0 0000:7C55 8ED8 MOV DS,AX DX=0 0000:7C57 BB7800 MOV BX,0078 BX=0078 0000:7C5A 8937 MOV [BX],SI 0000:7C5C 897F02 MOV [BX+02],DI 0000:7C5F 06 PUSH ES 0000:7C60 2BC0 SUB AX,AX 0000:7C62 50 PUSH AX 0000:7C63 CB RETF POP IP = 0, POP CS = ES
RETF pulls an instruction pointer and code segment from the stack, and execution moves there (0060:0000).
; ERROR 0000:7C64 2BDB SUB BX,BX BX=0 0000:7C66 B049 MOV AL,49 0000:7C68 B40E MOV AH,0E 0000:7C6A CD10 INT 10 AH=0E, teletype output, 49 "I" 0000:7C6C B04C MOV AL,4C 0000:7C6E B40E MOV AH,0E 0000:7C70 CD10 INT 10 "L" 0000:7C72 B04C MOV AL,4C 0000:7C74 B40E MOV AH,0E 0000:7C76 CD10 INT 10 "L" 0000:7C78 F4 HLT 0000:7C79 CF 02 25 03 04 2A FF 50 F6 19 04
This is the modified parameter block, 03 = 1024 bytes/sector (normally 2 = 512 bytes/sector) , 04 = 4 sectors per track (normally 8).
Of course MSDOS diskcopy barfs at 1024 byte sectors and there’s your copy protection.
ZORKTOOLS will rewrite the 4×1024 byte sectors to 8×512 byte sectors and patch the bootloader to match.