--------------Math Rabbit-------------- A 4am crack 2015-08-28 -------------------. updated 2015-09-01 |___________________ Name: Math Rabbit Version: 1.1 Genre: educational Year: 1986 Credits: Teri Paul (design) Analee Nunan (graphics) Aaron Weiss (program) Publisher: The Learning Company Media: single-sided 5.25-inch floppy OS: "RDOS.SYSTEM" (ProDOS-compatible) Previous cracks: none Similar cracks: Math Rabbit 1.0 (crack no. 14) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but the copy boots to the graphical title screen and asks you to select a level, then displays "Fatal Disk Error" and hangs Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer T00 -> looks like ProDOS bootloader and ProDOS disk catalog Why didn't any of my copies work? Probably a nibble check in the startup program Next steps: 1. Search for error message, maybe we'll get lucky 2. Disable nibble check 3. There is no step 3 (I hope) ~ Chapter 1 In Which We Get Lucky Turning to my trusty Disk Fixer sector editor, I scanned for the ASCII string "Fatal"... [S6,D1=non-working copy] [Disk Fixer] ["F"ind] ["A"SCII] "Fatal" ...but found nothing. However, knowing that many Apple II programs use 7-bit ASCII, I scanned for the hex sequence "46 61 74 61 6C" ("Fatal" without the high bit set): [Disk Fixer] ["F"ind] ["H"ex] "46 61 74 61 6C" Behold! A match on T0A,S0B. Based on some of the absolute JMP and JSR instructions, I'm guessing this sector is loaded at $4000. The Disk Fixer disassembly listing looks like this: --v-- T0A,S0B ----------- DISASSEMBLY MODE ---------- 0000:A9 00 LDA #$00 0002:85 46 STA $46 0004:85 47 STA $47 0006:85 44 STA $44 0008:A9 E0 LDA #$E0 000A:85 45 STA $45 000C:20 3A B8 JSR $B83A 000F:B0 EF BCS $0000 ; engage either drive 1 or 2 0011:A5 43 LDA $43 0013:30 07 BMI $001C 0015:AA TAX 0016:DD 8A C0 CMP $C08A,X 0019:4C 22 40 JMP $4022 001C:29 7F AND #$7F 001E:AA TAX 001F:DD 8B C0 CMP $C08B,X ; call below 0022:20 33 40 JSR $4033 ; save result (in accumulator) 0025:48 PHA ; wipe memory 0026:A0 00 LDY #$00 0028:98 TYA 0029:99 33 40 STA $4033,Y 002C:C8 INY 002D:C0 79 CPY #$79 002F:D0 F8 BNE $0029 ; restore result (to accumulator) 0031:68 PLA 0032:60 RTS ; turn on drive motor manually 0033:BD 89 C0 LDA $C089,X ; initialize Death Counter 0036:A9 56 LDA #$56 0038:85 08 STA $08 ; initialize accumulator 003A:A9 08 LDA #$08 003C:C6 07 DEC $07 003E:D0 04 BNE $0044 ; if Death Counter hits 0, jump to ; The Badlands 0040:C6 08 DEC $08 0042:F0 3C BEQ $0080 ; look for an $FB nibble 0044:BC 8C C0 LDY $C08C,X 0047:10 FB BPL $0044 0049:C0 FB CPY #$FB 004B:D0 ED BNE $003A 004D:F0 00 BEQ $004F ; 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) 004F:EA NOP 0050:EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 0051: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) 0054:C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 0056:2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 0057:B0 0B BCS $0064 ; next nibble needs to be $FF 0059:BC 8C C0 LDY $C08C,X 005C:10 FB BPL $0059 005E:C0 FF CPY #$FF ; ...otherwise we start over 0060:D0 D8 BNE $003A ; loop back to 0062:F0 EB BEQ $004F ; execution continues here (from $0057) ; get another nibble 0064:BC 8C C0 LDY $C08C,X 0067:10 FB BPL $0064 ; stash it in zero page 0069:84 07 STY $07 ; if the accumulator is anything but ; %00001010, start over 006B:C9 0A CMP #$0A 006D:D0 CB BNE $003A ; get a nibble 006F:BD 8C C0 LDA $C08C,X 0072:10 FB BPL $006F ; more bit twiddling 0074:38 SEC 0075:2A ROL ; AND it with the previously stashed ; nibble 0076:25 07 AND $07 0078:49 FF EOR #$FF ; branch on failure 007A:F0 04 BEQ $0080 --^-- 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: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 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 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 $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-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 00 START: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 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 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 $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. 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 Wrap Up Continuing the code listing... ; success path falls through to here -- ; turn off the drive motor and return ; to the caller 007C:DD 88 C0 CMP $C088,X 007F:60 RTS ; failure path (a.k.a. "The Badlands", ; from which there is no return) 0080:DD 88 C0 CMP $C088,X ; I'm pretty sure this is the routine ; that prints the error message on the ; screen after setting some parameters ; (possibly screen coordinates) 0083:A9 52 LDA #$52 0085:8D A8 65 STA $65A8 0088:A9 04 LDA #$04 008A:8D A9 65 STA $65A9 008D:A9 2E LDA #$2E 008F:8D AA 65 STA $65AA 0092:A9 20 LDA #$20 0094:8D AB 65 STA $65AB 0097:20 AC 65 JSR $65AC ; this jumps to a routine that wipes ; memory and never returns 009A:38 SEC 009B:6E 12 60 ROR $6012 009E:20 17 60 JSR $6017 ... ; error message is here 00AD:"Fatal Disk Error" There don't appear to be any side effects. The routine at offset $0033 either returns gracefully or prints an error and never returns at all. I should be able to put an "RTS" at the beginning of that routine (where it manually switches on the drive motor) to bypass it entirely. T0A,S0B,$33 change "BD" to "60" ]PR#6 ...works... Success! The game lets me select a level from the title screen and continues to run without complaint. Quod erat liberandum. ~ Changelog 2015-09-01 - Vastly improved explanation of the actual protection routine. Thanks to qkumba for pointing out that my original explanation was inaccurate. 2015-08-28 - initial release --------------------------------------- A 4am crack No. 430 ------------------EOF------------------