---------------Zork Quest-------------- A 4am crack 2016-09-18 -------------------. updated 2017-12-09 |___________________ Name: Zork Quest: Assault on Egreth Castle Genre: adventure Year: 1988 Authors: Tom Snyder Productions Publisher: Infocom Platform: Apple ][+ or later (64K) Media: 2 single-sided 5.25-inch disks OS: custom Previous cracks: The Necromancer/Fc ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy hangs with the drive motor running Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious nothing on track $23 hi-res disk scan shows no usage of half-tracks or quarter-tracks Disk Fixer custom bootloader on track $00 no sign of DOS, ProDOS, Pascal, or anything familiar whatsoever Why didn't any of my copies work? An excellent question. Based on the lack of any half-tracks or quarter- tracks, I'd guess the protection is procedural, not structural. i.e. There's some code explicitly checking whether the disk is original. Next steps: 1. Search for the runtime protection check (the "easy" way) 2. If that fails, trace the boot until I find it (the "hard" way) 3. If that fails, I dunno -- go feed the ducks or something? ~ Chapter 1 In Which We Get Lucky 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 you have to use the X-register as the index or the accumulator as the load register. But most RWTS code does, out of convention I suppose (or fear of messing up such low-level code in subtle ways). Also, since developers don't actually want people finding their protection- related code, they may try to encrypt it or obfuscate it on disk, in memory, or both. 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". [Disk Fixer] ["F"ind] ["H"ex] ["BD 89 C0"] --v-- ------------- DISK SEARCH ------------- $03/$0E-$02 $04/$03-$26 PRESS [RETURN] --^-- The match on track $04 seems legitimate (based on the nearly presence of an "LDA $C08A,X" to select drive 1 or 2 -- something that protection checks rarely bother to support). But the match on track $03 looks very... familiar. ; hard-code slot 6 -- very common for ; protection schemes (even if the real ; RWTS on the disk supports booting ; from any slot) 0000:A2 60 LDX #$60 ; turn on the drive 0002:BD 89 C0 LDA $C089,X ; some sort of Death Counter 0005:A9 56 LDA #$56 0007:85 F1 STA $F1 0009:A9 08 LDA #$08 000B:C6 F0 DEC $F0 000D:D0 04 BNE $0013 000F:C6 F1 DEC $F1 ; If the Death Counter hits zero, that ; would be bad. Which part of "Death ; Counter" sounded good to you, anyway? 0011:F0 3C BEQ $004F ; look for an #$FB nibble 0013:BC 8C C0 LDY $C08C,X 0016:10 FB BPL $0013 0018:C0 FB CPY #$FB 001A:D0 ED BNE $0009 001C:F0 00 BEQ $001E ; kill a few cycles (not pointless, ; because the disk spins independently ; of the CPU, so all of these low-level ; disk reads are highly time-sensitive) 001E:EA NOP 001F:EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 0020:BC 8C C0 LDY $C08C,X ; do a compare to set or clear the ; carry bit (among other things, but ; it's the carry bit we care about) 0023:C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 0025:2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 0026:B0 0B BCS $0033 ; next nibble needs to be #$FF 0028:BC 8C C0 LDY $C08C,X 002B:10 FB BPL $0028 ; ...otherwise we start over 002D:C0 FF CPY #$FF 002F:D0 D8 BNE $0009 ; loop back to get next nibble (this is ; an unconditional branch) 0031:F0 EB BEQ $001E ; execution continues here (from the ; "BCS" instruction 6 lines up) -- ; get another nibble 0033:BC 8C C0 LDY $C08C,X 0036:10 FB BPL $0033 ; stash it in zero page 0038:84 F0 STY $F0 ; if the accumulator is anything but ; the bit pattern %00001010, start over 003A:C9 0A CMP #$0A 003C:D0 CB BNE $0009 ; get one more nibble 003E:BD 8C C0 LDA $C08C,X 0041:10 FB BPL $003E ; more bit twiddling (apparently this ; and the previous nibble combine to ; form a single value, 4-4-encoded) 0043:38 SEC 0044:2A ROL 0045:25 F0 AND $F0 ; that 4-4-encoded value should be #$FF 0047:49 FF EOR #$FF ; branch if it isn't the expected value 0049:D0 04 BNE $004F ; (success path falls through to here) ; turn off drive motor manually 004B:DD 88 C0 CMP $C088,X ; and exit gracefully to the caller 004E:60 RTS ; crash 004F:00 BRK I got lost several times trying to follow this routine. I think the easiest way to explain it is to show the difference between the original disk and my non-working copy. ~ Chapter 2 In Which We Get Visual Here is the original disk, as seen by the Copy II+ nibble editor. Nibbles with extra "0" bits (timing bits) after them are displayed in inverse on an original machine, marked here with a "+" after the nibble. --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 00 START: 30EE LENGTH: 180C 3240: B4 D3 DD 96 B2 FB 9E E7 VIEW 3248: E5 D9 CF B2 D7 D7 F7 F7 3250: F7 F7 B2 AD 97 EA FA BB 3258: A7 BA B7 FA DE AA EB FB 3260: BF FD BB FB+FF FF+FF FF+ 3268: FF+FF+FF+D5 AA 96 FF FE 3270: AA AA AA AB FF FF DE AA 3278: EB FF+FF+FF+FF+FF+FF+D5 3280: AA AD FA AE 9D AF B2 9B --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- It's easy to understand why a simple sector copy failed. The sequence that this code is looking for starts at offset $3263, which is between the end of one sector and the beginning of the next. (The data epilogue is at $325C; the next address prologue is at $326B.) Sector copiers discard everything between those delimiters and rebuild the track with a default pattern of sync bytes. That pattern doesn't include an $FB nibble, so the nibble check fails. But the EDD bit copy also failed. Here is the original disk's pattern at offset $3263: - #$FB + timing bit - #$FF - #$FF + timing bit - #$FF - #$FF + timing bit And here is what the same part of the track looks like on my failed EDD copy: --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 00 START: 30EE LENGTH: 180C 3240: B4 D3 DD 96 B2 FB 9E E7 VIEW 3248: E5 D9 CF B2 D7 D7 F7 F7 3250: F7 F7 B2 AD 97 EA FA BB 3258: A7 BA B7 FA DE AA EB FB 3260: BF FD BB FB+FF FF FF+FF+ 3268: FF+FF+FF+D5 AA 96 FF FE 3270: AA AA AA AB FF FF DE AA 3278: EB FF+FF+FF+FF+FF+FF+D5 3280: AA AD FA AE 9D AF B2 9B --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- A subtle difference! The sequence at offset $3263 now looks like this: - #$FB + timing bit - #$FF - #$FF - #$FF + timing bit - #$FF + timing bit This code is looking for #$FF nibbles with an alternating pattern of timing bit, no timing bit, timing bit, no timing bit. It doesn't find that on the bit copy, so it knows it's not running on an original disk. ~ Chapter 3 In Which We Think About Automation Since the release of Passport https://archive.org/details/Passport4am I've changed the way I think about runtime protection checks like this. I used to think "what's the easiest way to defeat the code on this disk?" Now I think "what does the code on this disk have in common with code I've seen on other disks, and could I detect and defeat it automatically?" I've seen this protection check on many other disks, but not always in this form. This check appears to be a self- contained subroutine. I could probably just put an "RTS" at the very beginning of the sector to bypass the check. [Update: yes, this works.] But this check isn't always self- contained. What does it have in common with other disks? Let's revisit the code with this question in mind. According to my records, the last disk I cracked with this same check was #684 Math Shop v1986-10-27 That disk was ProDOS-based, although the actual magic bit sequence was still on track 0. But that's not guaranteed; the caller could put the drive head on any track before calling the protection check. This check starts at offset $00 within the sector, but there are no absolute jumps within the code; it's completely relocatable. On Math Shop, the code started at offset $9A. ; varies (Math Shop used ProDOS globals ; to determine the boot slot, instead ; of hard-coding it) 0000:A2 60 LDX #$60 0002:BD 89 C0 LDA $C089,X ; varies (Math Shop uses different zero ; page addresses) 0005:A9 56 LDA #$56 0007:85 F1 STA $F1 0009:A9 08 LDA #$08 000B:C6 F0 DEC $F0 000D:D0 04 BNE $0013 000F:C6 F1 DEC $F1 ; identical (Math Shop starts at a ; different offset, but the length of ; the branch -- #$3C -- is identical) 0011:F0 3C BEQ $004F ; identical (including branch lengths) 0013:BC 8C C0 LDY $C08C,X 0016:10 FB BPL $0013 0018:C0 FB CPY #$FB 001A:D0 ED BNE $0009 001C:F0 00 BEQ $001E 001E:EA NOP 001F:EA NOP 0020:BC 8C C0 LDY $C08C,X 0023:C0 08 CPY #$08 0025:2A ROL 0026:B0 0B BCS $0033 0028:BC 8C C0 LDY $C08C,X 002B:10 FB BPL $0028 002D:C0 FF CPY #$FF 002F:D0 D8 BNE $0009 0031:F0 EB BEQ $001E 0033:BC 8C C0 LDY $C08C,X 0036:10 FB BPL $0033 ; varies (Math Shop uses a different ; zero page address) 0038:84 F0 STY $F0 ; identical (including branch lengths) 003A:C9 0A CMP #$0A 003C:D0 CB BNE $0009 003E:BD 8C C0 LDA $C08C,X 0041:10 FB BPL $003E 0043:38 SEC 0044:2A ROL ; varies (address again) 0045:25 F0 AND $F0 ; identical 0047:49 FF EOR #$FF ; varies (Math Shop uses a "BEQ" to ; branch to the failure path -- the ; exact opposite logic as this disk!) 0049:D0 04 BNE $004F Well, that's upsetting. Right up until the last line, I was hopeful that I could automate this. But this disk demands a 4-4-encoded value of #$FF immediately after the magic bit sequence, and Math Shop demands any value except #$FF -- the exact opposite condition! Maybe I can distinguish between them based on the branch instruction on the last line? More research is clearly required. In the meantime, I have a working patch for this disk: putting an "RTS" at the beginning of the protection check. T03,S0E,$00: A2 -> 60 ]PR#6 ...works... To be continued... Quod erat liberandum. ~ Changelog 2017-12-09 - fixed minor data corruption in unused sector on disk 1 (T1E,S05) 2016-09-18 - initial release --------------------------------------- A 4am crack No. 843 ------------------EOF------------------