--------Math Tutor: Subtraction-------- A 4am crack 2016-03-08 --------------------------------------- Name: Math Tutor: Subtraction Genre: educational Year: 1986 Authors: Michael Maloney, Michael Summers, Pat Stuart Publisher: Scholastic, Inc. Media: 2 single-sided 5.25-inch disks OS: DOS 3.3 Previous cracks: none I only have disk 2. The first half of the items in the main menu ask for disk 1. Sorry. :( ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error, but it gets a participation trophy just for showing up Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) no errors, but copy loads DOS, runs a startup program, and crashes at $90C6 Copy ][+ nibble editor all tracks use standard prologues (address: D5 AA 96, data: D5 AA AD) but modified address epilogues (AB FF FF) Disk Fixer ["O" -> "Input/Output Control"] set Address Epilogue to "AB FF FF" Success! All tracks readable! T00 -> looks like a DOS 3.3 RWTS T11 -> DOS 3.3 disk catalog T01,S09 -> startup program is "XMGPRT1" Why didn't COPYA work? modified epilogue bytes (every track) Why didn't Locksmith FDB work? modified epilogue bytes (every track) Why didn't my EDD copy work? probably a nibble check during boot Next steps: 1. capture RWTS with AUTOTRACE 2. convert disk to standard format with Advanced Demuffin 3. find nibble check and bypass it ~ Chapter 1 In Which We Attempt To Use The Original Disk As A Weapon Against Itself The first step to cracking this disk is to convert it to a standard format, meaning standard prologue and epilogue sequences around each sector. The best (most robust) way to do this is to use the original disk's own disk reading routine to read each sector, then use a standard disk writing routine to write out the sector data to a standard disk. Advanced Demuffin (originally by The Stack, lightly updated in 2015 by yours truly, with some convenience functions and long-standing bug fixes) is the perfect tool for this kind of sector- by-sector conversion. It requires me to provide the original disk's RWTS (disk reading routines), which are often stored in $B800..$BFFF. To automate capturing the RWTS from the original disk, I wrote a script. When it works, it works very well; when it fails, it fails most spectacularly. [S6,D1=original disk] [S6,D2=blank disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 CAPTURING BOOT1 ...reboots slot 6... ...reboots slot 5... SAVING BOOT1 SAVING RWTS That worked very well. ]BRUN ADVANCED DEMUFFIN 1.5 ["5" to switch to slot 5] ["R" to load a new RWTS module] --> At $B8, load "RWTS" from drive 1 ["6" to switch to slot 6] ["C" to convert disk] --v-- ADVANCED DEMUFFIN 1.5 (C) 1983, 2014 ORIGINAL BY THE STACK UPDATES BY 4AM =======PRESS ANY KEY TO CONTINUE======= TRK:................................... +.5: 0123456789ABCDEF0123456789ABCDEF012 SC0:................................... SC1:................................... SC2:................................... SC3:................................... SC4:................................... SC5:................................... SC6:................................... SC7:................................... SC8:................................... SC9:................................... SCA:................................... SCB:................................... SCC:................................... SCD:................................... SCE:................................... SCF:................................... ======================================= 16SC $00,$00-$22,$0F BY1.0 S6,D1->S6,D2 --^-- The disk's own RWTS gave no read errors on any track. This is the power and the genius of Advanced Demuffin. Every disk must be able to read itself. So, let it read itself, then capture the data and write it out in a standard format. Now I have a copy of the disk in a standard format, which I can read and inspect with third-party tools. ]PR#5 ]CATALOG,S6,D2 C1983 DSR^C#254 002 FREE A 003 HELLO B 002 BLOAD LANG T 002 S2 B 002 BEGIN B 011 C&J B 014 P1 B 009 P2 B 013 P3 B 009 P4 B 010 TABLS B 017 EXTRA B 013 HANDLE B 029 MODULE B 044 SECF B 044 SECG B 044 SECH B 044 SECI B 044 SECJ B 044 SECK B 044 SECL B 044 SECM B 006 MATH TUTOR PAGE B 002 XMGPRT1 My failed bit copy booted, loaded DOS, and ran a startup program before crashing. From my initial inspection with a sector editor, I gleaned that the startup program is "XMGPRT1", so let's start there. ~ Chapter 2 In Which We Find A 30-Year-Old Bug ]BLOAD XMGPRT1 ]CALL -151 *BF55.BF56 BF55- 00 90 (Diversi-DOS 64K stores the program start location in $BF55. In standard DOS 3.3, it's at $AA72.) *9000L ; get address of RWTS parameter table 9000- 20 E3 03 JSR $03E3 9003- 85 FB STA $FB 9005- 84 FA STY $FA ; hmm 9007- A9 C5 LDA #$C5 9009- 48 PHA ; copy RWTS parameters 900A- A9 00 LDA #$00 900C- 85 FC STA $FC 900E- A2 03 LDX #$03 9010- BC 49 90 LDY $9049,X 9013- 91 FA STA ($FA),Y 9015- CA DEX 9016- 10 F8 BPL $9010 9018- 8A TXA ; hmm 9019- 48 PHA The X register will be $FF after the loop, so this means we've pushed $C5FF to the stack. At this point, a bare RTS will "return" to $C5FF+1, i.e. reboot. 901A- 20 3C 90 JSR $903C *903CL ; call the RWTS (most likely just to ; move the drive head to the proper ; position for an impending nibble ; check) 903C- 20 E3 03 JSR $03E3 903F- 20 D9 03 JSR $03D9 9042- A9 00 LDA #$00 9044- 85 48 STA $48 ; if that fails, off to The Badlands 9046- B0 78 BCS $90C0 9048- 60 RTS *90C0L 90C0- A8 TAY 90C1- DD 88 C0 CMP $C088,X 90C4- 00 BRK 90C5- 00 BRK 90C6- 00 BRK Oh, well, there's your problem: you cut your program short by a few bytes. I'm guessing there's supposed to be at least one more instruction there before it crashes (like something that reboots either explicitly or through some stack manipulation). But there isn't, because someone mis-saved a file 30 years ago. Always test your failure paths! Caller was $901A, so resuming at $901D: *901DL 901D- A0 01 LDY #$01 901F- B1 FA LDA ($FA),Y 9021- AA TAX 9022- 20 73 90 JSR $9073 *9073L ; turning on the drive motor manually ; is always suspicious 9073- BD 89 C0 LDA $C089,X ; initialize death counters 9076- A9 56 LDA #$56 9078- 85 FD STA $FD 907A- A9 08 LDA #$08 907C- C6 FC DEC $FC 907E- D0 04 BNE $9084 9080- C6 FD DEC $FD ; if death counter hits 0, give up 9082- F0 3C BEQ $90C0 ; look for an $FB nibble 9084- BC 8C C0 LDY $C08C,X 9087- 10 FB BPL $9084 9089- C0 FB CPY #$FB 908B- D0 ED BNE $907A 908D- F0 00 BEQ $908F ; 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) 908F- EA NOP 9090- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 9091- 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) 9094- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 9096- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 9097- B0 0B BCS $90A4 ; next nibble needs to be $FF 9099- BC 8C C0 LDY $C08C,X 909C- 10 FB BPL $9099 ; ...otherwise we start over 909E- C0 FF CPY #$FF 90A0- D0 D8 BNE $907A ; loop back to get next nibble 90A2- F0 EB BEQ $908F ; execution continues here (from $9097) ; get another nibble 90A4- BC 8C C0 LDY $C08C,X 90A7- 10 FB BPL $90A4 ; stash it in zero page 90A9- 84 FC STY $FC ; if the accumulator is anything but ; %00001010, start over 90AB- C9 0A CMP #$0A 90AD- D0 CB BNE $907A 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. 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: 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 AB FF FF+ 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: 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 AB FF FF+ 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. The accumulator holds the pattern of whether each sync byte had a timing bit after it. It's set one bit at a time, rotated into place from the carry bit that was set by the "CPY #$08" that happened after getting the value of the data latch (LDY $C08C,X) that happened after doing just enough NOPs that the value of the data latch will depend on the presence of a timing bit after the previous nibble. Which is brilliant. Anyway, if the value of the accumulator (i.e. the pattern of timing bits) is wrong, the program knows it's not running on an original disk. ~ Chapter 3 In Which We Stare Into Infinity And Infinity Stares Back And Executes A DOS Command Continuing from $90AF... *90AFL ; get a nibble 90AF- BD 8C C0 LDA $C08C,X 90B2- 10 FB BPL $90AF ; more bit twiddling 90B4- 38 SEC 90B5- 2A ROL ; AND it with the previously stashed ; nibble 90B6- 25 FC AND $FC 90B8- 49 FF EOR #$FF ; branch to failure path 90BA- D0 04 BNE $90C0 ; success path falls through to here -- ; turn off the drive motor and return ; to the caller 90BC- DD 88 C0 CMP $C088,X 90BF- 60 RTS This was called from $9022, so let's continue from $9025. *9025L ; pop the bogus return value ($C5FF) ; off the stack 9025- 68 PLA 9026- 68 PLA ; Route a series of bytes through the ; DOS output vector at ($36). This is ; how binary files "execute" DOS 3.3 ; commands, like PRINT CHR$(4)"..." in ; Applesoft BASIC. This is totally ; legitimate code, cleverly disguised ; as an infinite loop. 9027- A0 00 LDY #$00 9029- 84 FE STY $FE 902B- B9 4D 90 LDA $904D,Y 902E- 09 80 ORA #$80 9030- 20 39 90 JSR $9039 9033- A4 FE LDY $FE 9035- C8 INY 9036- 4C 29 90 JMP $9029 9039- 6C 36 00 JMP ($0036) Let's see what it's printing. *FC58G N 400<904D.9072M DRUN HELLO M@ ^ control characters ^^ Aha! It's not an infinite loop after all. Well, it is, technically, but it won't actually run forever. It's not easy to show in plain text, but the initial "D" and the final "M@" are in inverse. So that string starts with Ctrl-D and ends with Ctrl-M and a null byte. Since DOS is already loaded, printing this through the DOS vector will execute that command, as if you typed it from a prompt yourself. Since that program never returns to the caller, it will break out of the seemingly "infinite" loop. Since there are no side effects to the copy protection routine, I can change this file to simply skip the call to $9059 and continue on to running the real startup program. Using my trusty Disk Fixer sector editor, I press "D" for directory mode, select "XMGPRT1", and I find this code on track $14. T14,S0C,$26 change 20 to 2C Quod erat liberandum. ~ Epilogue In Which We Stop And Smell The RWTS Astute readers may notice that we converted the disk to a standard format (with Advanced Demuffin), but then we didn't need to patch the RWTS of the copy in order to read itself. Here is how that works. ]PR#5 ... ]BLOAD RWTS,A$3800 ]CALL -151 *FE89G FE93G ; disconnect DOS *B800<3800.3FFFM ; move RWTS into place *B98BL ; usually the code to match the address ; epilogue B98B- 4C 16 BD JMP $BD16 Wait, what? *BD16L ; accept either $AB or $DE as the first ; epilogue nibble BD16- BD 8C C0 LDA $C08C,X BD19- 10 FB BPL $BD16 BD1B- C9 AB CMP #$AB BD1D- F0 06 BEQ $BD25 BD1F- C9 DE CMP #$DE BD21- F0 02 BEQ $BD25 ; didn't find either -- set carry to ; indicate a disk read error, and exit ; via RTS BD23- 38 SEC BD24- 60 RTS ; Execution continues here (from either ; $BD1D or $BD21, depending on which ; nibble we found). Now we'll accept ; either $FF or $AA as the second ; epilogue nibble. BD25- BD 8C C0 LDA $C08C,X BD28- 10 FB BPL $BD25 BD2A- C9 FF CMP #$FF BD2C- F0 04 BEQ $BD32 BD2E- C9 AA CMP #$AA BD30- D0 F1 BNE $BD23 BD32- 18 CLC BD33- 60 RTS So this disk's RWTS will accept either "AB FF" or "DE AA". (Technically, it would also accept "AB AA" or "DE FF", but neither is used on this disk.) The third epilogue nibble is never checked. At the same time, the write part of the RWTS always uses the standard "DE AA EB" epilogues. The result: a single RWTS that can read the program disk (which uses "AB FF" epilogues) and standard disks (with the "DE AA" epilogues). This allows the program to initialize and store user data on a standard formatted disk, without the hassle of a mode-switching routine that changes the RWTS code in memory between protected and unprotected mode. There is only one mode, and since one of its features is being able to read standard formatted disks, no RWTS patches are required. --------------------------------------- A 4am crack No. 640 ------------------EOF------------------