--------------The Complete------------- ---------Scarsdale Medical Diet-------- A 4am crack 2016-02-16 --------------------------------------- Name: The Complete Scardale Medical Diet Genre: productivity Year: 1985 Authors: Prentice Associates Publisher: Bantam Electronic Publishing Media: two single-sided 5.25-inch disks OS: custom Previous cracks: none Similar cracks: #423 Sherlock Holmes in Another Bow #415 Teddy Bear-rels of Fun ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA disk 1 succeeds fails on last pass of disk 2 Locksmith Fast Disk Backup unable to read track $21 of disk 2 EDD 4 bit copy (no sync, no count) no errors, but copy asks for disk 2 then prints "COPY PROTECTION FAILURE" and hangs Copy ][+ nibble editor track $21 of disk 2 has no sectors, just a repeating sequence of nibbles: --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 21 START: 1800 LENGTH: 3DFF 1820: FF FF B6 FD B7 FF BA FA VIEW 1828: BB F7 BD F7 BE BF FB BE 1830: F6 FF FE BF BE BB EA EA 1838: FB AA BA EA EB EF DF BF 1840: FF F7 F6 EF EE AB AB AB <-1841 1848: FF D5 D5 D5 FF EA EA EA 1850: FF F5 F5 F5 FF FA FA FA 1858: FF FD FD FD FF FE FE FE 1860: FF FF B6 FD B7 FF BA FA --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- Disk Fixer T00 -> DOS 3.3 boot0 and RWTS no sign of rest of DOS no sign of a disk catalog anywhere Why didn't COPYA work? funny track $21 on disk 2 Why didn't Locksmith FDB work? ditto Why didn't my EDD copy work? probably a nibble check looking at the structure of track $21 Next steps: 1. Find the nibble check and disable it 2. There's no step 2 (I hope) ~ Chapter 1 In Which We Get Lucky My non-working copy is pretty clear why it's not running. Disks do not simply print "COPY PROTECTION FAILURE" for no reason. There's a runtime protection check somewhere. One thing that all on-disk 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 where the slot number has to be, although the disk controller ROM routine uses zero page $2B and lots of disks just reuse that. There's also nothing that says you have to use the X-register as the index, or that you must use the accumulator as the load register. But most RWTS code does, out of convention I suppose (or possibly 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 to prevent people from finding it. 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 (disk 2) 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 ------------- $16/$03-$A6 PRESS [RETURN] --^-- Seriously. One match. And just looking at the code, I can tell it's protection related. First of all, it hard-codes slot 6. --v-- T16,S03 ----------- DISASSEMBLY MODE ---------- 00A3:18 CLC 00A4:A2 60 LDX #$60 <-- ! 00A6:BD 89 C0 LDA $C089,X --^-- But I'm getting ahead of myself. Let's change the "LDA $C089,X" to "JMP $FF59" (ON THE NON-WORKING COPY, PLEASE DON'T EVER PATCH AN ORIGINAL DISK) and see if we can capture the code in situ. T16,S03,$A6 change BD89C0 to 4C59FF Rebooting disk 1, it once again gets as far as asking for disk 2, then breaks to the monitor. Jackpot! It took me a while to find the code in memory, because the sector I patched is not aligned to a memory page. But here it is: *8FD3L 8FD3- 18 CLC 8FD4- A2 60 LDX #$60 8FD6- 4C 59 FF JMP $FF59 <-- ! Let's change that back to its original code so we can see the protection check in all its glory. *8FD6:BD 89 C0 *8FD3L ; turn on drive motor (hard-coded to ; slot 6) 8FD3- 18 CLC 8FD4- A2 60 LDX #$60 o_O 8FD6- BD 89 C0 LDA $C089,X A fun(*) thing to do is boot original floppies from slot 5. Lots of copy protection routines (including this one) hard-code slot 6, so you can find out when they're called because the slot 6 drive light will suddenly go on. (*) not guaranteed, actual fun may vary ~ Chapter 2 Rollin', Rollin', Rollin' ; this seeks to track $21, which is the ; unreadable track (not shown) 8FD9- A9 21 LDA #$21 8FDB- 0A ASL 8FDC- A0 00 LDY #$00 8FDE- 20 1E 91 JSR $911E ; set up the Death Counter 8FE1- A9 40 LDA #$40 8FE3- 85 F1 STA $F1 ; reset data latch (still hard-coding ; slot 6) and get a nibble 8FE5- A2 60 LDX #$60 8FE7- BD 8E C0 LDA $C08E,X 8FEA- BD 8C C0 LDA $C08C,X 8FED- 10 FB BPL $8FEA ; if the Death Counter hits 0, fail 8FEF- C6 F0 DEC $F0 8FF1- D0 F7 BNE $8FEA 8FF3- C6 F1 DEC $F1 8FF5- D0 F3 BNE $8FEA 8FF7- AD DA 90 LDA $90DA 8FFA- 8D DB 90 STA $90DB 8FFD- 20 74 90 JSR $9074 *9074L ; Look for a specific nibble sequence ; "F7 F6 EF EE" while keeping some sort ; of rolling checksum. I've marked the ; success path with numbers in the ; margin. If we go too long without ; seeing the beginning of the sequence, ; we exit via $9057, which sets the ; carry and exits. 9074- A0 00 LDY #$00 9076- 18 CLC 9077- 2A ROL 9078- 85 F2 STA $F2 907A- AD EC C0 LDA $C0EC 907D- 10 FB BPL $907A 907F- AA TAX 9080- 45 F2 EOR $F2 9082- 2A ROL 9083- 49 41 EOR #$41 9085- 85 F2 STA $F2 9087- C8 INY 9088- F0 CD BEQ $9057 -> fail 908A- 8A TXA 908B- C9 F7 CMP #$F7 (1) 908D- D0 EB BNE $907A 908F- AD EC C0 LDA $C0EC 9092- 10 FB BPL $908F 9094- AA TAX 9095- 45 F2 EOR $F2 9097- 2A ROL 9098- 49 41 EOR #$41 909A- 85 F2 STA $F2 909C- C8 INY 909D- 8A TXA 909E- C9 F7 CMP #$F7 90A0- F0 ED BEQ $908F 90A2- C9 F6 CMP #$F6 (2) 90A4- D0 D4 BNE $907A 90A6- AD EC C0 LDA $C0EC 90A9- 10 FB BPL $90A6 90AB- AA TAX 90AC- 45 F2 EOR $F2 90AE- 2A ROL 90AF- 49 41 EOR #$41 90B1- 85 F2 STA $F2 90B3- C8 INY 90B4- 8A TXA 90B5- C9 F7 CMP #$F7 90B7- F0 D6 BEQ $908F 90B9- C9 EF CMP #$EF (3) 90BB- D0 BD BNE $907A 90BD- AD EC C0 LDA $C0EC 90C0- 10 FB BPL $90BD 90C2- AA TAX 90C3- 45 F2 EOR $F2 90C5- 2A ROL 90C6- 49 41 EOR #$41 90C8- 85 F2 STA $F2 90CA- C8 INY 90CB- 8A TXA 90CC- C9 F7 CMP #$F7 90CE- F0 BF BEQ $908F 90D0- C9 EE CMP #$EE (4) 90D2- D0 A6 BNE $907A ; get one more nibble on the way out, ; but don't test it yet 90D4- AD EC C0 LDA $C0EC 90D7- 10 FB BPL $90D4 90D9- 60 RTS Continuing from $9000... ; compare the nibble we just read ; (at $90D4) 9000- C9 AB CMP #$AB ; if it's not $AB, jump to failure path 9002- D0 53 BNE $9057 9004- A0 05 LDY #$05 9006- 20 5C 90 JSR $905C *905CL ; match next nibbles against an array ; of $40 known nibbles (starting at ; $09AF + an initial offset given in Y) ; over and over until we find a nibble ; that doesn't match 905C- A2 00 LDX #$00 905E- AD EC C0 LDA $C0EC 9061- 10 FB BPL $905E 9063- D9 DE 90 CMP $90DE,Y 9066- D0 0A BNE $9072 9068- C8 INY 9069- 98 TYA 906A- 29 3F AND #$3F 906C- A8 TAY 906D- D0 EF BNE $905E ; count the number of times we "wrapped ; around" to the start of the array 906F- E8 INX 9070- D0 EC BNE $905E ; the number of times we wrapped around ; is the number of groups of these $40 ; nibbles that we found on the track 9072- 86 F0 STX $F0 This is the nibble array (which is what I saw on track $21 in the nibble editor earlier -- over and over): *90DE.911D 90DE- F7 F6 90E0- EF EE AB AB AB FF D5 D5 90E8- D5 FF EA EA EA FF F5 F5 90F0- F5 FF FA FA FA FF FD FD 90F8- FD FF FE FE FE FF FF B6 9100- FD B7 FF BA FA BB F7 BD 9108- F7 BE BF FB BE F6 FF FE 9110- BF BE BB EA EA FB AA BA 9118- EA EB EF DF BF FF Then execution continues at $9074, which is the same entry point we called once before, looking for "F7 F6 EF EE". So, continuing from $9009, after having found "F7 F6 EF EE" for a second time: ; store Y (was used to count the total ; number of nibbles it took to find the ; sequence "F7 F6 EF EE") 9009- 84 F1 STY $F1 ; check the last nibble read (at $90D4) 900B- C9 AB CMP #$AB ; wrong nibble --> off to The Badlands 900D- D0 48 BNE $9057 ; save a copy of the rolling checksum 900F- A5 F2 LDA $F2 9011- 85 F3 STA $F3 ; now start over 9013- A2 00 LDX #$00 9015- A0 05 LDY #$05 9017- AD EC C0 LDA $C0EC 901A- 10 FB BPL $9017 ; go match nibbles and count nibble ; groups again (against the array at ; $90DE, same as last time) 901C- 20 63 90 JSR $9063 ; compare the rolling checksum we got ; this time to the one we got last time ; (but don't actually care if they ; differ -- possibly some options have ; been NOP'd out of a more generalized ; protection routine?) 901F- A5 F2 LDA $F2 9021- C5 F3 CMP $F3 9023- EA NOP 9024- EA NOP ; get number of nibble groups we found ; during $905C-$9071 (stored in $F0 at ; $9072) 9025- A5 F0 LDA $F0 9027- EA NOP 9028- EA NOP ; These were initialized to be equal ; (at $8FFA). $90DA is never changed, ; so this will check whether this is ; the first time we got to this point. 9029- AE DB 90 LDX $90DB 902C- EC DA 90 CPX $90DA 902F- D0 08 BNE $9039 ; yes, first run -- store the number of ; nibble groups we found 9031- 8D DC 90 STA $90DC ; loop back to do everything again ; several times 9034- CE DB 90 DEC $90DB 9037- D0 C4 BNE $8FFD ; execution always continues here on ; anything but the first run -- ; check that the total number of ; nibble groups we found is the same as ; it was last time 9039- 4D DC 90 EOR $90DC ; if we found a different number of ; nibble groups this time around, jump ; to The Badlands 903C- D0 19 BNE $9057 ; loop back to do everything again ; several times 903E- CE DB 90 DEC $90DB 9041- D0 BA BNE $8FFD ; check the number of nibble groups we ; found each time 9043- AD DC 90 LDA $90DC ; needs to be at least $5C 9046- C9 5C CMP #$5C ; otherwise -- you guessed it -- it's ; off to The Badlands 9048- 90 0D BCC $9057 ; Success path falls through to here. I ; did not find any prior references to ; $90DD, so I believe it would be safe ; to jump straight to here to bypass ; the nibble check. 904A- A9 60 LDA #$60 904C- 4D DD 90 EOR $90DD 904F- 8D CE 03 STA $03CE 9052- AD E8 C0 LDA $C0E8 9055- 18 CLC 9056- 60 RTS ; The Badlands - set carry and exit 9057- AD E8 C0 LDA $C0E8 905A- 38 SEC 905B- 60 RTS ~ Chapter 3 Now It Can Be Told It's clear that this code has been trying to determine if the disk is original, by counting a sequence of nibbles on track $21. But unlike other protection schemes, it is not at all clear why this works. What makes this particular nibble sequence so special that it can't be copied with EDD or some other bit copier? To answer this question, we need to do a very deep dive, all the way into the bit copier code itself. Here, in the bowels of EDD (v4.9, but versions 4.0+ share this code), is the routine that reads nibbles from a track while simultaneously checking if each nibble is followed by a timing bit: ; ($00) --> $6000, the buffer to store ; raw nibbles B834- A9 60 LDA #$60 B836- 85 01 STA $01 B838- A0 00 LDY #$00 B83A- 84 00 STY $00 ; All nibbles need to have the high bit ; set, so EDD uses an AND mask of #$7F ; (strip the high bit) if it determines ; that a nibble is followed by a timing ; bit. B83C- A9 7F LDA #$7F B83E- 85 02 STA $02 ; slot number (x16) B840- A6 10 LDX $10 ; Needless to say, this code is the ; epitome of cycle-counting, so every ; instruction matters, even if it does ; nothing but burn cycles. B842- EA NOP B843- 85 03 STA $03 B845- EA NOP B846- EA NOP ; The normal "LDA $C08C,X / BPL" loop ; is unrolled here. EDD tries to read ; the nibble value itself and detect ; whether a timing bit exists after it. B847- BD 8C C0 LDA $C08C,X B84A- 30 1B BMI $B867 B84C- BD 8C C0 LDA $C08C,X B84F- 30 16 BMI $B867 ; timing bit probably present B851- BD 8C C0 LDA $C08C,X B854- 30 1B BMI $B871 B856- BD 8C C0 LDA $C08C,X B859- 30 16 BMI $B871 B85B- BD 8C C0 LDA $C08C,X B85E- 30 11 BMI $B871 B860- BD 8C C0 LDA $C08C,X B863- 30 0C BMI $B871 ; 3-cycle penalty if branch is taken! ; BTW, this is an unconditional branch, ; since the instruction before this ; was a BMI, and every value is either ; minus or plus. B865- 10 E5 BPL $B84C ; Execution continues here from $B84A ; or $B84F. No timing bit was detected, ; so store the nibble and move on. B867- 91 00 STA ($00),Y B869- C8 INY B86A- D0 D6 BNE $B842 B86C- E6 01 INC $01 B86E- 10 D6 BPL $B846 B870- 60 RTS ; Execution continues here from $B854, ; $B859, $B85E, or $B863. A timing bit ; was detected, so apply the AND mask ; to indicate this. (The disk write ; routine will check this later.) B871- 25 02 AND $02 B873- 91 00 STA ($00),Y B875- C8 INY ; Unfortunately for EDD, branching here ; and applying the AND mask requires ; enough CPU cycles that we will miss ; one bit on disk by the time we branch ; back and start looking at nibble ; values again. This is usually not a ; problem, except when it is... B876- D0 CE BNE $B846 B878- E6 01 INC $01 B87A- 10 D0 BPL $B84C B87C- 60 RTS Normally, a nibble will be shifted in before the unrolled loop gets very far, so execution branches to $B867 and the nibble is stored intact. However, because EDD only checks the data latch six times, this nibble read routine is vulnerable to a well-placed timing bit, such that the "BPL" at $B865 will be reached just before the last bit of the nibble is shifted in. That 3-cycle time penalty when the branch is taken is just enough that, when combined with the 2-cycle instruction before it, the shift will complete, and the four CPU cycles will elapse, before the next read occurs. The result is that EDD gets "out of phase" with the proper start of the nibbles, and the next few nibbles that arrive will mistakenly branch to $B871 instead of $B867, losing one bit each. When those data are written to disk by the bit-copier, the values will be entirely wrong. Now imagine an entire track that is full of repeated sequences. Each of the sequences has a prologue, five nibbles in length. Every other prologues has a timing bit after each nibble. In the middle of the track is a collection of nibbles which do not match the sequence, so the entire track is split into two identical groups. When EDD attempts to read the track, it misses a crucial timing bit, gets "out of phase," and ends up misreading about half of the sequences on the track. What remain are far fewer sequences than exist on the original disk. Getting back to this particular disk, the protection check counts the total number of nibble groups (at $08FF). It knows that only an original disk will have enough ($5C or more), because the entire structure of the track exploits this design weakness in EDD. Fun fact(*): Copy II+'s nibble-and- timing-bit-reading code is so similar to EDD's that it shares the same design weakness and is also defeated by this protection scheme. (*) not guaranteed, actual fun may vary ~ Chapter 4 In Which Our Adventure Comes To A Rapid and Satisfying Conclusion Probing a bit further in memory, just past the end of the protection check, I stumbled upon the calling routine. *91BAL ; save X 91BA- 86 FA STX $FA ; call protection check 91BC- 20 D3 8F JSR $8FD3 ; branch on protection failure 91BF- B0 05 BCS $91C6 ; success path falls through 91C1- A9 01 LDA #$01 91C3- 4C C8 91 JMP $91C8 ; failure path (from $91BF) 91C6- A9 00 LDA #$00 91C8- 48 PHA 91C9- A9 00 LDA #$00 ; restore X 91CB- A6 FA LDX $FA ; exit via other code 91CD- 4C 3F 08 JMP $083F I should be able to bypass the entire protection check (but retain the side effect at $904F) by changing the JSR at $91BC from $8FD3 to $904A. Disk 2: T16,S05,$8D change "D3 8F" to "4A 90" Quod erat liberandum. ~ Acknowledgements The explanation of this copy protection scheme was first published by qkumba in PoC||GTFO 0x10, currently available at https://www.alchemistowl.org/pocorgtfo/ pocorgtfo10.pdf --------------------------------------- A 4am crack No. 600 ------------------EOF------------------