-------------Reader Rabbit------------- A 4am crack 2015-07-14 -------------------. updated 2015-09-03 |___________________ Name: Reader Rabbit Version: 1.3 Genre: educational Year: 1984 Credits: Author: Leslie Grimm Art: Corinne Grimm and Cindy Grimm Publisher: The Learning Company Media: single-sided 5.25-inch floppy OS: DOS 3.3 Previous cracks: none Similar cracks: Sliding Block (crack no. 290) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy displays a DOS prompts then reboots Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer T00 -> looks like DOS 3.3 boot0/boot1 (loaded in low memory, like a DOS 3.3 master disk) T01-T02 -> looks like DOS 3.3 T01,S09 -> startup program is "HELLO" T11,S00 -> looks like catalog VTOC, but rest of track is empty Why didn't any of my copies work? probably a nibble check in the auto- start program Next steps: 1. Load and trace startup program 2. Find nibble check and disable it 3. There is no step 3 (I hope) ~ Chapter 1 Hide Your Disk Catalog With This One Weird Trick! Crackers Hate It! Booting from my work disk, my non- working copy ought to have a catalog, but it has suspiciously vanished. [S6,D1=non-working copy from COPYA] [S5,D1=my work disk] ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 005 FREE ] Wait, what? [Disk Fixer] --> T11,S00 Looks like a standard DOS 3.3 VTOC. It points to T11,S0F as the first catalog sector. --> T11,S0F Blank. In fact, the entire rest of the track is blank. Here's what I know: if it looks like DOS 3.3 and quacks like DOS 3.3, it's probably DOS 3.3.(*) (*) not guaranteed, quacking may vary There are a number of ways to hide the disk catalog on a DOS 3.3 disk. But honestly, the best way to find it is to look at sector $0F on each track until you find something that looks like a DOS 3.3 directory sector. They're pretty easy to spot. --> T10,S0F nope --> T0F,S0F nope --> T0E,S0F nope ... --> T02,S0F Ah, there it is! --v-- -------------- DISK EDIT -------------- TRACK $02/SECTOR $0F/VOLUME $FE/BYTE$00 --------------------------------------- $00:>00<02 0E 00 00 00 00 00 @BN@@@@@ $08: 00 00 00 12 0F 02 C8 C5 @@@ROBHE $10: CC CC CF A0 A0 A0 A0 A0 LLO $18: A0 A0 A0 A0 A0 A0 A0 A0 $20: A0 A0 A0 A0 A0 A0 A0 A0 $28: A0 A0 A0 A0 04 00 05 0F D@EO $30: 02 D3 CF D2 D4 C5 D2 A0 BSORTER $38: A0 A0 A0 A0 A0 A0 A0 A0 $40: A0 A0 A0 A0 A0 A0 A0 A0 $48: A0 A0 A0 A0 A0 A0 A0 31 1 $50: 00 13 09 02 CC C1 C2 C5 @SIBLABE $58: CC C5 D2 A0 A0 A0 A0 A0 LER $60: A0 A0 A0 A0 A0 A0 A0 A0 $68: A0 A0 A0 A0 A0 A0 A0 A0 $70: A0 A0 2F 00 14 09 02 D7 /@TIBW $78: CF D2 C4 A0 D4 D2 C1 C9 ORD TRAI --------------------------------------- BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL --------------------------------------- COMMAND : _ --^-- So the disk catalog is hiding on track $02. But how does the original know where to look? I scoured "Beneath Apple DOS" until I found the answer on p8-28: --v-- B011-B036 Read a directory sector ; (If CARRY flag is zero on entry, read first directory sector. If CARRY is one, read next) ; Memorize entry code. ; Set buffer pointers (B045). ; First or next? ; If first, get track/sector of directory sector from VTOC at offset +1,+2. ; Otherwise, get track/sector from directory sector at offset +1,+2. If track is zero, exit with error code (end of directory). ; Call RWTS to read sector. ; Exit with normal return code. --^-- So, to read the first sector of file names and other metadata, this routine is supposed to look at the VTOC sector buffer (read from T11,S00 and stored at $B3BB..$B4BA). The VTOC says "hey, the first sector of files and stuff is in T11,S0F" so this routine is supposed to read T11,S0F. But the DOS on this disk made one small modification to that routine. --v-- [T01,S0F] ----------- DISASSEMBLY MODE ---------- 0011:08 PHP 0012:20 45 30 JSR $3045 0015:28 PLP 0016:B0 08 BCS $0020 0018:AC BD 33 LDY $33BD ------ 001B:A2 02 LDX #$02 << hey 001D:EA NOP << now ------ 001E:D0 0A BNE $002A 0020:AE BC 34 LDX $34BC 0023:D0 02 BNE $0027 0025:38 SEC 0026:60 RTS 0027:AC BD 34 LDY $34BD 002A:8E 97 33 STX $3397 002D:8C 98 33 STY $3398 0030:A9 01 LDA #$01 0032:20 52 30 JSR $3052 0035:18 CLC 0036:60 RTS 0037:20 45 30 JSR $3045 003A:AE 97 33 LDX $3397 003D:AC 98 33 LDY $3398 0040:A9 02 LDA #$02 --^-- Instead of getting the track number from the VTOC, it hard-codes track $02. Now that I've identified the problem, the fix is straightforward. If I change the VTOC header (T11,S00) to point to the actual first directory sector (T02,S0F), DOS 3.3 or any other copy utility should be able to read the disk catalog. T11,S00,$01 change "11" to "02" ~ Chapter 2 In Which It's All In The Timing (Bits) ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 005 FREE A 004 HELLO A 049 SORTER A 047 LABELER A 038 WORD TRAIN B 002 MUSIC ETC B 005 RRINPUTHI.OBJ0 B 020 PICTB.ANIMALS B 008 PICTB.RABBIT B 017 PICTB.HOUSE B 019 PICTB.PEOPLE B 018 PICTB.PLAY AND WEAR B 018 PICTB.VARIETY B 017 PICTB.OUTDOORS B 025 PICTB.TRAIN B 009 PICTB.CARDS B 013 PICTB.CONTAINERS B 020 PICTB.KITCHEN B 019 PICTB.TRAVEL B 005 PICTB.DANCING RABBIT B 026 WF.OBJ B 002 TLC.OBJ0 A 019 MENU A 042 MATCHUP B 024 PIC.HELLO+UNPK B 025 PIC.MENU+UNPK ]LOAD HELLO ]LIST 0 ONERR GOTO 10000 5 REM HELLO, COPYRIGHT 1984, T HE LEARNING COMPANY. 6 PRINT CHR$ (4);"BLOAD TLC.OB J0" 7 CALL 8192 10 IF PEEK (103) < > 1 OR PEEK (104) < > 93 OR PEEK (2380 8) < > 0 THEN POKE 103,1: POKE 104,93: POKE 23808,0: PRINT CHR$ (4);"RUNHELLO": REM RE LOCATE TO $5D00 11 PRINT CHR$ (4)"BLOAD MUSIC ETC": POKE 49281,0: POKE 492 81,0: PRINT CHR$ (4)"BLOAD RRINPUTHI.OBJ0": POKE 49822, 0: POKE 903,0 20 TEXT : HOME ... It doesn't seem like my non-working copy is getting as far as line 20, since it never clears the screen before rebooting. The CALL 8192 on line 7 looks suspect. ]BLOAD TLC.OBJ0 ]CALL -151 ; last BLOAD address (Diversi-DOS 64K) *BF55.BF56 BF55- 00 20 *2000L ; get address of RWTS parameter table 2000- 20 E3 03 JSR $03E3 2003- 85 FA STA $FA 2005- 84 F9 STY $F9 2007- A9 00 LDA #$00 2009- 85 09 STA $09 200B- 85 1B STA $1B ; set up some RWTS parameters 200D- A2 03 LDX #$03 200F- BC 3A 20 LDY $203A,X 2012- 91 F9 STA ($F9),Y 2014- CA DEX 2015- 10 F8 BPL $200F 2017- C8 INY 2018- A9 10 LDA #$10 201A- 85 08 STA $08 201C- 91 F9 STA ($F9),Y ; call RWTS 201E- 20 2D 20 JSR $202D 2021- A0 01 LDY #$01 2023- B1 F9 LDA ($F9),Y 2025- AA TAX ; probably important 2026- 20 3E 20 JSR $203E ; call RWTS again 2029- 20 2D 20 JSR $202D ; exit unconditionally (no flags) 202C- 60 RTS *203EL ; turn on drive motor 203E- BD 89 C0 LDA $C089,X ; set up death counters 2041- A9 56 LDA #$56 2043- 85 1C STA $1C 2045- A9 08 LDA #$08 2047- C6 1B DEC $1B 2049- D0 04 BNE $204F 204B- C6 1C DEC $1C ; if death counter hits 0, fail 204D- F0 3C BEQ $208B ; look for $FB nibble 204F- BC 8C C0 LDY $C08C,X 2052- 10 FB BPL $204F 2054- C0 FB CPY #$FB 2056- D0 ED BNE $2045 2058- F0 00 BEQ $205A ; 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) 205A- EA NOP 205B- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 205C- 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) 205F- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 2061- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 2062- B0 0B BCS $206F ; next nibble needs to be $FF 2064- BC 8C C0 LDY $C08C,X 2067- 10 FB BPL $2064 ; ...otherwise we start over 2069- C0 FF CPY #$FF 206B- D0 D8 BNE $2045 ; loop back to get next nibble 206D- F0 EB BEQ $205A ; execution continues here (from $2062) ; get another nibble 206F- BC 8C C0 LDY $C08C,X 2072- 10 FB BPL $206F ; stash it in zero page 2074- 84 1B STY $1B ; if the accumulator is anything but ; %00001010, start over 2076- C9 0A CMP #$0A 2078- D0 CB BNE $2045 ; get a nibble 207A- BD 8C C0 LDA $C08C,X 207D- 10 FB BPL $207A ; more bit twiddling 207F- 38 SEC 2080- 2A ROL ; AND it with the previously stashed ; nibble 2081- 25 1B AND $1B 2083- 49 FF EOR #$FF ; branch on failure 2085- F0 04 BEQ $208B ~ Chapter 3 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-- 1C70: 9F EB E5 FC D7 D7 D7 EE 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF+FF FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --^-- It's easy to understand why a simple sector copy failed. The sequence that this code is looking for starts at offset $1C93, which is between the end of one sector and the beginning of the next. (The data epilogue is at $1C8C; the next address prologue is at $1CA2.) 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 $1C93: - $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-- 1C70: 9F EB E5 FC D7 D7 D7 EE 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF FF+FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --^-- A subtle difference! The sequence at offset $1C93 now looks like this: - $FB + timing bit - $FF - $FF - $FF + timing bit - $FF + timing bit This code is looking for $FF bytes with an alternating pattern of timing bit, no timing bit, timing bit, no timing bit, timing bit. It doesn't find that on the bit copy, so it knows it's not running on an original disk. ~ Chapter 4 In Which We Wrap Up Continuing the code listing... ; success path falls through to here -- ; turn off the drive motor and return ; to the caller 2087- DD 88 C0 CMP $C088,X 208A- 60 RTS ; any failure ends up here and reboots 208B- 4C 00 C6 JMP $C600 That explains the behavior that I saw on my non-working copy (endlessly rebooting). Since there are no side effects to the copy protection routine, I can change the first byte to an "RTS" instruction and the entire file will do nothing. [Disk Fixer] ["D"irectory mode] [select "TLC.OBJ0"] T13,S0D,$04 change "20" to "60" Quod erat liberandum. ~ Changelog 2015-09-03 - Vastly improved explanation of the actual protection routine. Thanks to qkumba for pointing out that my original explanation was inaccurate. 2015-07-14 - initial release --------------------------------------- A 4am crack No. 365 ------------------EOF------------------