--------------Zoo Builder-------------- A 4am crack 2017-04-17 -------------------. updated 2017-12-09 |___________________ Name: Zoo Builder Genre: educational Year: 1987 Publisher: National Geographic Society Platform: Apple //e or later Media: double-sided 5.25-inch floppy OS: Pronto-DOS Previous cracks: none Similar cracks: #1140 Zoo Goer ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but the copy swings to a high track and hangs with the drive motor on Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer track 0 looks like Pronto-DOS loader, but it jumps to $B3C3 after loading the RWTS (instead of $9D84) Why didn't any of my copies work? I don't know. Not a lot to go on yet. Presumably if there was some structural protection I would have noticed read errors. Hanging with the drive motor on could mean anything. Next steps: 1. Search for common markers of a runtime protection check 2. If that fails, trace the boot 3. If that fails, I dunno, go feed the ducks or something? ~ Chapter 1 In Which The Ducks Will Have To Wait Turning to my trusty Disk Fixer sector editor, I search for "BD 89 C0", a common instruction to turn on the disk drive motor. It finds several matches, but nothing that looks like the start of a protection check. So we get to start at the beginning. T00,S00 is a standard DOS 3.3-style bootloader. It loads most of the rest of track 0 into $B600+ and jumps to $B700, stored on T00,S01, to load the rest of the disk. --v-- T00,S01 ----------- DISASSEMBLY MODE ---------- 0000:8E E9 B7 STX $B7E9 0003:8E F7 B7 STX $B7F7 0006:A9 01 LDA #$01 0008:8D F8 B7 STA $B7F8 000B:8D EA B7 STA $B7EA 000E:AD E0 B7 LDA $B7E0 0011:8D E1 B7 STA $B7E1 ; start reading DOS on T02,S00 -- looks ; like Pronto-DOS 0014:A9 02 LDA #$02 0016:8D EC B7 STA $B7EC 0019:A9 00 LDA #$00 001B:8D ED B7 STA $B7ED ; $B7E7 is $B4, so the first sector is ; read into $B300 001E:AC E7 B7 LDY $B7E7 0021:88 DEY 0022:8C F1 B7 STY $B7F1 ; RWTS read command 0025:A9 01 LDA #$01 0027:8D F4 B7 STA $B7F4 ; set up globals (normal) 002A:8A TXA 002B:4A LSR 002C:4A LSR 002D:4A LSR 002E:4A LSR 002F:AA TAX 0030:A9 00 LDA #$00 0032:9D F8 04 STA $04F8,X 0035:9D 78 04 STA $0478,X ; read DOS into memory (normal) 0038:20 93 B7 JSR $B793 ; reset stack (normal) 003B:A2 FF LDX #$FF 003D:9A TXS 003E:8E EB B7 STX $B7EB ; machine initialization (not shown, ; but this is definitely Pronto-DOS -- ; other DOS variants put this code ; somewhere else) 0041:20 69 BA JSR $BA69 0044:20 89 FE JSR $FE89 ; and continue in a strange spot 0047:4C C3 B3 JMP $B3C3 --^-- Standard unprotected Pronto-DOS would jump to $9D84 at this point, but this disk has other ideas. $B300 was on T02,S00, so let's jump over there and continue disassembly. --v-- T02,S00 ----------- DISASSEMBLY MODE ---------- ; set up another RWTS read: track $0A 00C3:A9 0A LDA #$0A 00C5:8D EC B7 STA $B7EC ; sector 0 00C8:A9 00 LDA #$00 00CA:8D ED B7 STA $B7ED ; into $B400 00CD:A9 B4 LDA #$B4 00CF:8D F1 B7 STA $B7F1 00D2:A9 00 LDA #$00 00D4:8D F3 B7 STA $B7F3 00D7:8D EB B7 STA $B7EB ; and go 00DA:A9 B7 LDA #$B7 00DC:A0 E8 LDY #$E8 00DE:20 B5 B7 JSR $B7B5 ; and decrypt it (!) 00E1:A0 00 LDY #$00 00E3:B9 12 B4 LDA $B412,Y 00E6:49 4C EOR #$4C 00E8:99 12 B4 STA $B412,Y 00EB:C8 INY 00EC:C0 FF CPY #$FF 00EE:D0 F3 BNE $00E3 ; munge reset vector 00F0:A9 00 LDA #$00 00F2:8D F4 03 STA $03F4 ; and continue in decrypted code 00F5:4C 12 B4 JMP $B412 --^-- Here's T0A,S00: --v-- -------------- DISK EDIT -------------- TRACK $0A/SECTOR $00/VOLUME $FE/BYTE$12 --------------------------------------- $00: A0 00 B9 12 20 49 4C 99 @9R IL. $08: 12 20 C8 C0 FF D0 F3 60 R H@Ps $10: EA EA>E5<6F C1 A0 FB E5 jje/A {e $18: 4C C9 48 C1 B8 FB C1 A7 LIHA8{A' $20: FB 6C A7 F8 6C BE F8 E5 {,'x,>xe $28: 4C C9 04 F1 C5 8C F1 C2 LIDqE.qB $30: 8C 6C 08 F5 E9 61 89 48 .,Hui!.H $38: 9C BB F1 C2 8C F1 C0 8C .;qB.q@. $40: 5C B7 85 B3 9C B8 EC 4C \7.3.8lL $48: 84 F1 C2 8C F1 C0 8C 5C .qB.q@.\ $50: B7 85 99 9C BF 8C 4B 9C 7...?.K. $58: F5 AA 48 E5 5C 89 48 9C u*He\.H. $60: 9C E5 4C C9 48 6C 08 F5 .eLIH,Hu $68: E9 61 89 48 9C BB F1 C0 i!.H.;q@ $70: 8C 5C B7 85 E6 9C BB F1 .\7.f.;q $78: C0 8C 5C B7 85 A7 9C A2 @.\7.'." --------------------------------------- BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL --------------------------------------- COMMAND : --^-- Not much to see, since everything after byte $12 is encrypted (in the 80s kind of way where it's just XOR'd with a constant byte, but still). Amusingly, there is unencrypted code at byte $00 which decrypts the rest of the sector in the same way as the bootloader. It assumes the code is stored at $2000 instead of $B400. Maybe debugging code left by the original developer? At this point, you might think I would need to switch to boot tracing the old fashioned way so I could see this decrypted code. But Disk Fixer is very powerful! It has a "MASK" mode that allows me to XOR every byte of a sector with a constant byte -- exactly as the bootloader is doing after it reads this sector into memory. Pressing "X" in Disk Fixer lets me set the mask: --v-- MASK WITH ADD $00/EOR $4C/AND $FF/OR$00 ^^^ +--- change this --^-- Then pressing applies the mask to the current sector: --v-- -------------- DISK EDIT -------------- TRACK $0A/SECTOR $00/VOLUME $FE/BYTE$12 --------------------------------------- $00: EC 4C F5 5E 6C 05 00 D5 lLu^,E@U $08: 5E 6C 84 8C B3 9C BF 2C ^,..3.?, $10: A6 A6>A9<23 8D EC B7 A9 &&)#.l7) $18: 00 85 04 8D F4 B7 8D EB @.D.t7.k $20: B7 20 EB B4 20 F2 B4 A9 7 k4 r4) $28: 00 85 48 BD 89 C0 BD 8E @.H=.@=. $30: C0 20 44 B9 A5 2D C5 04 @ D9%-ED $38: D0 F7 BD 8E C0 BD 8C C0 Pw=.@=.@ $40: 10 FB C9 FF D0 F4 A0 00 P{I.Pt @ $48: C8 BD 8E C0 BD 8C C0 10 H=.@=.@P $50: FB C9 D5 D0 F3 C0 07 D0 {IUPs@GP $58: B9 E6 04 A9 10 C5 04 D0 9fD)PEDP $60: D0 A9 00 85 04 20 44 B9 P)@.D D9 $68: A5 2D C5 04 D0 F7 BD 8C %-EDPw=. $70: C0 10 FB C9 AA D0 F7 BD @P{I*Pw= $78: 8C C0 10 FB C9 EB D0 EE .@P{IkPn --------------------------------------- BUFFER 0/SLOT 6/DRIVE 1/MASK ON /NORMAL --------------------------------------- COMMAND : --^-- And now we can list the encrypted routine at byte $12. ~ Chapter 2 The Belly Of The Beast The protection check starts at byte $12 (loaded into memory at $B412): --v-- T0A,S00 ----------- DISASSEMBLY MODE ---------- ; Hmm, seeking to track $23 perhaps? ; That would explain why even my EDD ; bit copy failed -- I didn't copy ; track $23! 0012:A9 23 LDA #$23 0014:8D EC B7 STA $B7EC ; 0 = RWTS seek command 0017:A9 00 LDA #$00 0019:85 04 STA $04 001B:8D F4 B7 STA $B7F4 ; 0 also = disk volume wildcard 001E:8D EB B7 STA $B7EB ; get address of RWTS parameter table ; and call RWTS entry point to seek ; (not shown) 0021:20 EB B4 JSR $B4EB 0024:20 F2 B4 JSR $B4F2 0027:A9 00 LDA #$00 0029:85 48 STA $48 ; Oh look! Here's that "BD 89 C0" ; instruction I was looking for earlier ; but couldn't find (because it was ; encrypted) 002B:BD 89 C0 LDA $C089,X 002E:BD 8E C0 LDA $C08E,X ; look for the next available address ; field 0031:20 44 B9 JSR $B944 ; compare the sector we found against ; the counter we initialized in zp$04 ; earlier 0034:A5 2D LDA $2D 0036:C5 04 CMP $04 ; loop until we find the address field ; for track $23, sector $00 0038:D0 F7 BNE $0031 ; skip to sync byte (#$FF) 003A:BD 8E C0 LDA $C08E,X 003D:BD 8C C0 LDA $C08C,X 0040:10 FB BPL $003D 0042:C9 FF CMP #$FF 0044:D0 F4 BNE $003A ; skip an exact number of nibbles and ; look for #$D5 0046:A0 00 LDY #$00 0048:C8 INY 0049:BD 8E C0 LDA $C08E,X 004C:BD 8C C0 LDA $C08C,X 004F:10 FB BPL $004C 0051:C9 D5 CMP #$D5 0053:D0 F3 BNE $0048 ; if not found in correct location, try ; again from the top (Y register is the ; nibble counter) 0055:C0 07 CPY #$07 0057:D0 B9 BNE $0012 ; do this for all sectors 0059:E6 04 INC $04 005B:A9 10 LDA #$10 005D:C5 04 CMP $04 005F:D0 D0 BNE $0031 ; start over on sector 0 0061:A9 00 LDA #$00 0063:85 04 STA $04 0065:20 44 B9 JSR $B944 0068:A5 2D LDA $2D 006A:C5 04 CMP $04 006C:D0 F7 BNE $0065 ; skip to address epilogue 006E:BD 8C C0 LDA $C08C,X 0071:10 FB BPL $006E 0073:C9 AA CMP #$AA 0075:D0 F7 BNE $006E 0077:BD 8C C0 LDA $C08C,X 007A:10 FB BPL $0077 007C:C9 EB CMP #$EB 007E:D0 EE BNE $006E ; skip to sync byte (#$FF) 0080:BD 8C C0 LDA $C08C,X 0083:10 FB BPL $0080 0085:C9 FF CMP #$FF 0087:D0 F7 BNE $0080 ; skip an exact number of nibbles and ; look for #$D5 0089:A0 00 LDY #$00 008B:C8 INY 008C:BD 8E C0 LDA $C08E,X 008F:BD 8C C0 LDA $C08C,X 0092:10 FB BPL $008F 0094:C9 D5 CMP #$D5 0096:D0 F3 BNE $008B ; if not found in correct location, try ; again from the top (Y register is the ; nibble counter again) 0098:C0 10 CPY #$10 009A:D0 C5 BNE $0061 ; do this for several other sectors 009C:E6 04 INC $04 009E:A9 0A LDA #$0A 00A0:20 A8 FC JSR $FCA8 00A3:A9 04 LDA #$04 00A5:C5 04 CMP $04 00A7:D0 04 BNE $00AD 00A9:E6 04 INC $04 00AB:D0 B8 BNE $0065 00AD:A9 0F LDA #$0F 00AF:C5 04 CMP $04 00B1:D0 BB BNE $006E 00B3:20 44 B9 JSR $B944 00B6:A5 2D LDA $2D 00B8:C9 0F CMP #$0F 00BA:D0 F7 BNE $00B3 ; read data field (not shown) 00BC:20 DC B8 JSR $B8DC ; look for #$A5 nibble in a specific ; place 00BF:A0 00 LDY #$00 00C1:C8 INY 00C2:BD 8E C0 LDA $C08E,X 00C5:BD 8C C0 LDA $C08C,X 00C8:10 FB BPL $00C5 00CA:C9 A5 CMP #$A5 00CC:D0 F3 BNE $00C1 ; if not found in correct location, try ; again from the top (Y register is the ; nibble counter again) 00CE:C0 2B CPY #$2B 00D0:D0 E1 BNE $00B3 ; look for #$D5 nibble in a specific ; place 00D2:A0 00 LDY #$00 00D4:C8 INY 00D5:BD 8E C0 LDA $C08E,X 00D8:BD 8C C0 LDA $C08C,X 00DB:10 FB BPL $00D8 00DD:C9 D5 CMP #$D5 00DF:D0 F3 BNE $00D4 ; if not found in correct location, try ; again from the top (Y register is the ; nibble counter again) 00E1:C0 5D CPY #$5D 00E3:D0 CE BNE $00B3 ; success path falls through to here -- ; turn off the drive motor and jump ; forward 00E5:BD 88 C0 LDA $C088,X 00E8:4C F5 B4 JMP $B4F5 ; set RUN flag to disable user control ; in case they manage to get to a BASIC ; prompt 00F5:A9 FF LDA #$FF 00F7:85 D6 STA $D6 ; continue with standard boot sequence 00F9:4C 84 9D JMP $9D84 --^-- My copy never makes it this far. It's stuck in an infinite loop, trying to find a precisely organized track $23 (and failing). It looks like I should be able to skip over the protection check by jumping straight to $9D84 instead of $B3C3 (all the way back at $B747). T00,S01,$48: C3B3 -> 849D ]PR#6 ...works... Side B has identical protection, except the encrypted protection check is read from T02,S01. The same patch bypasses it altogether. Quod erat liberandum. ~ Changelog 2017-12-09 - fixed minor data corruption on side A (T02,S04) 2017-04-17 - initial release --------------------------------------- A 4am crack No. 1141 ------------------EOF------------------