------------------Ankh----------------- A 4am crack 2017-08-19 --------------------------------------- Name: Ankh Genre: adventure Year: 1983 Credits: David von Brink Publisher: Datamost Platform: Apple ][+ or later Media: single-sided 5.25-inch floppy OS: custom Similar cracks: #1357 Mr. Robot and His Robot Factory ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy displays an initial (graphical) screen then hangs with the disk motor on Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer bootloader is custom (haha it sets the reset vector to $C65C then jumps to it to read the rest of track 0, that's hilarious) no disk catalog on any track no DOS everything is custom Why didn't any of my copies work? I'm guessing there's a runtime protection check somewhere, and it's looping forever to find its initial condition (whatever that is). Next steps: 1. Find the check 2. Disable the check 3. Declare victory (*) (*) go to the gym ~ Chapter 1 How Do I Read Thee? Let Me Count The Ways One thing that all protection checks have in common is they need to turn on the drive motor by accessing a specific address in the $C0xx range. For slot 6, it's $C0E9, but to allow disks to boot from any slot, developers usually use code like this: LDX LDA $C089,X There's nothing that says where the slot number has to be, although the disk controller ROM routine uses zero page $2B and lots of disks just reuse that. There's also nothing that says you have to use the X-register as the index, or that you must use the accumulator as the load register, or even that you need to use an "LDA" instuction. ("STA" works just as well. I've even seen "CMP"!) Also, since developers don't actually want people finding their protection- related code, they may try to encrypt it or obfuscate it to prevent people from finding it. But eventually, the code must exist and the code must run, and it must run on my machine, and I have the final say on what my machine does or does not do. But sometimes you get lucky. Turning to my trusty Disk Fixer sector editor, I search the non-working copy for "BD 89 C0", which is the opcode sequence for "LDA $C089,X". And I find it right away on track 0. --v-- T00,S0F ----------- DISASSEMBLY MODE ---------- 0000:A2 07 LDX #$07 0002:A0 00 LDY #$00 0004:A9 00 LDA #$00 ; check if slot peripheral space is ; writeable, by writing a #$00 to the ; first byte of each slot and checking ; if it worked 0006:8D 00 C7 STA $C700 0009:AD 00 C7 LDA $C700 000C:D0 06 BNE $0014 000E:88 DEY 000F:D0 F3 BNE $0004 ; if that worked, jump to The Badlands ; to wipe main memory and reboot 0011:4C D5 B6 JMP $B6D5 ; check all 7 slots 0014:CE 08 B6 DEC $B608 0017:CE 0B B6 DEC $B60B 001A:CA DEX 001B:D0 E5 BNE $0002 (Fun fact: this check fails in some modern emulators, because they emulate non-existent cards by filling the slot ROM with #$00 bytes which triggers a false positive here.) (Fun fact #2: I don't know what this check is for. One theory was that it was protection against hardware NMI cards like the Wildcard which allowed users to unconditionally break into the monitor and save the contents of memory despite the program's best efforts to block it. But a friend tested this code on a real Apple II with a Wildcard Plus card installed, and it did not reboot. So either that card specifically works around this check, or the theory was incorrect. Mysteries abound, even 30+ years later!) ~ Chapter 2 How Do I (M)align Thee? Let Me Count The Ways Continuing from $B61D... --v-- 001D:A2 FF LDX #$FF 001F:86 04 STX $04 0021:E8 INX 0022:86 01 STX $01 0024:86 03 STX $03 ; munge reset vector 0026:8E F2 03 STX $03F2 ; The self-modifying code in the slot ; checking loop tells me that this ; sector is loaded at $B600. Looking at ; it in a sector editor, it is pretty ; clear that there's an RWTS parameter ; table starting at $B6C0. Which means ; we're setting up a read (command $01) ; of track $01, sector $00. 0029:A2 01 LDX #$01 002B:8E C4 B6 STX $B6C4 002E:A9 01 LDA #$01 0030:8D CC B6 STA $B6CC ; Call the RWTS (not shown) to read ; T01,S00. 0033:20 B9 B6 JSR $B6B9 ; increment the track 0036:EE C4 B6 INC $B6C4 ; new RWTS command = 0 (seek), not read 0039:A9 00 LDA #$00 003B:8D CC B6 STA $B6CC ; seek to track 2 003E:20 B9 B6 JSR $B6B9 ; wait 0041:A9 44 LDA #$44 0043:20 A8 FC JSR $FCA8 ; turn on slot 6 drive motor manually 0046:AE F8 05 LDX $05F8 0049:9D 89 C0 STA $C089,X ; A is 0 coming out of the WAIT routine ; at $FCA8 004C:85 00 STA $00 ; increment Death Counter 004E:E6 00 INC $00 0050:F0 33 BEQ $0085 ; find the next address prologue 0052:BD 8C C0 LDA $C08C,X 0055:10 FB BPL $0052 0057:C9 D5 CMP #$D5 ; loop back if we didn't find a $D5 ; nibble (increments Death Counter, so ; kind of important that we find one ; sooner rather than later) 0059:D0 F3 BNE $004E 005B:BD 8C C0 LDA $C08C,X 005E:10 FB BPL $005B 0060:C9 AA CMP #$AA 0062:D0 F3 BNE $0057 0064:A0 02 LDY #$02 0066:BD 8C C0 LDA $C08C,X 0069:10 FB BPL $0066 006B:C9 96 CMP #$96 006D:D0 E8 BNE $0057 ; get the physical sector number (loop ; burns through and discards the first ; two 4-and-4-encoded values in the ; address field, which are the disk ; volume number and the track number, ; then exits after parsing the sector ; number into zp$02) 006F:BD 8C C0 LDA $C08C,X 0072:10 FB BPL $006F 0074:2A ROL 0075:85 02 STA $02 0077:BD 8C C0 LDA $C08C,X 007A:10 FB BPL $0077 007C:25 02 AND $02 007E:88 DEY 007F:10 EE BPL $006F ; is it sector 7? 0081:C9 07 CMP #$07 ; yes -> branch forward 0083:F0 04 BEQ $0089 ; no -> set a flag 0085:A2 FF LDX #$FF 0087:86 01 STX $01 ; Execution continues here regardless ; of which sector we found. Keep exact ; track of how long to took to find the ; next address prologue. Maximum number ; of nibbles ends up in zp$03; minimum ; number in zp$04. 0089:A5 00 LDA $00 008B:C5 03 CMP $03 008D:90 02 BCC $0091 008F:85 03 STA $03 0091:C5 04 CMP $04 0093:B0 02 BCS $0097 0095:85 04 STA $04 ; Loop back and do this all again for ; tracks $02-$10. 0097:AD C4 B6 LDA $B6C4 009A:C9 11 CMP #$11 009C:D0 90 BNE $002E ; Calculate the difference between the ; minimum and maximum number of nibbles ; before each track's next address ; prologue. 009E:A5 03 LDA $03 00A0:38 SEC 00A1:E5 04 SBC $04 00A3:C9 20 CMP #$20 ; If the difference is less than #$20, ; branch forward. 00A5:90 04 BCC $00AB ; Otherwise, set a flag. 00A7:A2 FF LDX #$FF 00A9:86 01 STX $01 ; Execution continues here regardless ; of the nibble count difference. ; Turn off the slot 6 drive motor. 00AB:AE F8 05 LDX $05F8 00AE:9D 88 C0 STA $C088,X ; Check that flag. 00B1:A5 01 LDA $01 ; If non-zero, some error occurred. ; Either we found the wrong sector on ; one of the tracks, or the difference ; in nibbles-before-address-prologue ; between tracks was too large. 00B3:F0 03 BEQ $00B8 ; Either way, wipe memory and reboot. 00B5:4C D5 B6 JMP $B6D5 ; Otherwise exit gracefully. 00B8:60 RTS ~ Chapter 3 How Do I Patch Thee? Let Me Count The Bytes Whew, that's quite a strict protection! It require that tracks $01-$10 be physically aligned, relative to each other, so that "blindly" seeking to the next track always finds sector $07. But that's not all! It literally counts the number of nibbles it takes to find the start of sector $07 (after a "blind" seek onto each track) and demands that it not vary too much between tracks -- no more than $20 nibbles difference between the minimum and maximum across 15 tracks! This would be very difficult to duplicate with a bit copier, even with the "synchronize tracks" option. In fact, I suspect this protection scheme was specifically designed to test the limits of that option. As there is no sync sensor on standard Apple II floppy drives, the "synchronize tracks" option had to rely on seeking back to track 0, finding a known pattern (like the start of T00,S00), then forward to the next track to write. This got less and less precise as the track number increased, because it spent more time seeking the drive head between tracks, with only approximate control over how far the disk would spin before arriving at the destination track. The entire design of DOS (and ProDOS, Pascal, and everything) compensates for the fact that you never quite know where you're going to end up on a track when you get there. This copy protection weaponizes that imprecision. Anyway, it's a one-byte patch to change the "JMP $B6D5" (which reboots) to "BIT $B6D5" (which does nothing). After all that work, it'll just fall through to the success path after failing and start the game anyway. T00,S0F,$B5: 4C -> 2C Oh, and one more byte so it doesn't reboot if it finds a #$00 in the first byte of a slot ROM. I would welcome any feedback on what that was supposed to protect against. T00,S0F,$11: 4C -> 2C Quod erat liberandum. ~ Epilogue Some miscellaneous notes that don't really fit anywhere else: - You can't simply put an "RTS" at the beginning of this protection check, because the bootloader intentionally lies about the current track after the protection check returns. The disk's custom RWTS doesn't check if it's on the right track, so if the protection check never runs, it loads the rest of the game code from the wrong track and crashes. - There is a second protection check on track $0C, sector $04 which is never loaded. As far as we can tell (and qkumba graciously volunteered to play test the ENTIRE GAME to verify this), the game is loaded into memory by the time you see the main title screen, and it never reads or writes from disk after that. - The only widely circulated version of this game was an unfinished beta that said "THIS IS A PRELIMINARY VERSION" during the title sequence. All the hints and maps and documentation that were distributed by pirates back in the day were from this stolen beta. There were no known cracks of the retail version until a few years ago. This is the retail version. --------------------------------------- A 4am crack No. 1367 ------------------EOF------------------